spell-spec

This commit is contained in:
Tommi Reiman 2019-04-19 22:39:45 -04:00
parent dc92f6f48e
commit 674b60a124
13 changed files with 100 additions and 47 deletions

View file

@ -112,6 +112,7 @@
:body {:total (- x y)}})}}]]] :body {:total (- x y)}})}}]]]
{;;:reitit.interceptor/transform dev/print-context-diffs {;;:reitit.interceptor/transform dev/print-context-diffs
;;:wrap-spec reitit.dev.pretty/closed-keys
:validate spec/validate :validate spec/validate
:exception pretty/exception :exception pretty/exception
:data {:coercion spec-coercion/coercion :data {:coercion spec-coercion/coercion

View file

@ -39,7 +39,8 @@
(s/def ::name keyword?) (s/def ::name keyword?)
(s/def ::handler fn?) (s/def ::handler fn?)
(s/def ::default-data (s/keys :opt-un [::name ::handler])) (s/def ::no-doc boolean?)
(s/def ::default-data (s/keys :opt-un [::name ::handler ::no-doc]))
;; ;;
;; router ;; router
@ -75,6 +76,8 @@
;; coercion ;; coercion
;; ;;
(s/def :reitit.core.coercion/coercion any?)
(s/def :reitit.core.coercion/model any?) (s/def :reitit.core.coercion/model any?)
(s/def :reitit.core.coercion/query :reitit.core.coercion/model) (s/def :reitit.core.coercion/query :reitit.core.coercion/model)
@ -90,7 +93,8 @@
:reitit.core.coercion/path])) :reitit.core.coercion/path]))
(s/def ::parameters (s/def ::parameters
(s/keys :opt-un [:reitit.core.coercion/parameters])) (s/keys :opt-un [:reitit.core.coercion/coercion
:reitit.core.coercion/parameters]))
(s/def :reitit.core.coercion/status (s/def :reitit.core.coercion/status
(s/or :number number? :default #{:default})) (s/or :number number? :default #{:default}))
@ -103,7 +107,8 @@
(s/map-of :reitit.core.coercion/status :reitit.core.coercion/response)) (s/map-of :reitit.core.coercion/status :reitit.core.coercion/response))
(s/def ::responses (s/def ::responses
(s/keys :opt-un [:reitit.core.coercion/responses])) (s/keys :opt-un [:reitit.core.coercion/coercion
:reitit.core.coercion/responses]))
;; ;;
;; Route data validator ;; Route data validator
@ -111,14 +116,14 @@
(defrecord Problem [path scope data spec problems]) (defrecord Problem [path scope data spec problems])
(defn validate-route-data [routes spec] (defn validate-route-data [routes wrap-spec spec]
(some->> (for [[p d _] routes] (some->> (for [[p d _] routes]
(when-let [problems (and spec (s/explain-data spec d))] (when-let [problems (and spec (s/explain-data (wrap-spec spec) d))]
(->Problem p nil d spec problems))) (->Problem p nil d spec problems)))
(keep identity) (seq) (vec))) (keep identity) (seq) (vec)))
(defn validate [routes {:keys [spec] :or {spec ::default-data}}] (defn validate [routes {:keys [spec wrap-spec] :or {spec ::default-data, wrap-spec identity}}]
(when-let [problems (validate-route-data routes spec)] (when-let [problems (validate-route-data routes wrap-spec spec)]
(exception/fail! (exception/fail!
::invalid-route-data ::invalid-route-data
{:problems problems}))) {:problems problems})))

View file

@ -9,5 +9,6 @@
:parent-project {:path "../../project.clj" :parent-project {:path "../../project.clj"
:inherit [:deploy-repositories :managed-dependencies]} :inherit [:deploy-repositories :managed-dependencies]}
:dependencies [[metosin/reitit-core] :dependencies [[metosin/reitit-core]
[com.bhauman/spell-spec]
[expound] [expound]
[fipp]]) [fipp]])

View file

