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)**
|
||||
|
||||
* 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
|
||||
|
||||
* Updated deps:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
[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
|
||||
(require '[reitit.coercion.malli])
|
||||
(require '[reitit.coercion :as coercion])
|
||||
|
|
@ -44,6 +48,20 @@ Failing coercion:
|
|||
; => 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
|
||||
|
||||
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}}
|
||||
;; set of keys to include in error messages
|
||||
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
||||
;; support lite syntax?
|
||||
:lite true
|
||||
;; schema identity function (default: close all map schemas)
|
||||
:compile mu/closed-schema
|
||||
;; validate request & response
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
[clojure.set :as set]
|
||||
[clojure.walk :as walk]
|
||||
[malli.core :as m]
|
||||
[malli.experimental.lite :as l]
|
||||
[malli.edn :as edn]
|
||||
[malli.error :as me]
|
||||
[malli.swagger :as swagger]
|
||||
|
|
@ -115,6 +116,8 @@
|
|||
:formats {"application/json" json-transformer-provider}}}
|
||||
;; set of keys to include in error messages
|
||||
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
||||
;; support lite syntax?
|
||||
:lite true
|
||||
;; schema identity function (default: close all map schemas)
|
||||
:compile mu/closed-schema
|
||||
;; validate request & response
|
||||
|
|
@ -134,9 +137,11 @@
|
|||
([]
|
||||
(create nil))
|
||||
([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))
|
||||
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}
|
||||
(reify coercion/Coercion
|
||||
(-get-name [_] :malli)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
[metosin/muuntaja "0.6.8"]
|
||||
[metosin/sieppari "0.0.0-alpha13"]
|
||||
[metosin/jsonista "0.3.5"]
|
||||
[metosin/malli "0.8.1"]
|
||||
[metosin/malli "0.8.2-SNAPSHOT"]
|
||||
[lambdaisland/deep-diff "0.0-47"]
|
||||
[meta-merge "1.0.0"]
|
||||
[com.bhauman/spell-spec "0.1.2"]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
[reitit.coercion.spec]
|
||||
[reitit.core :as r]
|
||||
[schema.core :as s]
|
||||
[spec-tools.data-spec :as ds])
|
||||
[spec-tools.data-spec :as ds]
|
||||
[malli.experimental.lite :as l])
|
||||
#?(:clj
|
||||
(:import
|
||||
(clojure.lang ExceptionInfo))))
|
||||
|
|
@ -23,6 +24,12 @@
|
|||
:query [:maybe [:map [:int int?]
|
||||
[:ints [:vector 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}
|
||||
["/:number/:keyword" {:parameters {:path {:number int?
|
||||
:keyword keyword?}
|
||||
|
|
@ -56,6 +63,18 @@
|
|||
(let [m (r/match-by-path r "/malli/kikka/abba")]
|
||||
(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
|
||||
(testing "spec-coercion"
|
||||
(testing "succeeds"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
[reitit.ring :as ring]
|
||||
[reitit.ring.coercion :as rrc]
|
||||
[schema.core :as s]
|
||||
[spec-tools.data-spec :as ds])
|
||||
[spec-tools.data-spec :as ds]
|
||||
[malli.experimental.lite :as l])
|
||||
#?(:clj
|
||||
(:import
|
||||
(clojure.lang ExceptionInfo)
|
||||
|
|
@ -210,11 +211,15 @@
|
|||
(is (= 500 status))))))))
|
||||
|
||||
(deftest malli-coercion-test
|
||||
(let [create (fn [middleware]
|
||||
(let [create (fn [middleware routes]
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/api"
|
||||
routes
|
||||
{:data {:middleware middleware
|
||||
:coercion malli/coercion}})))]
|
||||
|
||||
(doseq [{:keys [style routes]} [{:style "malli"
|
||||
:routes ["/api"
|
||||
["/validate" {:summary "just validation"
|
||||
:coercion (reitit.coercion.malli/create {:transformers {}})
|
||||
:post {:parameters {:body [:map [:x int?]]}
|
||||
|
|
@ -253,13 +258,55 @@
|
|||
:path [:map [:e int?]]}
|
||||
:responses {200 {:body [:map [:total pos-int?]]}
|
||||
500 {:description "fail"}}
|
||||
:handler handler}}]]
|
||||
{:data {:middleware middleware
|
||||
:coercion malli/coercion}})))]
|
||||
:handler handler}}]]}
|
||||
{:style "lite"
|
||||
: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"
|
||||
(let [app (create [rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware])]
|
||||
rrc/coerce-response-middleware] routes)]
|
||||
|
||||
(testing "all good"
|
||||
(is (= {:status 200
|
||||
|
|
@ -294,7 +341,7 @@
|
|||
(testing "with exception handling"
|
||||
(let [app (create [rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware])]
|
||||
rrc/coerce-response-middleware] routes)]
|
||||
|
||||
(testing "just validation"
|
||||
(is (= 400 (:status (app {:uri "/api/validate"
|
||||
|
|
@ -342,7 +389,7 @@
|
|||
|
||||
(testing "invalid response"
|
||||
(let [{:keys [status]} (app invalid-request2)]
|
||||
(is (= 500 status))))))
|
||||
(is (= 500 status))))))))
|
||||
|
||||
(testing "open & closed schemas"
|
||||
(let [endpoint (fn [schema]
|
||||
|
|
|
|||
Loading…
Reference in a new issue