mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
Merge pull request #263 from metosin/north
Closed spec validation demoed in Clojure/North (WIP)
This commit is contained in:
commit
e70837b140
20 changed files with 221 additions and 68 deletions
|
|
@ -17,11 +17,15 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
||||||
* Updated dependencies:
|
* Updated dependencies:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/spec-tools "0.9.1"] is available but we use "0.9.0"
|
[metosin/spec-tools "0.9.2-alpha1"] is available but we use "0.9.0"
|
||||||
[metosin/muuntaja "0.6.4"] is available but we use "0.6.3"
|
[metosin/muuntaja "0.6.4"] is available but we use "0.6.3"
|
||||||
[lambdaisland/deep-diff "0.0-47"] is available but we use "0.0-25"
|
[lambdaisland/deep-diff "0.0-47"] is available but we use "0.0-25"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `reitit-core`
|
||||||
|
|
||||||
|
* new options `:reitit.spec/wrap` to wrap top-level route data specs when spec validation is enabled. Using `spec-tools.spell/closed` closes top-level specs.
|
||||||
|
|
||||||
### `reitit.pedestal`
|
### `reitit.pedestal`
|
||||||
|
|
||||||
* Automatically coerce Sieppari-style 1-arity `:error` handlers into Pedestal-style 2-arity `:error` handlers. Thanks to [Mathieu MARCHANDISE](https://github.com/vielmath).
|
* Automatically coerce Sieppari-style 1-arity `:error` handlers into Pedestal-style 2-arity `:error` handlers. Thanks to [Mathieu MARCHANDISE](https://github.com/vielmath).
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,11 @@
|
||||||
[reitit.http.interceptors.muuntaja :as muuntaja]
|
[reitit.http.interceptors.muuntaja :as muuntaja]
|
||||||
[reitit.http.interceptors.exception :as exception]
|
[reitit.http.interceptors.exception :as exception]
|
||||||
[reitit.http.interceptors.multipart :as multipart]
|
[reitit.http.interceptors.multipart :as multipart]
|
||||||
|
[reitit.http.spec :as spec]
|
||||||
[reitit.http.interceptors.dev :as dev]
|
[reitit.http.interceptors.dev :as dev]
|
||||||
[reitit.interceptor.sieppari :as sieppari]
|
[reitit.interceptor.sieppari :as sieppari]
|
||||||
|
[reitit.dev.pretty :as pretty]
|
||||||
|
[spec-tools.spell :as spell]
|
||||||
[ring.adapter.jetty :as jetty]
|
[ring.adapter.jetty :as jetty]
|
||||||
[aleph.http :as client]
|
[aleph.http :as client]
|
||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
|
|
@ -72,7 +75,7 @@
|
||||||
"https://randomuser.me/api/"
|
"https://randomuser.me/api/"
|
||||||
{:query-params {:seed seed, :results results}})
|
{:query-params {:seed seed, :results results}})
|
||||||
:body
|
:body
|
||||||
(partial m/decode m/instance "application/json")
|
(partial m/decode "application/json")
|
||||||
:results
|
:results
|
||||||
(fn [results]
|
(fn [results]
|
||||||
{:status 200
|
{:status 200
|
||||||
|
|
@ -109,10 +112,15 @@
|
||||||
{:status 200
|
{:status 200
|
||||||
:body {:total (- x y)}})}}]]]
|
:body {:total (- x y)}})}}]]]
|
||||||
|
|
||||||
{;;:reitit.interceptor/transform dev/print-context-diffs
|
{;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs
|
||||||
|
:validate spec/validate ;; enable spec validation for route data
|
||||||
|
:reitit.spec/wrap spell/closed ;; strict top-level validation (alpha)
|
||||||
|
:exception pretty/exception
|
||||||
:data {:coercion spec-coercion/coercion
|
:data {:coercion spec-coercion/coercion
|
||||||
:muuntaja m/instance
|
:muuntaja m/instance
|
||||||
:interceptors [;; query-params & form-params
|
:interceptors [;; swagger feature
|
||||||
|
swagger/swagger-feature
|
||||||
|
;; query-params & form-params
|
||||||
(parameters/parameters-interceptor)
|
(parameters/parameters-interceptor)
|
||||||
;; content-negotiation
|
;; content-negotiation
|
||||||
(muuntaja/format-negotiate-interceptor)
|
(muuntaja/format-negotiate-interceptor)
|
||||||
|
|
|
||||||
|
|
@ -291,8 +291,8 @@ public class Trie {
|
||||||
staticMatcher("/auth/",
|
staticMatcher("/auth/",
|
||||||
linearMatcher(
|
linearMatcher(
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
staticMatcher("login", dataMatcher(null, 1)),
|
staticMatcher("login", dataMatcher(PersistentArrayMap.EMPTY, 1)),
|
||||||
staticMatcher("recovery", dataMatcher(null, 2))), true))), true);
|
staticMatcher("recovery", dataMatcher(PersistentArrayMap.EMPTY, 2))), true))), true);
|
||||||
System.err.println(matcher);
|
System.err.println(matcher);
|
||||||
System.out.println(lookup(matcher, "/auth/login"));
|
System.out.println(lookup(matcher, "/auth/login"));
|
||||||
System.out.println(lookup(matcher, "/auth/recovery"));
|
System.out.println(lookup(matcher, "/auth/recovery"));
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
(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) 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] :or {spec ::default-data, wrap identity}}]
|
||||||
(when-let [problems (validate-route-data routes spec)]
|
(when-let [problems (validate-route-data routes wrap spec)]
|
||||||
(exception/fail!
|
(exception/fail!
|
||||||
::invalid-route-data
|
::invalid-route-data
|
||||||
{:problems problems})))
|
{:problems problems})))
|
||||||
|
|
|
||||||
|
|
@ -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]])
|
||||||
|
|
|
||||||
|
|
@ -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) _
|
||||||
|
|
@ -220,10 +223,10 @@
|
||||||
(defn exception [e]
|
(defn exception [e]
|
||||||
(let [data (-> e ex-data :data)
|
(let [data (-> e ex-data :data)
|
||||||
message (format-exception (-> e ex-data :type) #?(:clj (.getMessage ^Exception e) :cljs (ex-message e)) data)
|
message (format-exception (-> e ex-data :type) #?(:clj (.getMessage ^Exception e) :cljs (ex-message e)) data)
|
||||||
source #?(:clj (->> e Throwable->map :trace
|
source #?(:clj (->> e Throwable->map :trace
|
||||||
(drop-while #(not= (name (first %)) "reitit.core$router"))
|
(drop-while #(not= (name (first %)) "reitit.core$router"))
|
||||||
(drop-while #(= (name (first %)) "reitit.core$router"))
|
(drop-while #(= (name (first %)) "reitit.core$router"))
|
||||||
next first source-str)
|
next first source-str)
|
||||||
: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))))
|
||||||
|
|
||||||
|
|
@ -316,12 +319,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)
|
||||||
|
|
|
||||||
|
|
@ -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)) (not (:handler top)) 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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 ::rs/wrap] :or {spec ::data, wrap identity}}]
|
||||||
(when-let [problems (rrs/validate-route-data routes :interceptors spec)]
|
(when-let [problems (rrs/validate-route-data routes :interceptors wrap spec)]
|
||||||
(exception/fail!
|
(exception/fail!
|
||||||
::invalid-route-data
|
::rs/invalid-route-data
|
||||||
{:problems problems})))
|
{:problems problems})))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)) (not (:handler top)) 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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,19 @@
|
||||||
;;
|
;;
|
||||||
|
|
||||||
(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 ::data
|
(s/def ::data
|
||||||
(s/keys :req-un [::rs/handler]
|
(s/keys :opt-un [::rs/handler ::rs/name ::rs/no-doc ::middleware]))
|
||||||
:opt-un [::rs/name ::middleware]))
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; Validator
|
;; Validator
|
||||||
|
|
@ -26,21 +35,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]
|
||||||
(->> (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 (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 ::rs/wrap] :or {spec ::data, wrap identity}}]
|
||||||
(when-let [problems (validate-route-data routes :middleware spec)]
|
(when-let [problems (validate-route-data routes :middleware wrap spec)]
|
||||||
(exception/fail!
|
(exception/fail!
|
||||||
::invalid-route-data
|
::rs/invalid-route-data
|
||||||
{:problems problems})))
|
{:problems problems})))
|
||||||
|
|
|
||||||
|
|
@ -89,12 +89,12 @@
|
||||||
(if (and data (not no-doc))
|
(if (and data (not no-doc))
|
||||||
[method
|
[method
|
||||||
(meta-merge
|
(meta-merge
|
||||||
(apply meta-merge (keep (comp :swagger :data) middleware))
|
(apply meta-merge (keep (comp :swagger :data) middleware))
|
||||||
(apply meta-merge (keep (comp :swagger :data) interceptors))
|
(apply meta-merge (keep (comp :swagger :data) interceptors))
|
||||||
(if coercion
|
(if coercion
|
||||||
(coercion/get-apidocs coercion :swagger data))
|
(coercion/get-apidocs coercion :swagger data))
|
||||||
(select-keys data [:tags :summary :description])
|
(select-keys data [:tags :summary :description])
|
||||||
(strip-top-level-keys swagger))]))
|
(strip-top-level-keys swagger))]))
|
||||||
transform-path (fn [[p _ c]]
|
transform-path (fn [[p _ c]]
|
||||||
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
|
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
|
||||||
[(swagger-path p) endpoint]))]
|
[(swagger-path p) endpoint]))]
|
||||||
|
|
|
||||||
|
|
@ -296,8 +296,7 @@
|
||||||
(def app
|
(def app
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
(reduce (partial add h) [] routes)
|
(reduce (partial add h) [] routes))
|
||||||
{::trie/parameters trie/record-parameters})
|
|
||||||
(ring/create-default-handler)
|
(ring/create-default-handler)
|
||||||
{:inject-match? false, :inject-router? false}))
|
{:inject-match? false, :inject-router? false}))
|
||||||
|
|
||||||
|
|
@ -319,6 +318,7 @@
|
||||||
;; 140µs (java-segment-router)
|
;; 140µs (java-segment-router)
|
||||||
;; 60ns (java-segment-router, no injects)
|
;; 60ns (java-segment-router, no injects)
|
||||||
;; 55ns (trie-router, no injects)
|
;; 55ns (trie-router, no injects)
|
||||||
|
;; 54µs (trie-router, quick-pam)
|
||||||
;; 54ns (trie-router, no injects, optimized)
|
;; 54ns (trie-router, no injects, optimized)
|
||||||
(let [req (map->Req {:request-method :get, :uri "/user/repos"})]
|
(let [req (map->Req {:request-method :get, :uri "/user/repos"})]
|
||||||
(title "static")
|
(title "static")
|
||||||
|
|
@ -337,6 +337,7 @@
|
||||||
;; 273ns (trie-router, no injects, direct-data)
|
;; 273ns (trie-router, no injects, direct-data)
|
||||||
;; 256ns (trie-router, pre-defined parameters)
|
;; 256ns (trie-router, pre-defined parameters)
|
||||||
;; 237ns (trie-router, single-sweep wild-params)
|
;; 237ns (trie-router, single-sweep wild-params)
|
||||||
|
;; 226µs (trie-router, quick-pam)
|
||||||
;; 191ns (trie-router, record parameters)
|
;; 191ns (trie-router, record parameters)
|
||||||
(let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})]
|
(let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})]
|
||||||
(title "param")
|
(title "param")
|
||||||
|
|
@ -354,6 +355,7 @@
|
||||||
;; 63µs (trie-router, no injects, switch-case) - 124µs (clojure)
|
;; 63µs (trie-router, no injects, switch-case) - 124µs (clojure)
|
||||||
;; 63µs (trie-router, no injects, direct-data)
|
;; 63µs (trie-router, no injects, direct-data)
|
||||||
;; 54µs (trie-router, non-transient params)
|
;; 54µs (trie-router, non-transient params)
|
||||||
|
;; 50µs (trie-router, quick-pam)
|
||||||
;; 49µs (trie-router, pre-defined parameters)
|
;; 49µs (trie-router, pre-defined parameters)
|
||||||
(let [requests (mapv route->req routes)]
|
(let [requests (mapv route->req routes)]
|
||||||
(title "all")
|
(title "all")
|
||||||
|
|
|
||||||
96
perf-test/clj/reitit/request_perf.cljc
Normal file
96
perf-test/clj/reitit/request_perf.cljc
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
(ns reitit.request-perf
|
||||||
|
(:require [criterium.core :as cc]
|
||||||
|
[reitit.perf-utils :refer :all]
|
||||||
|
[potemkin :as p]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; start repl with `lein perf repl`
|
||||||
|
;; perf measured with the following setup:
|
||||||
|
;;
|
||||||
|
;; Model Name: MacBook Pro
|
||||||
|
;; Model Identifier: MacBookPro113
|
||||||
|
;; Processor Name: Intel Core i7
|
||||||
|
;; Processor Speed: 2,5 GHz
|
||||||
|
;; Number of Processors: 1
|
||||||
|
;; Total Number of Cores: 4
|
||||||
|
;; L2 Cache (per Core): 256 KB
|
||||||
|
;; L3 Cache: 6 MB
|
||||||
|
;; Memory: 16 GB
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defprotocol RawRequest
|
||||||
|
(-uri [this])
|
||||||
|
(-request-method [this])
|
||||||
|
(-path-params [this]))
|
||||||
|
|
||||||
|
(p/def-derived-map
|
||||||
|
ZeroCopyRequest
|
||||||
|
[raw]
|
||||||
|
:uri (-uri raw)
|
||||||
|
:request-method (-request-method raw)
|
||||||
|
:path-params (-path-params raw))
|
||||||
|
|
||||||
|
(defprotocol RingRequest
|
||||||
|
(get-uri [this])
|
||||||
|
(get-request-method [this])
|
||||||
|
(get-path-params [this]))
|
||||||
|
|
||||||
|
(defn ring-request [raw]
|
||||||
|
{:uri (-uri raw)
|
||||||
|
:request-method (-request-method raw)
|
||||||
|
:path-params (-path-params raw)})
|
||||||
|
|
||||||
|
(defn record-request [raw]
|
||||||
|
(->RecordRequest (-uri raw) (-request-method raw) (-path-params raw)))
|
||||||
|
|
||||||
|
(defrecord RawRingRequest [raw]
|
||||||
|
RingRequest
|
||||||
|
(get-uri [_] (-uri raw))
|
||||||
|
(get-request-method [_] (-request-method raw))
|
||||||
|
(get-path-params [_] (-path-params raw)))
|
||||||
|
|
||||||
|
(def raw
|
||||||
|
(reify
|
||||||
|
RawRequest
|
||||||
|
(-uri [_] "/ping")
|
||||||
|
(-request-method [_] :get)
|
||||||
|
(-path-params [_] {:a 1})))
|
||||||
|
|
||||||
|
(defn bench-all! []
|
||||||
|
|
||||||
|
;; 530ns
|
||||||
|
(title "potemkin zero-copy")
|
||||||
|
(assert (= :get (:request-method (->ZeroCopyRequest raw))))
|
||||||
|
(cc/quick-bench
|
||||||
|
(let [req (->ZeroCopyRequest raw)]
|
||||||
|
(dotimes [_ 10]
|
||||||
|
(:request-method req))))
|
||||||
|
|
||||||
|
;; 73ns
|
||||||
|
(title "map copy-request")
|
||||||
|
(assert (= :get (:request-method (ring-request raw))))
|
||||||
|
(cc/quick-bench
|
||||||
|
(let [req (ring-request raw)]
|
||||||
|
(dotimes [_ 10]
|
||||||
|
(:request-method req))))
|
||||||
|
|
||||||
|
;; 7ns
|
||||||
|
(title "record copy-request")
|
||||||
|
(assert (= :get (:request-method (record-request raw))))
|
||||||
|
(cc/quick-bench
|
||||||
|
(let [req (record-request raw)]
|
||||||
|
(dotimes [_ 10]
|
||||||
|
(:request-method req))))
|
||||||
|
|
||||||
|
;; 7ns
|
||||||
|
(title "request protocols")
|
||||||
|
(assert (= :get (get-request-method (->RawRingRequest raw))))
|
||||||
|
(cc/quick-bench
|
||||||
|
(let [req (->RawRingRequest raw)]
|
||||||
|
(dotimes [_ 10]
|
||||||
|
(get-request-method req)))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(bench-all!))
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
[metosin/reitit-sieppari "0.3.1"]
|
[metosin/reitit-sieppari "0.3.1"]
|
||||||
[metosin/reitit-pedestal "0.3.1"]
|
[metosin/reitit-pedestal "0.3.1"]
|
||||||
[metosin/ring-swagger-ui "2.2.10"]
|
[metosin/ring-swagger-ui "2.2.10"]
|
||||||
[metosin/spec-tools "0.9.1"]
|
[metosin/spec-tools "0.9.2-alpha1"]
|
||||||
[metosin/schema-tools "0.11.0"]
|
[metosin/schema-tools "0.11.0"]
|
||||||
[metosin/muuntaja "0.6.4"]
|
[metosin/muuntaja "0.6.4"]
|
||||||
[metosin/jsonista "0.2.2"]
|
[metosin/jsonista "0.2.2"]
|
||||||
|
|
@ -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"]]
|
||||||
|
|
@ -69,7 +70,7 @@
|
||||||
|
|
||||||
:java-source-paths ["modules/reitit-core/java-src"]
|
:java-source-paths ["modules/reitit-core/java-src"]
|
||||||
|
|
||||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
:dependencies [[org.clojure/clojure "1.10.1-beta2"]
|
||||||
[org.clojure/clojurescript "1.10.520"]
|
[org.clojure/clojurescript "1.10.520"]
|
||||||
|
|
||||||
;; modules dependencies
|
;; modules dependencies
|
||||||
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -495,11 +495,11 @@
|
||||||
(fn [{:keys [::r/router]}]
|
(fn [{:keys [::r/router]}]
|
||||||
(is router))
|
(is router))
|
||||||
{:executor sieppari/executor})
|
{:executor sieppari/executor})
|
||||||
{}))
|
{}))
|
||||||
(testing "3-arity"
|
(testing "3-arity"
|
||||||
((http/ring-handler
|
((http/ring-handler
|
||||||
(http/router [])
|
(http/router [])
|
||||||
(fn [{:keys [::r/router]}]
|
(fn [{:keys [::r/router]}]
|
||||||
(is router))
|
(is router))
|
||||||
{:executor sieppari/executor})
|
{:executor sieppari/executor})
|
||||||
{} ::respond ::raise)))
|
{} ::respond ::raise)))
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@
|
||||||
(testing "with default spec validates :name, :handler and :middleware"
|
(testing "with default spec validates :name, :handler and :middleware"
|
||||||
(is (thrown-with-msg?
|
(is (thrown-with-msg?
|
||||||
ExceptionInfo
|
ExceptionInfo
|
||||||
#":reitit.ring.spec/invalid-route-data"
|
#"Invalid route data"
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api" {:handler "identity"}]
|
["/api" {:handler "identity"}]
|
||||||
{:validate rrs/validate})))
|
{:validate rrs/validate})))
|
||||||
(is (thrown-with-msg?
|
(is (thrown-with-msg?
|
||||||
ExceptionInfo
|
ExceptionInfo
|
||||||
#":reitit.ring.spec/invalid-route-data"
|
#"Invalid route data"
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api" {:handler identity
|
["/api" {:handler identity
|
||||||
:name "kikka"}]
|
:name "kikka"}]
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
(testing "all endpoints are validated"
|
(testing "all endpoints are validated"
|
||||||
(is (thrown-with-msg?
|
(is (thrown-with-msg?
|
||||||
ExceptionInfo
|
ExceptionInfo
|
||||||
#":reitit.ring.spec/invalid-route-data"
|
#"Invalid route data"
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api" {:patch {:handler "identity"}}]
|
["/api" {:patch {:handler "identity"}}]
|
||||||
{:validate rrs/validate}))))
|
{:validate rrs/validate}))))
|
||||||
|
|
@ -69,7 +69,7 @@
|
||||||
(handler request)))}]}})))
|
(handler request)))}]}})))
|
||||||
(is (thrown-with-msg?
|
(is (thrown-with-msg?
|
||||||
ExceptionInfo
|
ExceptionInfo
|
||||||
#":reitit.ring.spec/invalid-route-data"
|
#"Invalid route data"
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api" {:get {:handler identity
|
["/api" {:get {:handler identity
|
||||||
:roles #{:adminz}}}]
|
:roles #{:adminz}}}]
|
||||||
|
|
@ -113,7 +113,7 @@
|
||||||
|
|
||||||
(is (thrown-with-msg?
|
(is (thrown-with-msg?
|
||||||
ExceptionInfo
|
ExceptionInfo
|
||||||
#":reitit.ring.spec/invalid-route-data"
|
#"Invalid route data"
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api"
|
["/api"
|
||||||
["/plus/:e"
|
["/plus/:e"
|
||||||
|
|
|
||||||
|
|
@ -570,10 +570,10 @@
|
||||||
(ring/router [])
|
(ring/router [])
|
||||||
(fn [{:keys [::r/router]}]
|
(fn [{:keys [::r/router]}]
|
||||||
(is router)))
|
(is router)))
|
||||||
{}))
|
{}))
|
||||||
(testing "3-arity"
|
(testing "3-arity"
|
||||||
((ring/ring-handler
|
((ring/ring-handler
|
||||||
(ring/router [])
|
(ring/router [])
|
||||||
(fn [{:keys [::r/router]} _ _]
|
(fn [{:keys [::r/router]} _ _]
|
||||||
(is router)))
|
(is router)))
|
||||||
{} ::respond ::raise)))
|
{} ::respond ::raise)))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue