mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
Test out frontend optimized coercion impl for Malli
This commit is contained in:
parent
34b6bb9349
commit
be92cceb83
11 changed files with 1640 additions and 96 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -9,9 +9,10 @@ pom.xml.asc
|
||||||
.nrepl-port
|
.nrepl-port
|
||||||
/.nrepl-history
|
/.nrepl-history
|
||||||
/gh-pages
|
/gh-pages
|
||||||
/node_modules
|
node_modules
|
||||||
/_book
|
/_book
|
||||||
figwheel_server.log
|
figwheel_server.log
|
||||||
/.idea
|
/.idea
|
||||||
.clj-kondo
|
.clj-kondo
|
||||||
.shadow-cljs
|
.shadow-cljs
|
||||||
|
|
||||||
|
|
|
||||||
1452
examples/frontend-malli/package-lock.json
generated
Normal file
1452
examples/frontend-malli/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
18
examples/frontend-malli/package.json
Normal file
18
examples/frontend-malli/package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "frontend-malli",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "## Usage",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"shadow-cljs": "^2.28.20"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
(defproject frontend-malli "0.1.0-SNAPSHOT"
|
|
||||||
:description "FIXME: write description"
|
|
||||||
:url "http://example.com/FIXME"
|
|
||||||
:license {:name "Eclipse Public License"
|
|
||||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
|
||||||
|
|
||||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
|
||||||
[ring-server "0.5.0"]
|
|
||||||
[reagent "1.2.0"]
|
|
||||||
[ring "1.12.1"]
|
|
||||||
[hiccup "1.0.5"]
|
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
|
||||||
[metosin/reitit-malli "0.8.0-alpha1"]
|
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
|
||||||
[cljsjs/react "17.0.2-0"]
|
|
||||||
[cljsjs/react-dom "17.0.2-0"]
|
|
||||||
;; Just for pretty printting the match
|
|
||||||
[fipp "0.6.23"]]
|
|
||||||
|
|
||||||
:plugins [[lein-cljsbuild "1.1.8"]
|
|
||||||
[lein-figwheel "0.5.20"]]
|
|
||||||
|
|
||||||
:source-paths []
|
|
||||||
:resource-paths ["resources" "target/cljsbuild"]
|
|
||||||
|
|
||||||
:profiles {:dev {:dependencies [[binaryage/devtools "1.0.2"]]}}
|
|
||||||
|
|
||||||
:cljsbuild
|
|
||||||
{:builds
|
|
||||||
[{:id "app"
|
|
||||||
:figwheel true
|
|
||||||
:source-paths ["src"]
|
|
||||||
:watch-paths ["src" "checkouts/reitit-frontend/src"]
|
|
||||||
:compiler {:main "frontend.core"
|
|
||||||
:asset-path "/js/out"
|
|
||||||
:output-to "target/cljsbuild/public/js/app.js"
|
|
||||||
:output-dir "target/cljsbuild/public/js/out"
|
|
||||||
:source-map true
|
|
||||||
:optimizations :none
|
|
||||||
:pretty-print true
|
|
||||||
:preloads [devtools.preload]
|
|
||||||
:aot-cache true}}
|
|
||||||
{:id "min"
|
|
||||||
:source-paths ["src"]
|
|
||||||
:compiler {:output-to "target/cljsbuild/public/js/app.js"
|
|
||||||
:output-dir "target/cljsbuild/public/js"
|
|
||||||
:source-map "target/cljsbuild/public/js/app.js.map"
|
|
||||||
:optimizations :advanced
|
|
||||||
:pretty-print false
|
|
||||||
:aot-cache true}}]}
|
|
||||||
|
|
||||||
:figwheel {:http-server-root "public"
|
|
||||||
:server-port 3449
|
|
||||||
:nrepl-port 7002
|
|
||||||
;; Server index.html for all routes for HTML5 routing
|
|
||||||
:ring-handler backend.server/handler})
|
|
||||||
|
|
@ -5,6 +5,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="/js/app.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
13
examples/frontend-malli/shadow-cljs.edn
Normal file
13
examples/frontend-malli/shadow-cljs.edn
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{:builds
|
||||||
|
{:app {:asset-path "/js"
|
||||||
|
:modules {:main {:entries [frontend.core]}}
|
||||||
|
:output-dir "target/public/js"
|
||||||
|
:target :browser}}
|
||||||
|
:dependencies [[reagent "1.2.0"]
|
||||||
|
[metosin/reitit "0.8.0-alpha1"]
|
||||||
|
[metosin/reitit-malli "0.8.0-alpha1"]
|
||||||
|
[metosin/reitit-frontend "0.8.0-alpha1"]
|
||||||
|
[fipp "0.6.23"]]
|
||||||
|
:dev-http {3000 ["resources/public" "target/public"]}
|
||||||
|
:nrepl {:port 3333}
|
||||||
|
:source-paths ["src"]}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
(ns backend.server
|
|
||||||
(:require [ring.util.response :as resp]
|
|
||||||
[ring.middleware.content-type :as content-type]))
|
|
||||||
|
|
||||||
(def handler
|
|
||||||
(-> (fn [request]
|
|
||||||
(or (resp/resource-response (:uri request) {:root "public"})
|
|
||||||
(-> (resp/resource-response "index.html" {:root "public"})
|
|
||||||
(resp/content-type "text/html"))))
|
|
||||||
content-type/wrap-content-type))
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
[reagent.dom :as rd]
|
[reagent.dom :as rd]
|
||||||
[reitit.frontend :as rf]
|
[reitit.frontend :as rf]
|
||||||
[reitit.frontend.easy :as rfe]
|
[reitit.frontend.easy :as rfe]
|
||||||
[reitit.coercion.malli :as rsm]
|
[reitit.coercion.frontend.malli :as rsm]
|
||||||
[fipp.edn :as fedn]))
|
[fipp.edn :as fedn]))
|
||||||
|
|
||||||
(defn home-page []
|
(defn home-page []
|
||||||
|
|
|
||||||
130
modules/reitit-malli/src/reitit/coercion/frontend/malli.cljc
Normal file
130
modules/reitit-malli/src/reitit/coercion/frontend/malli.cljc
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
(ns reitit.coercion.frontend.malli
|
||||||
|
"Optimized coercion implementation use with
|
||||||
|
Reitit-frontend.
|
||||||
|
|
||||||
|
Only supports string coercion, OpenAPI and Swagger support
|
||||||
|
removed."
|
||||||
|
(:require [clojure.walk :as walk]
|
||||||
|
[malli.core :as m]
|
||||||
|
[malli.experimental.lite :as l]
|
||||||
|
[malli.transform :as mt]
|
||||||
|
[malli.util :as mu]
|
||||||
|
[reitit.coercion :as coercion]
|
||||||
|
[reitit.coercion.malli.protocols :as p]))
|
||||||
|
|
||||||
|
(defn- -provider [transformer]
|
||||||
|
(reify p/TransformationProvider
|
||||||
|
(-transformer [_ {:keys [strip-extra-keys default-values]}]
|
||||||
|
(mt/transformer
|
||||||
|
(if strip-extra-keys (mt/strip-extra-keys-transformer))
|
||||||
|
transformer
|
||||||
|
(if default-values (mt/default-value-transformer))))))
|
||||||
|
|
||||||
|
(def string-transformer-provider (-provider (mt/string-transformer)))
|
||||||
|
(def default-transformer-provider (-provider nil))
|
||||||
|
|
||||||
|
(defn- -coercer [schema type transformers {:keys [validate enabled options]}]
|
||||||
|
(if schema
|
||||||
|
(let [->coercer (fn [t]
|
||||||
|
(let [decoder (if t (m/decoder schema options t) identity)
|
||||||
|
encoder (if t (m/encoder schema options t) identity)
|
||||||
|
validator (if validate (m/validator schema options) (constantly true))
|
||||||
|
explainer (m/explainer schema options)]
|
||||||
|
(reify p/Coercer
|
||||||
|
(-decode [_ value] (decoder value))
|
||||||
|
(-encode [_ value] (encoder value))
|
||||||
|
(-validate [_ value] (validator value))
|
||||||
|
(-explain [_ value] (explainer value)))))
|
||||||
|
{:keys [default]} (transformers type)
|
||||||
|
default-coercer (->coercer default)
|
||||||
|
coercer default-coercer]
|
||||||
|
(if (and enabled coercer)
|
||||||
|
(fn [value format]
|
||||||
|
(if coercer
|
||||||
|
(let [transformed (p/-decode coercer value)]
|
||||||
|
(if (p/-validate coercer transformed)
|
||||||
|
transformed
|
||||||
|
(let [error (p/-explain coercer transformed)]
|
||||||
|
(coercion/map->CoercionError
|
||||||
|
(assoc error :transformed transformed)))))
|
||||||
|
value))))))
|
||||||
|
|
||||||
|
(defn- -query-string-coercer
|
||||||
|
"Create coercer for query-parameters, always allows extra params and does
|
||||||
|
encoding using string-transformer."
|
||||||
|
[schema string-transformer-provider options]
|
||||||
|
(let [;; Always allow extra paramaters on query-parameters encoding
|
||||||
|
open-schema (mu/open-schema schema)
|
||||||
|
;; Do not remove extra keys
|
||||||
|
string-transformer (if (satisfies? p/TransformationProvider string-transformer-provider)
|
||||||
|
(p/-transformer string-transformer-provider (assoc options :strip-extra-keys false))
|
||||||
|
string-transformer-provider)
|
||||||
|
encoder (m/encoder open-schema options string-transformer)]
|
||||||
|
(fn [value format]
|
||||||
|
(if encoder
|
||||||
|
(encoder value)
|
||||||
|
value))))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; public api
|
||||||
|
;;
|
||||||
|
|
||||||
|
;; TODO: this is much too compöex
|
||||||
|
(def default-options
|
||||||
|
{:transformers {:body {:default default-transformer-provider}
|
||||||
|
:string {:default string-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
|
||||||
|
:validate true
|
||||||
|
;; top-level short-circuit to disable request & response coercion
|
||||||
|
:enabled true
|
||||||
|
;; strip-extra-keys (affects only predefined transformers)
|
||||||
|
:strip-extra-keys true
|
||||||
|
;; add/set default values
|
||||||
|
:default-values true
|
||||||
|
;; encode-error
|
||||||
|
:encode-error nil
|
||||||
|
;; malli options
|
||||||
|
:options nil})
|
||||||
|
|
||||||
|
(defn create
|
||||||
|
([]
|
||||||
|
(create nil))
|
||||||
|
([opts]
|
||||||
|
(let [{:keys [transformers lite compile options encode-error] :as opts} (merge default-options opts)
|
||||||
|
;; Query-string-coercer needs to construct transfomer without strip-extra-keys so it will
|
||||||
|
;; use the transformer-provider directly.
|
||||||
|
string-transformer-provider (:default (:string transformers))
|
||||||
|
transformers (walk/prewalk #(if (satisfies? p/TransformationProvider %) (p/-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-frontend)
|
||||||
|
(-get-options [_] opts)
|
||||||
|
(-get-model-apidocs [this specification model options]
|
||||||
|
nil)
|
||||||
|
(-get-apidocs [this specification {:keys [parameters responses] :as data}]
|
||||||
|
nil)
|
||||||
|
(-compile-model [_ model _]
|
||||||
|
(if (= 1 (count model))
|
||||||
|
(compile (first model) options)
|
||||||
|
(reduce (fn [x y] (mu/merge x y options)) (map #(compile % options) model))))
|
||||||
|
(-open-model [_ schema] schema)
|
||||||
|
(-encode-error [_ error]
|
||||||
|
;; NOTE: Is this needed for FE?
|
||||||
|
nil)
|
||||||
|
(-request-coercer [_ type schema]
|
||||||
|
(-coercer schema type transformers opts))
|
||||||
|
(-response-coercer [_ schema]
|
||||||
|
nil)
|
||||||
|
(-query-string-coercer [_ schema]
|
||||||
|
(-query-string-coercer schema string-transformer-provider opts))))))
|
||||||
|
|
||||||
|
(def coercion (create default-options))
|
||||||
|
|
@ -10,23 +10,10 @@
|
||||||
[malli.transform :as mt]
|
[malli.transform :as mt]
|
||||||
[malli.util :as mu]
|
[malli.util :as mu]
|
||||||
[reitit.coercion :as coercion]
|
[reitit.coercion :as coercion]
|
||||||
[clojure.string :as string]))
|
[reitit.coercion.malli.protocols :as p]))
|
||||||
|
|
||||||
;;
|
|
||||||
;; coercion
|
|
||||||
;;
|
|
||||||
|
|
||||||
(defprotocol Coercer
|
|
||||||
(-decode [this value])
|
|
||||||
(-encode [this value])
|
|
||||||
(-validate [this value])
|
|
||||||
(-explain [this value]))
|
|
||||||
|
|
||||||
(defprotocol TransformationProvider
|
|
||||||
(-transformer [this options]))
|
|
||||||
|
|
||||||
(defn- -provider [transformer]
|
(defn- -provider [transformer]
|
||||||
(reify TransformationProvider
|
(reify p/TransformationProvider
|
||||||
(-transformer [_ {:keys [strip-extra-keys default-values]}]
|
(-transformer [_ {:keys [strip-extra-keys default-values]}]
|
||||||
(mt/transformer
|
(mt/transformer
|
||||||
(if strip-extra-keys (mt/strip-extra-keys-transformer))
|
(if strip-extra-keys (mt/strip-extra-keys-transformer))
|
||||||
|
|
@ -44,7 +31,7 @@
|
||||||
encoder (if t (m/encoder schema options t) identity)
|
encoder (if t (m/encoder schema options t) identity)
|
||||||
validator (if validate (m/validator schema options) (constantly true))
|
validator (if validate (m/validator schema options) (constantly true))
|
||||||
explainer (m/explainer schema options)]
|
explainer (m/explainer schema options)]
|
||||||
(reify Coercer
|
(reify p/Coercer
|
||||||
(-decode [_ value] (decoder value))
|
(-decode [_ value] (decoder value))
|
||||||
(-encode [_ value] (encoder value))
|
(-encode [_ value] (encoder value))
|
||||||
(-validate [_ value] (validator value))
|
(-validate [_ value] (validator value))
|
||||||
|
|
@ -59,20 +46,20 @@
|
||||||
;; decode: decode -> validate
|
;; decode: decode -> validate
|
||||||
(fn [value format]
|
(fn [value format]
|
||||||
(if-let [coercer (get-coercer format)]
|
(if-let [coercer (get-coercer format)]
|
||||||
(let [transformed (-decode coercer value)]
|
(let [transformed (p/-decode coercer value)]
|
||||||
(if (-validate coercer transformed)
|
(if (p/-validate coercer transformed)
|
||||||
transformed
|
transformed
|
||||||
(let [error (-explain coercer transformed)]
|
(let [error (p/-explain coercer transformed)]
|
||||||
(coercion/map->CoercionError
|
(coercion/map->CoercionError
|
||||||
(assoc error :transformed transformed)))))
|
(assoc error :transformed transformed)))))
|
||||||
value))
|
value))
|
||||||
;; encode: decode -> validate -> encode
|
;; encode: decode -> validate -> encode
|
||||||
(fn [value format]
|
(fn [value format]
|
||||||
(let [transformed (-decode default-coercer value)]
|
(let [transformed (p/-decode default-coercer value)]
|
||||||
(if-let [coercer (get-coercer format)]
|
(if-let [coercer (get-coercer format)]
|
||||||
(if (-validate coercer transformed)
|
(if (p/-validate coercer transformed)
|
||||||
(-encode coercer transformed)
|
(p/-encode coercer transformed)
|
||||||
(let [error (-explain coercer transformed)]
|
(let [error (p/-explain coercer transformed)]
|
||||||
(coercion/map->CoercionError
|
(coercion/map->CoercionError
|
||||||
(assoc error :transformed transformed))))
|
(assoc error :transformed transformed))))
|
||||||
value))))))))
|
value))))))))
|
||||||
|
|
@ -84,8 +71,8 @@
|
||||||
(let [;; Always allow extra paramaters on query-parameters encoding
|
(let [;; Always allow extra paramaters on query-parameters encoding
|
||||||
open-schema (mu/open-schema schema)
|
open-schema (mu/open-schema schema)
|
||||||
;; Do not remove extra keys
|
;; Do not remove extra keys
|
||||||
string-transformer (if (satisfies? TransformationProvider string-transformer-provider)
|
string-transformer (if (satisfies? p/TransformationProvider string-transformer-provider)
|
||||||
(-transformer string-transformer-provider (assoc options :strip-extra-keys false))
|
(p/-transformer string-transformer-provider (assoc options :strip-extra-keys false))
|
||||||
string-transformer-provider)
|
string-transformer-provider)
|
||||||
encoder (m/encoder open-schema options string-transformer)]
|
encoder (m/encoder open-schema options string-transformer)]
|
||||||
(fn [value format]
|
(fn [value format]
|
||||||
|
|
@ -132,7 +119,7 @@
|
||||||
;; Query-string-coercer needs to construct transfomer without strip-extra-keys so it will
|
;; Query-string-coercer needs to construct transfomer without strip-extra-keys so it will
|
||||||
;; use the transformer-provider directly.
|
;; use the transformer-provider directly.
|
||||||
string-transformer-provider (:default (:string transformers))
|
string-transformer-provider (:default (:string transformers))
|
||||||
transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers)
|
transformers (walk/prewalk #(if (satisfies? p/TransformationProvider %) (p/-transformer % opts) %) transformers)
|
||||||
compile (if lite (fn [schema options]
|
compile (if lite (fn [schema options]
|
||||||
(compile (binding [l/*options* options] (l/schema schema)) options))
|
(compile (binding [l/*options* options] (l/schema schema)) options))
|
||||||
compile)]
|
compile)]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
(ns reitit.coercion.malli.protocols)
|
||||||
|
|
||||||
|
(defprotocol Coercer
|
||||||
|
(-decode [this value])
|
||||||
|
(-encode [this value])
|
||||||
|
(-validate [this value])
|
||||||
|
(-explain [this value]))
|
||||||
|
|
||||||
|
(defprotocol TransformationProvider
|
||||||
|
(-transformer [this options]))
|
||||||
Loading…
Reference in a new issue