diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index 7ddcd9c3..5453665a 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -112,6 +112,7 @@ :body {:total (- x y)}})}}]]] {;;:reitit.interceptor/transform dev/print-context-diffs + ;;:wrap-spec reitit.dev.pretty/closed-keys :validate spec/validate :exception pretty/exception :data {:coercion spec-coercion/coercion diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index fbe340d9..11081f87 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -39,7 +39,8 @@ (s/def ::name keyword?) (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 @@ -75,6 +76,8 @@ ;; coercion ;; +(s/def :reitit.core.coercion/coercion any?) + (s/def :reitit.core.coercion/model any?) (s/def :reitit.core.coercion/query :reitit.core.coercion/model) @@ -90,7 +93,8 @@ :reitit.core.coercion/path])) (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/or :number number? :default #{:default})) @@ -103,7 +107,8 @@ (s/map-of :reitit.core.coercion/status :reitit.core.coercion/response)) (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 @@ -111,14 +116,14 @@ (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] - (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))) (keep identity) (seq) (vec))) -(defn validate [routes {:keys [spec] :or {spec ::default-data}}] - (when-let [problems (validate-route-data routes spec)] +(defn validate [routes {:keys [spec wrap-spec] :or {spec ::default-data, wrap-spec identity}}] + (when-let [problems (validate-route-data routes wrap-spec spec)] (exception/fail! ::invalid-route-data {:problems problems}))) diff --git a/modules/reitit-dev/project.clj b/modules/reitit-dev/project.clj index 444a2201..15aa954e 100644 --- a/modules/reitit-dev/project.clj +++ b/modules/reitit-dev/project.clj @@ -9,5 +9,6 @@ :parent-project {:path "../../project.clj" :inherit [:deploy-repositories :managed-dependencies]} :dependencies [[metosin/reitit-core] + [com.bhauman/spell-spec] [expound] [fipp]]) diff --git a/modules/reitit-dev/src/reitit/dev/pretty.cljc b/modules/reitit-dev/src/reitit/dev/pretty.cljc index fe7763ae..5b7d7d15 100644 --- a/modules/reitit-dev/src/reitit/dev/pretty.cljc +++ b/modules/reitit-dev/src/reitit/dev/pretty.cljc @@ -3,6 +3,9 @@ [clojure.spec.alpha :as s] [reitit.exception :as exception] [arrangement.core] + ;; spell-spec + [spec-tools.spell :as spell] + [spell-spec.expound] ;; expound [expound.ansi] [expound.alpha] @@ -178,7 +181,7 @@ (if (and (not= 1 line)) (let [file-name (str/replace file #"(.*?)\.\S[^\.]+" "$1") 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)) "repl") (catch #?(:clj Exception, :cljs js/Error) _ @@ -220,13 +223,16 @@ (defn exception [e] (let [data (-> e ex-data :data) message (format-exception (-> e ex-data :type) #?(:clj (.getMessage ^Exception e) :cljs (ex-message e)) data) - source #?(:clj (->> e Throwable->map :trace - (drop-while #(not= (name (first %)) "reitit.core$router")) - (drop-while #(= (name (first %)) "reitit.core$router")) - next first source-str) + source #?(:clj (->> e Throwable->map :trace + (drop-while #(not= (name (first %)) "reitit.core$router")) + (drop-while #(= (name (first %)) "reitit.core$router")) + next first source-str) :cljs "unknown")] (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] (let [s' (reduce (fn [s [from to]] @@ -316,12 +322,12 @@ (into [:group] (map - (fn [{:keys [data path spec]}] + (fn [{:keys [data path spec scope]}] [:group [:span (color :grey "-- On route -----------------------")] [:break] [:break] - (text path) + (text path) (if scope [:span " " (text scope)]) [:break] [:break] (-> (s/explain-data spec data) diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index ecb28fca..0f935966 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -2,8 +2,7 @@ (:require [meta-merge.core :refer [meta-merge]] [reitit.interceptor :as interceptor] [reitit.ring :as ring] - [reitit.core :as r] - [reitit.impl :as impl])) + [reitit.core :as r])) (defrecord Endpoint [data interceptors queue handler path method]) @@ -16,6 +15,9 @@ (defn compile-result [[path data] {:keys [::default-options-handler] :as opts}] (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] (interceptor/compile-result [path data] opts scope)) ->endpoint (fn [p d m s] @@ -29,12 +31,7 @@ (fn [acc method] (cond-> acc any? (assoc method (->endpoint path data method nil)))) - (ring/map->Methods - {:options - (if default-options-handler - (->endpoint path (assoc data - :handler default-options-handler - :no-doc true) :options nil))}) + (ring/map->Methods {}) ring/http-methods))] (if-not (seq childs) (->methods true top) diff --git a/modules/reitit-http/src/reitit/http/coercion.cljc b/modules/reitit-http/src/reitit/http/coercion.cljc index 45e62fd1..459aaadb 100644 --- a/modules/reitit-http/src/reitit/http/coercion.cljc +++ b/modules/reitit-http/src/reitit/http/coercion.cljc @@ -11,7 +11,13 @@ {:name ::coerce-request :spec ::rs/parameters :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)] {:enter (fn [ctx] (let [request (:request ctx) @@ -27,7 +33,13 @@ {:name ::coerce-response :spec ::rs/responses :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)] {:leave (fn [ctx] (let [request (:request ctx) diff --git a/modules/reitit-http/src/reitit/http/spec.cljc b/modules/reitit-http/src/reitit/http/spec.cljc index d9225d38..9c54685e 100644 --- a/modules/reitit-http/src/reitit/http/spec.cljc +++ b/modules/reitit-http/src/reitit/http/spec.cljc @@ -12,15 +12,15 @@ (s/def ::interceptors (s/coll-of (partial satisfies? interceptor/IntoInterceptor))) (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 ;; (defn validate - [routes {:keys [spec] :or {spec ::data}}] - (when-let [problems (rrs/validate-route-data routes :interceptors spec)] + [routes {:keys [spec wrap-spec] :or {spec ::data, wrap-spec identity}}] + (when-let [problems (rrs/validate-route-data routes :interceptors wrap-spec spec)] (exception/fail! ::rs/invalid-route-data {:problems problems}))) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj index 12bf779e..cb767c0d 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj @@ -11,6 +11,9 @@ (s/def ::bytes bytes?) (s/def ::size int?) +(s/def ::multipart :reitit.core.coercion/model) +(s/def ::parameters (s/keys :opt-un [::multipart])) + (def temp-file-part "Spec for file param created by ring.middleware.multipart-params.temp-file store." (st/spec @@ -41,6 +44,7 @@ (multipart-interceptor nil)) ([options] {:name ::multipart + :spec ::parameters :compile (fn [{:keys [parameters coercion]} opts] (if-let [multipart (:multipart parameters)] (let [parameter-coercion {:multipart (coercion/->ParameterCoercion diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index 4f2abef5..104e6a7d 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -30,6 +30,9 @@ (defn compile-result [[path data] {:keys [::default-options-handler] :as opts}] (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] (-> (middleware/compile-result [p d] opts s) (map->Endpoint) @@ -40,12 +43,7 @@ (fn [acc method] (cond-> acc any? (assoc method (->endpoint path data method nil)))) - (map->Methods - {:options - (if default-options-handler - (->endpoint path (assoc data - :handler default-options-handler - :no-doc true) :options nil))}) + (map->Methods {}) http-methods))] (if-not (seq childs) (->methods true top) diff --git a/modules/reitit-ring/src/reitit/ring/coercion.cljc b/modules/reitit-ring/src/reitit/ring/coercion.cljc index 489c058e..3ee5676f 100644 --- a/modules/reitit-ring/src/reitit/ring/coercion.cljc +++ b/modules/reitit-ring/src/reitit/ring/coercion.cljc @@ -25,7 +25,13 @@ {:name ::coerce-request :spec ::rs/parameters :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)] (fn [handler] (fn @@ -43,7 +49,13 @@ {:name ::coerce-response :spec ::rs/responses :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)] (fn [handler] (fn diff --git a/modules/reitit-ring/src/reitit/ring/spec.cljc b/modules/reitit-ring/src/reitit/ring/spec.cljc index ecdc2ff2..487a2211 100644 --- a/modules/reitit-ring/src/reitit/ring/spec.cljc +++ b/modules/reitit-ring/src/reitit/ring/spec.cljc @@ -9,10 +9,25 @@ ;; (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/keys :req-un [::rs/handler] - :opt-un [::rs/name ::middleware])) + (s/merge + ::endpoint + (s/map-of #{:get :head :post :put :delete :connect :options :trace :patch} map?))) ;; ;; Validator @@ -26,21 +41,21 @@ :invalid non-specs})) (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 [method {:keys [data] :as endpoint}] c :when endpoint :let [target (key endpoint) component-specs (seq (keep :spec target)) 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))] (rs/->Problem p method data spec problems))) (keep identity) (seq))) (defn validate - [routes {:keys [spec] :or {spec ::data}}] - (when-let [problems (validate-route-data routes :middleware spec)] + [routes {:keys [spec wrap-spec] :or {spec ::data, wrap-spec identity}}] + (when-let [problems (validate-route-data routes :middleware wrap-spec spec)] (exception/fail! ::rs/invalid-route-data {:problems problems}))) diff --git a/modules/reitit-swagger/src/reitit/swagger.cljc b/modules/reitit-swagger/src/reitit/swagger.cljc index eff32865..44255238 100644 --- a/modules/reitit-swagger/src/reitit/swagger.cljc +++ b/modules/reitit-swagger/src/reitit/swagger.cljc @@ -89,12 +89,12 @@ (if (and data (not no-doc)) [method (meta-merge - (apply meta-merge (keep (comp :swagger :data) middleware)) - (apply meta-merge (keep (comp :swagger :data) interceptors)) - (if coercion - (coercion/get-apidocs coercion :swagger data)) - (select-keys data [:tags :summary :description]) - (strip-top-level-keys swagger))])) + (apply meta-merge (keep (comp :swagger :data) middleware)) + (apply meta-merge (keep (comp :swagger :data) interceptors)) + (if coercion + (coercion/get-apidocs coercion :swagger data)) + (select-keys data [:tags :summary :description]) + (strip-top-level-keys swagger))])) transform-path (fn [[p _ c]] (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))] [(swagger-path p) endpoint]))] diff --git a/project.clj b/project.clj index 8b2a3176..5db61e4e 100644 --- a/project.clj +++ b/project.clj @@ -37,6 +37,7 @@ [fipp "0.6.17" :exclusions [org.clojure/core.rrb-vector]] [expound "0.7.2"] [lambdaisland/deep-diff "0.0-47"] + [com.bhauman/spell-spec "0.1.1"] [ring/ring-core "1.7.1"] [io.pedestal/pedestal.service "0.5.5"]] @@ -80,6 +81,7 @@ [metosin/jsonista] [lambdaisland/deep-diff] [meta-merge] + [com.bhauman/spell-spec] [expound] [fipp]