diff --git a/src/duct/lib/module.clj b/src/duct/lib/module.clj new file mode 100644 index 0000000..deb1374 --- /dev/null +++ b/src/duct/lib/module.clj @@ -0,0 +1,50 @@ +(ns duct.lib.module + (:require [duct.core :refer [merge-configs]] + [integrant.core :as ig])) + +(defn- not-member? [coll v] + (nil? (some (fn [x] (= x v)) coll))) + +(defn- unqualify [key] + (keyword (name key))) + +(defn- into-options? + "Returns true, when the given key's namespace == root + and key doesn't exist in 'blacklist" + [root key blacklist] + (and (= root (namespace key)) + (not-member? blacklist key))) + +(defn- into-options + "Returns a map with keys with root are taken into root/options." + [root schema-keys config] + (let [root (if (keyword? root) (name root) root)] + (reduce-kv + (fn [m k v] + (if (into-options? root k schema-keys) + (assoc-in m [(keyword root "options") (unqualify k)] v) + (assoc m k v))) + {} config))) + +(defn- vector-map-transformer + [store] + (let [ref-or-store #(if (qualified-keyword? %) (ig/ref %) (get store %)) + process-key (fn [k] [(unqualify k) (ref-or-store k)])] + #(into {} (mapv process-key %)))) + +(defn- transform-schema + [schema store] + (let [vector-transform (vector-map-transformer store) + vec-of-refs? #(and (vector? %) (every? keyword? %))] + (reduce-kv + (fn [m k v] + (assoc m k (cond + (keyword? v) (ig/ref v) + (vec-of-refs? v) (vector-transform v) + :else v))) {} schema))) + +(defn init [{:keys [root config extra store schema]}] + (let [root-namesspace (if (keyword? root) (name root) root) + stripped-config (into-options root-namesspace (keys schema) config) + final-config (apply merge-configs (cons stripped-config extra))] + (merge final-config (transform-schema schema store)))) diff --git a/src/duct/reitit.clj b/src/duct/reitit.clj index 9a23680..58f0218 100644 --- a/src/duct/reitit.clj +++ b/src/duct/reitit.clj @@ -1,64 +1,54 @@ (ns duct.reitit (:require [duct.core :as core :refer [merge-configs]] + [duct.lib.module :as module] + [duct.logger :as logger] + [duct.reitit.defaults :refer [reitit-module-defaults]] [duct.reitit.handler] - [duct.reitit.util :as util :refer [get-namespaces resolve-registry with-registry spy]] - [integrant.core :refer [init-key] :as ig] - [duct.logger :as logger])) + [duct.reitit.log] + [duct.reitit.util :as util :refer [get-namespaces resolve-key]] + [integrant.core :refer [init-key] :as ig])) -(def ^:private base-config - {:duct.core/handler-ns 'handler - :duct.core/middleware-ns 'middleware - ::environment {} - ::middleware [] - ::muuntaja true - ::coercion nil - ::logging {:exceptions? true - :pretty? false :logger nil}}) - ; ::logger (m/displace (ig/ref :duct/logger))}) +(defn registry-resolve + "Resolve registry keys into a map of {k [resolve config]}" + [namespaces registry] + (letfn [(process [f] (reduce f {} registry)) + (resolve [k] (resolve-key namespaces k))] + (process + (fn [acc [k v]] + (when-let [res (resolve k)] + (assoc acc k [res (or v {})])))))) -(def ^:private configs - {:development - {::logging {:pretty? true - :coercions? true - :requests? true} - ::muuntaja true - ::cross-origin {:origin [#".*"] :methods [:get :post :delete :options]}} - :production - {::logging {:exceptions? false - :coercions? false - :requests? true - :pretty? false}}}) +(defn registry-tree + "Returns a config tree that should be merged in duct configuration map" + [registry] + (reduce-kv (fn [m _ v] + (assoc m (first v) (second v))) {} registry)) -(defn- merge-to-options [config] - (reduce-kv - (fn [acc k v] - (if (= "duct.reitit" (namespace k)) - (assoc-in acc [::options (keyword (name k))] v) - (assoc acc k v))) - {} config)) +(defn- registry-references + "Returns a map of keys and their integrant reference." + [registry] + (reduce-kv (fn [m k v] + (assoc m k (ig/ref (first v)))) {} registry)) -(defmethod init-key ::log [_ {{:keys [enable logger pretty? exceptions? coercions?]} :logging}] - (when (and enable (or exceptions? coercions?)) - (if (and logger (not pretty?)) - (fn [level message] - (logger/log logger level message)) - println))) +(defn get-config + "Merge user configuration with default-configuration and + environment-default-configuration" + [user-config] + (let [profile-config (some-> user-config :duct.core/environment reitit-module-defaults)] + (merge-configs (reitit-module-defaults :base) profile-config user-config))) (defmethod init-key :duct.module/reitit [_ _] - (fn [{:duct.reitit/keys [registry routes] - :duct.core/keys [environment] :as user-config}] - (let [env-config (or (configs environment) {}) - config (merge-to-options (merge-configs base-config env-config user-config)) + (fn [{:duct.reitit/keys [registry routes] :as user-config}] + (let [config (get-config user-config) namespaces (get-namespaces config) - registry (resolve-registry namespaces registry) - merge (partial with-registry config registry)] - (merge - {::registry (reduce-kv (fn [m k v] (assoc m k (ig/ref (first v)))) {} registry) - ::log (ig/ref ::options) - :duct.handler/root {:options (ig/ref ::options) - :router (ig/ref :duct.router/reitit)} - :duct.router/reitit {:routes routes - :log (ig/ref ::log) - :registry (ig/ref ::registry) - :options (ig/ref ::options) - :namespaces namespaces}})))) + registry (registry-resolve namespaces registry)] + (module/init + {:root :duct.reitit + :config config + :extra [(registry-tree registry)] + :store {:namespaces namespaces :routes routes} + :schema {::registry (registry-references registry) + ::routes [:routes :namespaces ::registry] + ::router [::routes ::options ::log] + ::log ::options + ::handler [::router ::options ::log]}})))) diff --git a/src/duct/reitit/defaults.clj b/src/duct/reitit/defaults.clj new file mode 100644 index 0000000..28d2ad8 --- /dev/null +++ b/src/duct/reitit/defaults.clj @@ -0,0 +1,24 @@ +(ns duct.reitit.defaults) + +(def reitit-module-defaults + {:base {:duct.core/handler-ns 'handler + :duct.core/middleware-ns 'middleware + :duct.reitit/environment {} + :duct.reitit/middleware [] + :duct.reitit/muuntaja true + :duct.reitit/coercion nil + :duct.reitit/logging {:enable true + :exceptions? true + :pretty? false}} + + :development {:duct.reitit/logging + {:pretty? true :coercions? true :requests? true} + :duct.reitit/muuntaja true + :duct.reitit/cross-origin + {:origin [#".*"] :methods [:get :post :delete :options]}} + + :production {:duct.reitit/logging + {:exceptions? false + :coercions? false + :requests? true + :pretty? false}}}) diff --git a/src/duct/reitit/format.clj b/src/duct/reitit/format.clj new file mode 100644 index 0000000..c6e0d5d --- /dev/null +++ b/src/duct/reitit/format.clj @@ -0,0 +1,47 @@ +(ns duct.reitit.format + (:require [clojure.string :as str] + [duct.reitit.request :as request] + [expound.alpha :as expound])) + +(defn spec-print [{:keys [pre problems print-spec?]}] + (let [cfg {:theme :figwheel-theme :print-specs? print-spec?} + -print (expound/custom-printer (if pre (assoc cfg :value-str-fn pre) cfg))] + (-print problems))) + +(defn coercion-pretty [problems print-spec? request-info] + (with-out-str + (spec-print + {:problems problems + :print-spec? print-spec? + :pre (fn [_name form path _value] + (let [message (str (pr-str form) "\n\n" "Path: " (pr-str path))] + (if request-info + (str request-info "\n\n" message) + message)))}))) + +(defn exception-pretty [req-info ex-trace ex-cause ex-message] + (let [header "-- Exception Thrown ----------------" + footer (str "\n" (apply str (repeat (count header) "-")) "\n") + ifline #(when %1 (str "Exception " (str/upper-case %2) ": " (pr-str %1) "\n")) + header (str "\n" header "\n\n")] + (str header + (when req-info (str req-info "\n")) + (ifline ex-cause "cause") + (ifline ex-message "message") + (ifline ex-trace "trace") + footer))) + +(defn exception-compact [request ex-trace ex-message] + (format "Exception: :uri %s, :method %s, :params %s, :message %s, :trace %s" + (request :uri) + (request :request-method) + (request/params request) + ex-message ex-trace)) + +(defn trace-compact [exception] + (->> (.getStackTrace exception) + (map #(str (.getFileName %) ":" (.getLineNumber %))) + (take 5) + (str/join " => "))) + + diff --git a/src/duct/reitit/handler.clj b/src/duct/reitit/handler.clj index ecdda1b..993818f 100644 --- a/src/duct/reitit/handler.clj +++ b/src/duct/reitit/handler.clj @@ -2,6 +2,6 @@ (:require [integrant.core :as ig :refer [init-key]] [reitit.ring :as ring])) -(defmethod init-key :duct.handler/root +(defmethod init-key :duct.reitit/handler [_ {:keys [router options]}] (ring/ring-handler router)) diff --git a/src/duct/reitit/log.clj b/src/duct/reitit/log.clj new file mode 100644 index 0000000..aa8e261 --- /dev/null +++ b/src/duct/reitit/log.clj @@ -0,0 +1,13 @@ +(ns duct.reitit.log + (:require [integrant.core :refer [init-key]] + [duct.logger :as logger])) + +(defmethod init-key :duct.reitit/log + [_ {{:keys [enable logger pretty? exceptions? coercions?]} :logging}] + (when (and enable (or exceptions? coercions?)) + (if (and logger (not pretty?)) + (fn [level message] + (logger/log logger level message)) + #(println %2)))) + + diff --git a/src/duct/reitit/middleware.clj b/src/duct/reitit/middleware.clj deleted file mode 100644 index 1cb37e2..0000000 --- a/src/duct/reitit/middleware.clj +++ /dev/null @@ -1,34 +0,0 @@ -(ns duct.reitit.middleware - "Construct Ring-Reitit Global Middleware" - (:require [duct.reitit.util :refer [compact defm spy]] - [reitit.ring.middleware.muuntaja :refer [format-middleware]] - [reitit.ring.middleware.parameters :refer [parameters-middleware]] - [duct.reitit.middleware.exception :as exception] - [duct.reitit.middleware.coercion :as coercion])) - -;; TODO: inject environment keys instead -(defm environment-middleware [opts _ handler request] - (let [inject #(handler (into request %))] - (inject (assoc (opts :environment) - :id (java.util.UUID/randomUUID) - :start-date (java.util.Date.))))) - -(defn- get-format-middleware [muuntaja] - (when muuntaja format-middleware)) - -(defn- merge-middlewares [{:keys [middleware] :as options}] - (let [coercion-middlewares (coercion/get-middleware options)] - (fn [& defaults] - (-> (concat defaults coercion-middlewares middleware) - (vec) - (compact))))) - -(defn create-router-middleware [{:keys [muuntaja] :as options}] - (let [format-middleware (get-format-middleware muuntaja) - exception-middleware (exception/get-middleware options) - merge-middlewares (merge-middlewares options)] - (merge-middlewares parameters-middleware - environment-middleware - format-middleware - exception-middleware))) - diff --git a/src/duct/reitit/middleware/custom.clj b/src/duct/reitit/middleware/custom.clj new file mode 100644 index 0000000..2156d95 --- /dev/null +++ b/src/duct/reitit/middleware/custom.clj @@ -0,0 +1,8 @@ +(ns duct.reitit.middleware.custom + (:require [duct.reitit.util :refer [defm new-date new-uuid]])) + +(defm environment-middleware [data _ h r] + (h (into r (data :environment)))) + +(defm initialize-middleware [_ _ h r] + (h (assoc r :id (new-uuid) :start-date (new-date)))) diff --git a/src/duct/reitit/middleware/exception.clj b/src/duct/reitit/middleware/exception.clj index 2700362..7e4945f 100644 --- a/src/duct/reitit/middleware/exception.clj +++ b/src/duct/reitit/middleware/exception.clj @@ -1,8 +1,38 @@ (ns duct.reitit.middleware.exception - (:require [reitit.ring.middleware.exception :as exception :refer [create-exception-middleware default-handlers]] + (:require [duct.reitit.format :as format] [duct.reitit.middleware.coercion :as coercion] - [duct.reitit.middleware.format :refer [ex-format]] - [duct.reitit.util :refer [spy]])) + [duct.reitit.request :as request] + [reitit.coercion :refer [-get-name] :rename {-get-name spec-type}] + [reitit.ring.middleware.exception :as exception :refer [create-exception-middleware default-handlers]])) + +(defn coercion-ex? [type] + (or (= :reitit.coercion/request-coercion type) + (= :reitit.coercion/response-coercion type))) + +(defmulti ex-format + (fn [exception _request {:keys [coercions?]}] + (let [data (ex-data exception) + kind (if (and coercions? (coercion-ex? (:type data))) :coercion :exception) + type (when (= :coercion kind) (-> data :coercion spec-type))] + [kind type]))) + +(defmethod ex-format [:coercion :spec] + [exception request {:keys [_pretty? with-req-info? print-spec? coercions?]}] + (when coercions? + (let [problems (:problems (ex-data exception)) + request-info (when with-req-info? (request/info request))] + (duct.reitit.format/coercion-pretty problems print-spec? request-info)))) + +(defmethod ex-format [:exception nil] + [exception request {:keys [pretty? with-req-info? exceptions?]}] + (when exceptions? + (let [req-info (when with-req-info? (request/info request)) + ex-trace (format/trace-compact exception) + ex-cause (ex-cause exception) + ex-message (ex-message exception)] + (if pretty? + (format/exception-pretty req-info ex-trace ex-cause ex-message) + (format/exception-compact request ex-trace ex-message))))) (defn ^:private get-exception-wrapper [log config] (let [config (merge config {:with-req-info? true})] @@ -13,12 +43,10 @@ (defn get-middleware "Create custom exception middleware." [{:keys [coercion exception log logging]}] - (let [{:keys [enable coercions? exceptions?]} logging + (let [{:keys [enable coercions? exceptions?]} logging should-wrap (or (and enable coercions?) (and enable exceptions?)) - coercion-handlers (when coercions? (coercion/get-exception-handler coercion)) - exception-wrapper (when should-wrap {::exception/wrap (get-exception-wrapper log logging)}) create-middleware #(create-exception-middleware (apply merge default-handlers %))] (create-middleware - [exception-wrapper - coercion-handlers + [(when should-wrap {::exception/wrap (get-exception-wrapper log logging)}) + (when coercions? (coercion/get-exception-handler coercion)) exception]))) diff --git a/src/duct/reitit/middleware/format.clj b/src/duct/reitit/middleware/format.clj deleted file mode 100644 index b64b0e6..0000000 --- a/src/duct/reitit/middleware/format.clj +++ /dev/null @@ -1,68 +0,0 @@ -(ns duct.reitit.middleware.format - (:require [clojure.string :as str] - [expound.alpha :as expound] - [reitit.coercion :as coercion :refer [-get-name] :rename {-get-name spec-type}] - [duct.reitit.request :as request] - [duct.reitit.util :refer [spy]])) - -(defn coercion-ex? [type] - (or (= :reitit.coercion/request-coercion type) - (= :reitit.coercion/response-coercion type))) - -(defn ex-trace [exception] - (->> (.getStackTrace exception) - (map #(str (.getFileName %) ":" (.getLineNumber %))) - (take 5) - (str/join " => "))) - -(defn spec-print [{:keys [pre problems print-spec?]}] - (let [cfg {:theme :figwheel-theme :print-specs? print-spec?} - -print (expound/custom-printer (if pre (assoc cfg :value-str-fn pre) cfg))] - (-print problems))) - -(defmulti ex-format - (fn [exception _request {:keys [coercions?]}] - (let [data (ex-data exception) - kind (if (and coercions? (coercion-ex? (:type data))) :coercion :exception) - type (when (= :coercion kind) (-> data :coercion spec-type))] - [kind type]))) - -(defmethod ex-format [:coercion :spec] - [exception request {:keys [_pretty? with-req-info? print-spec? coercions?]}] - (when coercions? - (let [with-info - (when with-req-info? - (fn [_name form path _value] - (let [message (str (pr-str form) "\n\n" "Path: " (pr-str path))] - (if with-req-info? - (str (request/info request) "\n\n" message) - message))))] - (with-out-str - (spec-print - {:pre with-info - :problems (:problems (ex-data exception)) - :print-spec? print-spec?}))))) - -(defmethod ex-format [:exception nil] - [exception request {:keys [pretty? with-req-info? exceptions?]}] - (when exceptions? - (let [req-info (when with-req-info? (request/info request)) - ex-trace (ex-trace exception) - ex-cause (ex-cause exception) - ex-message (ex-message exception)] - (if pretty? - (let [header "-- Exception Thrown ----------------" - footer (str "\n" (apply str (repeat (count header) "-")) "\n") - ifline #(when %1 (str "Exception " (str/upper-case %2) ": " (pr-str %1) "\n")) - header (str "\n" header "\n\n")] - (str header - (when req-info (str req-info "\n")) - (ifline ex-cause "cause") - (ifline ex-message "message") - (ifline ex-trace "trace") - footer)) - (format "Exception: :uri %s, :method %s, :params %s, :message %s, :trace %s" - (request :uri) - (request :request-method) - (request/params request) - ex-message ex-trace))))) diff --git a/src/duct/reitit/middleware/logging.clj b/src/duct/reitit/middleware/logging.clj index 4766cf5..e69de29 100644 --- a/src/duct/reitit/middleware/logging.clj +++ b/src/duct/reitit/middleware/logging.clj @@ -1,19 +0,0 @@ -(ns duct.reitit.middleware.logging - (:require [integrant.core :refer [init-key]] - [duct.reitit.middleware.format :refer [ex-format]] - [duct.logger :as logger])) - -(defmethod init-key :ffduct.reitit/logging - [_ {{:keys [enable logger pretty? exceptions? coercions?] :as config} :logging}] - (when (and enable (or exceptions? coercions?)) - (let [enabled {:coercions? coercions? :exceptions? exceptions?} - production (and logger (not pretty?)) - loggerfn (if production #(logger/log logger %1 %2) #(println %2)) - loggerconf (assoc enabled :with-req-info? true :pretty? pretty?) - ex-logger (fn [next ex req] - (loggerfn :error (ex-format ex req loggerconf)) - (next ex req))] - (merge config enabled - {:log loggerfn - :ex-logger ex-logger - :request-logger-middleware nil})))) diff --git a/src/duct/reitit/router.clj b/src/duct/reitit/router.clj index 719906c..0d6e3b4 100644 --- a/src/duct/reitit/router.clj +++ b/src/duct/reitit/router.clj @@ -1,36 +1,58 @@ (ns duct.reitit.router (:require [clojure.walk :refer [postwalk]] [duct.logger :as logger] - [duct.reitit.util :as util :refer [compact member? resolve-key spy]] + [duct.reitit.util :as util :refer [compact member? resolve-key]] [integrant.core :as ig :refer [init-key]] [muuntaja.core :refer [instance] :rename {instance muuntaja-instance}] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] [reitit.coercion.spec :as spec] [reitit.ring :as ring] - [duct.reitit.middleware :refer [create-router-middleware]])) + [reitit.ring.middleware.muuntaja :refer [format-middleware]] + [reitit.ring.middleware.parameters :refer [parameters-middleware]] + [duct.reitit.middleware.exception :as exception] + [duct.reitit.middleware.coercion :as coercion] + [duct.reitit.middleware.custom :as custom])) -(def ^:private coercion-index - {:malli malli/coercion :spec spec/coercion :schema schema/coercion}) +(defn- get-muuntaja + "Returns muuntaja instance, when boolean use the default one, otherwise x" + [x] + (when x (if (boolean? x) muuntaja-instance x))) -(defn- get-resolver [registry namespaces] - (let [resolve (partial resolve-key namespaces) - member? (partial member? (keys registry)) - valid? (fn [x] (and (keyword? x) (member? x)))] - (fn [x] (cond (symbol? x) (or (resolve x) x) - (valid? x) (get registry x) - :else x)))) +(defn- get-coercion + "Returns coercion coercion is non-nil and coercer is defiend" + [coercion] + (some-> coercion :coercer keyword + {:malli malli/coercion + :spec spec/coercion + :schema schema/coercion})) -; :compile coercion/compile-request-coercers? -(defn process-options [{:keys [muuntaja environment coercion] :as options}] - {:data (compact - {:environment environment - :muuntaja (cond (boolean? muuntaja) muuntaja-instance (nil? muuntaja) nil :else muuntaja) - :coercion (some-> coercion :coercer keyword coercion-index) - :middleware (create-router-middleware options)})}) +(defn- get-router-middleware [{:keys [muuntaja middleware] :as options}] + (let [format-middleware (when muuntaja format-middleware) + exception-middleware (exception/get-middleware options) + coercion-middlewares (coercion/get-middleware options) + default-middelwares [custom/initialize-middleware + custom/environment-middleware + parameters-middleware + format-middleware + exception-middleware]] + (compact (vec (concat default-middelwares coercion-middlewares middleware))))) + +(defmethod init-key :duct.reitit/routes [_ {:keys [routes registry namespaces]}] + (let [resolve-key (partial resolve-key namespaces) + member? (partial member? (keys registry)) + valid? (fn [x] (and (keyword? x) (member? x))) + resolve (fn [x] (cond (symbol? x) (or (resolve-key x) x) + (valid? x) (get registry x) + :else x))] + (postwalk resolve routes))) + +(defmethod init-key :duct.reitit/router + [_ {:keys [routes log options]}] + (let [environment {:environment (:environment options)} + muuntaja {:muuntaja (get-muuntaja (:muuntaja options))} + coercion {:coercion (get-coercion (:coercion options))} + middleware {:middleware (get-router-middleware (assoc options :log log))} + data [environment muuntaja coercion middleware]] + (ring/router routes {:data (compact (apply merge data))}))) -(defmethod init-key :duct.router/reitit - [_ {:keys [registry routes namespaces options log]}] - ; (log :report ::initializing) - (ring/router (postwalk (get-resolver registry namespaces) routes) - (process-options (assoc options :log log)))) diff --git a/src/duct/reitit/util.clj b/src/duct/reitit/util.clj index edf374f..0aea330 100644 --- a/src/duct/reitit/util.clj +++ b/src/duct/reitit/util.clj @@ -1,8 +1,7 @@ (ns duct.reitit.util (:require [clojure.string :as str] [integrant.core :refer [init-key]] - [jsonista.core :as jsonista] - [duct.core :as duct])) + [jsonista.core :as jsonista])) (defn- qualify-key [key ns] (if (str/includes? (str key) "/") @@ -38,25 +37,6 @@ (ex-info {:data result}))) (first result)))) -(defn resolve-registry - "Resolve registry keys into a map of {k [resolve config]}" - [namespaces registry] - (letfn [(process [f] (reduce f {} registry)) - (resolve [k] (resolve-key namespaces k))] - (process - (fn [acc [k v]] - (when-let [res (resolve k)] - (assoc acc k [res (or v {})])))))) - -(defn with-registry - "Merge user registry integrant key with configuration in addition to other configs" - [config registry & configs] - (let [registry (reduce-kv (fn [m _ v] (assoc m (first v) (second v))) {} registry)] - (apply duct/merge-configs - (->> configs - (cons config) - (cons registry))))) - (defn get-namespaces "Get and merge namespaces using :duct.core/project-ns, :duct.core/handler-ns, and :duct.core/middleware-ns" [config] @@ -76,11 +56,6 @@ (map? coll) (into {} (filter #(and (seq %) ((comp not nil? second) %)) coll)))) -(defn get-environment - "Get environment from configuration" - [config options] - (:environment options (:duct.core/environment config :production))) - (defn member? "same as contains?, but check if v is part of coll." {:test @@ -119,13 +94,19 @@ (fn [opts _] (fn [handler] (fn [request] (f opts _ handler request))))) (defmacro defm [name args body] - `(let [fun# (fn ~args ~body)] - (def ~name - {:name ~(keyword (str *ns* "/" name)) - :compile (wrap-compile-middleware fun#)}))) + `(def ~name + {:name ~(keyword (str *ns* "/" name)) + :compile (wrap-compile-middleware (fn ~args ~body))})) (comment (test #'resolve-key) (test #'get-namespaces) (test #'compact) (test #'member?)) + +(defn new-uuid [] + (java.util.UUID/randomUUID)) + +(defn new-date [] + (java.util.Date.)) + diff --git a/src/duct_hierarchy.edn b/src/duct_hierarchy.edn index c10b468..97481ad 100644 --- a/src/duct_hierarchy.edn +++ b/src/duct_hierarchy.edn @@ -1,6 +1,10 @@ -{:duct.router/reitit [:duct/router] +{:duct.core/handler-ns [:duct/const] + :duct.core/middleware-ns [:duct/const] :duct.module/reitit [:duct/module] + + :duct.reitit/router [:duct/router] + :duct.reitit/handler [:duct/handler] :duct.reitit/registry [:duct/const] - :duct.core/handler-ns [:duct/const] - :duct.core/middleware-ns [:duct/const] :duct.reitit/options [:duct/const]} + + diff --git a/test/duct/reitit_test.clj b/test/duct/reitit_test.clj index 9e44f17..6774568 100644 --- a/test/duct/reitit_test.clj +++ b/test/duct/reitit_test.clj @@ -1,111 +1,61 @@ (ns duct.reitit-test - (:require [clojure.test :refer [deftest testing is are]] + (:require [clojure.test :refer [are deftest is testing]] + [duct.core :as core] [duct.reitit] [foo.handler] [foo.handler.plus] - [duct.core :as core] [integrant.core :as ig] - [reitit.ring :as ring] + [duct.test-helpers :refer [base-config init-system request with-base-config test-options routes]] [reitit.core :as r] - [fipp.clojure :refer [pprint]] [duct.reitit.util :refer [to-edn spy]] [clojure.string :as str])) (core/load-hierarchy) -(derive :duct.reitit/logging ::logging) -(derive :duct.reitit/coercion ::coercion) -(derive :duct.module/reitit ::module) -(derive :duct.router/reitit ::router) -(derive :foo/database :duct/const) -(derive :foo/index-path :duct/const) - -(defn- new-config-handling [base & [profiles]] - (let [config (core/build-config base profiles) - in-config (partial get-in config)] - #(in-config (cons :duct.reitit/options (if (vector? %) % [%]))))) - -(defn- request [method uri req] - (merge req {:request-method method :uri uri})) - -(defn- routes [router] - (reduce (fn [acc [k v]] (assoc acc k v)) {} (r/routes router))) - -(def base-config - {:duct.module/reitit {} - :duct.module/logging {} - :duct.profile/base {:duct.core/project-ns 'foo}}) - -(def test-config - {:duct.core/project-ns 'foo - :duct.core/handler-ns 'handler ;; Where should handlers keys be localated - :duct.core/middleware-ns 'middleware ;; Where should middleware keys be located - :duct.reitit/routes [["/" :index] ;; Routes Configuration - ["/author" :get-author] - ["/ping" {:get {:handler :ping}}] - ["/plus" {:post :plus/with-body - :get 'plus/with-query}] - ["/divide" {:get :divide}]] - :duct.reitit/registry {:index {:path (ig/ref :foo/index-path)} ;; init foo.handler/index with {:path} - :ping {:message "pong"} ;; init foo.handler/ping with {:message} - :plus/with-body {} ;; init foo.handler.plus/with-body - :get-author {} ;; init foo.handler/get-author - :divide {}} ;; init foo.handler/divide - :duct.reitit/exception (ig/ref :foo.handler/exceptions) - :duct.reitit/environment {:db (ig/ref :foo/database)} - :foo/database [{:author "tami5"}] - :foo/index-path "resources/index.html" - :foo.handler/exceptions {}}) - -(defn- with-base-config [config] - (->> config - (merge test-config) - (assoc base-config :duct.profile/base))) - -(defn- init - "Takes reitit options and merge it to base-config for testing" - [config] - (-> config with-base-config core/prep-config ig/init)) - -(deftest test-default-base-options - (let [config (core/prep-config base-config) - in-options (new-config-handling base-config)] - - ;; Reitit Module keys used for futher processing - (is (= [:options :registry :log] - (->> (keys config) +(deftest test-default-config + (let [config (core/prep-config base-config)] + (is (= (->> (keys config) (filterv #(= "duct.reitit" (namespace %))) - (mapv #(keyword (name %)))))) + (mapv #(keyword (name %)))) + [:options :registry :log :routes :handler :router])) - (are [key value] (= value (key config)) - ;; Defaulhandler middleware namespace + (are [key value] (= (key config) value) + ;; Default middleware namespace :duct.core/handler-ns 'handler ;; Default middleware namespace :duct.core/middleware-ns 'middleware + ;; Log function + :duct.reitit/log (ig/ref :duct.reitit/options) + ;; Resolve routes + :duct.reitit/routes {:routes nil + :namespaces ["foo.handler" "foo.middleware"] + :registry (ig/ref :duct.reitit/registry)} ;; Configuration Pass it reitit router to initialize it - :duct.router/reitit {:routes nil, - :log (ig/ref :duct.reitit/log) - :registry (ig/ref :duct.reitit/registry) - :options (ig/ref :duct.reitit/options) - :namespaces ["foo.handler" "foo.middleware"]} + :duct.reitit/router {:routes (ig/ref :duct.reitit/routes) + :options (ig/ref :duct.reitit/options) + :log (ig/ref :duct.reitit/log)} ;; Configuration Pass it ring handler to initialize it - :duct.handler/root {:router (ig/ref :duct.router/reitit) - :options (ig/ref :duct.reitit/options)}) + :duct.reitit/handler {:router (ig/ref :duct.reitit/router) + :options (ig/ref :duct.reitit/options) + :log (ig/ref :duct.reitit/log)}))) - ;; Configuration Values - (are [path value] (= (in-options path) value) - [:logging :exceptions?] true ;; default types supported by default - [:logging :pretty?] false ;; No pretty logging by default. - [:logging :logger] nil ;; No logger by default. +(deftest test-default-base-options + ;; Reitit Module keys using default base default options only + (let [get-in-options (test-options base-config)] + (are [path value] (= (get-in-options path) value) :muuntaja true ;; Muuntaja formatting is enabled by default :environment {} ;; Empty Environment :middleware [] ;; Empty Middleware - :coercion nil))) ;; no :coercion configuration + :coercion nil ;; no :coercion configuration + [:logging :exceptions?] true ;; default types supported by default + [:logging :pretty?] false ;; No pretty logging by default. + [:logging :logger] nil))) ;; No logger by default. (deftest test-default-dev-options - (let [in-options (-> (assoc base-config :duct.profile/dev {}) - (new-config-handling [:duct.profile/dev]))] - (are [path value] (= (in-options path) value) + ;; Reitit Module keys using default base + default dev options + (let [get-in-options (-> (assoc base-config :duct.profile/dev {}) + (test-options [:duct.profile/dev]))] + (are [path value] (= (get-in-options path) value) [:logging :exceptions?] true ;; exceptions enabled by default [:logging :coercions?] true ;; coercions enabled by default [:logging :requests?] true ;; requests enabled by default @@ -115,28 +65,28 @@ :environment {} ;; Empty Environment :middleware [] ;; Empty Middleware [:cross-origin :methods] [:get :post :delete :options]) ;; Cross-origin methods - (is (= ".*" (str (in-options [:cross-origin :origin 0])))))) ;; Cross-origin origin allowed + (is (= ".*" (str (get-in-options [:cross-origin :origin 0])))))) ;; Cross-origin origin allowed (deftest test-default-prod-options - (let [in-options (-> (assoc base-config :duct.profile/prod {}) - (new-config-handling [:duct.profile/prod]))] - (are [path value] (= (in-options path) value) - [:logging :requests?] true ;; default types supported by default - [:logging :coercions?] false ;; default types supported by default - [:logging :exceptions?] false ;; default types supported by default - [:logging :pretty?] false ;; No pretty logging by default. - [:logging :logger] nil ;; No logger by default. + (let [get-in-options (-> (assoc base-config :duct.profile/prod {}) + (test-options [:duct.profile/prod]))] + (are [path value] (= (get-in-options path) value) :muuntaja true ;; Muuntaja formatting is enabled by default :environment {} ;; Empty Environment :middleware [] ;; Empty Middleware - :cross-origin nil))) ;; No Cross-origin + :cross-origin nil ;; No Cross-origin + [:logging :requests?] true ;; default types supported by default + [:logging :coercions?] false ;; default types supported by default + [:logging :exceptions?] false ;; default types supported by default + [:logging :pretty?] false ;; No pretty logging by default. + [:logging :logger] nil))) ;; No logger by default. (deftest test-foo-module - (let [extra {::coercion {:enable true :coercer 'spec}} + (let [extra {:duct.reitit/coercion {:enable true :coercer 'spec}} config (-> extra with-base-config core/prep-config ig/init) - router (config :duct.router/reitit) + router (config :duct.reitit/router) routes (routes router) - handler (config :duct.handler/root)] + handler (config :duct.reitit/handler)] (testing "Registry-Merge:" (are [x] (not= nil (x config)) @@ -182,35 +132,35 @@ ex-compact? (does-include* "Exception: :uri") test-behavior (fn [method url extra-request-keys base cfg checkfn] (let [request (request method url extra-request-keys) - handler (-> base (core/merge-configs cfg) init :duct.handler/root)] + handler (-> base (core/merge-configs cfg) with-base-config init-system :duct.reitit/handler)] (->> request handler with-out-str checkfn)))] (testing "Exception-Logging:" - (let [base {::logging {:enable true :pretty? false :exception? true}}] + (let [base {:duct.reitit/logging {:enable true :pretty? false :exception? true}}] (are [checkfn cfg] (test-behavior :get "/divide" {:body-params {:y 0 :x 0}} base cfg checkfn) ;; Enabled ex-compact? {} ;; Enabled + logger (cant' check or confirm that) - empty? {::logging {:logger (ig/ref :duct/logger)}} + empty? {:duct.reitit/logging {:logger (ig/ref :duct/logger)}} ;; Enabled + Pretty - ex-pretty? {::logging {:pretty? true}} + ex-pretty? {:duct.reitit/logging {:pretty? true}} ;; Disabled - empty? {::logging {:enable false}} + empty? {:duct.reitit/logging {:enable false}} ;; Disabled through disabling - empty? {::logging {:exceptions? false}}))) + empty? {:duct.reitit/logging {:exceptions? false}}))) (testing "Coercion-Logging:" - (let [base {::coercion {:enable true :coercer 'spec} - ::logging {:enable true :pretty? false :coercions? true}}] + (let [base {:duct.reitit/coercion {:enable true :coercer 'spec} + :duct.reitit/logging {:enable true :pretty? false :coercions? true}}] (are [checkfn cfg] (test-behavior :get "/plus" {:query-params {:y "str" :x 0}} base cfg checkfn) ;; Enabled spec-compact? {} ;; Enabled + logger (cant' check or confirm that) - empty? {::logging {:logger (ig/ref :duct/logger)}} + empty? {:duct.reitit/logging {:logger (ig/ref :duct/logger)}} ;; Enabled + Pretty - spec-pretty? {::logging {:pretty? true}} + spec-pretty? {:duct.reitit/logging {:pretty? true}} ;; Disabled Logging - empty? {::logging {:enable false}} + empty? {:duct.reitit/logging {:enable false}} ;; Disabled Coercion Logging, loggs with exceptions handler instead - ex-pretty? {::logging {:pretty? true :exceptions? true :coercions? false}})))))) + ex-pretty? {:duct.reitit/logging {:pretty? true :exceptions? true :coercions? false}})))))) diff --git a/test/duct/test_helpers.clj b/test/duct/test_helpers.clj new file mode 100644 index 0000000..1493cc1 --- /dev/null +++ b/test/duct/test_helpers.clj @@ -0,0 +1,55 @@ +(ns duct.test-helpers + (:require [duct.core :as core] + [integrant.core :as ig] + [reitit.core :as r])) + +(defn test-options [base & [profiles]] + (let [config (core/build-config base profiles) + in-config (partial get-in config)] + #(in-config (cons :duct.reitit/options (if (vector? %) % [%]))))) + +(defn request [method uri req] + (merge req {:request-method method :uri uri})) + +(defn routes [router] + (reduce (fn [acc [k v]] (assoc acc k v)) {} (r/routes router))) + +(def base-config + {:duct.module/reitit {} + :duct.module/logging {} + :duct.profile/base {:duct.core/project-ns 'foo}}) + +(derive :foo/database :duct/const) +(derive :foo/index-path :duct/const) + +(def test-config + {:duct.core/project-ns 'foo + :duct.core/handler-ns 'handler ;; Where should handlers keys be localated + :duct.core/middleware-ns 'middleware ;; Where should middleware keys be located + :duct.reitit/routes [["/" :index] ;; Routes Configuration + ["/author" :get-author] + ["/ping" {:get {:handler :ping}}] + ["/plus" {:post :plus/with-body + :get 'plus/with-query}] + ["/divide" {:get :divide}]] + :duct.reitit/registry {:index {:path (ig/ref :foo/index-path)} ;; init foo.handler/index with {:path} + :ping {:message "pong"} ;; init foo.handler/ping with {:message} + :plus/with-body {} ;; init foo.handler.plus/with-body + :get-author {} ;; init foo.handler/get-author + :divide {}} ;; init foo.handler/divide + :duct.reitit/exception (ig/ref :foo.handler/exceptions) + :duct.reitit/environment {:db (ig/ref :foo/database)} + :foo/database [{:author "tami5"}] + :foo/index-path "resources/index.html" + :foo.handler/exceptions {}}) + +(defn with-base-config [config] + (->> config + (merge test-config) + (assoc base-config :duct.profile/base))) + +(defn init-system + "Takes reitit options and merge it to base-config for testing" + [config] + (-> config core/prep-config ig/init)) +