mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
Merge pull request #529 from metosin/malli-lite
Add support for malli-lite
This commit is contained in:
commit
45fbe5eaa2
6 changed files with 226 additions and 123 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -16,6 +16,18 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
||||||
|
|
||||||
**[compare](https://github.com/metosin/reitit/compare/0.5.15...master)**
|
**[compare](https://github.com/metosin/reitit/compare/0.5.15...master)**
|
||||||
|
|
||||||
|
* Support for [Malli Lite Syntax](https://github.com/metosin/malli#lite) in coercion (enabled by default):
|
||||||
|
|
||||||
|
```clj
|
||||||
|
["/add/:id" {:post {:parameters {:path {:id int?}
|
||||||
|
:query {:a (l/optional int?)}
|
||||||
|
:body {:id int?
|
||||||
|
:data {:id (l/maybe int?)
|
||||||
|
:orders (l/map-of uuid? {:name string?})}}}
|
||||||
|
:responses {200 {:body {:total pos-int?}}
|
||||||
|
500 {:description "fail"}}}}]
|
||||||
|
```
|
||||||
|
|
||||||
* Improved Reitit-frontend function docstrings
|
* Improved Reitit-frontend function docstrings
|
||||||
|
|
||||||
* Updated deps:
|
* Updated deps:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
[Malli](https://github.com/metosin/malli) is data-driven Schema library for Clojure/Script.
|
[Malli](https://github.com/metosin/malli) is data-driven Schema library for Clojure/Script.
|
||||||
|
|
||||||
|
## Default Syntax
|
||||||
|
|
||||||
|
By default, [Vector Syntax](https://github.com/metosin/malli#vector-syntax) is used:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.coercion.malli])
|
(require '[reitit.coercion.malli])
|
||||||
(require '[reitit.coercion :as coercion])
|
(require '[reitit.coercion :as coercion])
|
||||||
|
|
@ -44,6 +48,20 @@ Failing coercion:
|
||||||
; => ExceptionInfo Request coercion failed...
|
; => ExceptionInfo Request coercion failed...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Lite Syntax
|
||||||
|
|
||||||
|
Same using [Lite Syntax](https://github.com/metosin/malli#lite):
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def router
|
||||||
|
(r/router
|
||||||
|
["/:company/users/:user-id" {:name ::user-view
|
||||||
|
:coercion reitit.coercion.malli/coercion
|
||||||
|
:parameters {:path {:company string?
|
||||||
|
:user-id int?}}}]
|
||||||
|
{:compile coercion/compile-request-coercers}))
|
||||||
|
```
|
||||||
|
|
||||||
## Configuring coercion
|
## Configuring coercion
|
||||||
|
|
||||||
Using `create` with options to create the coercion instead of `coercion`:
|
Using `create` with options to create the coercion instead of `coercion`:
|
||||||
|
|
@ -58,6 +76,8 @@ Using `create` with options to create the coercion instead of `coercion`:
|
||||||
:response {:default reitit.coercion.malli/default-transformer-provider}}
|
:response {:default reitit.coercion.malli/default-transformer-provider}}
|
||||||
;; set of keys to include in error messages
|
;; set of keys to include in error messages
|
||||||
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
||||||
|
;; support lite syntax?
|
||||||
|
:lite true
|
||||||
;; schema identity function (default: close all map schemas)
|
;; schema identity function (default: close all map schemas)
|
||||||
:compile mu/closed-schema
|
:compile mu/closed-schema
|
||||||
;; validate request & response
|
;; validate request & response
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[clojure.walk :as walk]
|
[clojure.walk :as walk]
|
||||||
[malli.core :as m]
|
[malli.core :as m]
|
||||||
|
[malli.experimental.lite :as l]
|
||||||
[malli.edn :as edn]
|
[malli.edn :as edn]
|
||||||
[malli.error :as me]
|
[malli.error :as me]
|
||||||
[malli.swagger :as swagger]
|
[malli.swagger :as swagger]
|
||||||
|
|
@ -115,6 +116,8 @@
|
||||||
:formats {"application/json" json-transformer-provider}}}
|
:formats {"application/json" json-transformer-provider}}}
|
||||||
;; set of keys to include in error messages
|
;; set of keys to include in error messages
|
||||||
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
||||||
|
;; support lite syntax?
|
||||||
|
:lite true
|
||||||
;; schema identity function (default: close all map schemas)
|
;; schema identity function (default: close all map schemas)
|
||||||
:compile mu/closed-schema
|
:compile mu/closed-schema
|
||||||
;; validate request & response
|
;; validate request & response
|
||||||
|
|
@ -134,9 +137,11 @@
|
||||||
([]
|
([]
|
||||||
(create nil))
|
(create nil))
|
||||||
([opts]
|
([opts]
|
||||||
(let [{:keys [transformers compile options error-keys encode-error] :as opts} (merge default-options opts)
|
(let [{:keys [transformers lite compile options error-keys encode-error] :as opts} (merge default-options opts)
|
||||||
show? (fn [key] (contains? error-keys key))
|
show? (fn [key] (contains? error-keys key))
|
||||||
transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers)]
|
transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers)
|
||||||
|
compile (if lite (fn [schema options] (compile (binding [l/*options* options] (l/schema schema)) options))
|
||||||
|
compile)]
|
||||||
^{:type ::coercion/coercion}
|
^{:type ::coercion/coercion}
|
||||||
(reify coercion/Coercion
|
(reify coercion/Coercion
|
||||||
(-get-name [_] :malli)
|
(-get-name [_] :malli)
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@
|
||||||
[metosin/muuntaja "0.6.8"]
|
[metosin/muuntaja "0.6.8"]
|
||||||
[metosin/sieppari "0.0.0-alpha13"]
|
[metosin/sieppari "0.0.0-alpha13"]
|
||||||
[metosin/jsonista "0.3.5"]
|
[metosin/jsonista "0.3.5"]
|
||||||
[metosin/malli "0.8.1"]
|
[metosin/malli "0.8.2-SNAPSHOT"]
|
||||||
[lambdaisland/deep-diff "0.0-47"]
|
[lambdaisland/deep-diff "0.0-47"]
|
||||||
[meta-merge "1.0.0"]
|
[meta-merge "1.0.0"]
|
||||||
[com.bhauman/spell-spec "0.1.2"]
|
[com.bhauman/spell-spec "0.1.2"]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
[reitit.coercion.spec]
|
[reitit.coercion.spec]
|
||||||
[reitit.core :as r]
|
[reitit.core :as r]
|
||||||
[schema.core :as s]
|
[schema.core :as s]
|
||||||
[spec-tools.data-spec :as ds])
|
[spec-tools.data-spec :as ds]
|
||||||
|
[malli.experimental.lite :as l])
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import
|
(:import
|
||||||
(clojure.lang ExceptionInfo))))
|
(clojure.lang ExceptionInfo))))
|
||||||
|
|
@ -23,6 +24,12 @@
|
||||||
:query [:maybe [:map [:int int?]
|
:query [:maybe [:map [:int int?]
|
||||||
[:ints [:vector int?]]
|
[:ints [:vector int?]]
|
||||||
[:map [:map-of int? int?]]]]}}]]
|
[:map [:map-of int? int?]]]]}}]]
|
||||||
|
["/malli-lite" {:coercion reitit.coercion.malli/coercion}
|
||||||
|
["/:number/:keyword" {:parameters {:path {:number int?
|
||||||
|
:keyword keyword?}
|
||||||
|
:query (l/maybe {:int int?
|
||||||
|
:ints (l/vector int?)
|
||||||
|
:map (l/map-of int? int?)})}}]]
|
||||||
["/spec" {:coercion reitit.coercion.spec/coercion}
|
["/spec" {:coercion reitit.coercion.spec/coercion}
|
||||||
["/:number/:keyword" {:parameters {:path {:number int?
|
["/:number/:keyword" {:parameters {:path {:number int?
|
||||||
:keyword keyword?}
|
:keyword keyword?}
|
||||||
|
|
@ -56,6 +63,18 @@
|
||||||
(let [m (r/match-by-path r "/malli/kikka/abba")]
|
(let [m (r/match-by-path r "/malli/kikka/abba")]
|
||||||
(is (thrown? ExceptionInfo (coercion/coerce! m))))))
|
(is (thrown? ExceptionInfo (coercion/coerce! m))))))
|
||||||
|
|
||||||
|
(testing "malli-lite coercion"
|
||||||
|
(testing "succeeds"
|
||||||
|
(let [m (r/match-by-path r "/malli-lite/1/abba")]
|
||||||
|
(is (= {:path {:keyword :abba, :number 1}, :query nil}
|
||||||
|
(coercion/coerce! m))))
|
||||||
|
(let [m (r/match-by-path r "/malli-lite/1/abba")]
|
||||||
|
(is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, 2 2}}}
|
||||||
|
(coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1", "2" "2"}}))))))
|
||||||
|
(testing "throws with invalid input"
|
||||||
|
(let [m (r/match-by-path r "/malli-lite/kikka/abba")]
|
||||||
|
(is (thrown? ExceptionInfo (coercion/coerce! m))))))
|
||||||
|
|
||||||
;; TODO: :map-of fails with string-keys
|
;; TODO: :map-of fails with string-keys
|
||||||
(testing "spec-coercion"
|
(testing "spec-coercion"
|
||||||
(testing "succeeds"
|
(testing "succeeds"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
[reitit.ring :as ring]
|
[reitit.ring :as ring]
|
||||||
[reitit.ring.coercion :as rrc]
|
[reitit.ring.coercion :as rrc]
|
||||||
[schema.core :as s]
|
[schema.core :as s]
|
||||||
[spec-tools.data-spec :as ds])
|
[spec-tools.data-spec :as ds]
|
||||||
|
[malli.experimental.lite :as l])
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import
|
(:import
|
||||||
(clojure.lang ExceptionInfo)
|
(clojure.lang ExceptionInfo)
|
||||||
|
|
@ -210,11 +211,15 @@
|
||||||
(is (= 500 status))))))))
|
(is (= 500 status))))))))
|
||||||
|
|
||||||
(deftest malli-coercion-test
|
(deftest malli-coercion-test
|
||||||
(let [create (fn [middleware]
|
(let [create (fn [middleware routes]
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api"
|
routes
|
||||||
|
{:data {:middleware middleware
|
||||||
|
:coercion malli/coercion}})))]
|
||||||
|
|
||||||
|
(doseq [{:keys [style routes]} [{:style "malli"
|
||||||
|
:routes ["/api"
|
||||||
["/validate" {:summary "just validation"
|
["/validate" {:summary "just validation"
|
||||||
:coercion (reitit.coercion.malli/create {:transformers {}})
|
:coercion (reitit.coercion.malli/create {:transformers {}})
|
||||||
:post {:parameters {:body [:map [:x int?]]}
|
:post {:parameters {:body [:map [:x int?]]}
|
||||||
|
|
@ -253,13 +258,55 @@
|
||||||
:path [:map [:e int?]]}
|
:path [:map [:e int?]]}
|
||||||
:responses {200 {:body [:map [:total pos-int?]]}
|
:responses {200 {:body [:map [:total pos-int?]]}
|
||||||
500 {:description "fail"}}
|
500 {:description "fail"}}
|
||||||
:handler handler}}]]
|
:handler handler}}]]}
|
||||||
{:data {:middleware middleware
|
{:style "lite"
|
||||||
:coercion malli/coercion}})))]
|
:routes ["/api"
|
||||||
|
|
||||||
|
["/validate" {:summary "just validation"
|
||||||
|
:coercion (reitit.coercion.malli/create {:transformers {}})
|
||||||
|
:post {:parameters {:body {:x int?}}
|
||||||
|
:responses {200 {:body {:x int?}}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body (-> req :parameters :body)})}}]
|
||||||
|
|
||||||
|
["/no-op" {:summary "no-operation"
|
||||||
|
:coercion (reitit.coercion.malli/create {:transformers {}, :validate false})
|
||||||
|
:post {:parameters {:body {:x int?}}
|
||||||
|
:responses {200 {:body {:x int?}}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body (-> req :parameters :body)})}}]
|
||||||
|
|
||||||
|
["/skip" {:summary "skip"
|
||||||
|
:coercion (reitit.coercion.malli/create {:enabled false})
|
||||||
|
:post {:parameters {:body {:x int?}}
|
||||||
|
:responses {200 {:body {:x int?}}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body (-> req :parameters :body)})}}]
|
||||||
|
|
||||||
|
["/or" {:post {:summary "accepts either of two map schemas"
|
||||||
|
:parameters {:body (l/or {:x int?} {:y int?})}
|
||||||
|
:responses {200 {:body {:msg string?}}}
|
||||||
|
:handler (fn [{{{:keys [x]} :body} :parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:msg (if x "you sent x" "you sent y")}})}}]
|
||||||
|
|
||||||
|
["/plus/:e" {:get {:parameters {:query {:a (l/optional int?)}
|
||||||
|
:body {:b int?}
|
||||||
|
:form {:c [int? {:default 3}]}
|
||||||
|
:header {:d int?}
|
||||||
|
:path {:e int?}}
|
||||||
|
:responses {200 {:body {:total pos-int?}}
|
||||||
|
500 {:description "fail"}}
|
||||||
|
:handler handler}}]]}]]
|
||||||
|
|
||||||
|
(testing (str "malli with style " style)
|
||||||
|
|
||||||
(testing "without exception handling"
|
(testing "without exception handling"
|
||||||
(let [app (create [rrc/coerce-request-middleware
|
(let [app (create [rrc/coerce-request-middleware
|
||||||
rrc/coerce-response-middleware])]
|
rrc/coerce-response-middleware] routes)]
|
||||||
|
|
||||||
(testing "all good"
|
(testing "all good"
|
||||||
(is (= {:status 200
|
(is (= {:status 200
|
||||||
|
|
@ -294,7 +341,7 @@
|
||||||
(testing "with exception handling"
|
(testing "with exception handling"
|
||||||
(let [app (create [rrc/coerce-exceptions-middleware
|
(let [app (create [rrc/coerce-exceptions-middleware
|
||||||
rrc/coerce-request-middleware
|
rrc/coerce-request-middleware
|
||||||
rrc/coerce-response-middleware])]
|
rrc/coerce-response-middleware] routes)]
|
||||||
|
|
||||||
(testing "just validation"
|
(testing "just validation"
|
||||||
(is (= 400 (:status (app {:uri "/api/validate"
|
(is (= 400 (:status (app {:uri "/api/validate"
|
||||||
|
|
@ -342,7 +389,7 @@
|
||||||
|
|
||||||
(testing "invalid response"
|
(testing "invalid response"
|
||||||
(let [{:keys [status]} (app invalid-request2)]
|
(let [{:keys [status]} (app invalid-request2)]
|
||||||
(is (= 500 status))))))
|
(is (= 500 status))))))))
|
||||||
|
|
||||||
(testing "open & closed schemas"
|
(testing "open & closed schemas"
|
||||||
(let [endpoint (fn [schema]
|
(let [endpoint (fn [schema]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue