Merge pull request #529 from metosin/malli-lite

Add support for malli-lite
This commit is contained in:
Tommi Reiman 2022-02-14 16:55:03 +02:00 committed by GitHub
commit 45fbe5eaa2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 226 additions and 123 deletions

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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"]

View file

@ -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"

View file

@ -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]