217 lines
6.6 KiB
Clojure
217 lines
6.6 KiB
Clojure
(ns adl-support.core
|
|
(:require [clojure.core.memoize :as memo]
|
|
[clojure.java.io :as io]
|
|
[clojure.string :refer [split join]]
|
|
[clojure.tools.logging]))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;;;
|
|
;;;; adl-support.core: functions used by ADL-generated code.
|
|
;;;;
|
|
;;;; This program is free software; you can redistribute it and/or
|
|
;;;; modify it under the terms of the MIT-style licence provided; see LICENSE.
|
|
;;;;
|
|
;;;; This program is distributed in the hope that it will be useful,
|
|
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;;;; License for more details.
|
|
;;;;
|
|
;;;; Copyright (C) 2018 Simon Brooke
|
|
;;;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
(def ^:dynamic *warn*
|
|
"The idea here is to have a function with which to show warnings to the user,
|
|
which can be dynamically bound. Any binding should be a function of one
|
|
argument, which it should print, log, or otherwise display."
|
|
(fn [s] (println s)))
|
|
|
|
|
|
(defn massage-value
|
|
"Return a map with one key, this `k` as a keyword, whose value is the binding of
|
|
`k` in map `m`, as read by read."
|
|
[k m]
|
|
(let [v (m k)
|
|
vr (if
|
|
(string? v)
|
|
(try
|
|
(read-string v)
|
|
(catch Exception _ nil)))]
|
|
(cond
|
|
(nil? v) {}
|
|
(= v "") {}
|
|
(number? vr) {(keyword k) vr}
|
|
true
|
|
{(keyword k) v})))
|
|
|
|
|
|
(defn raw-massage-params
|
|
"Sending empty strings, or numbers as strings, to the database often isn't
|
|
helpful. Massage these `params` and `form-params` to eliminate these problems.
|
|
We must take key field values out of just params, but if form-params are present
|
|
we should take all other values out of form-params - because we need the key to
|
|
load the form in the first place. `form-params` always override `params`.
|
|
|
|
**NOTE THAT** the parameter `key-fields` is deprecated and ignored."
|
|
([params form-params key-fields]
|
|
(let
|
|
[p (reduce
|
|
merge
|
|
{}
|
|
(map
|
|
#(massage-value % params)
|
|
(keys params)))]
|
|
(if
|
|
(empty? (keys form-params))
|
|
p
|
|
(reduce
|
|
merge
|
|
;; do the keyfields first, from params
|
|
p
|
|
;; then merge in everything from form-params, potentially overriding what
|
|
;; we got from params.
|
|
(map
|
|
#(massage-value % form-params)
|
|
(keys form-params))))))
|
|
([request key-fields]
|
|
(raw-massage-params (:params request) (:form-params request) key-fields))
|
|
([request]
|
|
(raw-massage-params (:params request) (:form-params request) #{})))
|
|
|
|
|
|
(def massage-params
|
|
"Sending empty strings, or numbers as strings, to the database often isn't
|
|
helpful. Massage these `params` and `form-params` to eliminate these problems.
|
|
We must take key field values out of just params, but we should take all other
|
|
values out of form-params - because we need the key to load the form in
|
|
the first place, but just accepting values of other params would allow spoofing."
|
|
(memo/ttl raw-massage-params {} :ttl/threshold 5000))
|
|
|
|
|
|
(defn
|
|
raw-resolve-template
|
|
[n]
|
|
(if
|
|
(.exists (io/as-file (str "resources/templates/" n)))
|
|
n
|
|
(str "auto/" n)))
|
|
|
|
|
|
(def resolve-template (memoize raw-resolve-template))
|
|
|
|
|
|
(defmacro compose-exception-reason
|
|
"Compose and return a sensible reason message for this `exception`."
|
|
([exception intro]
|
|
`(str
|
|
~intro
|
|
(if ~intro ": ")
|
|
(join
|
|
"\n\tcaused by: "
|
|
(reverse
|
|
(loop [ex# ~exception result# ()]
|
|
(if-not (nil? ex#)
|
|
(recur
|
|
(.getCause ex#)
|
|
(cons (str
|
|
(.getName (.getClass ex#))
|
|
": "
|
|
(.getMessage ex#)) result#))
|
|
result#))))))
|
|
([exception]
|
|
`(compose-exception-reason ~exception nil)))
|
|
|
|
|
|
(defmacro compose-reason-and-log
|
|
"Compose a reason message for this `exception`, log it (with its
|
|
stacktrace), and return the reason message."
|
|
([exception intro]
|
|
`(let [reason# (compose-exception-reason ~exception ~intro)]
|
|
(clojure.tools.logging/error
|
|
reason#
|
|
"\n"
|
|
(with-out-str
|
|
(-> ~exception .printStackTrace)))
|
|
reason#))
|
|
([exception]
|
|
`(compose-reason-and-log ~exception nil)))
|
|
|
|
|
|
(defmacro do-or-log-error
|
|
"Evaluate the supplied `form` in a try/catch block. If the
|
|
keyword param `:message` is supplied, the value will be used
|
|
as the log message; if the keyword param `:error-return` is
|
|
supplied, the value will be returned if an exception is caught."
|
|
[form & {:keys [message error-return]
|
|
:or {message `(str "A failure occurred in "
|
|
~(list 'quote form))}}]
|
|
`(try
|
|
~form
|
|
(catch Exception any#
|
|
(compose-reason-and-log any# ~message)
|
|
~error-return)))
|
|
|
|
|
|
(defmacro do-or-return-reason
|
|
"Clojure stacktraces are unreadable. We have to do better; evaluate
|
|
this `form` in a try-catch block; return a map. If the evaluation
|
|
succeeds, the map will have a key `:result` whose value is the result;
|
|
otherwise it will have a key `:error` which will be bound to the most
|
|
sensible error message we can construct."
|
|
([form intro]
|
|
`(try
|
|
{:result ~form}
|
|
(catch Exception any#
|
|
{:error (compose-exception-reason any# ~intro)})))
|
|
([form]
|
|
`(do-or-return-reason ~form nil)))
|
|
|
|
|
|
(defmacro do-or-log-and-return-reason
|
|
"Clojure stacktraces are unreadable. We have to do better; evaluate
|
|
this `form` in a try-catch block; return a map. If the evaluation
|
|
succeeds, the map will have a key `:result` whose value is the result;
|
|
otherwise it will have a key `:error` which will be bound to the most
|
|
sensible error message we can construct. Additionally, log the exception"
|
|
[form]
|
|
`(try
|
|
{:result ~form}
|
|
(catch Exception any#
|
|
{:error (compose-reason-and-log any#)})))
|
|
|
|
|
|
(defmacro do-or-warn
|
|
"Evaluate this `form`; if any exception is thrown, show it to the user
|
|
via the `*warn*` mechanism."
|
|
([form]
|
|
`(try
|
|
~form
|
|
(catch Exception any#
|
|
(*warn* (compose-exception-reason any#))
|
|
nil)))
|
|
([form intro]
|
|
`(try
|
|
~form
|
|
(catch Exception any#
|
|
(*warn* (str ~intro ":\n\t" (compose-exception-reason any#)))
|
|
nil))))
|
|
|
|
|
|
(defmacro do-or-warn-and-log
|
|
"Evaluate this `form`; if any exception is thrown, log the reason and
|
|
show it to the user via the `*warn*` mechanism."
|
|
([form]
|
|
`(try
|
|
~form
|
|
(catch Exception any#
|
|
(*warn* (compose-reason-and-log any#))
|
|
nil)))
|
|
([form intro]
|
|
`(try
|
|
~form
|
|
(catch Exception any#
|
|
(*warn* (compose-reason-and-log any# ~intro ))
|
|
nil))))
|
|
|