@ -3,6 +3,9 @@
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[reitit.exception :as exception] [reitit.exception :as exception]
[arrangement.core] [arrangement.core]
;; spell-spec
[spec-tools.spell :as spell]
[spell-spec.expound]
;; expound ;; expound
[expound.ansi] [expound.ansi]
[expound.alpha] [expound.alpha]
@ -178,7 +181,7 @@
(if (and (not= 1 line)) (if (and (not= 1 line))
(let [file-name (str/replace file #"(.*?)\.\S[^\.]+" "$1") (let [file-name (str/replace file #"(.*?)\.\S[^\.]+" "$1")
target-name (name target) target-name (name target)
ns (str (subs target-name 0 (str/index-of target-name (str "user" "$"))) file-name)] ns (str (subs target-name 0 (or (str/index-of target-name (str file-name "$")) 0)) file-name)]
(str ns ":" line)) (str ns ":" line))
"repl") "repl")
(catch #?(:clj Exception, :cljs js/Error) _ (catch #?(:clj Exception, :cljs js/Error) _
@ -227,6 +230,9 @@
:cljs "unknown")] :cljs "unknown")]
(ex-info (exception-str message source (printer)) (assoc (or data {}) ::exception/cause e)))) (ex-info (exception-str message source (printer)) (assoc (or data {}) ::exception/cause e))))
;; FIXME
(def closed-keys spec-tools.spell/closed-keys)
(defn de-expound-colors [^String s mappings] (defn de-expound-colors [^String s mappings]
(let [s' (reduce (let [s' (reduce
(fn [s [from to]] (fn [s [from to]]
@ -316,12 +322,12 @@
(into (into
[:group] [:group]
(map (map
(fn [{:keys [data path spec]}] (fn [{:keys [data path spec scope]}]
[:group [:group
[:span (color :grey "-- On route -----------------------")] [:span (color :grey "-- On route -----------------------")]
[:break] [:break]
[:break] [:break]
(text path) (text path) (if scope [:span " " (text scope)])
[:break] [:break]
[:break] [:break]
(-> (s/explain-data spec data) (-> (s/explain-data spec data)

View file

@ -2,8 +2,7 @@
(:require [meta-merge.core :refer [meta-merge]] (:require [meta-merge.core :refer [meta-merge]]
[reitit.interceptor :as interceptor] [reitit.interceptor :as interceptor]
[reitit.ring :as ring] [reitit.ring :as ring]
[reitit.core :as r] [reitit.core :as r]))
[reitit.impl :as impl]))
(defrecord Endpoint [data interceptors queue handler path method]) (defrecord Endpoint [data interceptors queue handler path method])
@ -16,6 +15,9 @@
(defn compile-result [[path data] {:keys [::default-options-handler] :as opts}] (defn compile-result [[path data] {:keys [::default-options-handler] :as opts}]
(let [[top childs] (ring/group-keys data) (let [[top childs] (ring/group-keys data)
childs (cond-> childs
(and (not (:options childs)) default-options-handler)
(assoc :options {:no-doc true, :handler default-options-handler}))
compile (fn [[path data] opts scope] compile (fn [[path data] opts scope]
(interceptor/compile-result [path data] opts scope)) (interceptor/compile-result [path data] opts scope))
->endpoint (fn [p d m s] ->endpoint (fn [p d m s]
@ -29,12 +31,7 @@
(fn [acc method] (fn [acc method]
(cond-> acc (cond-> acc
any? (assoc method (->endpoint path data method nil)))) any? (assoc method (->endpoint path data method nil))))
(ring/map->Methods (ring/map->Methods {})
{:options
(if default-options-handler
(->endpoint path (assoc data
:handler default-options-handler
:no-doc true) :options nil))})
ring/http-methods))] ring/http-methods))]
(if-not (seq childs) (if-not (seq childs)
(->methods true top) (->methods true top)

View file

@ -11,7 +11,13 @@
{:name ::coerce-request {:name ::coerce-request
:spec ::rs/parameters :spec ::rs/parameters
:compile (fn [{:keys [coercion parameters]} opts] :compile (fn [{:keys [coercion parameters]} opts]
(if (and coercion parameters) (cond
;; no coercion, skip
(not coercion) nil
;; just coercion, don't mount
(not parameters) {}
;; mount
:else
(let [coercers (coercion/request-coercers coercion parameters opts)] (let [coercers (coercion/request-coercers coercion parameters opts)]
{:enter (fn [ctx] {:enter (fn [ctx]
(let [request (:request ctx) (let [request (:request ctx)
@ -27,7 +33,13 @@
{:name ::coerce-response {:name ::coerce-response
:spec ::rs/responses :spec ::rs/responses
:compile (fn [{:keys [coercion responses]} opts] :compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses) (cond
;; no coercion, skip
(not coercion) nil
;; just coercion, don't mount
(not responses) {}
;; mount
:else
(let [coercers (coercion/response-coercers coercion responses opts)] (let [coercers (coercion/response-coercers coercion responses opts)]
{:leave (fn [ctx] {:leave (fn [ctx]
(let [request (:request ctx) (let [request (:request ctx)

View file

@ -12,15 +12,15 @@
(s/def ::interceptors (s/coll-of (partial satisfies? interceptor/IntoInterceptor))) (s/def ::interceptors (s/coll-of (partial satisfies? interceptor/IntoInterceptor)))
(s/def ::data (s/def ::data
(s/keys :opt-un [::rs/handler ::rs/name ::interceptors])) (s/keys :opt-un [::rs/handler ::rs/name ::rs/no-doc ::interceptors]))
;; ;;
;; Validator ;; Validator
;; ;;
(defn validate (defn validate
[routes {:keys [spec] :or {spec ::data}}] [routes {:keys [spec wrap-spec] :or {spec ::data, wrap-spec identity}}]
(when-let [problems (rrs/validate-route-data routes :interceptors spec)] (when-let [problems (rrs/validate-route-data routes :interceptors wrap-spec spec)]
(exception/fail! (exception/fail!
::rs/invalid-route-data ::rs/invalid-route-data
{:problems problems}))) {:problems problems})))

View file

@ -11,6 +11,9 @@
(s/def ::bytes bytes?) (s/def ::bytes bytes?)
(s/def ::size int?) (s/def ::size int?)
(s/def ::multipart :reitit.core.coercion/model)
(s/def ::parameters (s/keys :opt-un [::multipart]))
(def temp-file-part (def temp-file-part
"Spec for file param created by ring.middleware.multipart-params.temp-file store." "Spec for file param created by ring.middleware.multipart-params.temp-file store."
(st/spec (st/spec
@ -41,6 +44,7 @@
(multipart-interceptor nil)) (multipart-interceptor nil))
([options] ([options]
{:name ::multipart {:name ::multipart
:spec ::parameters
:compile (fn [{:keys [parameters coercion]} opts] :compile (fn [{:keys [parameters coercion]} opts]
(if-let [multipart (:multipart parameters)] (if-let [multipart (:multipart parameters)]
(let [parameter-coercion {:multipart (coercion/->ParameterCoercion (let [parameter-coercion {:multipart (coercion/->ParameterCoercion

View file

@ -30,6 +30,9 @@
(defn compile-result [[path data] {:keys [::default-options-handler] :as opts}] (defn compile-result [[path data] {:keys [::default-options-handler] :as opts}]
(let [[top childs] (group-keys data) (let [[top childs] (group-keys data)
childs (cond-> childs
(and (not (:options childs)) default-options-handler)
(assoc :options {:no-doc true, :handler default-options-handler}))
->endpoint (fn [p d m s] ->endpoint (fn [p d m s]
(-> (middleware/compile-result [p d] opts s) (-> (middleware/compile-result [p d] opts s)
(map->Endpoint) (map->Endpoint)
@ -40,12 +43,7 @@
(fn [acc method] (fn [acc method]
(cond-> acc (cond-> acc
any? (assoc method (->endpoint path data method nil)))) any? (assoc method (->endpoint path data method nil))))
(map->Methods (map->Methods {})
{:options
(if default-options-handler
(->endpoint path (assoc data
:handler default-options-handler
:no-doc true) :options nil))})
http-methods))] http-methods))]
(if-not (seq childs) (if-not (seq childs)
(->methods true top) (->methods true top)

View file

@ -25,7 +25,13 @@
{:name ::coerce-request {:name ::coerce-request
:spec ::rs/parameters :spec ::rs/parameters
:compile (fn [{:keys [coercion parameters]} opts] :compile (fn [{:keys [coercion parameters]} opts]
(if (and coercion parameters) (cond
;; no coercion, skip
(not coercion) nil
;; just coercion, don't mount
(not parameters) {}
;; mount
:else
(let [coercers (coercion/request-coercers coercion parameters opts)] (let [coercers (coercion/request-coercers coercion parameters opts)]
(fn [handler] (fn [handler]
(fn (fn
@ -43,7 +49,13 @@
{:name ::coerce-response {:name ::coerce-response
:spec ::rs/responses :spec ::rs/responses
:compile (fn [{:keys [coercion responses]} opts] :compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses) (cond
;; no coercion, skip
(not coercion) nil
;; just coercion, don't mount
(not responses) {}
;; mount
:else
(let [coercers (coercion/response-coercers coercion responses opts)] (let [coercers (coercion/response-coercers coercion responses opts)]
(fn [handler] (fn [handler]
(fn (fn

View file

@ -9,10 +9,25 @@
;; ;;
(s/def ::middleware (s/coll-of #(satisfies? middleware/IntoMiddleware %))) (s/def ::middleware (s/coll-of #(satisfies? middleware/IntoMiddleware %)))
(s/def ::get map?)
(s/def ::head map?)
(s/def ::post map?)
(s/def ::put map?)
(s/def ::delete map?)
(s/def ::connect map?)
(s/def ::options map?)
(s/def ::trace map?)
(s/def ::patch map?)
(s/def ::endpoint
(s/keys :req-un [::rs/handler]
:opt-un [::rs/name ::rs/no-doc ::middleware]))
(s/def ::data (s/def ::data
(s/keys :req-un [::rs/handler] (s/merge
:opt-un [::rs/name ::middleware])) ::endpoint
(s/map-of #{:get :head :post :put :delete :connect :options :trace :patch} map?)))
;; ;;
;; Validator ;; Validator
@ -26,21 +41,21 @@
:invalid non-specs})) :invalid non-specs}))
(s/merge-spec-impl (vec specs) (vec specs) nil)) (s/merge-spec-impl (vec specs) (vec specs) nil))
(defn validate-route-data [routes key spec] (defn validate-route-data [routes key wrap-spec spec]
(->> (for [[p _ c] routes (->> (for [[p _ c] routes
[method {:keys [data] :as endpoint}] c [method {:keys [data] :as endpoint}] c
:when endpoint :when endpoint
:let [target (key endpoint) :let [target (key endpoint)
component-specs (seq (keep :spec target)) component-specs (seq (keep :spec target))
specs (keep identity (into [spec] component-specs)) specs (keep identity (into [spec] component-specs))
spec (merge-specs specs)]] spec (wrap-spec (merge-specs specs))]]
(when-let [problems (and spec (s/explain-data spec data))] (when-let [problems (and spec (s/explain-data spec data))]
(rs/->Problem p method data spec problems))) (rs/->Problem p method data spec problems)))
(keep identity) (seq))) (keep identity) (seq)))
(defn validate (defn validate
[routes {:keys [spec] :or {spec ::data}}] [routes {:keys [spec wrap-spec] :or {spec ::data, wrap-spec identity}}]
(when-let [problems (validate-route-data routes :middleware spec)] (when-let [problems (validate-route-data routes :middleware wrap-spec spec)]
(exception/fail! (exception/fail!
::rs/invalid-route-data ::rs/invalid-route-data
{:problems problems}))) {:problems problems})))

View file

@ -37,6 +37,7 @@
[fipp "0.6.17" :exclusions [org.clojure/core.rrb-vector]] [fipp "0.6.17" :exclusions [org.clojure/core.rrb-vector]]
[expound "0.7.2"] [expound "0.7.2"]
[lambdaisland/deep-diff "0.0-47"] [lambdaisland/deep-diff "0.0-47"]
[com.bhauman/spell-spec "0.1.1"]
[ring/ring-core "1.7.1"] [ring/ring-core "1.7.1"]
[io.pedestal/pedestal.service "0.5.5"]] [io.pedestal/pedestal.service "0.5.5"]]
@ -80,6 +81,7 @@
[metosin/jsonista] [metosin/jsonista]
[lambdaisland/deep-diff] [lambdaisland/deep-diff]
[meta-merge] [meta-merge]
[com.bhauman/spell-spec]
[expound] [expound]
[fipp] [fipp]