diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index f1384b24..9c08ee56 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -1,6 +1,6 @@ {;;:skip-comments true :lint-as {potemkin/def-derived-map clojure.core/defrecord} - :linters {:if {:level :off} + :linters {:missing-else-branch {:level :off} :unused-binding {:level :off} :unused-referred-var {:exclude {clojure.test [deftest testing is are] cljs.test [deftest testing is are]}}}} diff --git a/.clj-kondo/module_config.edn b/.clj-kondo/module_config.edn new file mode 100644 index 00000000..e01b2bf1 --- /dev/null +++ b/.clj-kondo/module_config.edn @@ -0,0 +1 @@ +{:config-paths ["../../../.clj-kondo"]} diff --git a/.gitignore b/.gitignore index e0b0772e..84175044 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ pom.xml.asc /_book figwheel_server.log /.idea -.clj-kondo \ No newline at end of file +.clj-kondo diff --git a/.lsp/config.edn b/.lsp/config.edn new file mode 100644 index 00000000..577a7c9c --- /dev/null +++ b/.lsp/config.edn @@ -0,0 +1,3 @@ +{:cljfmt {:indents {for-all [[:inner 0]] + are [[:inner 0]]}} + :clean {:ns-inner-blocks-indentation :same-line}} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f9329394..645dc1f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,50 @@ We use [Break Versioning][breakver]. The version numbers follow a `. data` to expand route arg to route data (default `reitit.core/expand`) -| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` -| `:compile` | Function of `route opts => result` to compile a route handler -| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects -| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes -| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) -| `:router` | Function of `routes opts => router` to override the actual router implementation +| `:path` | Base-path for routes +| `:routes` | Initial resolved routes (default `[]`) +| `:data` | Initial route data (default `{}`) +| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this +| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon}) +| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`) +| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` +| `:meta-merge-fn` | Function which follows the signature of `meta-merge.core/meta-merge`, useful for when you want to have more control over the meta merging +| `:compile` | Function of `route opts => result` to compile a route handler +| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects +| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes +| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) +| `:router` | Function of `routes opts => router` to override the actual router implementation diff --git a/doc/basics/error_messages.md b/doc/basics/error_messages.md index 22d318c5..2458dca7 100644 --- a/doc/basics/error_messages.md +++ b/doc/basics/error_messages.md @@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces ## Pretty Errors ```clj -[metosin/reitit-dev "0.5.15"] +[metosin/reitit-dev "0.5.18"] ``` For human-readable and developer-friendly exception messages, there is `reitit.dev.pretty/exception` (in the `reitit-dev` module). It is inspired by the lovely errors messages of [ELM](https://elm-lang.org/blog/compiler-errors-for-humans) and [ETA](https://twitter.com/jyothsnasrin/status/1037703436043603968) and uses [fipp](https://github.com/brandonbloom/fipp), [expound](https://github.com/bhb/expound) and [spell-spec](https://github.com/bhauman/spell-spec) for most of heavy lifting. diff --git a/doc/coercion/malli_coercion.md b/doc/coercion/malli_coercion.md index 0c6a7fb9..fe4a17e5 100644 --- a/doc/coercion/malli_coercion.md +++ b/doc/coercion/malli_coercion.md @@ -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 diff --git a/doc/development.md b/doc/development.md index 46109017..b0721609 100644 --- a/doc/development.md +++ b/doc/development.md @@ -13,6 +13,13 @@ ./scripts/test.sh cljs ``` +## Formatting + +```bash +clojure-lsp format +clojure-lsp clean-ns +``` + ## Documentation The documentation lives under `doc` and it is hosted on [cljdoc](https://cljdoc.org). See their diff --git a/doc/http/default_interceptors.md b/doc/http/default_interceptors.md index 49428e86..4ea63b95 100644 --- a/doc/http/default_interceptors.md +++ b/doc/http/default_interceptors.md @@ -1,7 +1,7 @@ # Default Interceptors ```clj -[metosin/reitit-interceptors "0.5.15"] +[metosin/reitit-interceptors "0.5.18"] ``` Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors. diff --git a/doc/http/interceptors.md b/doc/http/interceptors.md index 621f00f4..373360f7 100644 --- a/doc/http/interceptors.md +++ b/doc/http/interceptors.md @@ -5,7 +5,7 @@ Reitit has also support for [interceptors](http://pedestal.io/reference/intercep ## Reitit-http ```clj -[metosin/reitit-http "0.5.15"] +[metosin/reitit-http "0.5.18"] ``` A module for http-routing using interceptors instead of middleware. Builds on top of the [`reitit-ring`](../ring/ring.md) module having all the same features. diff --git a/doc/http/pedestal.md b/doc/http/pedestal.md index 4dd83e00..25b5e9ba 100644 --- a/doc/http/pedestal.md +++ b/doc/http/pedestal.md @@ -3,7 +3,7 @@ [Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal. ```clj -[metosin/reitit-pedestal "0.5.15"] +[metosin/reitit-pedestal "0.5.18"] ``` Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)? @@ -26,8 +26,8 @@ A minimalistic example on how to to swap the default-router with a reitit router ```clj ; [io.pedestal/pedestal.service "0.5.5"] ; [io.pedestal/pedestal.jetty "0.5.5"] -; [metosin/reitit-pedestal "0.5.15"] -; [metosin/reitit "0.5.15"] +; [metosin/reitit-pedestal "0.5.18"] +; [metosin/reitit "0.5.18"] (require '[io.pedestal.http :as server]) (require '[reitit.pedestal :as pedestal]) diff --git a/doc/http/sieppari.md b/doc/http/sieppari.md index 0881e4c6..f886efb2 100644 --- a/doc/http/sieppari.md +++ b/doc/http/sieppari.md @@ -1,7 +1,7 @@ # Sieppari ```clj -[metosin/reitit-sieppari "0.5.15"] +[metosin/reitit-sieppari "0.5.18"] ``` [Sieppari](https://github.com/metosin/sieppari) is a new and fast interceptor implementation for Clojure, with pluggable async supporting [core.async](https://github.com/clojure/core.async), [Manifold](https://github.com/ztellman/manifold) and [Promesa](http://funcool.github.io/promesa/latest). diff --git a/doc/http/transforming_interceptor_chain.md b/doc/http/transforming_interceptor_chain.md index 3b9ca651..a0e5c6b8 100644 --- a/doc/http/transforming_interceptor_chain.md +++ b/doc/http/transforming_interceptor_chain.md @@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor ### Printing Context Diffs ```clj -[metosin/reitit-interceptors "0.5.15"] +[metosin/reitit-interceptors "0.5.18"] ``` Using `reitit.http.interceptors.dev/print-context-diffs` transformation, the context diffs between each interceptor are printed out to the console. To use it, add the following router option: diff --git a/doc/ring/default_middleware.md b/doc/ring/default_middleware.md index 007828d8..ea9274aa 100644 --- a/doc/ring/default_middleware.md +++ b/doc/ring/default_middleware.md @@ -1,7 +1,7 @@ # Default Middleware ```clj -[metosin/reitit-middleware "0.5.15"] +[metosin/reitit-middleware "0.5.18"] ``` Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware. diff --git a/doc/ring/exceptions.md b/doc/ring/exceptions.md index 78c15027..0ba1321b 100644 --- a/doc/ring/exceptions.md +++ b/doc/ring/exceptions.md @@ -1,7 +1,7 @@ # Exception Handling with Ring ```clj -[metosin/reitit-middleware "0.5.15"] +[metosin/reitit-middleware "0.5.18"] ``` Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practice is to have a top-level exception handler to log and format errors for clients. diff --git a/doc/ring/ring.md b/doc/ring/ring.md index f609d0c1..daf4b0a6 100644 --- a/doc/ring/ring.md +++ b/doc/ring/ring.md @@ -5,7 +5,7 @@ Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts). ```clj -[metosin/reitit-ring "0.5.15"] +[metosin/reitit-ring "0.5.18"] ``` ## `reitit.ring/ring-router` diff --git a/doc/ring/swagger.md b/doc/ring/swagger.md index 1f8460de..719a0611 100644 --- a/doc/ring/swagger.md +++ b/doc/ring/swagger.md @@ -1,7 +1,7 @@ # Swagger Support ``` -[metosin/reitit-swagger "0.5.15"] +[metosin/reitit-swagger "0.5.18"] ``` Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys. @@ -23,6 +23,7 @@ The following route data keys contribute to the generated swagger specification: | :tags | optional set of string or keyword tags for an endpoint api docs | :summary | optional short string summary of an endpoint | :description | optional long description of an endpoint. Supports http://spec.commonmark.org/ +| :operationId | optional string specifying the unique ID of an Operation Coercion keys also contribute to the docs: @@ -44,7 +45,7 @@ If you need to post-process the generated spec, just wrap the handler with a cus [Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module. ``` -[metosin/reitit-swagger-ui "0.5.15"] +[metosin/reitit-swagger-ui "0.5.18"] ``` `reitit.swagger-ui/create-swagger-ui-handler` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options: diff --git a/doc/ring/transforming_middleware_chain.md b/doc/ring/transforming_middleware_chain.md index a9fa3060..5175758e 100644 --- a/doc/ring/transforming_middleware_chain.md +++ b/doc/ring/transforming_middleware_chain.md @@ -59,7 +59,7 @@ There is an extra option in ring-router (actually, in the underlying middleware- ### Printing Request Diffs ```clj -[metosin/reitit-middleware "0.5.15"] +[metosin/reitit-middleware "0.5.18"] ``` Using `reitit.ring.middleware.dev/print-request-diffs` transformation, the request diffs between each middleware are printed out to the console. To use it, add the following router option: diff --git a/examples/buddy-auth/project.clj b/examples/buddy-auth/project.clj index 237773a6..cdd0217a 100644 --- a/examples/buddy-auth/project.clj +++ b/examples/buddy-auth/project.clj @@ -2,6 +2,6 @@ :description "Reitit Buddy Auth App" :dependencies [[org.clojure/clojure "1.10.1"] [ring/ring-jetty-adapter "1.8.1"] - [metosin/reitit "0.5.15"] + [metosin/reitit "0.5.18"] [buddy "2.0.0"]] :repl-options {:init-ns example.server}) diff --git a/examples/frontend-auth/project.clj b/examples/frontend-auth/project.clj index a5b3bf58..4258aadc 100644 --- a/examples/frontend-auth/project.clj +++ b/examples/frontend-auth/project.clj @@ -10,9 +10,9 @@ [ring "1.7.1"] [hiccup "1.0.5"] [org.clojure/clojurescript "1.10.439"] - [metosin/reitit "0.5.15"] - [metosin/reitit-schema "0.5.15"] - [metosin/reitit-frontend "0.5.15"] + [metosin/reitit "0.5.18"] + [metosin/reitit-schema "0.5.18"] + [metosin/reitit-frontend "0.5.18"] ;; Just for pretty printting the match [fipp "0.6.14"]] diff --git a/examples/frontend-controllers/project.clj b/examples/frontend-controllers/project.clj index a5b3bf58..4258aadc 100644 --- a/examples/frontend-controllers/project.clj +++ b/examples/frontend-controllers/project.clj @@ -10,9 +10,9 @@ [ring "1.7.1"] [hiccup "1.0.5"] [org.clojure/clojurescript "1.10.439"] - [metosin/reitit "0.5.15"] - [metosin/reitit-schema "0.5.15"] - [metosin/reitit-frontend "0.5.15"] + [metosin/reitit "0.5.18"] + [metosin/reitit-schema "0.5.18"] + [metosin/reitit-frontend "0.5.18"] ;; Just for pretty printting the match [fipp "0.6.14"]] diff --git a/examples/frontend-links/project.clj b/examples/frontend-links/project.clj index c48b8ec5..16c82efc 100644 --- a/examples/frontend-links/project.clj +++ b/examples/frontend-links/project.clj @@ -10,9 +10,9 @@ [ring "1.7.1"] [hiccup "1.0.5"] [org.clojure/clojurescript "1.10.520"] - [metosin/reitit "0.5.15"] - [metosin/reitit-spec "0.5.15"] - [metosin/reitit-frontend "0.5.15"] + [metosin/reitit "0.5.18"] + [metosin/reitit-spec "0.5.18"] + [metosin/reitit-frontend "0.5.18"] ;; Just for pretty printting the match [fipp "0.6.14"]] diff --git a/examples/frontend-prompt/project.clj b/examples/frontend-prompt/project.clj index c48b8ec5..16c82efc 100644 --- a/examples/frontend-prompt/project.clj +++ b/examples/frontend-prompt/project.clj @@ -10,9 +10,9 @@ [ring "1.7.1"] [hiccup "1.0.5"] [org.clojure/clojurescript "1.10.520"] - [metosin/reitit "0.5.15"] - [metosin/reitit-spec "0.5.15"] - [metosin/reitit-frontend "0.5.15"] + [metosin/reitit "0.5.18"] + [metosin/reitit-spec "0.5.18"] + [metosin/reitit-frontend "0.5.18"] ;; Just for pretty printting the match [fipp "0.6.14"]] diff --git a/examples/frontend-re-frame/project.clj b/examples/frontend-re-frame/project.clj index 694ede82..f5387c41 100644 --- a/examples/frontend-re-frame/project.clj +++ b/examples/frontend-re-frame/project.clj @@ -1,7 +1,7 @@ (defproject frontend-re-frame "0.1.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.10.0"] [org.clojure/clojurescript "1.10.520"] - [metosin/reitit "0.5.15"] + [metosin/reitit "0.5.18"] [reagent "0.8.1"] [re-frame "0.10.6"]] diff --git a/examples/frontend/project.clj b/examples/frontend/project.clj index 75f999b0..b359c55e 100644 --- a/examples/frontend/project.clj +++ b/examples/frontend/project.clj @@ -10,9 +10,9 @@ [ring "1.8.1"] [hiccup "1.0.5"] [org.clojure/clojurescript "1.10.773"] - [metosin/reitit "0.5.15"] - [metosin/reitit-spec "0.5.15"] - [metosin/reitit-frontend "0.5.15"] + [metosin/reitit "0.5.18"] + [metosin/reitit-spec "0.5.18"] + [metosin/reitit-frontend "0.5.18"] ;; Just for pretty printting the match [fipp "0.6.23"]] diff --git a/examples/http-swagger/project.clj b/examples/http-swagger/project.clj index 6a99a2e2..84d58af8 100644 --- a/examples/http-swagger/project.clj +++ b/examples/http-swagger/project.clj @@ -3,5 +3,5 @@ :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.1"] [aleph "0.4.7-alpha5"] - [metosin/reitit "0.5.15"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/examples/http/project.clj b/examples/http/project.clj index 5484918a..78769ce7 100644 --- a/examples/http/project.clj +++ b/examples/http/project.clj @@ -5,5 +5,5 @@ [funcool/promesa "1.9.0"] [manifold "0.1.8"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.5.15"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/examples/just-coercion-with-ring/project.clj b/examples/just-coercion-with-ring/project.clj index c339536c..8b35a3c2 100644 --- a/examples/just-coercion-with-ring/project.clj +++ b/examples/just-coercion-with-ring/project.clj @@ -2,4 +2,4 @@ :description "Reitit coercion with vanilla ring" :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.5.15"]]) + [metosin/reitit "0.5.18"]]) diff --git a/examples/pedestal-malli-swagger/.gitignore b/examples/pedestal-malli-swagger/.gitignore new file mode 100644 index 00000000..708f60f6 --- /dev/null +++ b/examples/pedestal-malli-swagger/.gitignore @@ -0,0 +1,11 @@ +/target +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +/.lein-* +/.nrepl-port +.hgignore +.hg/ \ No newline at end of file diff --git a/examples/pedestal-malli-swagger/project.clj b/examples/pedestal-malli-swagger/project.clj new file mode 100644 index 00000000..694d3beb --- /dev/null +++ b/examples/pedestal-malli-swagger/project.clj @@ -0,0 +1,9 @@ +(defproject pedestal-malli-swagger-example "0.1.0-SNAPSHOT" + :description "Reitit-http with pedestal" + :dependencies [[org.clojure/clojure "1.10.0"] + [io.pedestal/pedestal.service "0.5.5"] + [io.pedestal/pedestal.jetty "0.5.5"] + [metosin/reitit-malli "0.5.18"] + [metosin/reitit-pedestal "0.5.18"] + [metosin/reitit "0.5.18"]] + :repl-options {:init-ns server}) diff --git a/examples/pedestal-malli-swagger/src/server.clj b/examples/pedestal-malli-swagger/src/server.clj new file mode 100644 index 00000000..72c93967 --- /dev/null +++ b/examples/pedestal-malli-swagger/src/server.clj @@ -0,0 +1,164 @@ +(ns example.server + (:require [clojure.java.io :as io] + [io.pedestal.http.route] + [reitit.interceptor] + [reitit.dev.pretty :as pretty] + [reitit.coercion.malli] + [io.pedestal.http] + [reitit.ring] + [reitit.ring.malli] + [reitit.http] + [reitit.pedestal] + [reitit.swagger :as swagger] + [reitit.swagger-ui :as swagger-ui] + [reitit.http.coercion :as coercion] + [reitit.http.interceptors.parameters :as parameters] + [reitit.http.interceptors.muuntaja :as muuntaja] + [reitit.http.interceptors.multipart :as multipart] + [muuntaja.core] + [malli.util :as mu])) + +(defn reitit-routes + [_config] + [["/swagger.json" {:get {:no-doc true + :swagger {:info {:title "my-api" + :description "with [malli](https://github.com/metosin/malli) and reitit-ring"} + :tags [{:name "files", + :description "file api"} + {:name "math", + :description "math api"}]} + :handler (swagger/create-swagger-handler)}}] + ["/files" {:swagger {:tags ["files"]}} + ["/upload" + {:post {:summary "upload a file" + :parameters {:multipart [:map [:file reitit.ring.malli/temp-file-part]]} + :responses {200 {:body [:map + [:name string?] + [:size int?]]}} + :handler (fn [{{{{:keys [filename + size]} :file} + :multipart} + :parameters}] + {:status 200 + :body {:name filename + :size size}})}}] + ["/download" {:get {:summary "downloads a file" + :swagger {:produces ["image/png"]} + :handler (fn [_] + {:status 200 + :headers {"Content-Type" "image/png"} + :body (-> "reitit.png" + (io/resource) + (io/input-stream))})}}]] + ["/math" {:swagger {:tags ["math"]}} + ["/plus" + {:get {:summary "plus with malli query parameters" + :parameters {:query [:map + [:x + {:title "X parameter" + :description "Description for X parameter" + :json-schema/default 42} + int?] + [:y int?]]} + :responses {200 {:body [:map [:total int?]]}} + :handler (fn [{{{:keys [x + y]} + :query} + :parameters}] + {:status 200 + :body {:total (+ x y)}})} + :post {:summary "plus with malli body parameters" + :parameters {:body [:map + [:x + {:title "X parameter" + :description "Description for X parameter" + :json-schema/default 42} + int?] + [:y int?]]} + :responses {200 {:body [:map [:total int?]]}} + :handler (fn [{{{:keys [x + y]} + :body} + :parameters}] + {:status 200 + :body {:total (+ x y)}})}}]]]) + +(defn reitit-ring-routes + [_config] + [(swagger-ui/create-swagger-ui-handler + {:path "/" + :config {:validatorUrl nil + :operationsSorter "alpha"}}) + (reitit.ring/create-resource-handler) + (reitit.ring/create-default-handler)]) + + +(defn reitit-router-config + [_config] + {:exception pretty/exception + :data {:coercion (reitit.coercion.malli/create + {:error-keys #{:coercion + :in + :schema + :value + :errors + :humanized} + :compile mu/closed-schema + :strip-extra-keys true + :default-values true + :options nil}) + :muuntaja muuntaja.core/instance + :interceptors [swagger/swagger-feature + (parameters/parameters-interceptor) + (muuntaja/format-negotiate-interceptor) + (muuntaja/format-response-interceptor) + (muuntaja/format-request-interceptor) + (coercion/coerce-response-interceptor) + (coercion/coerce-request-interceptor) + (multipart/multipart-interceptor)]}}) + +(def config + {:env :dev + :io.pedestal.http/routes [] + :io.pedestal.http/type :jetty + :io.pedestal.http/port 3000 + :io.pedestal.http/join? false + :io.pedestal.http/secure-headers {:content-security-policy-settings + {:default-src "'self'" + :style-src "'self' 'unsafe-inline'" + :script-src "'self' 'unsafe-inline'"}} + ::reitit-routes reitit-routes + ::reitit-ring-routes reitit-ring-routes + ::reitit-router-config reitit-router-config}) + +(defn reitit-http-router + [{::keys [reitit-routes + reitit-ring-routes + reitit-router-config] + :as config}] + (reitit.pedestal/routing-interceptor + (reitit.http/router + (reitit-routes config) + (reitit-router-config config)) + (->> config + reitit-ring-routes + (apply reitit.ring/routes)))) + +(defonce server (atom nil)) + +(defn start + [server + config] + (when @server + (io.pedestal.http/stop @server) + (println "server stopped")) + (-> config + io.pedestal.http/default-interceptors + (reitit.pedestal/replace-last-interceptor (reitit-http-router config)) + io.pedestal.http/dev-interceptors + io.pedestal.http/create-server + io.pedestal.http/start + (->> (reset! server))) + (println "server running in port 3000")) + +#_(start server config) \ No newline at end of file diff --git a/examples/pedestal-swagger/project.clj b/examples/pedestal-swagger/project.clj index 876382fd..2cce08f7 100644 --- a/examples/pedestal-swagger/project.clj +++ b/examples/pedestal-swagger/project.clj @@ -3,6 +3,6 @@ :dependencies [[org.clojure/clojure "1.10.0"] [io.pedestal/pedestal.service "0.5.5"] [io.pedestal/pedestal.jetty "0.5.5"] - [metosin/reitit-pedestal "0.5.15"] - [metosin/reitit "0.5.15"]] + [metosin/reitit-pedestal "0.5.18"] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/examples/pedestal/project.clj b/examples/pedestal/project.clj index 876382fd..2cce08f7 100644 --- a/examples/pedestal/project.clj +++ b/examples/pedestal/project.clj @@ -3,6 +3,6 @@ :dependencies [[org.clojure/clojure "1.10.0"] [io.pedestal/pedestal.service "0.5.5"] [io.pedestal/pedestal.jetty "0.5.5"] - [metosin/reitit-pedestal "0.5.15"] - [metosin/reitit "0.5.15"]] + [metosin/reitit-pedestal "0.5.18"] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/examples/ring-example/project.clj b/examples/ring-example/project.clj index 99ed61b4..286f65fd 100644 --- a/examples/ring-example/project.clj +++ b/examples/ring-example/project.clj @@ -2,5 +2,5 @@ :description "Reitit Ring App" :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.5.15"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/examples/ring-integrant/project.clj b/examples/ring-integrant/project.clj index e66dd45f..22b3ab9d 100644 --- a/examples/ring-integrant/project.clj +++ b/examples/ring-integrant/project.clj @@ -2,7 +2,7 @@ :description "Reitit Ring App with Integrant" :dependencies [[org.clojure/clojure "1.10.1"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.5.15"] + [metosin/reitit "0.5.18"] [integrant "0.7.0"]] :main example.server :repl-options {:init-ns user} diff --git a/examples/ring-malli-lite-swagger/.gitignore b/examples/ring-malli-lite-swagger/.gitignore new file mode 100644 index 00000000..c53038ec --- /dev/null +++ b/examples/ring-malli-lite-swagger/.gitignore @@ -0,0 +1,11 @@ +/target +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +/.lein-* +/.nrepl-port +.hgignore +.hg/ diff --git a/examples/ring-malli-lite-swagger/README.md b/examples/ring-malli-lite-swagger/README.md new file mode 100644 index 00000000..3a2144c2 --- /dev/null +++ b/examples/ring-malli-lite-swagger/README.md @@ -0,0 +1,23 @@ +# reitit-ring, malli, swagger + +## Usage + +```clj +> lein repl +(start) +``` + +To test the endpoints using [httpie](https://httpie.org/): + +```bash +http GET :3000/math/plus x==1 y==20 +http POST :3000/math/plus x:=1 y:=20 + +http GET :3000/swagger.json +``` + + + +## License + +Copyright © 2017-2019 Metosin Oy diff --git a/examples/ring-malli-lite-swagger/project.clj b/examples/ring-malli-lite-swagger/project.clj new file mode 100644 index 00000000..42e408a8 --- /dev/null +++ b/examples/ring-malli-lite-swagger/project.clj @@ -0,0 +1,8 @@ +(defproject ring-example "0.1.0-SNAPSHOT" + :description "Reitit Ring App with Swagger" + :dependencies [[org.clojure/clojure "1.10.0"] + [metosin/jsonista "0.2.6"] + [ring/ring-jetty-adapter "1.7.1"] + [metosin/reitit "0.5.18"]] + :repl-options {:init-ns example.server} + :profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}}) diff --git a/examples/ring-malli-lite-swagger/resources/reitit.png b/examples/ring-malli-lite-swagger/resources/reitit.png new file mode 100644 index 00000000..c89c3654 Binary files /dev/null and b/examples/ring-malli-lite-swagger/resources/reitit.png differ diff --git a/examples/ring-malli-lite-swagger/src/example/server.clj b/examples/ring-malli-lite-swagger/src/example/server.clj new file mode 100644 index 00000000..09e632b1 --- /dev/null +++ b/examples/ring-malli-lite-swagger/src/example/server.clj @@ -0,0 +1,123 @@ +(ns example.server + (:require [reitit.ring :as ring] + [reitit.coercion.malli] + [reitit.ring.malli] + [reitit.swagger :as swagger] + [reitit.swagger-ui :as swagger-ui] + [reitit.ring.coercion :as coercion] + [reitit.dev.pretty :as pretty] + [reitit.ring.middleware.muuntaja :as muuntaja] + [reitit.ring.middleware.exception :as exception] + [reitit.ring.middleware.multipart :as multipart] + [reitit.ring.middleware.parameters :as parameters] + ; [reitit.ring.middleware.dev :as dev] + ; [reitit.ring.spec :as spec] + ; [spec-tools.spell :as spell] + [ring.adapter.jetty :as jetty] + [muuntaja.core :as m] + [clojure.java.io :as io] + [malli.util :as mu])) + +(def app + (ring/ring-handler + (ring/router + [["/swagger.json" + {:get {:no-doc true + :swagger {:info {:title "my-api" + :description "with [malli](https://github.com/metosin/malli) and reitit-ring"} + :tags [{:name "files", :description "file api"} + {:name "math", :description "math api"}]} + :handler (swagger/create-swagger-handler)}}] + + ["/files" + {:swagger {:tags ["files"]}} + + ["/upload" + {:post {:summary "upload a file" + :parameters {:multipart {:file reitit.ring.malli/temp-file-part}} + :responses {200 {:body {:name :string, :size :int}}} + :handler (fn [{{{:keys [file]} :multipart} :parameters}] + {:status 200 + :body {:name (:filename file) + :size (:size file)}})}}] + + ["/download" + {:get {:summary "downloads a file" + :swagger {:produces ["image/png"]} + :handler (fn [_] + {:status 200 + :headers {"Content-Type" "image/png"} + :body (-> "reitit.png" + (io/resource) + (io/input-stream))})}}]] + + ["/math" + {:swagger {:tags ["math"]}} + + ["/plus" + {:get {:summary "plus with malli query parameters" + :parameters {:query {:x [:int {:title "X parameter" + :description "Description for X parameter" + :json-schema/default 42}] + :y :int}} + :responses {200 {:body {:total :int}}} + :handler (fn [{{{:keys [x y]} :query} :parameters}] + {:status 200 + :body {:total (+ x y)}})} + :post {:summary "plus with malli body parameters" + :parameters {:body {:x [:int {:title "X parameter" + :description "Description for X parameter" + :json-schema/default 42}] + :y :int}} + :responses {200 {:body {:total :int}}} + :handler (fn [{{{:keys [x y]} :body} :parameters}] + {:status 200 + :body {:total (+ x y)}})}}]]] + + {;;:reitit.middleware/transform dev/print-request-diffs ;; pretty diffs + ;;:validate spec/validate ;; enable spec validation for route data + ;;:reitit.spec/wrap spell/closed ;; strict top-level validation + :exception pretty/exception + :data {:coercion (reitit.coercion.malli/create + {;; set of keys to include in error messages + :error-keys #{#_:type :coercion :in :schema :value :errors :humanized #_:transformed} + ;; schema identity function (default: close all map schemas) + :compile mu/closed-schema + ;; strip-extra-keys (effects only predefined transformers) + :strip-extra-keys true + ;; add/set default values + :default-values true + ;; malli options + :options nil}) + :muuntaja m/instance + :middleware [;; swagger feature + swagger/swagger-feature + ;; query-params & form-params + parameters/parameters-middleware + ;; content-negotiation + muuntaja/format-negotiate-middleware + ;; encoding response body + muuntaja/format-response-middleware + ;; exception handling + exception/exception-middleware + ;; decoding request body + muuntaja/format-request-middleware + ;; coercing response bodys + coercion/coerce-response-middleware + ;; coercing request parameters + coercion/coerce-request-middleware + ;; multipart + multipart/multipart-middleware]}}) + (ring/routes + (swagger-ui/create-swagger-ui-handler + {:path "/" + :config {:validatorUrl nil + :operationsSorter "alpha"}}) + (ring/create-default-handler)))) + +(defn start [] + (jetty/run-jetty #'app {:port 3000, :join? false}) + (println "server running in port 3000")) + +(comment + (start)) diff --git a/examples/ring-malli-lite-swagger/swagger.png b/examples/ring-malli-lite-swagger/swagger.png new file mode 100644 index 00000000..9d5a55b8 Binary files /dev/null and b/examples/ring-malli-lite-swagger/swagger.png differ diff --git a/examples/ring-malli-lite-swagger/test/example/server_test.clj b/examples/ring-malli-lite-swagger/test/example/server_test.clj new file mode 100644 index 00000000..e296b5a8 --- /dev/null +++ b/examples/ring-malli-lite-swagger/test/example/server_test.clj @@ -0,0 +1,38 @@ +(ns example.server-test + (:require [clojure.test :refer [deftest testing is]] + [example.server :refer [app]] + [ring.mock.request :refer [request json-body]])) + +(deftest example-server + + (testing "GET" + (is (= (-> (request :get "/math/plus?x=20&y=3") + app :body slurp) + (-> {:request-method :get :uri "/math/plus" :query-string "x=20&y=3"} + app :body slurp) + (-> {:request-method :get :uri "/math/plus" :query-params {:x 20 :y 3}} + app :body slurp) + "{\"total\":23}"))) + + (testing "POST" + (is (= (-> (request :post "/math/plus") (json-body {:x 40 :y 2}) + app :body slurp) + (-> {:request-method :post :uri "/math/plus" :body-params {:x 40 :y 2}} + app :body slurp) + "{\"total\":42}"))) + + (testing "Download" + (is (= (-> {:request-method :get :uri "/files/download"} + app :body (#(slurp % :encoding "ascii")) count) ;; binary + (.length (clojure.java.io/file "resources/reitit.png")) + 506325))) + + (testing "Upload" + (let [file (clojure.java.io/file "resources/reitit.png") + multipart-temp-file-part {:tempfile file + :size (.length file) + :filename (.getName file) + :content-type "image/png;"}] + (is (= (-> {:request-method :post :uri "/files/upload" :multipart-params {:file multipart-temp-file-part}} + app :body slurp) + "{\"name\":\"reitit.png\",\"size\":506325}"))))) diff --git a/examples/ring-malli-swagger/project.clj b/examples/ring-malli-swagger/project.clj index 9a0d997e..42e408a8 100644 --- a/examples/ring-malli-swagger/project.clj +++ b/examples/ring-malli-swagger/project.clj @@ -3,6 +3,6 @@ :dependencies [[org.clojure/clojure "1.10.0"] [metosin/jsonista "0.2.6"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.5.15"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server} :profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}}) diff --git a/examples/ring-spec-swagger/project.clj b/examples/ring-spec-swagger/project.clj index aa7103f0..88225b36 100644 --- a/examples/ring-spec-swagger/project.clj +++ b/examples/ring-spec-swagger/project.clj @@ -2,6 +2,6 @@ :description "Reitit Ring App with Swagger" :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.5.15"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server} :profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}}) diff --git a/examples/ring-swagger/project.clj b/examples/ring-swagger/project.clj index 135fc565..335d63d4 100644 --- a/examples/ring-swagger/project.clj +++ b/examples/ring-swagger/project.clj @@ -2,5 +2,5 @@ :description "Reitit Ring App with Swagger" :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.5.15"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/modules/reitit-core/.clj-kondo/config.edn b/modules/reitit-core/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-core/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-core/project.clj b/modules/reitit-core/project.clj index 1030a3c6..9903a3c7 100644 --- a/modules/reitit-core/project.clj +++ b/modules/reitit-core/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-core "0.5.15" +(defproject metosin/reitit-core "0.5.18" :description "Snappy data-driven router for Clojure(Script)" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 57d55628..75312c86 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -39,38 +39,47 @@ :body (->ParameterCoercion :body-params :body false false) :form (->ParameterCoercion :form-params :string true true) :header (->ParameterCoercion :headers :string true true) - :path (->ParameterCoercion :path-params :string true true)}) + :path (->ParameterCoercion :path-params :string true true) + :fragment (->ParameterCoercion :fragment-params :string true true)}) -(defn ^:no-doc request-coercion-failed! [result coercion value in request] +(defn ^:no-doc request-coercion-failed! [result coercion value in request serialize-failed-result] (throw - (ex-info + (ex-info + (if serialize-failed-result (str "Request coercion failed: " (pr-str result)) - (merge - (into {} result) - {:type ::request-coercion - :coercion coercion - :value value - :in [:request in] - :request request})))) + "Request coercion failed") + (-> {} + transient + (as-> $ (reduce conj! $ result)) + (assoc! :type ::request-coercion) + (assoc! :coercion coercion) + (assoc! :value value) + (assoc! :in [:request in]) + (assoc! :request request) + persistent!)))) -(defn ^:no-doc response-coercion-failed! [result coercion value request response] +(defn ^:no-doc response-coercion-failed! [result coercion value request response serialize-failed-result] (throw - (ex-info + (ex-info + (if serialize-failed-result (str "Response coercion failed: " (pr-str result)) - (merge - (into {} result) - {:type ::response-coercion - :coercion coercion - :value value - :in [:response :body] - :request request - :response response})))) + "Response coercion failed") + (-> {} + transient + (as-> $ (reduce conj! $ result)) + (assoc! :type ::response-coercion) + (assoc! :coercion coercion) + (assoc! :value value) + (assoc! :in [:response :body]) + (assoc! :request request) + (assoc! :response response) + persistent!)))) (defn extract-request-format-default [request] (-> request :muuntaja/request :format)) ;; TODO: support faster key walking, walk/keywordize-keys is quite slow... -(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion] +(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result] :or {extract-request-format extract-request-format-default parameter-coercion default-parameter-coercion}}] (if coercion @@ -83,13 +92,13 @@ format (extract-request-format request) result (coercer value format)] (if (error? result) - (request-coercion-failed! result coercion value in request) + (request-coercion-failed! result coercion value in request serialize-failed-result) result)))))))) (defn extract-response-format-default [request _] (-> request :muuntaja/response :format)) -(defn response-coercer [coercion body {:keys [extract-response-format] +(defn response-coercer [coercion body {:keys [extract-response-format serialize-failed-result] :or {extract-response-format extract-response-format-default}}] (if coercion (if-let [coercer (-response-coercer coercion body)] @@ -98,7 +107,7 @@ value (:body response) result (coercer value format)] (if (error? result) - (response-coercion-failed! result coercion value request response) + (response-coercion-failed! result coercion value request response serialize-failed-result) result)))))) (defn encode-error [data] @@ -109,9 +118,9 @@ (defn coerce-request [coercers request] (reduce-kv - (fn [acc k coercer] - (impl/fast-assoc acc k (coercer request))) - {} coercers)) + (fn [acc k coercer] + (impl/fast-assoc acc k (coercer request))) + {} coercers)) (defn coerce-response [coercers request response] (if response @@ -147,13 +156,13 @@ :multipart :formData}] (case specification :swagger (->> (update - data - :parameters - (fn [parameters] - (->> parameters - (map (fn [[k v]] [(swagger-parameter k) v])) - (filter first) - (into {})))) + data + :parameters + (fn [parameters] + (->> parameters + (map (fn [[k v]] [(swagger-parameter k) v])) + (filter first) + (into {})))) (-get-apidocs coercion specification))))) ;; diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index bcd54ac4..0b32fd73 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -1,6 +1,6 @@ (ns reitit.core - (:require [reitit.impl :as impl] - [reitit.exception :as exception] + (:require [reitit.exception :as exception] + [reitit.impl :as impl] [reitit.trie :as trie])) ;; @@ -61,7 +61,7 @@ (if-not (partial-match? match) match (impl/throw-on-missing-path-params - (:template match) (:required match) path-params))))) + (:template match) (:required match) path-params))))) (defn match->path ([match] @@ -87,15 +87,15 @@ (let [compiler (::trie/trie-compiler opts (trie/compiler)) names (impl/find-names compiled-routes opts) [pl nl] (reduce - (fn [[pl nl] [p {:keys [name] :as data} result]] - (let [{:keys [path-params] :as route} (impl/parse p opts) - f #(if-let [path (impl/path-for route %)] - (->Match p data result (impl/url-decode-coll %) path) - (->PartialMatch p data result (impl/url-decode-coll %) path-params))] - [(conj pl (-> (trie/insert nil p (->Match p data result nil nil) opts) (trie/compile))) - (if name (assoc nl name f) nl)])) - [[] {}] - compiled-routes) + (fn [[pl nl] [p {:keys [name] :as data} result]] + (let [{:keys [path-params] :as route} (impl/parse p opts) + f #(if-let [path (impl/path-for route %)] + (->Match p data result (impl/url-decode-coll %) path) + (->PartialMatch p data result (impl/url-decode-coll %) path-params))] + [(conj pl (-> (trie/insert nil p (->Match p data result nil nil) opts) (trie/compile))) + (if name (assoc nl name f) nl)])) + [[] {}] + compiled-routes) lookup (impl/fast-map nl) matcher (trie/linear-matcher compiler pl true) match-by-path (trie/path-matcher matcher compiler) @@ -103,16 +103,11 @@ ^{:type ::router} (reify Router - (router-name [_] - :linear-router) - (routes [_] - routes) - (compiled-routes [_] - compiled-routes) - (options [_] - opts) - (route-names [_] - names) + (router-name [_] :linear-router) + (routes [_] routes) + (compiled-routes [_] compiled-routes) + (options [_] opts) + (route-names [_] names) (match-by-path [_ path] (if-let [match (match-by-path path)] (-> (:data match) @@ -133,33 +128,28 @@ ([compiled-routes opts] (when-let [wilds (seq (filter (impl/->wild-route? opts) compiled-routes))] (exception/fail! - (str "can't create :lookup-router with wildcard routes: " wilds) - {:wilds wilds - :routes compiled-routes})) + (str "can't create :lookup-router with wildcard routes: " wilds) + {:wilds wilds + :routes compiled-routes})) (let [names (impl/find-names compiled-routes opts) [pl nl] (reduce - (fn [[pl nl] [p {:keys [name] :as data} result]] - [(assoc pl p (->Match p data result {} p)) - (if name - (assoc nl name #(->Match p data result % p)) - nl)]) - [{} {}] - compiled-routes) + (fn [[pl nl] [p {:keys [name] :as data} result]] + [(assoc pl p (->Match p data result {} p)) + (if name + (assoc nl name #(->Match p data result % p)) + nl)]) + [{} {}] + compiled-routes) data (impl/fast-map pl) lookup (impl/fast-map nl) routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router - (router-name [_] - :lookup-router) - (routes [_] - routes) - (compiled-routes [_] - compiled-routes) - (options [_] - opts) - (route-names [_] - names) + (router-name [_] :lookup-router) + (routes [_] routes) + (compiled-routes [_] compiled-routes) + (options [_] opts) + (route-names [_] names) (match-by-path [_ path] (impl/fast-get data path)) (match-by-name [_ name] @@ -183,34 +173,29 @@ (let [compiler (::trie/trie-compiler opts (trie/compiler)) names (impl/find-names compiled-routes opts) [pl nl] (reduce - (fn [[pl nl] [p {:keys [name] :as data} result]] - (let [{:keys [path-params] :as route} (impl/parse p opts) - f #(if-let [path (impl/path-for route %)] - (->Match p data result (impl/url-decode-coll %) path) - (->PartialMatch p data result (impl/url-decode-coll %) path-params))] - [(trie/insert pl p (->Match p data result nil nil) opts) - (if name (assoc nl name f) nl)])) - [nil {}] - compiled-routes) + (fn [[pl nl] [p {:keys [name] :as data} result]] + (let [{:keys [path-params] :as route} (impl/parse p opts) + f #(if-let [path (impl/path-for route %)] + (->Match p data result (impl/url-decode-coll %) path) + (->PartialMatch p data result (impl/url-decode-coll %) path-params))] + [(trie/insert pl p (->Match p data result nil nil) opts) + (if name (assoc nl name f) nl)])) + [nil {}] + compiled-routes) matcher (trie/compile pl compiler) - match-by-path (trie/path-matcher matcher compiler) + match-by-path (if matcher (trie/path-matcher matcher compiler)) lookup (impl/fast-map nl) routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router - (router-name [_] - :trie-router) - (routes [_] - routes) - (compiled-routes [_] - compiled-routes) - (options [_] - opts) - (route-names [_] - names) + (router-name [_] :trie-router) + (routes [_] routes) + (compiled-routes [_] compiled-routes) + (options [_] opts) + (route-names [_] names) (match-by-path [_ path] - (if-let [match (match-by-path path)] + (if-let [match (and match-by-path (match-by-path path))] (-> (:data match) (assoc :path-params (:params match)) (assoc :path path)))) @@ -229,8 +214,8 @@ ([compiled-routes opts] (when (or (not= (count compiled-routes) 1) (some (impl/->wild-route? opts) compiled-routes)) (exception/fail! - (str ":single-static-path-router requires exactly 1 static route: " compiled-routes) - {:routes compiled-routes})) + (str ":single-static-path-router requires exactly 1 static route: " compiled-routes) + {:routes compiled-routes})) (let [[n :as names] (impl/find-names compiled-routes opts) [[p data result]] compiled-routes p #?(:clj (.intern ^String p) :cljs p) @@ -238,25 +223,17 @@ routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router - (router-name [_] - :single-static-path-router) - (routes [_] - routes) - (compiled-routes [_] - compiled-routes) - (options [_] - opts) - (route-names [_] - names) + (router-name [_] :single-static-path-router) + (routes [_] routes) + (compiled-routes [_] compiled-routes) + (options [_] opts) + (route-names [_] names) (match-by-path [_ path] - (if (#?(:clj .equals :cljs =) p path) - match)) + (if (#?(:clj .equals :cljs =) p path) match)) (match-by-name [_ name] - (if (= n name) - match)) + (if (= n name) match)) (match-by-name [_ name path-params] - (if (= n name) - (impl/fast-assoc match :path-params (impl/path-params path-params)))))))) + (if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params)))))))) (defn mixed-router "Creates two routers: [[lookup-router]] or [[single-static-path-router]] for @@ -274,16 +251,11 @@ routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router - (router-name [_] - :mixed-router) - (routes [_] - routes) - (compiled-routes [_] - compiled-routes) - (options [_] - opts) - (route-names [_] - names) + (router-name [_] :mixed-router) + (routes [_] routes) + (compiled-routes [_] compiled-routes) + (options [_] opts) + (route-names [_] names) (match-by-path [_ path] (or (match-by-path static-router path) (match-by-path wildcard-router path))) @@ -310,16 +282,11 @@ routes (impl/uncompile-routes compiled-routes)] ^{:type ::router} (reify Router - (router-name [_] - :quarantine-router) - (routes [_] - routes) - (compiled-routes [_] - compiled-routes) - (options [_] - opts) - (route-names [_] - names) + (router-name [_] :quarantine-router) + (routes [_] routes) + (compiled-routes [_] compiled-routes) + (options [_] opts) + (route-names [_] names) (match-by-path [_ path] (or (match-by-path mixed-router path) (match-by-path linear-router path))) diff --git a/modules/reitit-core/src/reitit/dependency.cljc b/modules/reitit-core/src/reitit/dependency.cljc index 4dcc6e97..6f1209eb 100644 --- a/modules/reitit-core/src/reitit/dependency.cljc +++ b/modules/reitit-core/src/reitit/dependency.cljc @@ -10,8 +10,8 @@ (map (fn [provide] (when (contains? acc provide) (exception/fail! - (str "multiple providers for: " provide) - {::multiple-providers provide})) + (str "multiple providers for: " provide) + {::multiple-providers provide})) [provide dependent])) (get-provides dependent))) {} nodes)) @@ -22,8 +22,8 @@ (if (contains? providers k) (get providers k) (exception/fail! - (str "provider missing for dependency: " k) - {::missing-provider k}))) + (str "provider missing for dependency: " k) + {::missing-provider k}))) (defn post-order "Put `nodes` in post-order. Can also be described as a reverse topological sort. diff --git a/modules/reitit-core/src/reitit/exception.cljc b/modules/reitit-core/src/reitit/exception.cljc index ff848910..df61b66a 100644 --- a/modules/reitit-core/src/reitit/exception.cljc +++ b/modules/reitit-core/src/reitit/exception.cljc @@ -31,20 +31,20 @@ path " " (not-empty (select-keys route-data [:conflicting]))))] (apply str "Router contains conflicting route paths:\n\n" (mapv - (fn [[[path route-data] vals]] - (str (resolve-str path route-data) - "\n" - (str/join "\n" (mapv (fn [[path route-data]] - (resolve-str path route-data)) vals)) - "\n\n")) - conflicts)))) + (fn [[[path route-data] vals]] + (str (resolve-str path route-data) + "\n" + (str/join "\n" (mapv (fn [[path route-data]] + (resolve-str path route-data)) vals)) + "\n\n")) + conflicts)))) (defmethod format-exception :name-conflicts [_ _ conflicts] (apply str "Router contains conflicting route names:\n\n" (mapv - (fn [[name vals]] - (str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n")) - conflicts))) + (fn [[name vals]] + (str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n")) + conflicts))) (defmethod format-exception :reitit.impl/merge-data [_ _ data] (str "Error merging route-data\n\n" (pr-str data))) diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 34bd12b4..40d02bf9 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -1,13 +1,13 @@ (ns ^:no-doc reitit.impl #?(:cljs (:require-macros [reitit.impl])) - (:require [clojure.string :as str] - [clojure.set :as set] + (:require [clojure.set :as set] + [clojure.string :as str] [meta-merge.core :as mm] - [reitit.trie :as trie] - [reitit.exception :as ex]) + [reitit.exception :as ex] + [reitit.trie :as trie]) #?(:clj - (:import (java.util HashMap Map) - (java.net URLEncoder URLDecoder)))) + (:import (java.net URLEncoder URLDecoder) + (java.util HashMap Map)))) (defn parse [path opts] (let [path #?(:clj (.intern ^String (trie/normalize path opts)) :cljs (trie/normalize path opts)) @@ -28,59 +28,59 @@ Also works on vectors. Maintains key for maps, order for vectors." [f coll] (reduce-kv - (fn [coll k v] - (if-some [v' (f v)] - (assoc coll k v') - coll)) - coll - coll)) + (fn [coll k v] + (if-some [v' (f v)] + (assoc coll k v') + coll)) + coll + coll)) (defn walk [raw-routes {:keys [path data routes expand] :or {data [], routes []} :as opts}] (letfn - [(walk-many [p m r] - (reduce #(into %1 (walk-one p m %2)) [] r)) - (walk-one [pacc macc routes] - (if (vector? (first routes)) - (walk-many pacc macc routes) - (when (string? (first routes)) - (let [[path & [maybe-arg :as args]] routes - [data childs] (if (or (vector? maybe-arg) - (and (sequential? maybe-arg) - (sequential? (first maybe-arg))) - (nil? maybe-arg)) - [{} args] - [maybe-arg (rest args)]) - macc (into macc (expand data opts)) - child-routes (walk-many (str pacc path) macc (keep identity childs))] - (if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))] + [(walk-many [p m r] + (reduce #(into %1 (walk-one p m %2)) [] r)) + (walk-one [pacc macc routes] + (if (vector? (first routes)) + (walk-many pacc macc routes) + (when (string? (first routes)) + (let [[path & [maybe-arg :as args]] routes + [data childs] (if (or (vector? maybe-arg) + (and (sequential? maybe-arg) + (sequential? (first maybe-arg))) + (nil? maybe-arg)) + [{} args] + [maybe-arg (rest args)]) + macc (into macc (expand data opts)) + child-routes (walk-many (str pacc path) macc (keep identity childs))] + (if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))] (walk-one path (mapv identity data) raw-routes))) (defn map-data [f routes] (mapv (fn [[p ds]] [p (f p ds)]) routes)) -(defn merge-data [p x] +(defn merge-data [{:keys [meta-merge-fn] :as g} p x] (reduce - (fn [acc [k v]] - (try - (mm/meta-merge acc {k v}) - (catch #?(:clj Exception, :cljs js/Error) e - (ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e})))) - {} x)) + (fn [acc [k v]] + (try + ((or meta-merge-fn mm/meta-merge) acc {k v}) + (catch #?(:clj Exception, :cljs js/Error) e + (ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e})))) + {} x)) (defn resolve-routes [raw-routes {:keys [coerce] :as opts}] - (cond->> (->> (walk raw-routes opts) (map-data merge-data)) - coerce (into [] (keep #(coerce % opts))))) + (cond->> (->> (walk raw-routes opts) (map-data #(merge-data opts %1 %2))) + coerce (into [] (keep #(coerce % opts))))) (defn path-conflicting-routes [routes opts] (let [parts-and-routes (mapv (fn [[s :as r]] [(trie/split-path s opts) r]) routes)] (-> (into {} (comp (map-indexed (fn [index [p r]] [r (reduce - (fn [acc [p' r']] - (if (trie/conflicting-parts? p p') - (conj acc r') acc)) - #{} (subvec parts-and-routes (inc index)))])) + (fn [acc [p' r']] + (if (trie/conflicting-parts? p p') + (conj acc r') acc)) + #{} (subvec parts-and-routes (inc index)))])) (filter (comp seq second))) parts-and-routes) (not-empty)))) @@ -123,13 +123,13 @@ (defn path-for [route path-params] (if (:path-params route) (if-let [parts (reduce - (fn [acc part] - (if (string? part) - (conj acc part) - (if-let [p (get path-params (:value part))] - (conj acc p) - (reduced nil)))) - [] (:path-parts route))] + (fn [acc part] + (if (string? part) + (conj acc part) + (if-let [p (get path-params (:value part))] + (conj acc p) + (reduced nil)))) + [] (:path-parts route))] (apply str parts)) (:path route))) @@ -138,8 +138,8 @@ (let [defined (-> path-params keys set) missing (set/difference required defined)] (ex/fail! - (str "missing path-params for route " template " -> " missing) - {:path-params path-params, :required required})))) + (str "missing path-params for route " template " -> " missing) + {:path-params path-params, :required required})))) (defn fast-assoc #?@(:clj [[^clojure.lang.Associative a k v] (.assoc a k v)] @@ -178,10 +178,10 @@ (if s #?(:clj (if (.contains ^String s "%") (URLDecoder/decode - (if (.contains ^String s "+") - (.replace ^String s "+" "%2B") - ^String s) - "UTF-8")) + (if (.contains ^String s "+") + (.replace ^String s "+" "%2B") + ^String s) + "UTF-8")) :cljs (js/decodeURIComponent s)))) (defn url-decode [s] @@ -249,6 +249,10 @@ (->> params (map (fn [[k v]] (if (or (sequential? v) (set? v)) - (str/join "&" (map query-parameter (repeat k) v)) + (if (seq v) + (str/join "&" (map query-parameter (repeat k) v)) + ;; Empty seq results in single & character in the query string. + ;; Handle as empty string to behave similarly as when the value is nil. + (query-parameter k "")) (query-parameter k v)))) (str/join "&"))) diff --git a/modules/reitit-core/src/reitit/interceptor.cljc b/modules/reitit-core/src/reitit/interceptor.cljc index 9231c42a..b4871a72 100644 --- a/modules/reitit-core/src/reitit/interceptor.cljc +++ b/modules/reitit-core/src/reitit/interceptor.cljc @@ -1,9 +1,9 @@ (ns reitit.interceptor - (:require [meta-merge.core :refer [meta-merge]] - [clojure.pprint :as pprint] + (:require [clojure.pprint :as pprint] + [meta-merge.core :refer [meta-merge]] [reitit.core :as r] - [reitit.impl :as impl] - [reitit.exception :as exception])) + [reitit.exception :as exception] + [reitit.impl :as impl])) (defprotocol IntoInterceptor (into-interceptor [this data opts])) @@ -42,25 +42,25 @@ (if-let [interceptor (if registry (registry this))] (into-interceptor interceptor data opts) (throw - (ex-info - (str - "Interceptor " this " not found in registry.\n\n" - (if (seq registry) - (str - "Available interceptors in registry:\n" - (with-out-str - (pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v})))) - "see [reitit.interceptor/router] on how to add interceptor to the registry.\n") "\n") - {:id this - :registry registry})))) + (ex-info + (str + "Interceptor " this " not found in registry.\n\n" + (if (seq registry) + (str + "Available interceptors in registry:\n" + (with-out-str + (pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v})))) + "see [reitit.interceptor/router] on how to add interceptor to the registry.\n") "\n") + {:id this + :registry registry})))) #?(:clj clojure.lang.APersistentVector :cljs cljs.core.PersistentVector) (into-interceptor [[f & args :as form] data opts] (when (and (seq args) (not (fn? f))) (exception/fail! - (str "Invalid Interceptor form: " form "") - {:form form})) + (str "Invalid Interceptor form: " form "") + {:form form})) (into-interceptor (apply f args) data opts)) #?(:clj clojure.lang.Fn @@ -91,13 +91,13 @@ opts (assoc opts ::compiled (inc ^long compiled))] (when (>= ^long compiled ^long *max-compile-depth*) (exception/fail! - (str "Too deep Interceptor compilation - " compiled) - {:this this, :data data, :opts opts})) + (str "Too deep Interceptor compilation - " compiled) + {:this this, :data data, :opts opts})) (if-let [interceptor (into-interceptor (compile data opts) data opts)] (map->Interceptor - (merge - (dissoc this :compile) - (impl/strip-nils interceptor))))))) + (merge + (dissoc this :compile) + (impl/strip-nils interceptor))))))) nil (into-interceptor [_ _ _])) @@ -127,9 +127,9 @@ ([[_ {:keys [interceptors handler] :as data}] {::keys [queue] :as opts} _] (let [chain (chain (into (vec interceptors) [handler]) data opts)] (map->Endpoint - {:interceptors chain - :queue ((or queue identity) chain) - :data data})))) + {:interceptors chain + :queue ((or queue identity) chain) + :data data})))) (defn transform-butlast "Returns a function to that takes a interceptor transformation function and @@ -137,8 +137,8 @@ [f] (fn [interceptors] (concat - (f (butlast interceptors)) - [(last interceptors)]))) + (f (butlast interceptors)) + [(last interceptors)]))) (defn router "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with @@ -160,8 +160,8 @@ :handler get-user}]])" ([data] (router data nil)) - ([data opts] - (let [opts (meta-merge {:compile compile-result} opts)] + ([data {:keys [meta-merge-fn] :as opts}] + (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)] (r/router data opts)))) (defn interceptor-handler [router] diff --git a/modules/reitit-core/src/reitit/middleware.cljc b/modules/reitit-core/src/reitit/middleware.cljc index b27e1d0c..58c13f67 100644 --- a/modules/reitit-core/src/reitit/middleware.cljc +++ b/modules/reitit-core/src/reitit/middleware.cljc @@ -1,9 +1,9 @@ (ns reitit.middleware - (:require [meta-merge.core :refer [meta-merge]] - [clojure.pprint :as pprint] + (:require [clojure.pprint :as pprint] + [meta-merge.core :refer [meta-merge]] [reitit.core :as r] - [reitit.impl :as impl] - [reitit.exception :as exception])) + [reitit.exception :as exception] + [reitit.impl :as impl])) (defprotocol IntoMiddleware (into-middleware [this data opts])) @@ -21,17 +21,17 @@ (if-let [middleware (if registry (registry this))] (into-middleware middleware data opts) (throw - (ex-info - (str - "Middleware " this " not found in registry.\n\n" - (if (seq registry) - (str - "Available middleware in registry:\n" - (with-out-str - (pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v})))) - "see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n") - {:id this - :registry registry})))) + (ex-info + (str + "Middleware " this " not found in registry.\n\n" + (if (seq registry) + (str + "Available middleware in registry:\n" + (with-out-str + (pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v})))) + "see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n") + {:id this + :registry registry})))) #?(:clj clojure.lang.APersistentVector :cljs cljs.core.PersistentVector) @@ -43,7 +43,7 @@ :cljs function) (into-middleware [this _ _] (map->Middleware - {:wrap this})) + {:wrap this})) #?(:clj clojure.lang.PersistentArrayMap :cljs cljs.core.PersistentArrayMap) @@ -63,13 +63,13 @@ opts (assoc opts ::compiled (inc ^long compiled))] (when (>= ^long compiled ^long *max-compile-depth*) (exception/fail! - (str "Too deep Middleware compilation - " compiled) - {:this this, :data data, :opts opts})) + (str "Too deep Middleware compilation - " compiled) + {:this this, :data data, :opts opts})) (if-let [middeware (into-middleware (compile data opts) data opts)] (map->Middleware - (merge - (dissoc this :compile) - (impl/strip-nils middeware))))))) + (merge + (dissoc this :compile) + (impl/strip-nils middeware))))))) nil (into-middleware [_ _ _])) @@ -77,10 +77,10 @@ (defn- ensure-handler! [path data scope] (when-not (:handler data) (exception/fail! - (str "path \"" path "\" doesn't have a :handler defined" - (if scope (str " for " scope))) - (merge {:path path, :data data} - (if scope {:scope scope}))))) + (str "path \"" path "\" doesn't have a :handler defined" + (if scope (str " for " scope))) + (merge {:path path, :data data} + (if scope {:scope scope}))))) (defn- expand-and-transform [middleware data {::keys [transform] :or {transform identity} :as opts}] @@ -116,9 +116,9 @@ (ensure-handler! path data scope) (let [middleware (expand-and-transform middleware data opts)] (map->Endpoint - {:handler (compile-handler middleware handler) - :middleware middleware - :data data})))) + {:handler (compile-handler middleware handler) + :middleware middleware + :data data})))) (defn router "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with @@ -138,8 +138,8 @@ :handler get-user}]])" ([data] (router data nil)) - ([data opts] - (let [opts (meta-merge {:compile compile-result} opts)] + ([data {:keys [meta-merge-fn] :as opts}] + (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)] (r/router data opts)))) (defn middleware-handler [router] diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index f538a3f8..6715eae3 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -1,8 +1,8 @@ (ns reitit.spec (:require [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as gen] - [reitit.exception :as exception] - [reitit.core :as r])) + [reitit.core :as r] + [reitit.exception :as exception])) ;; ;; routes @@ -16,9 +16,9 @@ (s/def ::raw-route (s/nilable - (s/cat :path ::path - :arg (s/? ::arg) - :childs (s/* (s/and (s/nilable ::raw-routes)))))) + (s/cat :path ::path + :arg (s/? ::arg) + :childs (s/* (s/and (s/nilable ::raw-routes)))))) (s/def ::raw-routes (s/or :route ::raw-route @@ -60,19 +60,19 @@ (s/def ::opts (s/nilable - (s/keys :opt-un [:reitit.router/path - :reitit.router/routes - :reitit.router/data - :reitit.router/expand - :reitit.router/coerce - :reitit.router/compile - :reitit.router/conflicts - :reitit.router/router]))) + (s/keys :opt-un [:reitit.router/path + :reitit.router/routes + :reitit.router/data + :reitit.router/expand + :reitit.router/coerce + :reitit.router/compile + :reitit.router/conflicts + :reitit.router/router]))) (s/fdef r/router - :args (s/or :1arity (s/cat :data (s/spec ::raw-routes)) - :2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts)) - :ret ::router) + :args (s/or :1arity (s/cat :data (s/spec ::raw-routes)) + :2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts)) + :ret ::router) ;; ;; coercion @@ -119,24 +119,25 @@ (defrecord Problem [path scope data spec problems]) (defn validate-route-data [routes wrap spec] - (let [spec (wrap spec)] - (some->> (for [[p d _] routes] - (when-let [problems (and spec (s/explain-data spec d))] - (->Problem p nil d spec problems))) - (keep identity) (seq) (vec)))) + (let [spec (wrap spec) + spec-explain (fn [[p d _]] + (when-let [problems (and spec (s/explain-data spec d))] + (->Problem p nil d spec problems))) + errors (into [] (keep spec-explain) routes)] + (when (pos? (count errors)) errors))) (defn validate [routes {:keys [spec] ::keys [wrap] :or {spec ::default-data, wrap identity}}] (when-let [problems (validate-route-data routes wrap spec)] (exception/fail! - ::invalid-route-data - {:problems problems}))) + ::invalid-route-data + {:problems problems}))) (defmethod exception/format-exception :reitit.spec/invalid-route-data [_ _ {:keys [problems]}] (apply str "Invalid route data:\n\n" (mapv - (fn [{:keys [path scope data spec]}] - (str "-- On route -----------------------\n\n" - (pr-str path) (if scope (str " " (pr-str scope))) "\n\n" - (pr-str data) "\n\n" - (s/explain-str spec data) "\n")) - problems))) + (fn [{:keys [path scope data spec]}] + (str "-- On route -----------------------\n\n" + (pr-str path) (if scope (str " " (pr-str scope))) "\n\n" + (pr-str data) "\n\n" + (s/explain-str spec data) "\n")) + problems))) diff --git a/modules/reitit-core/src/reitit/trie.cljc b/modules/reitit-core/src/reitit/trie.cljc index 8cb8f792..22c6356b 100644 --- a/modules/reitit-core/src/reitit/trie.cljc +++ b/modules/reitit-core/src/reitit/trie.cljc @@ -2,8 +2,8 @@ (:refer-clojure :exclude [compile]) (:require [clojure.string :as str] [reitit.exception :as ex]) - #?(:clj (:import [reitit Trie Trie$Match Trie$Matcher] - (java.net URLDecoder)))) + #?(:clj (:import (java.net URLDecoder) + [reitit Trie Trie$Match Trie$Matcher]))) (defn ^:no-doc into-set [x] (cond @@ -90,12 +90,12 @@ (defn join-path [xs] (reduce - (fn [s x] - (str s (cond - (string? x) x - (instance? Wild x) (str "{" (-> x :value str (subs 1)) "}") - (instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}")))) - "" xs)) + (fn [s x] + (str s (cond + (string? x) x + (instance? Wild x) (str "{" (-> x :value str (subs 1)) "}") + (instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}")))) + "" xs)) (defn normalize [s opts] (-> s (split-path opts) (join-path))) @@ -172,25 +172,25 @@ :else (or - (reduce - (fn [_ [p n]] - (if-let [cp (common-prefix p path)] - (if (= cp p) + (reduce + (fn [_ [p n]] + (if-let [cp (common-prefix p path)] + (if (= cp p) ;; insert into child node - (let [n' (-insert n (conj ps (subs path (count p))) fp params data)] - (reduced (assoc-in node [:children p] n'))) + (let [n' (-insert n (conj ps (subs path (count p))) fp params data)] + (reduced (assoc-in node [:children p] n'))) ;; split child node - (let [rp (subs p (count cp)) - rp' (subs path (count cp)) - n' (-insert (-node {}) ps fp params data) - n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)] - (reduced (update node :children (fn [children] - (-> children - (dissoc p) - (assoc cp n''))))))))) - nil (:children node)) + (let [rp (subs p (count cp)) + rp' (subs path (count cp)) + n' (-insert (-node {}) ps fp params data) + n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)] + (reduced (update node :children (fn [children] + (-> children + (dissoc p) + (assoc cp n''))))))))) + nil (:children node)) ;; new child node - (assoc-in node [:children path] (-insert (-node {}) ps fp params data))))] + (assoc-in node [:children path] (-insert (-node {}) ps fp params data))))] (if-let [child (get-in node' [:children ""])] ;; optimize by removing empty paths (-> (merge-with merge (dissoc node' :data) child) @@ -202,10 +202,10 @@ (if percent? #?(:cljs (js/decodeURIComponent param) :clj (URLDecoder/decode - (if (.contains ^String param "+") - (.replace ^String param "+" "%2B") - param) - "UTF-8")) + (if (.contains ^String param "+") + (.replace ^String param "+" "%2B") + param) + "UTF-8")) param))) ;; @@ -313,13 +313,13 @@ (def record-parameters "Memoized function to transform parameters into runtime generated Record." (memoize - (fn [keys] - (if (some qualified-keyword? keys) - (map-parameters keys) - (let [sym (gensym "PathParams") - ctor (symbol (str "map->" sym))] - (binding [*ns* (find-ns 'user)] - (eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {})))))))))) + (fn [keys] + (if (some qualified-keyword? keys) + (map-parameters keys) + (let [sym (gensym "PathParams") + ctor (symbol (str "map->" sym))] + (binding [*ns* (find-ns 'user)] + (eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {})))))))))) (defn insert "Returns a trie with routes added to it." @@ -327,9 +327,9 @@ (insert nil routes)) ([node routes] (reduce - (fn [acc [p d]] - (insert acc p d)) - node routes)) + (fn [acc [p d]] + (insert acc p d)) + node routes)) ([node path data] (insert node path data nil)) ([node path data {::keys [parameters] :or {parameters map-parameters} :as opts}] @@ -355,17 +355,16 @@ (cond-> data (conj (data-matcher compiler params data))) (into (for [[p c] children] (static-matcher compiler p (compile c compiler (conj cp p))))) (into - (for [[p c] wilds] - (let [pv (:value p) - ends (ends c)] - (if (next ends) - (ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))}) - (wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv))))))) + (for [[p c] wilds] + (let [pv (:value p) + ends (ends c)] + (if (next ends) + (ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))}) + (wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv))))))) (into (for [[p c] catch-all] (catch-all-matcher compiler (:value p) params (:data c)))))] (cond (> (count matchers) 1) (linear-matcher compiler matchers false) - (= (count matchers) 1) (first matchers) - :else (data-matcher compiler {} nil))))) + (= (count matchers) 1) (first matchers))))) (defn pretty "Returns a simplified EDN structure of a compiled trie for printing purposes." @@ -387,61 +386,61 @@ (comment (-> - [["/v2/whoami" 1] - ["/v2/users/:user-id/datasets" 2] - ["/v2/public/projects/:project-id/datasets" 3] - ["/v1/public/topics/:topic" 4] - ["/v1/users/:user-id/orgs/:org-id" 5] - ["/v1/search/topics/:term" 6] - ["/v1/users/:user-id/invitations" 7] - ["/v1/users/:user-id/topics" 9] - ["/v1/users/:user-id/bookmarks/followers" 10] - ["/v2/datasets/:dataset-id" 11] - ["/v1/orgs/:org-id/usage-stats" 12] - ["/v1/orgs/:org-id/devices/:client-id" 13] - ["/v1/messages/user/:user-id" 14] - ["/v1/users/:user-id/devices" 15] - ["/v1/public/users/:user-id" 16] - ["/v1/orgs/:org-id/errors" 17] - ["/v1/public/orgs/:org-id" 18] - ["/v1/orgs/:org-id/invitations" 19] - ["/v1/users/:user-id/device-errors" 22] - ["/v2/login" 23] - ["/v1/users/:user-id/usage-stats" 24] - ["/v2/users/:user-id/devices" 25] - ["/v1/users/:user-id/claim-device/:client-id" 26] - ["/v2/public/projects/:project-id" 27] - ["/v2/public/datasets/:dataset-id" 28] - ["/v2/users/:user-id/topics/bulk" 29] - ["/v1/messages/device/:client-id" 30] - ["/v1/users/:user-id/owned-orgs" 31] - ["/v1/topics/:topic" 32] - ["/v1/users/:user-id/bookmark/:topic" 33] - ["/v1/orgs/:org-id/members/:user-id" 34] - ["/v1/users/:user-id/devices/:client-id" 35] - ["/v1/users/:user-id" 36] - ["/v1/orgs/:org-id/devices" 37] - ["/v1/orgs/:org-id/members" 38] - ["/v2/orgs/:org-id/topics" 40] - ["/v1/whoami" 41] - ["/v1/orgs/:org-id" 42] - ["/v1/users/:user-id/api-key" 43] - ["/v2/schemas" 44] - ["/v2/users/:user-id/topics" 45] - ["/v1/orgs/:org-id/confirm-membership/:token" 46] - ["/v2/topics/:topic" 47] - ["/v1/messages/topic/:topic" 48] - ["/v1/users/:user-id/devices/:client-id/reset-password" 49] - ["/v2/topics" 50] - ["/v1/login" 51] - ["/v1/users/:user-id/orgs" 52] - ["/v2/public/messages/dataset/:dataset-id" 53] - ["/v1/topics" 54] - ["/v1/orgs" 55] - ["/v1/users/:user-id/bookmarks" 56] - ["/v1/orgs/:org-id/topics" 57] - ["/command1 {arg1} {arg2}" ::cmd1] - ["/command2 {arg1} {arg2} {arg3}" ::cmd2]] - (insert) - (compile) - (pretty))) + [["/v2/whoami" 1] + ["/v2/users/:user-id/datasets" 2] + ["/v2/public/projects/:project-id/datasets" 3] + ["/v1/public/topics/:topic" 4] + ["/v1/users/:user-id/orgs/:org-id" 5] + ["/v1/search/topics/:term" 6] + ["/v1/users/:user-id/invitations" 7] + ["/v1/users/:user-id/topics" 9] + ["/v1/users/:user-id/bookmarks/followers" 10] + ["/v2/datasets/:dataset-id" 11] + ["/v1/orgs/:org-id/usage-stats" 12] + ["/v1/orgs/:org-id/devices/:client-id" 13] + ["/v1/messages/user/:user-id" 14] + ["/v1/users/:user-id/devices" 15] + ["/v1/public/users/:user-id" 16] + ["/v1/orgs/:org-id/errors" 17] + ["/v1/public/orgs/:org-id" 18] + ["/v1/orgs/:org-id/invitations" 19] + ["/v1/users/:user-id/device-errors" 22] + ["/v2/login" 23] + ["/v1/users/:user-id/usage-stats" 24] + ["/v2/users/:user-id/devices" 25] + ["/v1/users/:user-id/claim-device/:client-id" 26] + ["/v2/public/projects/:project-id" 27] + ["/v2/public/datasets/:dataset-id" 28] + ["/v2/users/:user-id/topics/bulk" 29] + ["/v1/messages/device/:client-id" 30] + ["/v1/users/:user-id/owned-orgs" 31] + ["/v1/topics/:topic" 32] + ["/v1/users/:user-id/bookmark/:topic" 33] + ["/v1/orgs/:org-id/members/:user-id" 34] + ["/v1/users/:user-id/devices/:client-id" 35] + ["/v1/users/:user-id" 36] + ["/v1/orgs/:org-id/devices" 37] + ["/v1/orgs/:org-id/members" 38] + ["/v2/orgs/:org-id/topics" 40] + ["/v1/whoami" 41] + ["/v1/orgs/:org-id" 42] + ["/v1/users/:user-id/api-key" 43] + ["/v2/schemas" 44] + ["/v2/users/:user-id/topics" 45] + ["/v1/orgs/:org-id/confirm-membership/:token" 46] + ["/v2/topics/:topic" 47] + ["/v1/messages/topic/:topic" 48] + ["/v1/users/:user-id/devices/:client-id/reset-password" 49] + ["/v2/topics" 50] + ["/v1/login" 51] + ["/v1/users/:user-id/orgs" 52] + ["/v2/public/messages/dataset/:dataset-id" 53] + ["/v1/topics" 54] + ["/v1/orgs" 55] + ["/v1/users/:user-id/bookmarks" 56] + ["/v1/orgs/:org-id/topics" 57] + ["/command1 {arg1} {arg2}" ::cmd1] + ["/command2 {arg1} {arg2} {arg3}" ::cmd2]] + (insert) + (compile) + (pretty))) diff --git a/modules/reitit-dev/.clj-kondo/config.edn b/modules/reitit-dev/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-dev/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-dev/project.clj b/modules/reitit-dev/project.clj index 507b0204..65da7e02 100644 --- a/modules/reitit-dev/project.clj +++ b/modules/reitit-dev/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-dev "0.5.15" +(defproject metosin/reitit-dev "0.5.18" :description "Snappy data-driven router for Clojure(Script)" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-dev/src/reitit/dev/pretty.cljc b/modules/reitit-dev/src/reitit/dev/pretty.cljc index 5379a442..158d09e0 100644 --- a/modules/reitit-dev/src/reitit/dev/pretty.cljc +++ b/modules/reitit-dev/src/reitit/dev/pretty.cljc @@ -1,18 +1,16 @@ (ns reitit.dev.pretty - (:require [clojure.string :as str] + (:require [arrangement.core] ;; spell-spec [clojure.spec.alpha :as s] - [reitit.exception :as exception] - [arrangement.core] - ;; spell-spec - [spell-spec.expound] - ;; expound + [clojure.string :as str] + [expound.alpha] ;; fipp [expound.ansi] - [expound.alpha] - ;; fipp - [fipp.visit] [fipp.edn] [fipp.ednize] - [fipp.engine])) + [fipp.engine] + [fipp.visit] + [reitit.exception :as exception] + [spell-spec.expound] ;; expound +)) ;; ;; colors @@ -152,13 +150,13 @@ (printer nil)) ([options] (map->EdnPrinter - (merge - {:width 80 - :symbols {} - :print-length *print-length* - :print-level *print-level* - :print-meta *print-meta*} - options)))) + (merge + {:width 80 + :symbols {} + :print-length *print-length* + :print-level *print-level* + :print-meta *print-meta*} + options)))) (defn pprint ([x] (pprint x {})) @@ -209,13 +207,13 @@ (defn exception-str [message source printer] (with-out-str (print-doc - [:group - (title "Router creation failed" source printer) - [:break] [:break] - message - [:break] - (footer printer)] - printer))) + [:group + (title "Router creation failed" source printer) + [:break] [:break] + message + [:break] + (footer printer)] + printer))) (defmulti format-exception (fn [type _ _] type)) @@ -231,11 +229,11 @@ (defn de-expound-colors [^String s mappings] (let [s' (reduce - (fn [s [from to]] - (.replace ^String s - ^String (expound.ansi/esc [from]) - ^String (-start (colors to)))) - s mappings)] + (fn [s [from to]] + (.replace ^String s + ^String (expound.ansi/esc [from]) + ^String (-start (colors to)))) + s mappings)] (.replace ^String s' ^String (expound.ansi/esc [:none]) (str (expound.ansi/esc [:none]) (-start (colors :text)))))) @@ -254,9 +252,9 @@ (def expound-printer (expound.alpha/custom-printer - {:theme :figwheel-theme - :show-valid-values? false - :print-specs? false})) + {:theme :figwheel-theme + :show-valid-values? false + :print-specs? false})) ;; ;; Formatters @@ -276,18 +274,18 @@ " ") (edn (not-empty (select-keys route-data [:conflicting])))])] (into - [:group] - (mapv - (fn [[[path route-data] vals]] - [:group - (path-report path route-data) - (into - [:group] - (map - (fn [[path route-data]] (path-report path route-data)) - vals)) - [:break]]) - conflicts))) + [:group] + (mapv + (fn [[[path route-data] vals]] + [:group + (path-report path route-data) + (into + [:group] + (map + (fn [[path route-data]] (path-report path route-data)) + vals)) + [:break]]) + conflicts))) [:span (text "Either fix the conflicting paths or disable the conflict resolution") [:break] (text "by setting route data for conflicting route: ") [:break] [:break] (edn {:conflicting true} {:margin 3}) @@ -302,19 +300,19 @@ (text "Router contains conflicting route names:") [:break] [:break] (into - [:group] - (mapv - (fn [[name vals]] - [:group - [:span (text name)] - [:break] - (into - [:group] - (map - (fn [p] [:span (color :grey "-> " p) [:break]]) - (mapv first vals))) - [:break]]) - conflicts)) + [:group] + (mapv + (fn [[name vals]] + [:group + [:span (text name)] + [:break] + (into + [:group] + (map + (fn [p] [:span (color :grey "-> " p) [:break]]) + (mapv first vals))) + [:break]]) + conflicts)) (color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts") [:break]]) @@ -323,22 +321,22 @@ (text "Invalid route data:") [:break] [:break] (into - [:group] - (map - (fn [{:keys [data path spec scope]}] - [:group - [:span (color :grey "-- On route -----------------------")] - [:break] - [:break] - (text path) (if scope [:span " " (text scope)]) - [:break] - [:break] - (-> (s/explain-data spec data) - (expound-printer) - (with-out-str) - (fippify)) - [:break]]) - problems)) + [:group] + (map + (fn [{:keys [data path spec scope]}] + [:group + [:span (color :grey "-- On route -----------------------")] + [:break] + [:break] + (text path) (if scope [:span " " (text scope)]) + [:break] + [:break] + (-> (s/explain-data spec data) + (expound-printer) + (with-out-str) + (fippify)) + [:break]]) + problems)) (color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-data-validation") [:break]]) diff --git a/modules/reitit-frontend/.clj-kondo/config.edn b/modules/reitit-frontend/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-frontend/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-frontend/project.clj b/modules/reitit-frontend/project.clj index 84f02bc4..9860511f 100644 --- a/modules/reitit-frontend/project.clj +++ b/modules/reitit-frontend/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-frontend "0.5.15" +(defproject metosin/reitit-frontend "0.5.18" :description "Reitit: Clojurescript frontend routing core" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-frontend/src/reitit/frontend.cljs b/modules/reitit-frontend/src/reitit/frontend.cljs index 6913a438..922b5a8b 100644 --- a/modules/reitit-frontend/src/reitit/frontend.cljs +++ b/modules/reitit-frontend/src/reitit/frontend.cljs @@ -1,5 +1,6 @@ (ns reitit.frontend (:require [clojure.set :as set] + [clojure.string :as str] [reitit.coercion :as coercion] [reitit.core :as r]) (:import goog.Uri @@ -20,20 +21,45 @@ (map (juxt keyword #(query-param q %))) (into {})))) +(defn fragment-params + "Given goog.Uri, read fragment parameters into Clojure map." + [^Uri uri] + (let [fp (.getFragment uri)] + (if-not (seq fp) + {} + (into {} + (comp + (map #(str/split % #"=")) + (map (fn [[k v]] + [(keyword k) v]))) + (str/split fp #"&"))))) + (defn match-by-path "Given routing tree and current path, return match with possibly - coerced parameters. Return nil if no match found." - [router path] - (let [uri (.parse Uri path)] - (if-let [match (r/match-by-path router (.getPath uri))] - (let [q (query-params uri) - match (assoc match :query-params q) - ;; Return uncoerced values if coercion is not enabled - so - ;; that tha parameters are always accessible from same property. - parameters (or (coercion/coerce! match) - {:path (:path-params match) - :query q})] - (assoc match :parameters parameters))))) + coerced parameters. Return nil if no match found. + + :on-coercion-error - a sideeffecting fn of `match exception -> nil`" + ([router path] (match-by-path router path nil)) + ([router path {:keys [on-coercion-error]}] + (let [uri (.parse Uri path) + coerce! (if on-coercion-error + (fn [match] + (try (coercion/coerce! match) + (catch js/Error e + (on-coercion-error match e) + (throw e)))) + coercion/coerce!)] + (if-let [match (r/match-by-path router (.getPath uri))] + (let [q (query-params uri) + fp (fragment-params uri) + match (assoc match :query-params q :fragment-params fp) + ;; Return uncoerced values if coercion is not enabled - so + ;; that tha parameters are always accessible from same property. + parameters (or (coerce! match) + {:path (:path-params match) + :query q + :fragment fp})] + (assoc match :parameters parameters)))))) (defn match-by-name "Given a router, route name and optionally path-parameters, @@ -64,11 +90,11 @@ (let [defined (-> path-params keys set) missing (set/difference (:required match) defined)] (js/console.warn - "missing path-params for route" name - {:template (:template match) - :missing missing - :path-params path-params - :required (:required match)}) + "missing path-params for route" name + {:template (:template match) + :missing missing + :path-params path-params + :required (:required match)}) nil)) match) (do (js/console.warn "missing route" name) diff --git a/modules/reitit-frontend/src/reitit/frontend/easy.cljs b/modules/reitit-frontend/src/reitit/frontend/easy.cljs index 5e329e38..8a76b900 100644 --- a/modules/reitit-frontend/src/reitit/frontend/easy.cljs +++ b/modules/reitit-frontend/src/reitit/frontend/easy.cljs @@ -38,7 +38,7 @@ (when (nil? @history) (reset! history this)) (on-navigate m this)) - opts)) + opts)) (defn ^{:see-also ["reitit.frontend.history/href"]} diff --git a/modules/reitit-frontend/src/reitit/frontend/history.cljs b/modules/reitit-frontend/src/reitit/frontend/history.cljs index f135d7bc..f95dcf12 100644 --- a/modules/reitit-frontend/src/reitit/frontend/history.cljs +++ b/modules/reitit-frontend/src/reitit/frontend/history.cljs @@ -1,9 +1,9 @@ (ns reitit.frontend.history "Provides integration to hash-change or HTML5 History events." - (:require [reitit.core :as reitit] - [reitit.frontend :as rf] - [goog.events :as gevents]) + (:require [goog.events :as gevents] + [reitit.core :as reitit] + [reitit.frontend :as rf]) (:import goog.Uri)) (defprotocol History @@ -40,7 +40,7 @@ nil) (-on-navigate [this path] (reset! last-fragment path) - (on-navigate (rf/match-by-path router path) this)) + (on-navigate (rf/match-by-path router path this) this)) (-get-path [this] ;; Remove # ;; "" or "#" should be same as "#/" @@ -125,7 +125,7 @@ (-on-navigate this (-get-path this)) this)) (-on-navigate [this path] - (on-navigate (rf/match-by-path router path) this)) + (on-navigate (rf/match-by-path router path this) this)) (-stop [this] (gevents/unlistenByKey listen-key) (gevents/unlistenByKey click-listen-key) diff --git a/modules/reitit-http/.clj-kondo/config.edn b/modules/reitit-http/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-http/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-http/project.clj b/modules/reitit-http/project.clj index c4aa584b..968d74fa 100644 --- a/modules/reitit-http/project.clj +++ b/modules/reitit-http/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-http "0.5.15" +(defproject metosin/reitit-http "0.5.18" :description "Reitit: HTTP routing with interceptors" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index be2d49e3..36e96504 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -1,24 +1,24 @@ (ns reitit.http (:require [meta-merge.core :refer [meta-merge]] - [reitit.interceptor :as interceptor] + [reitit.core :as r] [reitit.exception :as ex] - [reitit.ring :as ring] - [reitit.core :as r])) + [reitit.interceptor :as interceptor] + [reitit.ring :as ring])) (defrecord Endpoint [data interceptors queue handler path method]) (defn coerce-handler [[path data] {:keys [expand] :as opts}] [path (reduce - (fn [acc method] - (if (contains? acc method) - (update acc method expand opts) - acc)) data ring/http-methods)]) + (fn [acc method] + (if (contains? acc method) + (update acc method expand opts) + acc)) data ring/http-methods)]) -(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}] +(defn compile-result [[path data] {:keys [::default-options-endpoint expand meta-merge-fn] :as opts}] (let [[top childs] (ring/group-keys data) childs (cond-> childs - (and (not (:options childs)) (not (:handler top)) default-options-endpoint) - (assoc :options (expand default-options-endpoint opts))) + (and (not (:options childs)) (not (:handler top)) default-options-endpoint) + (assoc :options (expand default-options-endpoint opts))) compile (fn [[path data] opts scope] (interceptor/compile-result [path data] opts scope)) ->endpoint (fn [p d m s] @@ -29,19 +29,19 @@ (assoc :method m)))) ->methods (fn [any? data] (reduce - (fn [acc method] - (cond-> acc - any? (assoc method (->endpoint path data method nil)))) - (ring/map->Methods {}) - ring/http-methods))] + (fn [acc method] + (cond-> acc + any? (assoc method (->endpoint path data method nil)))) + (ring/map->Methods {}) + ring/http-methods))] (if-not (seq childs) (->methods true top) (reduce-kv - (fn [acc method data] - (let [data (meta-merge top data)] - (assoc acc method (->endpoint path data method method)))) - (->methods (:handler top) data) - childs)))) + (fn [acc method data] + (let [data ((or meta-merge-fn meta-merge) top data)] + (assoc acc method (->endpoint path data method method)))) + (->methods (:handler top) data) + childs)))) (defn router "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with @@ -133,7 +133,7 @@ (assoc ::interceptor/queue (partial interceptor/queue executor)) (dissoc :data) ; data is already merged into routes (cond-> (seq interceptors) - (update-in [:data :interceptors] (partial into (vec interceptors))))) + (update-in [:data :interceptors] (partial into (vec interceptors))))) router (reitit.http/router (r/routes router) router-opts) ;; will re-compile the interceptors enrich-request (ring/create-enrich-request inject-match? inject-router?) enrich-default-request (ring/create-enrich-default-request inject-router?)] diff --git a/modules/reitit-http/src/reitit/http/coercion.cljc b/modules/reitit-http/src/reitit/http/coercion.cljc index 30af60db..8e63db28 100644 --- a/modules/reitit-http/src/reitit/http/coercion.cljc +++ b/modules/reitit-http/src/reitit/http/coercion.cljc @@ -1,7 +1,7 @@ (ns reitit.http.coercion (:require [reitit.coercion :as coercion] - [reitit.spec :as rs] - [reitit.impl :as impl])) + [reitit.impl :as impl] + [reitit.spec :as rs])) (defn coerce-request-interceptor "Interceptor for pluggable request coercion. diff --git a/modules/reitit-http/src/reitit/http/spec.cljc b/modules/reitit-http/src/reitit/http/spec.cljc index f665c250..f6112c5a 100644 --- a/modules/reitit-http/src/reitit/http/spec.cljc +++ b/modules/reitit-http/src/reitit/http/spec.cljc @@ -1,8 +1,8 @@ (ns reitit.http.spec (:require [clojure.spec.alpha :as s] - [reitit.ring.spec :as rrs] - [reitit.interceptor :as interceptor] [reitit.exception :as exception] + [reitit.interceptor :as interceptor] + [reitit.ring.spec :as rrs] [reitit.spec :as rs])) ;; @@ -22,5 +22,5 @@ [routes {:keys [spec ::rs/wrap] :or {spec ::data, wrap identity}}] (when-let [problems (rrs/validate-route-data routes :interceptors wrap spec)] (exception/fail! - ::rs/invalid-route-data - {:problems problems}))) + ::rs/invalid-route-data + {:problems problems}))) diff --git a/modules/reitit-interceptors/.clj-kondo/config.edn b/modules/reitit-interceptors/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-interceptors/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-interceptors/project.clj b/modules/reitit-interceptors/project.clj index 00c326f4..a4cbab4e 100644 --- a/modules/reitit-interceptors/project.clj +++ b/modules/reitit-interceptors/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-interceptors "0.5.15" +(defproject metosin/reitit-interceptors "0.5.18" :description "Reitit, common interceptors bundled" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj index 285434f4..54173d57 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/dev.clj @@ -20,7 +20,9 @@ (defn- polish [ctx] (-> ctx - (dissoc ::original ::previous :stack :queue) + (dissoc ::original ::previous :stack :queue + :io.pedestal.interceptor.chain/stack + :io.pedestal.interceptor.chain/queue) (update :request dissoc ::r/match ::r/router))) (defn- handle [name stage] @@ -36,16 +38,16 @@ [stages {:keys [enter leave error name] :as interceptor}] (if (->> (select-keys interceptor stages) (vals) (keep identity) (seq)) (cond-> {:name ::diff} - (and enter (stages :enter)) (assoc :enter (handle name :enter)) - (and leave (stages :leave)) (assoc :leave (handle name :leave)) - (and error (stages :error)) (assoc :error (handle name :error))))) + (and enter (stages :enter)) (assoc :enter (handle name :enter)) + (and leave (stages :leave)) (assoc :leave (handle name :leave)) + (and error (stages :error)) (assoc :error (handle name :error))))) (defn print-context-diffs "A interceptor chain transformer that adds a context diff printer between all interceptors" [interceptors] (reduce - (fn [chain interceptor] - (into chain (keep identity [(diff-interceptor #{:leave :error} interceptor) - interceptor - (diff-interceptor #{:enter} interceptor)]))) - [(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors)) + (fn [chain interceptor] + (into chain (keep identity [(diff-interceptor #{:leave :error} interceptor) + interceptor + (diff-interceptor #{:enter} interceptor)]))) + [(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors)) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/exception.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/exception.clj index 89d07927..559a55b3 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/exception.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/exception.clj @@ -1,10 +1,10 @@ (ns reitit.http.interceptors.exception - (:require [reitit.coercion :as coercion] - [reitit.ring :as ring] - [clojure.spec.alpha :as s] - [clojure.string :as str]) - (:import (java.time Instant) - (java.io PrintWriter Writer))) + (:require [clojure.spec.alpha :as s] + [clojure.string :as str] + [reitit.coercion :as coercion] + [reitit.ring :as ring]) + (:import (java.io PrintWriter Writer) + (java.time Instant))) (s/def ::handlers (s/map-of any? fn?)) (s/def ::spec (s/keys :opt-un [::handlers])) @@ -25,11 +25,11 @@ error-handler (or (get handlers type) (get handlers ex-class) (some - (partial get handlers) - (descendants type)) + (partial get handlers) + (descendants type)) (some - (partial get handlers) - (super-classes ex-class)) + (partial get handlers) + (super-classes ex-class)) (get handlers ::default))] (if-let [wrap (get handlers ::wrap)] (wrap error-handler error request) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj index ae5e4af0..d616c689 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj @@ -1,8 +1,8 @@ (ns reitit.http.interceptors.multipart - (:require [reitit.coercion :as coercion] + (:require [clojure.spec.alpha :as s] + [reitit.coercion :as coercion] [reitit.spec] [ring.middleware.multipart-params :as multipart-params] - [clojure.spec.alpha :as s] [spec-tools.core :as st]) (:import (java.io File))) @@ -18,14 +18,14 @@ (def temp-file-part "Spec for file param created by ring.middleware.multipart-params.temp-file store." (st/spec - {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) - :swagger/type "file"})) + {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) + :swagger/type "file"})) (def bytes-part "Spec for file param created by ring.middleware.multipart-params.byte-array store." (st/spec - {:spec (s/keys :req-un [::filename ::content-type ::bytes]) - :swagger/type "file"})) + {:spec (s/keys :req-un [::filename ::content-type ::bytes]) + :swagger/type "file"})) (defn- coerced-request [request coercers] (if-let [coerced (if coercers (coercion/coerce-request coercers request))] @@ -49,7 +49,7 @@ :compile (fn [{:keys [parameters coercion]} opts] (if-let [multipart (:multipart parameters)] (let [parameter-coercion {:multipart (coercion/->ParameterCoercion - :multipart-params :string true true)} + :multipart-params :string true true)} opts (assoc opts ::coercion/parameter-coercion parameter-coercion) coercers (if multipart (coercion/request-coercers coercion parameters opts))] {:data {:swagger {:consumes ^:replace #{"multipart/form-data"}}} diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/muuntaja.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/muuntaja.clj index 8b9fac7a..3dbf7285 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/muuntaja.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/muuntaja.clj @@ -1,7 +1,7 @@ (ns reitit.http.interceptors.muuntaja - (:require [muuntaja.core :as m] - [muuntaja.interceptor] - [clojure.spec.alpha :as s])) + (:require [clojure.spec.alpha :as s] + [muuntaja.core :as m] + [muuntaja.interceptor])) (s/def ::muuntaja m/muuntaja?) (s/def ::spec (s/keys :opt-un [::muuntaja])) @@ -40,10 +40,10 @@ :compile (fn [{:keys [muuntaja parameters]} _] (if-let [muuntaja (or muuntaja default-muuntaja)] (merge - (stripped (muuntaja.interceptor/format-interceptor muuntaja)) - (if (publish-swagger-data? parameters) - {:data {:swagger {:produces (displace (m/encodes muuntaja)) - :consumes (displace (m/decodes muuntaja))}}}))))})) + (stripped (muuntaja.interceptor/format-interceptor muuntaja)) + (if (publish-swagger-data? parameters) + {:data {:swagger {:produces (displace (m/encodes muuntaja)) + :consumes (displace (m/decodes muuntaja))}}}))))})) (defn format-negotiate-interceptor "Interceptor for content-negotiation. @@ -87,9 +87,9 @@ :compile (fn [{:keys [muuntaja parameters]} _] (if-let [muuntaja (or muuntaja default-muuntaja)] (merge - (stripped (muuntaja.interceptor/format-request-interceptor muuntaja)) - (when (publish-swagger-data? parameters) - {:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))})) + (stripped (muuntaja.interceptor/format-request-interceptor muuntaja)) + (when (publish-swagger-data? parameters) + {:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))})) (defn format-response-interceptor "Interceptor for response formatting. @@ -112,6 +112,6 @@ :compile (fn [{:keys [muuntaja parameters]} _] (if-let [muuntaja (or muuntaja default-muuntaja)] (merge - (stripped (muuntaja.interceptor/format-response-interceptor muuntaja)) - (when (publish-swagger-data? parameters) - {:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))})) + (stripped (muuntaja.interceptor/format-response-interceptor muuntaja)) + (when (publish-swagger-data? parameters) + {:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))})) diff --git a/modules/reitit-malli/.clj-kondo/config.edn b/modules/reitit-malli/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-malli/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-malli/project.clj b/modules/reitit-malli/project.clj index 6c456fd4..049c01eb 100644 --- a/modules/reitit-malli/project.clj +++ b/modules/reitit-malli/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-malli "0.5.15" +(defproject metosin/reitit-malli "0.5.18" :description "Reitit: Malli coercion" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index ee4a4bdc..e7b2607c 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -1,13 +1,14 @@ (ns reitit.coercion.malli - (:require [reitit.coercion :as coercion] - [malli.transform :as mt] + (:require [clojure.set :as set] + [clojure.walk :as walk] + [malli.core :as m] [malli.edn :as edn] [malli.error :as me] - [malli.util :as mu] + [malli.experimental.lite :as l] [malli.swagger :as swagger] - [malli.core :as m] - [clojure.set :as set] - [clojure.walk :as walk])) + [malli.transform :as mt] + [malli.util :as mu] + [reitit.coercion :as coercion])) ;; ;; coercion @@ -26,9 +27,9 @@ (reify 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)))))) + (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 json-transformer-provider (-provider (mt/json-transformer))) @@ -61,7 +62,7 @@ transformed (let [error (-explain coercer transformed)] (coercion/map->CoercionError - (assoc error :transformed transformed))))) + (assoc error :transformed transformed))))) value)) ;; encode: decode -> validate -> encode (fn [value format] @@ -71,7 +72,7 @@ (-encode coercer transformed) (let [error (-explain coercer transformed)] (coercion/map->CoercionError - (assoc error :transformed transformed)))) + (assoc error :transformed transformed)))) value)))))))) ;; @@ -91,15 +92,15 @@ (defmethod extract-parameter :default [in schema options] (let [{:keys [properties required]} (swagger/transform schema (merge options {:in in, :type :parameter}))] (mapv - (fn [[k {:keys [type] :as schema}]] - (merge - {:in (name in) - :name k - :description (:description schema "") - :type type - :required (contains? (set required) k)} - schema)) - properties))) + (fn [[k {:keys [type] :as schema}]] + (merge + {:in (name in) + :name k + :description (:description schema "") + :type type + :required (contains? (set required) k)} + schema)) + properties))) ;; ;; public api @@ -113,7 +114,9 @@ :response {:default default-transformer-provider :formats {"application/json" json-transformer-provider}}} ;; 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) :compile mu/closed-schema ;; validate request & response @@ -133,9 +136,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) @@ -143,39 +148,39 @@ (-get-apidocs [_ specification {:keys [parameters responses]}] (case specification :swagger (merge - (if parameters - {:parameters - (->> (for [[in schema] parameters - parameter (extract-parameter in (compile schema options) options)] - parameter) - (into []))}) - (if responses - {:responses - (into - (empty responses) - (for [[status response] responses] - [status (as-> response $ - (set/rename-keys $ {:body :schema}) - (update $ :description (fnil identity "")) - (if (:schema $) - (-> $ - (update :schema compile options) - (update :schema swagger/transform {:type :schema})) - $))]))})) + (if parameters + {:parameters + (->> (for [[in schema] parameters + parameter (extract-parameter in (compile schema options) options)] + parameter) + (into []))}) + (if responses + {:responses + (into + (empty responses) + (for [[status response] responses] + [status (as-> response $ + (set/rename-keys $ {:body :schema}) + (update $ :description (fnil identity "")) + (if (:schema $) + (-> $ + (update :schema compile options) + (update :schema swagger/transform {:type :schema})) + $))]))})) (throw - (ex-info - (str "Can't produce Schema apidocs for " specification) - {:type specification, :coercion :schema})))) + (ex-info + (str "Can't produce Schema apidocs for " specification) + {:type specification, :coercion :schema})))) (-compile-model [_ model _] (compile model options)) (-open-model [_ schema] schema) (-encode-error [_ error] (cond-> error - (show? :humanized) (assoc :humanized (me/humanize error {:wrap :message})) - (show? :schema) (update :schema edn/write-string opts) - (show? :errors) (-> (me/with-error-messages opts) - (update :errors (partial map #(update % :schema edn/write-string opts)))) - (seq error-keys) (select-keys error-keys) - encode-error (encode-error))) + (show? :humanized) (assoc :humanized (me/humanize error {:wrap :message})) + (show? :schema) (update :schema edn/write-string opts) + (show? :errors) (-> (me/with-error-messages opts) + (update :errors (partial map #(update % :schema edn/write-string opts)))) + (seq error-keys) (select-keys error-keys) + encode-error (encode-error))) (-request-coercer [_ type schema] (-coercer (compile schema options) type transformers :decode opts)) (-response-coercer [_ schema] diff --git a/modules/reitit-middleware/.clj-kondo/config.edn b/modules/reitit-middleware/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-middleware/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-middleware/project.clj b/modules/reitit-middleware/project.clj index 9c52873d..c28fd24b 100644 --- a/modules/reitit-middleware/project.clj +++ b/modules/reitit-middleware/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-middleware "0.5.15" +(defproject metosin/reitit-middleware "0.5.18" :description "Reitit, common middleware bundled" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-middleware/src/reitit/ring/middleware/dev.clj b/modules/reitit-middleware/src/reitit/ring/middleware/dev.clj index de89996e..587416ff 100644 --- a/modules/reitit-middleware/src/reitit/ring/middleware/dev.clj +++ b/modules/reitit-middleware/src/reitit/ring/middleware/dev.clj @@ -48,6 +48,6 @@ printer between all middleware." [chain] (reduce - (fn [chain mw] - (into chain [mw (print-diff-middleware (select-keys mw [:name]))])) - [(print-diff-middleware)] chain)) + (fn [chain mw] + (into chain [mw (print-diff-middleware (select-keys mw [:name]))])) + [(print-diff-middleware)] chain)) diff --git a/modules/reitit-middleware/src/reitit/ring/middleware/exception.clj b/modules/reitit-middleware/src/reitit/ring/middleware/exception.clj index b14d0fca..7aa0db92 100644 --- a/modules/reitit-middleware/src/reitit/ring/middleware/exception.clj +++ b/modules/reitit-middleware/src/reitit/ring/middleware/exception.clj @@ -1,10 +1,10 @@ (ns reitit.ring.middleware.exception - (:require [reitit.coercion :as coercion] - [reitit.ring :as ring] - [clojure.spec.alpha :as s] - [clojure.string :as str]) - (:import (java.time Instant) - (java.io Writer PrintWriter))) + (:require [clojure.spec.alpha :as s] + [clojure.string :as str] + [reitit.coercion :as coercion] + [reitit.ring :as ring]) + (:import (java.io Writer PrintWriter) + (java.time Instant))) (s/def ::handlers (s/map-of any? fn?)) (s/def ::spec (s/keys :opt-un [::handlers])) @@ -25,11 +25,11 @@ error-handler (or (get handlers type) (get handlers ex-class) (some - (partial get handlers) - (descendants type)) + (partial get handlers) + (descendants type)) (some - (partial get handlers) - (super-classes ex-class)) + (partial get handlers) + (super-classes ex-class)) (get handlers ::default))] (if-let [wrap (get handlers ::wrap)] (wrap error-handler error request) diff --git a/modules/reitit-middleware/src/reitit/ring/middleware/multipart.clj b/modules/reitit-middleware/src/reitit/ring/middleware/multipart.clj index 07a1d8c2..2542b0a3 100644 --- a/modules/reitit-middleware/src/reitit/ring/middleware/multipart.clj +++ b/modules/reitit-middleware/src/reitit/ring/middleware/multipart.clj @@ -1,8 +1,8 @@ (ns reitit.ring.middleware.multipart (:refer-clojure :exclude [compile]) - (:require [reitit.coercion :as coercion] + (:require [clojure.spec.alpha :as s] + [reitit.coercion :as coercion] [ring.middleware.multipart-params :as multipart-params] - [clojure.spec.alpha :as s] [spec-tools.core :as st]) (:import (java.io File))) @@ -15,14 +15,14 @@ (def temp-file-part "Spec for file param created by ring.middleware.multipart-params.temp-file store." (st/spec - {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) - :swagger/type "file"})) + {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) + :swagger/type "file"})) (def bytes-part "Spec for file param created by ring.middleware.multipart-params.byte-array store." (st/spec - {:spec (s/keys :req-un [::filename ::content-type ::bytes]) - :swagger/type "file"})) + {:spec (s/keys :req-un [::filename ::content-type ::bytes]) + :swagger/type "file"})) (defn- coerced-request [request coercers] (if-let [coerced (if coercers (coercion/coerce-request coercers request))] @@ -33,7 +33,7 @@ (fn [{:keys [parameters coercion]} opts] (if-let [multipart (:multipart parameters)] (let [parameter-coercion {:multipart (coercion/->ParameterCoercion - :multipart-params :string true true)} + :multipart-params :string true true)} opts (assoc opts ::coercion/parameter-coercion parameter-coercion) coercers (if multipart (coercion/request-coercers coercion parameters opts))] {:data {:swagger {:consumes ^:replace #{"multipart/form-data"}}} diff --git a/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj b/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj index 351eaeca..06d0b0b8 100644 --- a/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj +++ b/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj @@ -1,7 +1,7 @@ (ns reitit.ring.middleware.muuntaja - (:require [muuntaja.core :as m] - [muuntaja.middleware] - [clojure.spec.alpha :as s])) + (:require [clojure.spec.alpha :as s] + [muuntaja.core :as m] + [muuntaja.middleware])) (s/def ::muuntaja m/muuntaja?) (s/def ::spec (s/keys :opt-un [::muuntaja])) @@ -34,10 +34,10 @@ :compile (fn [{:keys [muuntaja parameters]} _] (if muuntaja (merge - (if (publish-swagger-data? parameters) - {:data {:swagger {:produces (displace (m/encodes muuntaja)) - :consumes (displace (m/decodes muuntaja))}}}) - {:wrap #(muuntaja.middleware/wrap-format % muuntaja)})))}) + (if (publish-swagger-data? parameters) + {:data {:swagger {:produces (displace (m/encodes muuntaja)) + :consumes (displace (m/decodes muuntaja))}}}) + {:wrap #(muuntaja.middleware/wrap-format % muuntaja)})))}) (def format-negotiate-middleware "Middleware for content-negotiation. @@ -71,9 +71,9 @@ :compile (fn [{:keys [muuntaja parameters]} _] (if muuntaja (merge - (when (publish-swagger-data? parameters) - {:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}) - {:wrap #(muuntaja.middleware/wrap-format-request % muuntaja)})))}) + (when (publish-swagger-data? parameters) + {:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}) + {:wrap #(muuntaja.middleware/wrap-format-request % muuntaja)})))}) (def format-response-middleware "Middleware for response formatting. @@ -91,6 +91,6 @@ :compile (fn [{:keys [muuntaja parameters]} _] (if muuntaja (merge - (when (publish-swagger-data? parameters) - {:data {:swagger {:produces (displace (m/encodes muuntaja))}}}) - {:wrap #(muuntaja.middleware/wrap-format-response % muuntaja)})))}) + (when (publish-swagger-data? parameters) + {:data {:swagger {:produces (displace (m/encodes muuntaja))}}}) + {:wrap #(muuntaja.middleware/wrap-format-response % muuntaja)})))}) diff --git a/modules/reitit-pedestal/.clj-kondo/config.edn b/modules/reitit-pedestal/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-pedestal/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-pedestal/project.clj b/modules/reitit-pedestal/project.clj index b913876e..fc2b0ee1 100644 --- a/modules/reitit-pedestal/project.clj +++ b/modules/reitit-pedestal/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-pedestal "0.5.15" +(defproject metosin/reitit-pedestal "0.5.18" :description "Reitit + Pedestal Integration" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-pedestal/src/reitit/pedestal.clj b/modules/reitit-pedestal/src/reitit/pedestal.clj index 7d53cd06..04c262f8 100644 --- a/modules/reitit-pedestal/src/reitit/pedestal.clj +++ b/modules/reitit-pedestal/src/reitit/pedestal.clj @@ -1,11 +1,11 @@ (ns reitit.pedestal - (:require [io.pedestal.interceptor.chain :as chain] + (:require [io.pedestal.http :as http] [io.pedestal.interceptor :as interceptor] - [io.pedestal.http :as http] - [reitit.interceptor] - [reitit.http]) - (:import (reitit.interceptor Executor) - (java.lang.reflect Method))) + [io.pedestal.interceptor.chain :as chain] + [reitit.http] + [reitit.interceptor]) + (:import (java.lang.reflect Method) + (reitit.interceptor Executor))) ;; TODO: variadic (defn- arities [f] @@ -36,9 +36,9 @@ interceptor (->> (select-keys interceptor [:enter :leave :error]) (vals) (keep identity) (seq)) (interceptor/interceptor - (if (error-without-arity-2? interceptor) - (wrap-error-arity-2->1 interceptor) - interceptor)))) + (if (error-without-arity-2? interceptor) + (wrap-error-arity-2->1 interceptor) + interceptor)))) ;; ;; Public API @@ -62,11 +62,11 @@ (routing-interceptor router default-handler nil)) ([router default-handler {:keys [interceptors]}] (interceptor/interceptor - (reitit.http/routing-interceptor - router - default-handler - {:executor pedestal-executor - :interceptors interceptors})))) + (reitit.http/routing-interceptor + router + default-handler + {:executor pedestal-executor + :interceptors interceptors})))) (defn replace-last-interceptor [service-map interceptor] (-> service-map diff --git a/modules/reitit-ring/.clj-kondo/config.edn b/modules/reitit-ring/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-ring/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-ring/project.clj b/modules/reitit-ring/project.clj index 66c54555..64b5cda2 100644 --- a/modules/reitit-ring/project.clj +++ b/modules/reitit-ring/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-ring "0.5.15" +(defproject metosin/reitit-ring "0.5.18" :description "Reitit: Ring routing" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index ab897c11..c98b2bc8 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -1,12 +1,12 @@ (ns reitit.ring - (:require [meta-merge.core :refer [meta-merge]] - [reitit.middleware :as middleware] - [reitit.exception :as ex] - [reitit.core :as r] - [reitit.impl :as impl] + (:require [clojure.string :as str] + [meta-merge.core :refer [meta-merge]] #?@(:clj [[ring.util.mime-type :as mime-type] - [ring.util.response :as response]]) - [clojure.string :as str])) + [ring.util.response :as response]]) + [reitit.core :as r] + [reitit.exception :as ex] + [reitit.impl :as impl] + [reitit.middleware :as middleware])) (declare get-match) (declare get-router) @@ -17,23 +17,23 @@ (defn ^:no-wiki group-keys [data] (reduce-kv - (fn [[top childs] k v] - (if (http-methods k) - [top (assoc childs k v)] - [(assoc top k v) childs])) [{} {}] data)) + (fn [[top childs] k v] + (if (http-methods k) + [top (assoc childs k v)] + [(assoc top k v) childs])) [{} {}] data)) (defn coerce-handler [[path data] {:keys [expand] :as opts}] [path (reduce - (fn [acc method] - (if (contains? acc method) - (update acc method expand opts) - acc)) data http-methods)]) + (fn [acc method] + (if (contains? acc method) + (update acc method expand opts) + acc)) data http-methods)]) -(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}] +(defn compile-result [[path data] {:keys [::default-options-endpoint expand meta-merge-fn] :as opts}] (let [[top childs] (group-keys data) childs (cond-> childs - (and (not (:options childs)) (not (:handler top)) default-options-endpoint) - (assoc :options (expand default-options-endpoint opts))) + (and (not (:options childs)) (not (:handler top)) default-options-endpoint) + (assoc :options (expand default-options-endpoint opts))) ->endpoint (fn [p d m s] (-> (middleware/compile-result [p d] opts s) (map->Endpoint) @@ -41,30 +41,30 @@ (assoc :method m))) ->methods (fn [any? data] (reduce - (fn [acc method] - (cond-> acc - any? (assoc method (->endpoint path data method nil)))) - (map->Methods {}) - http-methods))] + (fn [acc method] + (cond-> acc + any? (assoc method (->endpoint path data method nil)))) + (map->Methods {}) + http-methods))] (if-not (seq childs) (->methods true top) (reduce-kv - (fn [acc method data] - (let [data (meta-merge top data)] - (assoc acc method (->endpoint path data method method)))) - (->methods (:handler top) data) - childs)))) + (fn [acc method data] + (let [data ((or meta-merge-fn meta-merge) top data)] + (assoc acc method (->endpoint path data method method)))) + (->methods (:handler top) data) + childs)))) (def default-options-handler - (let [handle (fn [request] - (let [methods (->> request get-match :result (keep (fn [[k v]] (if v k)))) - allow (->> methods (map (comp str/upper-case name)) (str/join ","))] - {:status 200, :body "", :headers {"Allow" allow}}))] + (let [handler (fn [request] + (let [methods (->> request get-match :result (keep (fn [[k v]] (if v k)))) + allow (->> methods (map (comp str/upper-case name)) (str/join ","))] + {:status 200, :body "", :headers {"Allow" allow}}))] (fn ([request] - (handle request)) + (handler request)) ([request respond _] - (respond (handle request)))))) + (respond (handler request)))))) (def default-options-endpoint {:no-doc true @@ -318,26 +318,26 @@ enrich-default-request (create-enrich-default-request inject-router?)] (with-meta (wrap - (fn - ([request] - (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request) - path-params (:path-params match) - result (:result match) - handler (-> result method :handler (or default-handler)) - request (enrich-request request path-params match router)] - (or (handler request) (default-handler request))) - (default-handler (enrich-default-request request router)))) - ([request respond raise] - (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request) - path-params (:path-params match) - result (:result match) - handler (-> result method :handler (or default-handler)) - request (enrich-request request path-params match router)] - ((routes handler default-handler) request respond raise)) - (default-handler (enrich-default-request request router) respond raise)) - nil))) + (fn + ([request] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + result (:result match) + handler (-> result method :handler (or default-handler)) + request (enrich-request request path-params match router)] + (or (handler request) (default-handler request))) + (default-handler (enrich-default-request request router)))) + ([request respond raise] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + result (:result match) + handler (-> result method :handler (or default-handler)) + request (enrich-request request path-params match router)] + ((routes handler default-handler) request respond raise)) + (default-handler (enrich-default-request request router) respond raise)) + nil))) {::r/router router})))) (defn get-router [handler] diff --git a/modules/reitit-ring/src/reitit/ring/coercion.cljc b/modules/reitit-ring/src/reitit/ring/coercion.cljc index 3f1c280d..efbe83f7 100644 --- a/modules/reitit-ring/src/reitit/ring/coercion.cljc +++ b/modules/reitit-ring/src/reitit/ring/coercion.cljc @@ -1,7 +1,7 @@ (ns reitit.ring.coercion (:require [reitit.coercion :as coercion] - [reitit.spec :as rs] - [reitit.impl :as impl])) + [reitit.impl :as impl] + [reitit.spec :as rs])) (defn handle-coercion-exception [e respond raise] (let [data (ex-data e)] @@ -10,8 +10,8 @@ ::coercion/response-coercion 500 nil)] (respond - {:status status - :body (coercion/encode-error data)}) + {:status status + :body (coercion/encode-error data)}) (raise e)))) ;; diff --git a/modules/reitit-ring/src/reitit/ring/spec.cljc b/modules/reitit-ring/src/reitit/ring/spec.cljc index ec047d5a..9d68e8ed 100644 --- a/modules/reitit-ring/src/reitit/ring/spec.cljc +++ b/modules/reitit-ring/src/reitit/ring/spec.cljc @@ -1,8 +1,8 @@ (ns reitit.ring.spec (:require [clojure.spec.alpha :as s] + [reitit.exception :as exception] [reitit.middleware :as middleware] - [reitit.spec :as rs] - [reitit.exception :as exception])) + [reitit.spec :as rs])) ;; ;; Specs @@ -19,7 +19,6 @@ (s/def ::trace map?) (s/def ::patch map?) - (s/def ::data (s/keys :opt-un [::rs/handler ::rs/name ::rs/no-doc ::middleware])) @@ -30,9 +29,9 @@ (defn merge-specs [specs] (when-let [non-specs (seq (remove #(or (s/spec? %) (s/get-spec %)) specs))] (exception/fail! - ::invalid-specs - {:specs specs - :invalid non-specs})) + ::invalid-specs + {:specs specs + :invalid non-specs})) (s/merge-spec-impl (vec specs) (vec specs) nil)) (defn validate-route-data [routes key wrap spec] @@ -51,5 +50,5 @@ [routes {:keys [spec ::rs/wrap] :or {spec ::data, wrap identity}}] (when-let [problems (validate-route-data routes :middleware wrap spec)] (exception/fail! - ::rs/invalid-route-data - {:problems problems}))) + ::rs/invalid-route-data + {:problems problems}))) diff --git a/modules/reitit-schema/.clj-kondo/config.edn b/modules/reitit-schema/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-schema/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-schema/project.clj b/modules/reitit-schema/project.clj index e822fbb9..de6f0d29 100644 --- a/modules/reitit-schema/project.clj +++ b/modules/reitit-schema/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-schema "0.5.15" +(defproject metosin/reitit-schema "0.5.18" :description "Reitit: Plumatic Schema coercion" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index 1985ba72..022f3872 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -1,13 +1,13 @@ (ns reitit.coercion.schema - (:require [clojure.walk :as walk] - [schema.core :as s] - [schema-tools.core :as st] - [schema.coerce :as sc] - [schema.utils :as su] - [schema-tools.coerce :as stc] - [schema-tools.swagger.core :as swagger] + (:require [clojure.set :as set] + [clojure.walk :as walk] [reitit.coercion :as coercion] - [clojure.set :as set])) + [schema-tools.coerce :as stc] + [schema-tools.core :as st] + [schema-tools.swagger.core :as swagger] + [schema.coerce :as sc] + [schema.core :as s] + [schema.utils :as su])) (def string-coercion-matcher stc/string-coercion-matcher) @@ -23,16 +23,16 @@ (defn stringify [schema] (walk/prewalk - (fn [x] - (cond - #?@(:clj [(class? x) (.getName ^Class x)]) - (instance? schema.core.OptionalKey x) (pr-str (list 'opt (:k x))) - (instance? schema.core.RequiredKey x) (pr-str (list 'req (:k x))) - (and (satisfies? s/Schema x) (record? x)) (try (pr-str (s/explain x)) (catch #?(:clj Exception :cljs js/Error) _ x)) - (instance? schema.utils.ValidationError x) (str (su/validation-error-explain x)) - (instance? schema.utils.NamedError x) (str (su/named-error-explain x)) - :else x)) - schema)) + (fn [x] + (cond + #?@(:clj [(class? x) (.getName ^Class x)]) + (instance? schema.core.OptionalKey x) (pr-str (list 'opt (:k x))) + (instance? schema.core.RequiredKey x) (pr-str (list 'req (:k x))) + (and (satisfies? s/Schema x) (record? x)) (try (pr-str (s/explain x)) (catch #?(:clj Exception :cljs js/Error) _ x)) + (instance? schema.utils.ValidationError x) (str (su/validation-error-explain x)) + (instance? schema.utils.NamedError x) (str (su/named-error-explain x)) + :else x)) + schema)) (def default-options {:coerce-response? coerce-response? @@ -50,27 +50,27 @@ ;; TODO: this looks identical to spec, refactor when schema is done. (case specification :swagger (swagger/swagger-spec - (merge - (if parameters - {::swagger/parameters - (into - (empty parameters) - (for [[k v] parameters] - [k (coercion/-compile-model this v nil)]))}) - (if responses - {::swagger/responses - (into - (empty responses) - (for [[k response] responses] - [k (as-> response $ - (set/rename-keys $ {:body :schema}) - (if (:schema $) - (update $ :schema #(coercion/-compile-model this % nil)) - $))]))}))) + (merge + (if parameters + {::swagger/parameters + (into + (empty parameters) + (for [[k v] parameters] + [k (coercion/-compile-model this v nil)]))}) + (if responses + {::swagger/responses + (into + (empty responses) + (for [[k response] responses] + [k (as-> response $ + (set/rename-keys $ {:body :schema}) + (if (:schema $) + (update $ :schema #(coercion/-compile-model this % nil)) + $))]))}))) (throw - (ex-info - (str "Can't produce Schema apidocs for " specification) - {:type specification, :coercion :schema})))) + (ex-info + (str "Can't produce Schema apidocs for " specification) + {:type specification, :coercion :schema})))) (-compile-model [_ model _] model) (-open-model [_ schema] (st/open-schema schema)) (-encode-error [_ error] @@ -88,8 +88,8 @@ coerced (coercer value)] (if-let [error (su/error-val coerced)] (coercion/map->CoercionError - {:schema schema - :errors error}) + {:schema schema + :errors error}) coerced)) value)))) (-response-coercer [this schema] diff --git a/modules/reitit-schema/src/reitit/ring/schema.cljc b/modules/reitit-schema/src/reitit/ring/schema.cljc index c4f400ab..934948d8 100644 --- a/modules/reitit-schema/src/reitit/ring/schema.cljc +++ b/modules/reitit-schema/src/reitit/ring/schema.cljc @@ -1,6 +1,6 @@ (ns reitit.ring.schema - (:require [schema.core :as s] - [schema-tools.swagger.core :as swagger]) + (:require [schema-tools.swagger.core :as swagger] + [schema.core :as s]) #?(:clj (:import (java.io File)))) (defrecord Upload [m] diff --git a/modules/reitit-sieppari/.clj-kondo/config.edn b/modules/reitit-sieppari/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-sieppari/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-sieppari/project.clj b/modules/reitit-sieppari/project.clj index 996aa6a1..bab5da48 100644 --- a/modules/reitit-sieppari/project.clj +++ b/modules/reitit-sieppari/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-sieppari "0.5.15" +(defproject metosin/reitit-sieppari "0.5.18" :description "Reitit: Sieppari Interceptors" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-sieppari/src/reitit/interceptor/sieppari.clj b/modules/reitit-sieppari/src/reitit/interceptor/sieppari.clj index 1198dff4..1fa4e242 100644 --- a/modules/reitit-sieppari/src/reitit/interceptor/sieppari.clj +++ b/modules/reitit-sieppari/src/reitit/interceptor/sieppari.clj @@ -1,17 +1,17 @@ (ns reitit.interceptor.sieppari (:require [reitit.interceptor :as interceptor] - [sieppari.queue :as queue] - [sieppari.core :as sieppari])) + [sieppari.core :as sieppari] + [sieppari.queue :as queue])) (def executor (reify interceptor/Executor (queue [_ interceptors] (queue/into-queue - (map - (fn [{::interceptor/keys [handler] :as interceptor}] - (or handler interceptor)) - interceptors))) + (map + (fn [{::interceptor/keys [handler] :as interceptor}] + (or handler interceptor)) + interceptors))) (execute [_ interceptors request] (sieppari/execute interceptors request)) (execute [_ interceptors request respond raise] diff --git a/modules/reitit-spec/.clj-kondo/config.edn b/modules/reitit-spec/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-spec/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-spec/project.clj b/modules/reitit-spec/project.clj index 78d8b4af..602cf25c 100644 --- a/modules/reitit-spec/project.clj +++ b/modules/reitit-spec/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-spec "0.5.15" +(defproject metosin/reitit-spec "0.5.18" :description "Reitit: clojure.spec coercion" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index bac09a25..5f9809a7 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -1,23 +1,23 @@ (ns reitit.coercion.spec - (:require [clojure.spec.alpha :as s] + (:require [clojure.set :as set] + [clojure.spec.alpha :as s] + [reitit.coercion :as coercion] [spec-tools.core :as st #?@(:cljs [:refer [Spec]])] [spec-tools.data-spec :as ds #?@(:cljs [:refer [Maybe]])] - [spec-tools.swagger.core :as swagger] - [reitit.coercion :as coercion] - [clojure.set :as set]) + [spec-tools.swagger.core :as swagger]) #?(:clj (:import (spec_tools.core Spec) (spec_tools.data_spec Maybe)))) (def string-transformer (st/type-transformer - st/strip-extra-keys-transformer - st/string-transformer)) + st/strip-extra-keys-transformer + st/string-transformer)) (def json-transformer (st/type-transformer - st/strip-extra-keys-transformer - st/json-transformer)) + st/strip-extra-keys-transformer + st/json-transformer)) (def strip-extra-keys-transformer st/strip-extra-keys-transformer) @@ -88,27 +88,27 @@ (-get-apidocs [this specification {:keys [parameters responses]}] (case specification :swagger (swagger/swagger-spec - (merge - (if parameters - {::swagger/parameters - (into - (empty parameters) - (for [[k v] parameters] - [k (coercion/-compile-model this v nil)]))}) - (if responses - {::swagger/responses - (into - (empty responses) - (for [[k response] responses] - [k (as-> response $ - (set/rename-keys $ {:body :schema}) - (if (:schema $) - (update $ :schema #(coercion/-compile-model this % nil)) - $))]))}))) + (merge + (if parameters + {::swagger/parameters + (into + (empty parameters) + (for [[k v] parameters] + [k (coercion/-compile-model this v nil)]))}) + (if responses + {::swagger/responses + (into + (empty responses) + (for [[k response] responses] + [k (as-> response $ + (set/rename-keys $ {:body :schema}) + (if (:schema $) + (update $ :schema #(coercion/-compile-model this % nil)) + $))]))}))) (throw - (ex-info - (str "Can't produce Spec apidocs for " specification) - {:specification specification, :coercion :spec})))) + (ex-info + (str "Can't produce Spec apidocs for " specification) + {:specification specification, :coercion :spec})))) (-compile-model [_ model name] (into-spec model name)) (-open-model [_ spec] spec) @@ -129,8 +129,8 @@ (if (s/invalid? transformed) (let [problems (st/explain-data spec coerced transformer)] (coercion/map->CoercionError - {:spec spec - :problems problems})) + {:spec spec + :problems problems})) (s/unform spec transformed))))) value)))) (-response-coercer [this spec] diff --git a/modules/reitit-swagger-ui/.clj-kondo/config.edn b/modules/reitit-swagger-ui/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-swagger-ui/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-swagger-ui/project.clj b/modules/reitit-swagger-ui/project.clj index edbe8789..9086493a 100644 --- a/modules/reitit-swagger-ui/project.clj +++ b/modules/reitit-swagger-ui/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-swagger-ui "0.5.15" +(defproject metosin/reitit-swagger-ui "0.5.18" :description "Reitit: Swagger-ui support" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc b/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc index 4603b11c..1c10ea27 100644 --- a/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc +++ b/modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc @@ -1,6 +1,6 @@ (ns reitit.swagger-ui - #?(:clj (:require [reitit.ring :as ring] - [jsonista.core :as j]))) + #?(:clj (:require [jsonista.core :as j] + [reitit.ring :as ring]))) #?(:clj (defn create-swagger-ui-handler @@ -31,10 +31,10 @@ ([options] (let [config-json (fn [{:keys [url config]}] (j/write-value-as-string (merge config {:url url}))) options (as-> options $ - (update $ :root (fnil identity "swagger-ui")) - (update $ :url (fnil identity "/swagger.json")) - (assoc $ :paths {"/config.json" {:headers {"Content-Type" "application/json"} - :status 200 - :body (config-json $)}}))] + (update $ :root (fnil identity "swagger-ui")) + (update $ :url (fnil identity "/swagger.json")) + (assoc $ :paths {"/config.json" {:headers {"Content-Type" "application/json"} + :status 200 + :body (config-json $)}}))] (ring/routes - (ring/create-resource-handler options)))))) + (ring/create-resource-handler options)))))) diff --git a/modules/reitit-swagger/.clj-kondo/config.edn b/modules/reitit-swagger/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit-swagger/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit-swagger/project.clj b/modules/reitit-swagger/project.clj index 4178a895..06f59705 100644 --- a/modules/reitit-swagger/project.clj +++ b/modules/reitit-swagger/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-swagger "0.5.15" +(defproject metosin/reitit-swagger "0.5.18" :description "Reitit: Swagger-support" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/modules/reitit-swagger/src/reitit/swagger.cljc b/modules/reitit-swagger/src/reitit/swagger.cljc index 998457ac..0286565a 100644 --- a/modules/reitit-swagger/src/reitit/swagger.cljc +++ b/modules/reitit-swagger/src/reitit/swagger.cljc @@ -1,20 +1,22 @@ (ns reitit.swagger - (:require [reitit.core :as r] - [meta-merge.core :refer [meta-merge]] + (:require [clojure.set :as set] [clojure.spec.alpha :as s] - [clojure.set :as set] [clojure.string :as str] + [meta-merge.core :refer [meta-merge]] [reitit.coercion :as coercion] + [reitit.core :as r] [reitit.trie :as trie])) (s/def ::id (s/or :keyword keyword? :set (s/coll-of keyword? :into #{}))) (s/def ::no-doc boolean?) (s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{})) +(s/def ::operationId string?) (s/def ::summary string?) (s/def ::description string?) +(s/def ::operationId string?) (s/def ::swagger (s/keys :opt-un [::id])) -(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description])) +(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description ::operationId])) (def swagger-feature "Feature for handling swagger-documentation for routes. @@ -52,6 +54,7 @@ [\"/plus\" {:get {:swagger {:tags \"math\"} + :operationId \"addTwoNumbers\" :summary \"adds numbers together\" :description \"takes `x` and `y` query-params and adds them together\" :parameters {:query {:x int?, :y int?}} @@ -75,7 +78,7 @@ (let [{:keys [id] :or {id ::default} :as swagger} (-> match :result request-method :data :swagger) ids (trie/into-set id) strip-top-level-keys #(dissoc % :id :info :host :basePath :definitions :securityDefinitions) - strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description) + strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description :operationId) swagger (->> (strip-endpoint-keys swagger) (merge {:swagger "2.0" :x-id ids})) @@ -93,7 +96,7 @@ (apply meta-merge (keep (comp :swagger :data) interceptors)) (if coercion (coercion/get-apidocs coercion :swagger data)) - (select-keys data [:tags :summary :description]) + (select-keys data [:tags :summary :description :operationId]) (strip-top-level-keys swagger))])) transform-path (fn [[p _ c]] (if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))] diff --git a/modules/reitit/.clj-kondo/config.edn b/modules/reitit/.clj-kondo/config.edn new file mode 120000 index 00000000..31dc332a --- /dev/null +++ b/modules/reitit/.clj-kondo/config.edn @@ -0,0 +1 @@ +../../../.clj-kondo/module_config.edn \ No newline at end of file diff --git a/modules/reitit/project.clj b/modules/reitit/project.clj index 525c96b2..632819e9 100644 --- a/modules/reitit/project.clj +++ b/modules/reitit/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit "0.5.15" +(defproject metosin/reitit "0.5.18" :description "Snappy data-driven router for Clojure(Script)" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" diff --git a/project.clj b/project.clj index 99cc6b00..edfe3e1b 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-parent "0.5.15" +(defproject metosin/reitit-parent "0.5.18" :description "Snappy data-driven router for Clojure(Script)" :url "https://github.com/metosin/reitit" :license {:name "Eclipse Public License" @@ -12,48 +12,48 @@ :url "https://github.com/metosin/reitit"} ;; TODO: need to verify that the code actually worked with Java1.8, see #242 :javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"] - :managed-dependencies [[metosin/reitit "0.5.15"] - [metosin/reitit-core "0.5.15"] - [metosin/reitit-dev "0.5.15"] - [metosin/reitit-spec "0.5.15"] - [metosin/reitit-malli "0.5.15"] - [metosin/reitit-schema "0.5.15"] - [metosin/reitit-ring "0.5.15"] - [metosin/reitit-middleware "0.5.15"] - [metosin/reitit-http "0.5.15"] - [metosin/reitit-interceptors "0.5.15"] - [metosin/reitit-swagger "0.5.15"] - [metosin/reitit-swagger-ui "0.5.15"] - [metosin/reitit-frontend "0.5.15"] - [metosin/reitit-sieppari "0.5.15"] - [metosin/reitit-pedestal "0.5.15"] - [metosin/ring-swagger-ui "3.46.0"] + :managed-dependencies [[metosin/reitit "0.5.18"] + [metosin/reitit-core "0.5.18"] + [metosin/reitit-dev "0.5.18"] + [metosin/reitit-spec "0.5.18"] + [metosin/reitit-malli "0.5.18"] + [metosin/reitit-schema "0.5.18"] + [metosin/reitit-ring "0.5.18"] + [metosin/reitit-middleware "0.5.18"] + [metosin/reitit-http "0.5.18"] + [metosin/reitit-interceptors "0.5.18"] + [metosin/reitit-swagger "0.5.18"] + [metosin/reitit-swagger-ui "0.5.18"] + [metosin/reitit-frontend "0.5.18"] + [metosin/reitit-sieppari "0.5.18"] + [metosin/reitit-pedestal "0.5.18"] + [metosin/ring-swagger-ui "4.3.0"] [metosin/spec-tools "0.10.5"] [metosin/schema-tools "0.12.3"] [metosin/muuntaja "0.6.8"] - [metosin/jsonista "0.3.3"] + [metosin/jsonista "0.3.5"] [metosin/sieppari "0.0.0-alpha13"] - [metosin/malli "0.5.1"] + [metosin/malli "0.8.2"] ;; https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111 - [com.fasterxml.jackson.core/jackson-core "2.12.4"] - [com.fasterxml.jackson.core/jackson-databind "2.12.4"] + [com.fasterxml.jackson.core/jackson-core "2.14.1"] + [com.fasterxml.jackson.core/jackson-databind "2.14.1"] [meta-merge "1.0.0"] - [fipp "0.6.24" :exclusions [org.clojure/core.rrb-vector]] - [expound "0.8.9"] + [fipp "0.6.25" :exclusions [org.clojure/core.rrb-vector]] + [expound "0.9.0"] [lambdaisland/deep-diff "0.0-47"] [com.bhauman/spell-spec "0.1.2"] - [ring/ring-core "1.9.4"] + [ring/ring-core "1.9.5"] - [io.pedestal/pedestal.service "0.5.9"]] + [io.pedestal/pedestal.service "0.5.10"]] - :plugins [[jonase/eastwood "0.9.6"] + :plugins [[jonase/eastwood "1.2.2"] ;[lein-virgil "0.1.7"] [lein-doo "0.1.11"] [lein-cljsbuild "1.1.8"] [lein-cloverage "1.2.2"] - [lein-codox "0.10.7"] + [lein-codox "0.10.8"] [metosin/bat-test "0.4.4"]] :profiles {:dev {:jvm-opts ^:replace ["-server"] @@ -85,31 +85,31 @@ [metosin/spec-tools "0.10.5"] [metosin/muuntaja "0.6.8"] [metosin/sieppari "0.0.0-alpha13"] - [metosin/jsonista "0.3.3"] - [metosin/malli "0.5.1"] + [metosin/jsonista "0.3.5"] + [metosin/malli "0.8.2"] [lambdaisland/deep-diff "0.0-47"] [meta-merge "1.0.0"] [com.bhauman/spell-spec "0.1.2"] - [expound "0.8.9"] - [fipp "0.6.24"] + [expound "0.9.0"] + [fipp "0.6.25"] [orchestra "2021.01.01-1"] - [ring "1.9.4"] + [ring "1.9.5"] [ikitommi/immutant-web "3.0.0-alpha1"] - [metosin/ring-http-response "0.9.2"] - [metosin/ring-swagger-ui "3.46.0"] + [metosin/ring-http-response "0.9.3"] + [metosin/ring-swagger-ui "4.3.0"] [criterium "0.4.6"] - [org.clojure/test.check "1.1.0"] - [org.clojure/tools.namespace "1.1.0"] - [com.gfredericks/test.chuck "0.2.11"] + [org.clojure/test.check "1.1.1"] + [org.clojure/tools.namespace "1.2.0"] + [com.gfredericks/test.chuck "0.2.13"] - [io.pedestal/pedestal.service "0.5.9"] + [io.pedestal/pedestal.service "0.5.10"] - [org.clojure/core.async "1.3.618"] - [manifold "0.1.8"] - [funcool/promesa "6.0.2"] + [org.clojure/core.async "1.5.648"] + [manifold "0.2.3"] + [funcool/promesa "6.1.434"] [com.clojure-goes-fast/clj-async-profiler "0.5.1"] [ring-cors "0.1.13"] @@ -123,12 +123,12 @@ :dependencies [[compojure "1.6.2"] [ring/ring-defaults "0.3.3"] [ikitommi/immutant-web "3.0.0-alpha1"] - [io.pedestal/pedestal.service "0.5.9"] - [io.pedestal/pedestal.jetty "0.5.9"] + [io.pedestal/pedestal.service "0.5.10"] + [io.pedestal/pedestal.jetty "0.5.10"] [calfpath "0.8.1"] - [org.clojure/core.async "1.3.618"] - [manifold "0.1.8"] - [funcool/promesa "6.0.2"] + [org.clojure/core.async "1.5.648"] + [manifold "0.2.3"] + [funcool/promesa "6.1.434"] [metosin/sieppari] [yada "1.2.16"] [aleph "0.4.6"] diff --git a/test/clj/cljdoc/reaper.clj b/test/clj/cljdoc/reaper.clj index c760ed8f..bce3e68c 100644 --- a/test/clj/cljdoc/reaper.clj +++ b/test/clj/cljdoc/reaper.clj @@ -11,11 +11,10 @@ wrap (if (pos? (count indent)) vector identity)] (wrap [name {:file (str "doc/" file)}]))) (reduce - (fn [acc data] - (if (vector? (first data)) - (update-in acc [(dec (count acc)) 2] (fnil into []) data) - (conj acc data)) - ) []) + (fn [acc data] + (if (vector? (first data)) + (update-in acc [(dec (count acc)) 2] (fnil into []) data) + (conj acc data))) []) ;; third sweep to flatten chids... (mapv (fn [[n o c]] (if c (into [n o] c) [n o])))) data {:cljdoc/include-namespaces-from-dependencies ['metosin/reitit diff --git a/test/clj/reitit/http/interceptors/exception_test.clj b/test/clj/reitit/http/interceptors/exception_test.clj index c50ce106..15a53197 100644 --- a/test/clj/reitit/http/interceptors/exception_test.clj +++ b/test/clj/reitit/http/interceptors/exception_test.clj @@ -1,12 +1,12 @@ (ns reitit.http.interceptors.exception-test - (:require [clojure.test :refer [deftest testing is]] - [reitit.ring :as ring] + (:require [clojure.test :refer [deftest is testing]] + [muuntaja.core :as m] + [reitit.coercion.spec] [reitit.http :as http] + [reitit.http.coercion] [reitit.http.interceptors.exception :as exception] [reitit.interceptor.sieppari :as sieppari] - [reitit.coercion.spec] - [reitit.http.coercion] - [muuntaja.core :as m]) + [reitit.ring :as ring]) (:import (java.sql SQLException SQLWarning))) (derive ::kikka ::kukka) @@ -17,23 +17,23 @@ (create f nil)) ([f wrap] (http/ring-handler - (http/router - [["/defaults" - {:handler f}] - ["/coercion" - {:interceptors [(reitit.http.coercion/coerce-request-interceptor) - (reitit.http.coercion/coerce-response-interceptor)] - :coercion reitit.coercion.spec/coercion - :parameters {:query {:x int?, :y int?}} - :responses {200 {:body {:total pos-int?}}} - :handler f}]] - {:data {:interceptors [(exception/exception-interceptor - (merge - exception/default-handlers - {::kikka (constantly {:status 400, :body "kikka"}) - SQLException (constantly {:status 400, :body "sql"}) - ::exception/wrap wrap}))]}}) - {:executor sieppari/executor})))] + (http/router + [["/defaults" + {:handler f}] + ["/coercion" + {:interceptors [(reitit.http.coercion/coerce-request-interceptor) + (reitit.http.coercion/coerce-response-interceptor)] + :coercion reitit.coercion.spec/coercion + :parameters {:query {:x int?, :y int?}} + :responses {200 {:body {:total pos-int?}}} + :handler f}]] + {:data {:interceptors [(exception/exception-interceptor + (merge + exception/default-handlers + {::kikka (constantly {:status 400, :body "kikka"}) + SQLException (constantly {:status 400, :body "sql"}) + ::exception/wrap wrap}))]}}) + {:executor sieppari/executor})))] (testing "normal calls work ok" (let [response {:status 200, :body "ok"} diff --git a/test/clj/reitit/http/interceptors/muuntaja_test.clj b/test/clj/reitit/http/interceptors/muuntaja_test.clj index b7c086de..c26a5160 100644 --- a/test/clj/reitit/http/interceptors/muuntaja_test.clj +++ b/test/clj/reitit/http/interceptors/muuntaja_test.clj @@ -1,19 +1,19 @@ (ns reitit.http.interceptors.muuntaja-test - (:require [clojure.test :refer [deftest testing is]] + (:require [clojure.test :refer [deftest is testing]] + [muuntaja.core :as m] [reitit.http :as http] [reitit.http.interceptors.muuntaja :as muuntaja] - [reitit.swagger :as swagger] [reitit.interceptor.sieppari :as sieppari] - [muuntaja.core :as m])) + [reitit.swagger :as swagger])) (deftest muuntaja-test (let [data {:kikka "kukka"} app (http/ring-handler - (http/router - ["/ping" {:get (constantly {:status 200, :body data})}] - {:data {:muuntaja m/instance - :interceptors [(muuntaja/format-interceptor)]}}) - {:executor sieppari/executor})] + (http/router + ["/ping" {:get (constantly {:status 200, :body data})}] + {:data {:muuntaja m/instance + :interceptors [(muuntaja/format-interceptor)]}}) + {:executor sieppari/executor})] (is (= data (->> {:request-method :get, :uri "/ping"} (app) :body @@ -24,27 +24,27 @@ no-edn-decode (m/create (-> m/default-options (update-in [:formats "application/edn"] dissoc :decoder))) just-edn (m/create (-> m/default-options (m/select-formats ["application/edn"]))) app (http/ring-handler - (http/router - [["/defaults" - {:get identity}] - ["/explicit-defaults" - {:muuntaja with-defaults - :get identity}] - ["/no-edn-decode" - {:muuntaja no-edn-decode - :get identity}] - ["/just-edn" - {:muuntaja just-edn - :get identity}] - ["/form-params" - {:post {:parameters {:form {:x string?}} - :handler identity}}] - ["/swagger.json" - {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]] - {:data {:muuntaja m/instance - :interceptors [(muuntaja/format-interceptor)]}}) - {:executor sieppari/executor}) + (http/router + [["/defaults" + {:get identity}] + ["/explicit-defaults" + {:muuntaja with-defaults + :get identity}] + ["/no-edn-decode" + {:muuntaja no-edn-decode + :get identity}] + ["/just-edn" + {:muuntaja just-edn + :get identity}] + ["/form-params" + {:post {:parameters {:form {:x string?}} + :handler identity}}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:muuntaja m/instance + :interceptors [(muuntaja/format-interceptor)]}}) + {:executor sieppari/executor}) spec (fn [method path] (let [path (keyword path)] (-> {:request-method :get :uri "/swagger.json"} @@ -99,42 +99,42 @@ (deftest muuntaja-swagger-parts-test (let [app (http/ring-handler - (http/router - [["/request" - {:interceptors [(muuntaja/format-negotiate-interceptor) - (muuntaja/format-request-interceptor)] - :get identity}] - ["/response" - {:interceptors [(muuntaja/format-negotiate-interceptor) - (muuntaja/format-response-interceptor)] - :get identity}] - ["/both" - {:interceptors [(muuntaja/format-negotiate-interceptor) - (muuntaja/format-response-interceptor) - (muuntaja/format-request-interceptor)] - :get identity}] - ["/form-request" - {:interceptors [(muuntaja/format-negotiate-interceptor) + (http/router + [["/request" + {:interceptors [(muuntaja/format-negotiate-interceptor) (muuntaja/format-request-interceptor)] - :post {:parameters {:form {:x string?}} - :handler identity}}] - ["/form-response" - {:interceptors [(muuntaja/format-negotiate-interceptor) + :get identity}] + ["/response" + {:interceptors [(muuntaja/format-negotiate-interceptor) (muuntaja/format-response-interceptor)] - :post {:parameters {:form {:x string?}} - :handler identity}}] - ["/form-with-both" - {:interceptors [(muuntaja/format-negotiate-interceptor) + :get identity}] + ["/both" + {:interceptors [(muuntaja/format-negotiate-interceptor) (muuntaja/format-response-interceptor) (muuntaja/format-request-interceptor)] - :post {:parameters {:form {:x string?}} - :handler identity}}] + :get identity}] + ["/form-request" + {:interceptors [(muuntaja/format-negotiate-interceptor) + (muuntaja/format-request-interceptor)] + :post {:parameters {:form {:x string?}} + :handler identity}}] + ["/form-response" + {:interceptors [(muuntaja/format-negotiate-interceptor) + (muuntaja/format-response-interceptor)] + :post {:parameters {:form {:x string?}} + :handler identity}}] + ["/form-with-both" + {:interceptors [(muuntaja/format-negotiate-interceptor) + (muuntaja/format-response-interceptor) + (muuntaja/format-request-interceptor)] + :post {:parameters {:form {:x string?}} + :handler identity}}] - ["/swagger.json" - {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]] - {:data {:muuntaja m/instance}}) - {:executor sieppari/executor}) + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:muuntaja m/instance}}) + {:executor sieppari/executor}) spec (fn [method path] (-> {:request-method :get :uri "/swagger.json"} (app) :body :paths (get path) method)) diff --git a/test/clj/reitit/http/interceptors/parameters_test.clj b/test/clj/reitit/http/interceptors/parameters_test.clj index bb326782..648e1d4c 100644 --- a/test/clj/reitit/http/interceptors/parameters_test.clj +++ b/test/clj/reitit/http/interceptors/parameters_test.clj @@ -1,16 +1,16 @@ (ns reitit.http.interceptors.parameters-test - (:require [clojure.test :refer [deftest testing is]] - [reitit.http.interceptors.parameters :as parameters] + (:require [clojure.test :refer [deftest is testing]] [reitit.http :as http] + [reitit.http.interceptors.parameters :as parameters] [reitit.interceptor.sieppari :as sieppari] [reitit.swagger :as swagger])) (deftest parameters-test (let [app (http/ring-handler - (http/router - ["/ping" {:get #(select-keys % [:params :query-params])}] - {:data {:interceptors [(parameters/parameters-interceptor)]}}) - {:executor sieppari/executor})] + (http/router + ["/ping" {:get #(select-keys % [:params :query-params])}] + {:data {:interceptors [(parameters/parameters-interceptor)]}}) + {:executor sieppari/executor})] (is (= {:query-params {"kikka" "kukka"} :params {"kikka" "kukka"}} (app {:request-method :get @@ -19,15 +19,15 @@ (deftest parameters-swagger-test (let [app (http/ring-handler - (http/router - [["/form-params" {:post {:parameters {:form {:x string?}} - :handler identity}}] - ["/body-params" {:post {:parameters {:body {:x string?}} - :handler identity}}] - ["/swagger.json" {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]] - {:data {:interceptors [(parameters/parameters-interceptor)]}}) -  {:executor sieppari/executor}) + (http/router + [["/form-params" {:post {:parameters {:form {:x string?}} + :handler identity}}] + ["/body-params" {:post {:parameters {:body {:x string?}} + :handler identity}}] + ["/swagger.json" {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:interceptors [(parameters/parameters-interceptor)]}}) + {:executor sieppari/executor}) spec (fn [path] (-> {:request-method :get :uri "/swagger.json"} app diff --git a/test/clj/reitit/http_coercion_test.clj b/test/clj/reitit/http_coercion_test.clj index 5c5c8352..53c749a7 100644 --- a/test/clj/reitit/http_coercion_test.clj +++ b/test/clj/reitit/http_coercion_test.clj @@ -1,15 +1,15 @@ (ns reitit.http-coercion-test - (:require [clojure.test :refer [deftest testing is]] - [schema.core :as s] + (:require [clojure.test :refer [deftest is testing]] + [jsonista.core :as j] + [muuntaja.interceptor] + [reitit.coercion.malli :as malli] + [reitit.coercion.schema :as schema] + [reitit.coercion.spec :as spec] + [reitit.core :as r] [reitit.http :as http] [reitit.http.coercion :as rrc] - [reitit.coercion.spec :as spec] - [reitit.coercion.schema :as schema] - [muuntaja.interceptor] - [jsonista.core :as j] [reitit.interceptor.sieppari :as sieppari] - [reitit.coercion.malli :as malli] - [reitit.core :as r]) + [schema.core :as s]) (:import (clojure.lang ExceptionInfo) (java.io ByteArrayInputStream))) @@ -74,20 +74,20 @@ (deftest spec-coercion-test (let [create (fn [interceptors] (http/ring-handler - (http/router - ["/api" - ["/plus/:e" - {:get {:parameters {:query {:a int?} - :body {:b int?} - :form {:c int?} - :header {:d int?} - :path {:e int?}} - :responses {200 {:body {:total pos-int?}} - 500 {:description "fail"}} - :handler handler}}]] - {:data {:interceptors interceptors - :coercion spec/coercion}}) - {:executor sieppari/executor}))] + (http/router + ["/api" + ["/plus/:e" + {:get {:parameters {:query {:a int?} + :body {:b int?} + :form {:c int?} + :header {:d int?} + :path {:e int?}} + :responses {200 {:body {:total pos-int?}} + 500 {:description "fail"}} + :handler handler}}]] + {:data {:interceptors interceptors + :coercion spec/coercion}}) + {:executor sieppari/executor}))] (testing "without exception handling" (let [app (create [(rrc/coerce-request-interceptor) @@ -103,15 +103,15 @@ (testing "invalid request" (is (thrown-with-msg? - ExceptionInfo - #"Request coercion failed" - (app invalid-request1)))) + ExceptionInfo + #"Request coercion failed" + (app invalid-request1)))) (testing "invalid response" (is (thrown-with-msg? - ExceptionInfo - #"Response coercion failed" - (app invalid-request2)))))) + ExceptionInfo + #"Response coercion failed" + (app invalid-request2)))))) (testing "with exception handling" (let [app (create [(rrc/coerce-exceptions-interceptor) @@ -134,20 +134,20 @@ (deftest schema-coercion-test (let [create (fn [middleware] (http/ring-handler - (http/router - ["/api" - ["/plus/:e" - {:get {:parameters {:query {:a s/Int} - :body {:b s/Int} - :form {:c s/Int} - :header {:d s/Int} - :path {:e s/Int}} - :responses {200 {:body {:total (s/constrained s/Int pos? 'positive)}} - 500 {:description "fail"}} - :handler handler}}]] - {:data {:interceptors middleware - :coercion schema/coercion}}) - {:executor sieppari/executor}))] + (http/router + ["/api" + ["/plus/:e" + {:get {:parameters {:query {:a s/Int} + :body {:b s/Int} + :form {:c s/Int} + :header {:d s/Int} + :path {:e s/Int}} + :responses {200 {:body {:total (s/constrained s/Int pos? 'positive)}} + 500 {:description "fail"}} + :handler handler}}]] + {:data {:interceptors middleware + :coercion schema/coercion}}) + {:executor sieppari/executor}))] (testing "without exception handling" (let [app (create [(rrc/coerce-request-interceptor) @@ -163,15 +163,15 @@ (testing "invalid request" (is (thrown-with-msg? - ExceptionInfo - #"Request coercion failed" - (app invalid-request1)))) + ExceptionInfo + #"Request coercion failed" + (app invalid-request1)))) (testing "invalid response" (is (thrown-with-msg? - ExceptionInfo - #"Response coercion failed" - (app invalid-request2)))) + ExceptionInfo + #"Response coercion failed" + (app invalid-request2)))) (testing "with exception handling" (let [app (create [(rrc/coerce-exceptions-interceptor) @@ -194,51 +194,51 @@ (deftest malli-coercion-test (let [create (fn [interceptors] (http/ring-handler - (http/router - ["/api" + (http/router + ["/api" - ["/validate" {:summary "just validation" - :coercion (reitit.coercion.malli/create {:transformers {}}) - :post {:parameters {:body [:map [:x int?]]} - :responses {200 {:body [:map [:x int?]]}} - :handler (fn [req] - {:status 200 - :body (-> req :parameters :body)})}}] + ["/validate" {:summary "just validation" + :coercion (reitit.coercion.malli/create {:transformers {}}) + :post {:parameters {:body [:map [:x int?]]} + :responses {200 {:body [:map [: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 [:map [:x int?]]} - :responses {200 {:body [:map [: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 [:map [:x int?]]} + :responses {200 {:body [:map [:x int?]]}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :body)})}}] - ["/skip" {:summary "skip" - :coercion (reitit.coercion.malli/create {:enabled false}) - :post {:parameters {:body [:map [:x int?]]} - :responses {200 {:body [:map [:x int?]]}} - :handler (fn [req] - {:status 200 - :body (-> req :parameters :body)})}}] - - ["/or" {:post {:summary "accepts either of two map schemas" - :parameters {:body [:or [:map [:x int?]] [:map [:y int?]]]} - :responses {200 {:body [:map [:msg string?]]}} - :handler (fn [{{{:keys [x]} :body} :parameters}] + ["/skip" {:summary "skip" + :coercion (reitit.coercion.malli/create {:enabled false}) + :post {:parameters {:body [:map [:x int?]]} + :responses {200 {:body [:map [:x int?]]}} + :handler (fn [req] {:status 200 - :body {:msg (if x "you sent x" "you sent y")}})}}] + :body (-> req :parameters :body)})}}] - ["/plus/:e" {:get {:parameters {:query [:map [:a {:optional true} int?]] - :body [:map [:b int?]] - :form [:map [:c [int? {:default 3}]]] - :header [:map [:d int?]] - :path [:map [:e int?]]} - :responses {200 {:body [:map [:total pos-int?]]} - 500 {:description "fail"}} - :handler handler}}]] - {:data {:interceptors interceptors - :coercion malli/coercion}}) - {:executor sieppari/executor}))] + ["/or" {:post {:summary "accepts either of two map schemas" + :parameters {:body [:or [:map [:x int?]] [:map [:y int?]]]} + :responses {200 {:body [:map [: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 [:map [:a {:optional true} int?]] + :body [:map [:b int?]] + :form [:map [:c [int? {:default 3}]]] + :header [:map [:d int?]] + :path [:map [:e int?]]} + :responses {200 {:body [:map [:total pos-int?]]} + 500 {:description "fail"}} + :handler handler}}]] + {:data {:interceptors interceptors + :coercion malli/coercion}}) + {:executor sieppari/executor}))] (testing "withut exception handling" (let [app (create [(rrc/coerce-request-interceptor) @@ -249,8 +249,8 @@ :body {:total 15}} (app valid-request1))) #_(is (= {:status 200 - :body {:total 115}} - (app valid-request2))) + :body {:total 115}} + (app valid-request2))) (is (= {:status 200 :body {:total 15}} (app valid-request3))) @@ -264,15 +264,15 @@ (testing "invalid request" (is (thrown-with-msg? - ExceptionInfo - #"Request coercion failed" - (app invalid-request1)))) + ExceptionInfo + #"Request coercion failed" + (app invalid-request1)))) (testing "invalid response" (is (thrown-with-msg? - ExceptionInfo - #"Response coercion failed" - (app invalid-request2)))))) + ExceptionInfo + #"Response coercion failed" + (app invalid-request2)))))) (testing "with exception handling" (let [app (create [(rrc/coerce-exceptions-interceptor) @@ -339,16 +339,16 @@ {:status 200, :body (assoc body :response true)})}}) ->app (fn [options] (http/ring-handler - (http/router - ["/api" - ["/default" (endpoint [:map [:x int?]])] - ["/closed" (endpoint [:map {:closed true} [:x int?]])] - ["/open" (endpoint [:map {:closed false} [:x int?]])]] - {:data {:interceptors [(rrc/coerce-exceptions-interceptor) - (rrc/coerce-request-interceptor) - (rrc/coerce-response-interceptor)] - :coercion (malli/create options)}}) - {:executor sieppari/executor})) + (http/router + ["/api" + ["/default" (endpoint [:map [:x int?]])] + ["/closed" (endpoint [:map {:closed true} [:x int?]])] + ["/open" (endpoint [:map {:closed false} [:x int?]])]] + {:data {:interceptors [(rrc/coerce-exceptions-interceptor) + (rrc/coerce-request-interceptor) + (rrc/coerce-response-interceptor)] + :coercion (malli/create options)}}) + {:executor sieppari/executor})) ->request (fn [uri] {:uri (str "/api/" uri) :request-method :get :muuntaja/request {:format "application/json"} @@ -399,23 +399,23 @@ (testing "sequence schemas" (let [app (http/ring-handler - (http/router - ["/ping" {:get {:parameters {:body [:vector [:map [:message string?]]]} - :responses {200 {:body [:vector [:map [:pong string?]]]} - 501 {:body [:vector [:map [:error string?]]]}} - :handler (fn [{{[{:keys [message]}] :body} :parameters :as req}] - (condp = message - "ping" {:status 200 - :body [{:pong message}]} - "fail" {:status 501 - :body [{:error "fail"}]} - {:status 200 - :body {:invalid "response"}}))}}] - {:data {:interceptors [(rrc/coerce-exceptions-interceptor) - (rrc/coerce-request-interceptor) - (rrc/coerce-response-interceptor)] - :coercion malli/coercion}}) - {:executor sieppari/executor}) + (http/router + ["/ping" {:get {:parameters {:body [:vector [:map [:message string?]]]} + :responses {200 {:body [:vector [:map [:pong string?]]]} + 501 {:body [:vector [:map [:error string?]]]}} + :handler (fn [{{[{:keys [message]}] :body} :parameters :as req}] + (condp = message + "ping" {:status 200 + :body [{:pong message}]} + "fail" {:status 501 + :body [{:error "fail"}]} + {:status 200 + :body {:invalid "response"}}))}}] + {:data {:interceptors [(rrc/coerce-exceptions-interceptor) + (rrc/coerce-request-interceptor) + (rrc/coerce-response-interceptor)] + :coercion malli/coercion}}) + {:executor sieppari/executor}) ->request (fn [body] {:uri "/ping" :request-method :get @@ -439,19 +439,19 @@ (deftest muuntaja-test (let [app (http/ring-handler - (http/router - ["/api" - ["/plus" - {:post {:parameters {:body {:int int?, :keyword keyword?}} - :responses {200 {:body {:int int?, :keyword keyword?}}} - :handler (fn [{{:keys [body]} :parameters}] - {:status 200 - :body body})}}]] - {:data {:interceptors [(muuntaja.interceptor/format-interceptor) - (rrc/coerce-response-interceptor) - (rrc/coerce-request-interceptor)] - :coercion spec/coercion}}) - {:executor sieppari/executor}) + (http/router + ["/api" + ["/plus" + {:post {:parameters {:body {:int int?, :keyword keyword?}} + :responses {200 {:body {:int int?, :keyword keyword?}}} + :handler (fn [{{:keys [body]} :parameters}] + {:status 200 + :body body})}}]] + {:data {:interceptors [(muuntaja.interceptor/format-interceptor) + (rrc/coerce-response-interceptor) + (rrc/coerce-request-interceptor)] + :coercion spec/coercion}}) + {:executor sieppari/executor}) request (fn [content-type body] (-> {:request-method :post :headers {"content-type" content-type, "accept" content-type} diff --git a/test/clj/reitit/http_test.clj b/test/clj/reitit/http_test.clj index 196fb732..0894ab9a 100644 --- a/test/clj/reitit/http_test.clj +++ b/test/clj/reitit/http_test.clj @@ -1,13 +1,13 @@ (ns reitit.http-test "just Clojure before Sieppari is ported into cljs" - (:require [clojure.test :refer [deftest testing is]] + (:require [clojure.core.async :as a] [clojure.set :as set] + [clojure.test :refer [deftest is testing]] + [reitit.core :as r] + [reitit.http :as http] [reitit.interceptor :as interceptor] [reitit.interceptor.sieppari :as sieppari] - [reitit.http :as http] - [reitit.ring :as ring] - [reitit.core :as r] - [clojure.core.async :as a]) + [reitit.ring :as ring]) (:import (clojure.lang ExceptionInfo))) (defn interceptor [name] @@ -21,14 +21,14 @@ (testing "http-handler" (let [api-interceptor (interceptor :api) router (http/router - ["/api" {:interceptors [api-interceptor]} - ["/all" handler] - ["/get" {:get handler}] - ["/users" {:interceptors [[interceptor :users]] - :get handler - :post {:handler handler - :interceptors [[interceptor :post]]} - :handler handler}]]) + ["/api" {:interceptors [api-interceptor]} + ["/all" handler] + ["/get" {:get handler}] + ["/users" {:interceptors [[interceptor :users]] + :get handler + :post {:handler handler + :interceptors [[interceptor :post]]} + :handler handler}]]) app (http/ring-handler router nil {:executor sieppari/executor})] (testing "router can be extracted" @@ -68,14 +68,14 @@ (testing "named routes" (let [router (http/router - [["/api" - ["/all" {:handler handler :name ::all}] - ["/get" {:get {:handler handler :name ::HIDDEN} - :name ::get}] - ["/users" {:get handler - :post handler - :handler handler - :name ::users}]]]) + [["/api" + ["/all" {:handler handler :name ::all}] + ["/get" {:get {:handler handler :name ::HIDDEN} + :name ::get}] + ["/users" {:get handler + :post handler + :handler handler + :name ::users}]]]) app (http/ring-handler router nil {:executor sieppari/executor})] (testing "router can be extracted" @@ -102,13 +102,13 @@ (deftest enforcing-data-rules-at-runtime-test (let [handler (constantly {:status 200, :body "ok"}) app (http/ring-handler - (http/router - [["/api" - ["/ping" handler] - ["/admin" {::roles #{:admin}} - ["/ping" handler]]]] - {:data {:interceptors [enforce-roles-interceptor]}}) - nil {:executor sieppari/executor})] + (http/router + [["/api" + ["/ping" handler] + ["/admin" {::roles #{:admin}} + ["/ping" handler]]]] + {:data {:interceptors [enforce-roles-interceptor]}}) + nil {:executor sieppari/executor})] (testing "public handler" (is (= {:status 200, :body "ok"} @@ -128,8 +128,8 @@ (deftest default-handler-test (let [response {:status 200, :body "ok"} router (http/router - [["/ping" {:get (constantly response)}] - ["/pong" (constantly nil)]]) + [["/ping" {:get (constantly response)}] + ["/pong" (constantly nil)]]) app (http/ring-handler router nil {:executor sieppari/executor})] (testing "match" @@ -146,9 +146,9 @@ (testing "with default http responses" (let [app (http/ring-handler - router - (ring/create-default-handler) - {:executor sieppari/executor})] + router + (ring/create-default-handler) + {:executor sieppari/executor})] (testing "route doesn't match yields 404" (is (= 404 (:status (app {:request-method :get, :uri "/"}))))) (testing "method doesn't match yields 405" @@ -158,12 +158,12 @@ (testing "with custom http responses" (let [app (http/ring-handler - router - (ring/create-default-handler - {:not-found (constantly {:status -404}) - :method-not-allowed (constantly {:status -405}) - :not-acceptable (constantly {:status -406})}) - {:executor sieppari/executor})] + router + (ring/create-default-handler + {:not-found (constantly {:status -404}) + :method-not-allowed (constantly {:status -405}) + :not-acceptable (constantly {:status -406})}) + {:executor sieppari/executor})] (testing "route doesn't match" (is (= -404 (:status (app {:request-method :get, :uri "/"}))))) (testing "method doesn't match" @@ -180,12 +180,12 @@ (testing "with defaults" (let [app (http/ring-handler - (http/router - [["/get" {:get (constantly response) - :post (constantly response)}] - ["/options" {:options (constantly response)}] - ["/any" (constantly response)]]) - {:executor sieppari/executor})] + (http/router + [["/get" {:get (constantly response) + :post (constantly response)}] + ["/options" {:options (constantly response)}] + ["/any" (constantly response)]]) + {:executor sieppari/executor})] (testing "endpoint with a non-options handler" (is (= response (app {:request-method :get, :uri "/get"}))) @@ -201,12 +201,12 @@ (testing "disabled via options" (let [app (http/ring-handler - (http/router - [["/get" {:get (constantly response)}] - ["/options" {:options (constantly response)}] - ["/any" (constantly response)]] - {::http/default-options-endpoint nil}) - {:executor sieppari/executor})] + (http/router + [["/get" {:get (constantly response)}] + ["/options" {:options (constantly response)}] + ["/any" (constantly response)]] + {::http/default-options-endpoint nil}) + {:executor sieppari/executor})] (testing "endpoint with a non-options handler" (is (= response (app {:request-method :get, :uri "/get"}))) @@ -227,8 +227,8 @@ (reset! value x)))) response {:status 200, :body "ok"} router (http/router - [["/ping" {:get (fn [_] response)}] - ["/pong" (fn [_] nil)]]) + [["/ping" {:get (fn [_] response)}] + ["/pong" (fn [_] nil)]]) app (http/ring-handler router nil {:executor sieppari/executor})] (testing "match" @@ -287,11 +287,11 @@ (require '[sieppari.async.core-async]) (let [response {:status 200, :body "ok"} app (http/ring-handler - (http/router - ["/ping" {:get {:interceptors [{:enter #(a/go %)}] - :handler (fn [_] (a/go response))}}]) - (ring/create-default-handler) - {:executor sieppari/executor})] + (http/router + ["/ping" {:get {:interceptors [{:enter #(a/go %)}] + :handler (fn [_] (a/go response))}}]) + (ring/create-default-handler) + {:executor sieppari/executor})] (let [respond (promise)] (app {:request-method :get, :uri "/ping"} respond ::irrelevant) (is (= response (deref respond 100 ::timeout))))))) @@ -302,11 +302,11 @@ (testing "works if registered" (let [response {:status 200, :body "ok"} app (http/ring-handler - (http/router - ["/ping" {:get {:interceptors [{:enter map->MyAsyncContext}] - :handler (fn [_] response)}}]) - (ring/create-default-handler) - {:executor sieppari/executor}) + (http/router + ["/ping" {:get {:interceptors [{:enter map->MyAsyncContext}] + :handler (fn [_] response)}}]) + (ring/create-default-handler) + {:executor sieppari/executor}) respond (promise) raise (promise)] (app {:request-method :get, :uri "/ping"} respond raise) @@ -321,14 +321,14 @@ request {:uri "/api/avaruus" :request-method :get} create (fn [options] (http/ring-handler - (http/router - ["/api" {:interceptors [(interceptor :olipa)]} - ["/avaruus" {:interceptors [(interceptor :kerran)] - :get {:handler handler - :interceptors [(interceptor :avaruus)]}}]] - options) - nil - {:executor sieppari/executor}))] + (http/router + ["/api" {:interceptors [(interceptor :olipa)]} + ["/avaruus" {:interceptors [(interceptor :kerran)] + :get {:handler handler + :interceptors [(interceptor :avaruus)]}}]] + options) + nil + {:executor sieppari/executor}))] (testing "by default, all middleware are applied in order" (let [app (create nil)] @@ -352,10 +352,10 @@ (testing "from root" (let [app (http/ring-handler - (http/router - ["/*" (ring/create-resource-handler)]) - (ring/create-default-handler) - {:executor sieppari/executor})] + (http/router + ["/*" (ring/create-resource-handler)]) + (ring/create-default-handler) + {:executor sieppari/executor})] (testing test (testing "different file-types" (let [response (app (request "/hello.json"))] @@ -388,10 +388,10 @@ (testing "from path" (let [app (http/ring-handler - (http/router - ["/files/*" (ring/create-resource-handler)]) - (ring/create-default-handler) - {:executor sieppari/executor}) + (http/router + ["/files/*" (ring/create-resource-handler)]) + (ring/create-default-handler) + {:executor sieppari/executor}) request #(request (str "/files" %)) redirect #(redirect (str "/files" %))] (testing test @@ -428,11 +428,11 @@ (testing "from root" (let [app (http/ring-handler - (http/router []) - (ring/routes - (ring/create-resource-handler {:path "/"}) - (ring/create-default-handler)) - {:executor sieppari/executor})] + (http/router []) + (ring/routes + (ring/create-resource-handler {:path "/"}) + (ring/create-default-handler)) + {:executor sieppari/executor})] (testing test (testing "different file-types" (let [response (app (request "/hello.json"))] @@ -465,11 +465,11 @@ (testing "from path" (let [app (http/ring-handler - (http/router []) - (ring/routes - (ring/create-resource-handler {:path "/files"}) - (ring/create-default-handler)) - {:executor sieppari/executor}) + (http/router []) + (ring/routes + (ring/create-resource-handler {:path "/files"}) + (ring/create-default-handler)) + {:executor sieppari/executor}) request #(request (str "/files" %)) redirect #(redirect (str "/files" %))] (testing test @@ -510,18 +510,18 @@ interceptor (fn [x] {:enter (fn [ctx] (swap! times update-in [:enter x] (fnil inc 0)) ctx) :leave (fn [ctx] (swap! times update-in [:leave x] (fnil inc 0)) ctx)}) app (http/ring-handler - (http/router - ["/api" - {:interceptors [(interceptor :api)]} - ["/ping" - {:interceptors [(interceptor :ping)] - :get {:interceptors [(interceptor :get)] - :handler (fn [_] response)}}]]) - (ring/routes - (ring/create-default-handler) - {:data {:interceptors [(interceptor :router)]}}) - {:executor sieppari/executor - :interceptors [(interceptor :top)]})] + (http/router + ["/api" + {:interceptors [(interceptor :api)]} + ["/ping" + {:interceptors [(interceptor :ping)] + :get {:interceptors [(interceptor :get)] + :handler (fn [_] response)}}]]) + (ring/routes + (ring/create-default-handler) + {:data {:interceptors [(interceptor :router)]}}) + {:executor sieppari/executor + :interceptors [(interceptor :top)]})] (is (= response (app {:request-method :get, :uri "/api/ping"}))) (is (= {:enter {:top 1, :api 1, :ping 1, :get 1} :leave {:get 1, :ping 1, :api 1, :top 1}} @@ -530,15 +530,15 @@ (deftest router-available-in-default-branch (testing "1-arity" ((http/ring-handler - (http/router []) - (fn [{::r/keys [router]}] - (is router)) - {:executor sieppari/executor}) + (http/router []) + (fn [{::r/keys [router]}] + (is router)) + {:executor sieppari/executor}) {})) (testing "3-arity" ((http/ring-handler - (http/router []) - (fn [{::r/keys [router]}] - (is router)) - {:executor sieppari/executor}) + (http/router []) + (fn [{::r/keys [router]}] + (is router)) + {:executor sieppari/executor}) {} ::respond ::raise))) diff --git a/test/clj/reitit/pedestal_test.clj b/test/clj/reitit/pedestal_test.clj index 8a0ea30b..6f5150a0 100644 --- a/test/clj/reitit/pedestal_test.clj +++ b/test/clj/reitit/pedestal_test.clj @@ -1,10 +1,10 @@ (ns reitit.pedestal-test - (:require [clojure.test :refer [deftest testing is]] - [io.pedestal.test] + (:require [clojure.test :refer [deftest is testing]] [io.pedestal.http] + [io.pedestal.test] [reitit.http :as http] - [reitit.pedestal :as pedestal] - [reitit.http.interceptors.exception :as exception])) + [reitit.http.interceptors.exception :as exception] + [reitit.pedestal :as pedestal])) (deftest arities-test (is (= #{0} (#'pedestal/arities (fn [])))) @@ -28,11 +28,11 @@ (deftest pedestal-e2e-test (let [router (pedestal/routing-interceptor - (http/router - ["" - {:interceptors [{:name :nop} (exception/exception-interceptor)]} - ["/ok" (fn [_] {:status 200, :body "ok"})] - ["/fail" (fn [_] (throw (ex-info "kosh" {})))]])) + (http/router + ["" + {:interceptors [{:name :nop} (exception/exception-interceptor)]} + ["/ok" (fn [_] {:status 200, :body "ok"})] + ["/fail" (fn [_] (throw (ex-info "kosh" {})))]])) service (-> {:io.pedestal.http/request-logger nil :io.pedestal.http/routes []} (io.pedestal.http/default-interceptors) diff --git a/test/clj/reitit/ring/middleware/exception_test.clj b/test/clj/reitit/ring/middleware/exception_test.clj index 5ef64770..f905d994 100644 --- a/test/clj/reitit/ring/middleware/exception_test.clj +++ b/test/clj/reitit/ring/middleware/exception_test.clj @@ -1,12 +1,12 @@ (ns reitit.ring.middleware.exception-test - (:require [clojure.test :refer [deftest testing is]] - [reitit.ring :as ring] - [reitit.ring.middleware.exception :as exception] - [reitit.coercion :as coercion] - [clojure.spec.alpha :as s] - [reitit.coercion.spec] - [reitit.ring.coercion] + (:require [clojure.spec.alpha :as s] + [clojure.test :refer [deftest is testing]] [muuntaja.core :as m] + [reitit.coercion :as coercion] + [reitit.coercion.spec] + [reitit.ring :as ring] + [reitit.ring.coercion] + [reitit.ring.middleware.exception :as exception] [ring.util.http-response :as http-response]) (:import (java.sql SQLException SQLWarning))) @@ -18,25 +18,25 @@ (create f nil)) ([f wrap] (ring/ring-handler - (ring/router - [["/defaults" - {:handler f}] - ["/http-response" - {:handler (fn [req] - (http-response/unauthorized! "Unauthorized"))}] - ["/coercion" - {:middleware [reitit.ring.coercion/coerce-request-middleware - reitit.ring.coercion/coerce-response-middleware] - :coercion reitit.coercion.spec/coercion - :parameters {:query {:x int?, :y int?}} - :responses {200 {:body {:total pos-int?}}} - :handler f}]] - {:data {:middleware [(exception/create-exception-middleware - (merge - exception/default-handlers - {::kikka (constantly {:status 400, :body "kikka"}) - SQLException (constantly {:status 400, :body "sql"}) - ::exception/wrap wrap}))]}}))))] + (ring/router + [["/defaults" + {:handler f}] + ["/http-response" + {:handler (fn [req] + (http-response/unauthorized! "Unauthorized"))}] + ["/coercion" + {:middleware [reitit.ring.coercion/coerce-request-middleware + reitit.ring.coercion/coerce-response-middleware] + :coercion reitit.coercion.spec/coercion + :parameters {:query {:x int?, :y int?}} + :responses {200 {:body {:total pos-int?}}} + :handler f}]] + {:data {:middleware [(exception/create-exception-middleware + (merge + exception/default-handlers + {::kikka (constantly {:status 400, :body "kikka"}) + SQLException (constantly {:status 400, :body "sql"}) + ::exception/wrap wrap}))]}}))))] (testing "normal calls work ok" (let [response {:status 200, :body "ok"} @@ -66,7 +66,6 @@ :response response}))))] (is (= response (app {:request-method :post, :uri "/http-response"}))))) - (testing ":muuntaja/decode" (let [app (create (fn [_] (m/decode m/instance "application/json" "{:so \"invalid\"}")))] (is (= {:body "Malformed \"application/json\" request." @@ -130,21 +129,21 @@ (deftest spec-coercion-exception-test (let [app (ring/ring-handler - (ring/router - ["/plus" - {:get - {:parameters {:query {:x int?, :y int?}} - :responses {200 {:body {:total pos-int?}}} - :handler (fn [{{{:keys [x y]} :query} :parameters}] - {:status 200, :body {:total (+ x y)}})}}] - {:data {:coercion reitit.coercion.spec/coercion - :middleware [(exception/create-exception-middleware - (merge - exception/default-handlers - {::coercion/request-coercion (fn [e _] {:status 400, :body (ex-data e)}) - ::coercion/response-coercion (fn [e _] {:status 500, :body (ex-data e)})})) - reitit.ring.coercion/coerce-request-middleware - reitit.ring.coercion/coerce-response-middleware]}}))] + (ring/router + ["/plus" + {:get + {:parameters {:query {:x int?, :y int?}} + :responses {200 {:body {:total pos-int?}}} + :handler (fn [{{{:keys [x y]} :query} :parameters}] + {:status 200, :body {:total (+ x y)}})}}] + {:data {:coercion reitit.coercion.spec/coercion + :middleware [(exception/create-exception-middleware + (merge + exception/default-handlers + {::coercion/request-coercion (fn [e _] {:status 400, :body (ex-data e)}) + ::coercion/response-coercion (fn [e _] {:status 500, :body (ex-data e)})})) + reitit.ring.coercion/coerce-request-middleware + reitit.ring.coercion/coerce-response-middleware]}}))] (testing "success" (let [{:keys [status body]} (app {:uri "/plus", :request-method :get, :query-params {"x" "1", "y" "2"}})] (is (= 200 status)) diff --git a/test/clj/reitit/ring/middleware/muuntaja_test.clj b/test/clj/reitit/ring/middleware/muuntaja_test.clj index 6483192c..8442b5a0 100644 --- a/test/clj/reitit/ring/middleware/muuntaja_test.clj +++ b/test/clj/reitit/ring/middleware/muuntaja_test.clj @@ -1,17 +1,17 @@ (ns reitit.ring.middleware.muuntaja-test - (:require [clojure.test :refer [deftest testing is]] + (:require [clojure.test :refer [deftest is testing]] + [muuntaja.core :as m] [reitit.ring :as ring] [reitit.ring.middleware.muuntaja :as muuntaja] - [reitit.swagger :as swagger] - [muuntaja.core :as m])) + [reitit.swagger :as swagger])) (deftest muuntaja-test (let [data {:kikka "kukka"} app (ring/ring-handler - (ring/router - ["/ping" {:get (constantly {:status 200, :body data})}] - {:data {:muuntaja m/instance - :middleware [muuntaja/format-middleware]}}))] + (ring/router + ["/ping" {:get (constantly {:status 200, :body data})}] + {:data {:muuntaja m/instance + :middleware [muuntaja/format-middleware]}}))] (is (= data (->> {:request-method :get, :uri "/ping"} (app) :body @@ -22,26 +22,26 @@ no-edn-decode (m/create (-> m/default-options (update-in [:formats "application/edn"] dissoc :decoder))) just-edn (m/create (-> m/default-options (m/select-formats ["application/edn"]))) app (ring/ring-handler - (ring/router - [["/defaults" - {:get identity}] - ["/explicit-defaults" - {:muuntaja with-defaults - :get identity}] - ["/no-edn-decode" - {:muuntaja no-edn-decode - :get identity}] - ["/just-edn" - {:muuntaja just-edn - :get identity}] - ["/form-params" - {:post {:parameters {:form {:x string?}} - :handler identity}}] - ["/swagger.json" - {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]] - {:data {:muuntaja m/instance - :middleware [muuntaja/format-middleware]}})) + (ring/router + [["/defaults" + {:get identity}] + ["/explicit-defaults" + {:muuntaja with-defaults + :get identity}] + ["/no-edn-decode" + {:muuntaja no-edn-decode + :get identity}] + ["/just-edn" + {:muuntaja just-edn + :get identity}] + ["/form-params" + {:post {:parameters {:form {:x string?}} + :handler identity}}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:muuntaja m/instance + :middleware [muuntaja/format-middleware]}})) spec (fn [method path] (let [path (keyword path)] (-> {:request-method :get :uri "/swagger.json"} @@ -97,41 +97,41 @@ (deftest muuntaja-swagger-parts-test (let [app (ring/ring-handler - (ring/router - [["/request" - {:middleware [muuntaja/format-negotiate-middleware - muuntaja/format-request-middleware] - :get identity}] - ["/response" - {:middleware [muuntaja/format-negotiate-middleware - muuntaja/format-response-middleware] - :get identity}] - ["/both" - {:middleware [muuntaja/format-negotiate-middleware - muuntaja/format-response-middleware - muuntaja/format-request-middleware] - :get identity}] - ["/form-request" - {:middleware [muuntaja/format-negotiate-middleware - muuntaja/format-request-middleware] - :post {:parameters {:form {:x string?}} - :handler identity}}] - ["/form-response" - {:middleware [muuntaja/format-negotiate-middleware - muuntaja/format-response-middleware] - :post {:parameters {:form {:x string?}} - :handler identity}}] - ["/form-with-both" - {:middleware [muuntaja/format-negotiate-middleware - muuntaja/format-response-middleware - muuntaja/format-request-middleware] - :post {:parameters {:form {:x string?}} - :handler identity}}] + (ring/router + [["/request" + {:middleware [muuntaja/format-negotiate-middleware + muuntaja/format-request-middleware] + :get identity}] + ["/response" + {:middleware [muuntaja/format-negotiate-middleware + muuntaja/format-response-middleware] + :get identity}] + ["/both" + {:middleware [muuntaja/format-negotiate-middleware + muuntaja/format-response-middleware + muuntaja/format-request-middleware] + :get identity}] + ["/form-request" + {:middleware [muuntaja/format-negotiate-middleware + muuntaja/format-request-middleware] + :post {:parameters {:form {:x string?}} + :handler identity}}] + ["/form-response" + {:middleware [muuntaja/format-negotiate-middleware + muuntaja/format-response-middleware] + :post {:parameters {:form {:x string?}} + :handler identity}}] + ["/form-with-both" + {:middleware [muuntaja/format-negotiate-middleware + muuntaja/format-response-middleware + muuntaja/format-request-middleware] + :post {:parameters {:form {:x string?}} + :handler identity}}] - ["/swagger.json" - {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]] - {:data {:muuntaja m/instance}})) + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:muuntaja m/instance}})) spec (fn [method path] (-> {:request-method :get :uri "/swagger.json"} (app) :body :paths (get path) method)) diff --git a/test/clj/reitit/ring/middleware/parameters_test.clj b/test/clj/reitit/ring/middleware/parameters_test.clj index 06d297ad..dec3f9d5 100644 --- a/test/clj/reitit/ring/middleware/parameters_test.clj +++ b/test/clj/reitit/ring/middleware/parameters_test.clj @@ -1,14 +1,14 @@ (ns reitit.ring.middleware.parameters-test - (:require [clojure.test :refer [deftest testing is]] - [reitit.ring.middleware.parameters :as parameters] + (:require [clojure.test :refer [deftest is testing]] [reitit.ring :as ring] + [reitit.ring.middleware.parameters :as parameters] [reitit.swagger :as swagger])) (deftest parameters-test (let [app (ring/ring-handler - (ring/router - ["/ping" {:get #(select-keys % [:params :query-params])}] - {:data {:middleware [parameters/parameters-middleware]}}))] + (ring/router + ["/ping" {:get #(select-keys % [:params :query-params])}] + {:data {:middleware [parameters/parameters-middleware]}}))] (is (= {:query-params {"kikka" "kukka"} :params {"kikka" "kukka"}} (app {:request-method :get @@ -17,14 +17,14 @@ (deftest parameters-swagger-test (let [app (ring/ring-handler - (ring/router - [["/form-params" {:post {:parameters {:form {:x string?}} - :handler identity}}] - ["/body-params" {:post {:parameters {:body {:x string?}} - :handler identity}}] - ["/swagger.json" {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]] - {:data {:middleware [parameters/parameters-middleware]}})) + (ring/router + [["/form-params" {:post {:parameters {:form {:x string?}} + :handler identity}}] + ["/body-params" {:post {:parameters {:body {:x string?}} + :handler identity}}] + ["/swagger.json" {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:middleware [parameters/parameters-middleware]}})) spec (fn [path] (-> {:request-method :get :uri "/swagger.json"} app diff --git a/test/cljc/reitit/coercion_test.cljc b/test/cljc/reitit/coercion_test.cljc index 93b067ec..97ab0aae 100644 --- a/test/cljc/reitit/coercion_test.cljc +++ b/test/cljc/reitit/coercion_test.cljc @@ -1,34 +1,41 @@ (ns reitit.coercion-test - (:require [clojure.test :refer [deftest testing is]] - [schema.core :as s] - [spec-tools.data-spec :as ds] - [reitit.core :as r] + (:require [clojure.test :refer [deftest is testing]] + [malli.experimental.lite :as l] [reitit.coercion :as coercion] - [reitit.coercion.spec] [reitit.coercion.malli] - [reitit.coercion.schema]) + [reitit.coercion.schema] + [reitit.coercion.spec] + [reitit.core :as r] + [schema.core :as s] + [spec-tools.data-spec :as ds]) #?(:clj (:import (clojure.lang ExceptionInfo)))) (deftest coercion-test (let [r (r/router - [["/schema" {:coercion reitit.coercion.schema/coercion} - ["/:number/:keyword" {:parameters {:path {:number s/Int - :keyword s/Keyword} - :query (s/maybe {:int s/Int, :ints [s/Int], :map {s/Int s/Int}})}}]] - ["/malli" {:coercion reitit.coercion.malli/coercion} - ["/:number/:keyword" {:parameters {:path [:map [:number int?] [:keyword keyword?]] - :query [:maybe [:map [:int int?] - [:ints [:vector int?]] - [:map [:map-of int? int?]]]]}}]] - ["/spec" {:coercion reitit.coercion.spec/coercion} - ["/:number/:keyword" {:parameters {:path {:number int? - :keyword keyword?} - :query (ds/maybe {:int int?, :ints [int?], :map {int? int?}})}}]] - ["/none" - ["/:number/:keyword" {:parameters {:path {:number int? - :keyword keyword?}}}]]] - {:compile coercion/compile-request-coercers})] + [["/schema" {:coercion reitit.coercion.schema/coercion} + ["/:number/:keyword" {:parameters {:path {:number s/Int + :keyword s/Keyword} + :query (s/maybe {:int s/Int, :ints [s/Int], :map {s/Int s/Int}})}}]] + ["/malli" {:coercion reitit.coercion.malli/coercion} + ["/:number/:keyword" {:parameters {:path [:map [:number int?] [:keyword keyword?]] + :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?} + :query (ds/maybe {:int int?, :ints [int?], :map {int? int?}})}}]] + ["/none" + ["/:number/:keyword" {:parameters {:path {:number int? + :keyword keyword?}}}]]] + {:compile coercion/compile-request-coercers})] (testing "schema-coercion" (testing "succeeds" @@ -54,6 +61,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" @@ -80,10 +99,10 @@ (deftest data-spec-example-test (let [router (r/router - ["/:company/users/:user-id" {:name ::user-view - :coercion reitit.coercion.spec/coercion - :parameters {:path {:company string? - :user-id int?}}}] - {:compile coercion/compile-request-coercers})] + ["/:company/users/:user-id" {:name ::user-view + :coercion reitit.coercion.spec/coercion + :parameters {:path {:company string? + :user-id int?}}}] + {:compile coercion/compile-request-coercers})] (is (= {:path {:user-id 123, :company "metosin"}} (:parameters (match-by-path-and-coerce! router "/metosin/users/123")))))) diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index 290e46ab..fc112239 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -1,10 +1,10 @@ (ns reitit.core-test - (:require [clojure.test :refer [deftest testing is are]] + (:require [clojure.test :refer [are deftest is testing]] [reitit.core :as r #?@(:cljs [:refer [Router]])] [reitit.impl :as impl]) #?(:clj - (:import (reitit.core Router) - (clojure.lang ExceptionInfo)))) + (:import (clojure.lang ExceptionInfo) + (reitit.core Router)))) (deftest reitit-test @@ -21,38 +21,38 @@ (is (= nil (r/match-by-path router "/api"))) (is (= (r/map->Match - {:template "/api/ipa/:size" - :data {:name ::beer} - :path "/api/ipa/large" - :path-params {:size "large"}}) + {:template "/api/ipa/:size" + :data {:name ::beer} + :path "/api/ipa/large" + :path-params {:size "large"}}) (r/match-by-path router "/api/ipa/large"))) (is (= (r/map->Match - {:template "/api/ipa/:size" - :data {:name ::beer} - :path "/api/ipa/large" - :path-params {:size "large"}}) + {:template "/api/ipa/:size" + :data {:name ::beer} + :path "/api/ipa/large" + :path-params {:size "large"}}) (r/match-by-name router ::beer {:size "large"}))) (is (= (r/map->Match - {:template "/api/ipa/:size" - :data {:name ::beer} - :path "/api/ipa/large" - :path-params {:size "large"}}) + {:template "/api/ipa/:size" + :data {:name ::beer} + :path "/api/ipa/large" + :path-params {:size "large"}}) (r/match-by-name router ::beer {:size :large}))) (is (= nil (r/match-by-name router "ILLEGAL"))) (is (= [::beer] (r/route-names router))) (testing "name-based routing with missing parameters" (is (= (r/map->PartialMatch - {:template "/api/ipa/:size" - :data {:name ::beer} - :required #{:size} - :path-params nil}) + {:template "/api/ipa/:size" + :data {:name ::beer} + :required #{:size} + :path-params nil}) (r/match-by-name router ::beer))) (is (r/partial-match? (r/match-by-name router ::beer))) (is (thrown-with-msg? - ExceptionInfo - #"^missing path-params for route /api/ipa/:size -> \#\{:size\}$" - (r/match-by-name! router ::beer)))))) + ExceptionInfo + #"^missing path-params for route /api/ipa/:size -> \#\{:size\}$" + (r/match-by-name! router ::beer)))))) (testing "decode %-encoded path params" (let [router (r/router [["/one-param-path/:param1" ::one] @@ -71,14 +71,14 @@ (testing "complex" (let [router (r/router - [["/:abba" ::abba] - ["/abba/1" ::abba2] - ["/:jabba/2" ::jabba2] - ["/:abba/:dabba/doo" ::doo] - ["/abba/dabba/boo/baa" ::baa] - ["/abba/:dabba/boo" ::boo] - ["/:jabba/:dabba/:doo/:daa/*foo" ::wild]] - {:router r}) + [["/:abba" ::abba] + ["/abba/1" ::abba2] + ["/:jabba/2" ::jabba2] + ["/:abba/:dabba/doo" ::doo] + ["/abba/dabba/boo/baa" ::baa] + ["/abba/:dabba/boo" ::boo] + ["/:jabba/:dabba/:doo/:daa/*foo" ::wild]] + {:router r}) by-path #(-> router (r/match-by-path %) :data :name)] (is (= ::abba (by-path "/abba"))) (is (= ::abba2 (by-path "/abba/1"))) @@ -93,19 +93,19 @@ (testing "bracket-params" (testing "successful" (let [router (r/router - [["/{abba}" ::abba] - ["/abba/1" ::abba2] - ["/{jabba}/2" ::jabba2] - ["/{abba}/{dabba}/doo" ::doo] - ["/abba/dabba/boo/baa" ::baa] - ["/abba/{dabba}/boo" ::boo] - ["/{a/jabba}/{a.b/dabba}/{a.b.c/doo}/{a.b.c.d/daa}/{*foo/bar}" ::wild] - ["/files/file-{name}.html" ::html] - ["/files/file-{name}.json" ::json] - ["/{eskon}/{saum}/pium\u2215paum" ::loru] - ["/{🌈}🤔/🎈" ::emoji] - ["/extra-end}s-are/ok" ::bracket]] - {:router r}) + [["/{abba}" ::abba] + ["/abba/1" ::abba2] + ["/{jabba}/2" ::jabba2] + ["/{abba}/{dabba}/doo" ::doo] + ["/abba/dabba/boo/baa" ::baa] + ["/abba/{dabba}/boo" ::boo] + ["/{a/jabba}/{a.b/dabba}/{a.b.c/doo}/{a.b.c.d/daa}/{*foo/bar}" ::wild] + ["/files/file-{name}.html" ::html] + ["/files/file-{name}.json" ::json] + ["/{eskon}/{saum}/pium\u2215paum" ::loru] + ["/{🌈}🤔/🎈" ::emoji] + ["/extra-end}s-are/ok" ::bracket]] + {:router r}) by-path #(-> router (r/match-by-path %) ((juxt (comp :name :data) :path-params)))] (is (= [::abba {:abba "abba"}] (by-path "/abba"))) (is (= [::abba2 {}] (by-path "/abba/1"))) @@ -130,22 +130,22 @@ (testing "invalid syntax fails fast" (testing "unclosed brackets" (is (thrown-with-msg? - ExceptionInfo - #":reitit.trie/unclosed-brackets" - (r/router ["/kikka/{kukka"])))) + ExceptionInfo + #":reitit.trie/unclosed-brackets" + (r/router ["/kikka/{kukka"])))) (testing "multiple terminators" (is (thrown-with-msg? - ExceptionInfo - #":reitit.trie/multiple-terminators" - (r/router [["/{kukka}.json"] - ["/{kukka}-json"]])))))) + ExceptionInfo + #":reitit.trie/multiple-terminators" + (r/router [["/{kukka}.json"] + ["/{kukka}-json"]])))))) (testing "empty path segments" (let [router (r/router - [["/items" ::list] - ["/items/:id" ::item] - ["/items/:id/:side" ::deep]] - {:router r}) + [["/items" ::list] + ["/items/:id" ::item] + ["/items/:id/:side" ::deep]] + {:router r}) matches #(-> router (r/match-by-path %) :data :name)] (is (= ::list (matches "/items"))) (is (= nil (matches "/items/"))) @@ -169,28 +169,28 @@ (is (= nil (r/match-by-path router "/api"))) (is (= (r/map->Match - {:template "/api/ipa/large" - :data {:name ::beer} - :path "/api/ipa/large" - :path-params {}}) + {:template "/api/ipa/large" + :data {:name ::beer} + :path "/api/ipa/large" + :path-params {}}) (r/match-by-path router "/api/ipa/large"))) (is (= (r/map->Match - {:template "/api/ipa/large" - :data {:name ::beer} - :path "/api/ipa/large" - :path-params {:size "large"}}) + {:template "/api/ipa/large" + :data {:name ::beer} + :path "/api/ipa/large" + :path-params {:size "large"}}) (r/match-by-name router ::beer {:size "large"}))) (is (= nil (r/match-by-name router "ILLEGAL"))) (is (= [::beer] (r/route-names router))) (testing "can't be created with wildcard routes" (is (thrown-with-msg? - ExceptionInfo - #"can't create :lookup-router with wildcard routes" - (r/lookup-router - (impl/resolve-routes - ["/api/:version/ping"] - (r/default-router-options))))))) + ExceptionInfo + #"can't create :lookup-router with wildcard routes" + (r/lookup-router + (impl/resolve-routes + ["/api/:version/ping"] + (r/default-router-options))))))) r/lookup-router :lookup-router r/single-static-path-router :single-static-path-router @@ -216,14 +216,14 @@ (swap! compile-times inc) (constantly path)) router (r/router - ["/api" {:roles #{:admin}} - ["/ping" ::ping] - ["/pong" ::pong] - ["/hidden" {:invalid? true} - ["/utter"] - ["/crap"]]] - {:coerce coerce - :compile compile})] + ["/api" {:roles #{:admin}} + ["/ping" ::ping] + ["/pong" ::pong] + ["/hidden" {:invalid? true} + ["/utter"] + ["/crap"]]] + {:coerce coerce + :compile compile})] (testing "routes are coerced" (is (= [["/api/ping" {:name ::ping @@ -281,10 +281,10 @@ router (r/router routes)] (is (= expected (impl/resolve-routes routes (r/default-router-options)))) (is (= (r/map->Match - {:template "/api/user/:id/:sub-id" - :data {:mw [:api], :parameters {:id "String", :sub-id "String"}} - :path "/api/user/1/2" - :path-params {:id "1", :sub-id "2"}}) + {:template "/api/user/:id/:sub-id" + :data {:mw [:api], :parameters {:id "String", :sub-id "String"}} + :path "/api/user/1/2" + :path-params {:id "1", :sub-id "2"}}) (r/match-by-path router "/api/user/1/2")))))) (deftest conflicting-routes-test @@ -334,10 +334,10 @@ (testing "router with conflicting routes" (testing "throws by default" (is (thrown-with-msg? - ExceptionInfo - #"Router contains conflicting route paths" - (r/router - [["/a"] ["/a"]])))) + ExceptionInfo + #"Router contains conflicting route paths" + (r/router + [["/a"] ["/a"]])))) (testing "can be configured to ignore with route data" (are [paths expected] (let [router (r/router paths)] @@ -364,9 +364,9 @@ (testing "unmarked path conflicts throw" (are [paths] (is (thrown-with-msg? - ExceptionInfo - #"Router contains conflicting route paths" - (r/router paths))) + ExceptionInfo + #"Router contains conflicting route paths" + (r/router paths))) [["/a"] ["/a" {:conflicting true}]] [["/a" {:conflicting true}] ["/a"]]))) (testing "can be configured to ignore with router option" @@ -375,10 +375,10 @@ (testing "name conflicts" (testing "router with conflicting routes always throws" (is (thrown-with-msg? - ExceptionInfo - #"Router contains conflicting route names" - (r/router - [["/1" ::1] ["/2" ::1]])))))) + ExceptionInfo + #"Router contains conflicting route names" + (r/router + [["/1" ::1] ["/2" ::1]])))))) (deftest match->path-test (let [router (r/router ["/:a/:b" ::route])] @@ -406,10 +406,10 @@ (r/routes))))) (testing "sequential route definition fails" (is (thrown? - #?(:clj Exception, :cljs js/Error) - (-> ["/api" - (list "/ipa")] - (r/router)))))) + #?(:clj Exception, :cljs js/Error) + (-> ["/api" + (list "/ipa")] + (r/router)))))) (defrecord Named [n] r/Expand @@ -422,12 +422,16 @@ (deftest routing-order-test-229 (let [router (r/router - [["/" :root] - ["/" {:name :create :method :post}]] - {:conflicts nil}) + [["/" :root] + ["/" {:name :create :method :post}]] + {:conflicts nil}) router2 (r/router - [["/*a" :root] - ["/:a/b/c/d" {:name :create :method :post}]] - {:conflicts nil})] + [["/*a" :root] + ["/:a/b/c/d" {:name :create :method :post}]] + {:conflicts nil})] (is (= :root (-> (r/match-by-path router "/") :data :name))) (is (= :root (-> (r/match-by-path router2 "/") :data :name))))) + +(deftest routing-bug-test-538 + (let [router (r/router [["/:a"] ["/:b"]] {:conflicts nil})] + (is (nil? (r/match-by-path router ""))))) diff --git a/test/cljc/reitit/dependency_test.cljc b/test/cljc/reitit/dependency_test.cljc index 85ee206c..eff7b5a1 100644 --- a/test/cljc/reitit/dependency_test.cljc +++ b/test/cljc/reitit/dependency_test.cljc @@ -1,5 +1,5 @@ (ns reitit.dependency-test - (:require [clojure.test :refer [deftest testing is are]] + (:require [clojure.test :refer [are deftest is testing]] [reitit.dependency :as rc]) #?(:clj (:import [clojure.lang ExceptionInfo]))) diff --git a/test/cljc/reitit/exception_test.cljc b/test/cljc/reitit/exception_test.cljc index bde403a7..bb77421d 100644 --- a/test/cljc/reitit/exception_test.cljc +++ b/test/cljc/reitit/exception_test.cljc @@ -1,10 +1,10 @@ (ns reitit.exception-test - (:require [clojure.test :refer [deftest testing is are]] - [reitit.spec :as rs] + (:require [clojure.spec.alpha :as s] + [clojure.test :refer [are deftest is testing]] [reitit.core :as r] [reitit.dev.pretty :as pretty] - [clojure.spec.alpha :as s] - [reitit.exception :as exception]) + [reitit.exception :as exception] + [reitit.spec :as rs]) #?(:clj (:import (clojure.lang ExceptionInfo)))) @@ -17,12 +17,12 @@ (are [exception] (are [error routes] (is (thrown-with-msg? - ExceptionInfo - error - (r/router - routes - {:validate rs/validate - :exception exception}))) + ExceptionInfo + error + (r/router + routes + {:validate rs/validate + :exception exception}))) #"Router contains conflicting route paths" [["/:a/1"] diff --git a/test/cljc/reitit/impl_test.cljc b/test/cljc/reitit/impl_test.cljc index 1b2165c5..119a5fad 100644 --- a/test/cljc/reitit/impl_test.cljc +++ b/test/cljc/reitit/impl_test.cljc @@ -1,5 +1,5 @@ (ns reitit.impl-test - (:require [clojure.test :refer [deftest testing is are]] + (:require [clojure.test :refer [are deftest is testing]] [reitit.impl :as impl])) (deftest strip-nils-test @@ -50,6 +50,8 @@ {"a" "b"} "a=b" {:a 1} "a=1" {:a nil} "a=" + {:a []} "a=" + {:a '()} "a=" {:a :b :c "d"} "a=b&c=d" {:a "b c"} "a=b+c" {:a ["b" "c"]} "a=b&a=c" diff --git a/test/cljc/reitit/interceptor_test.cljc b/test/cljc/reitit/interceptor_test.cljc index d4be30e2..effa4c4d 100644 --- a/test/cljc/reitit/interceptor_test.cljc +++ b/test/cljc/reitit/interceptor_test.cljc @@ -1,7 +1,7 @@ (ns reitit.interceptor-test - (:require [clojure.test :refer [deftest testing is are]] - [reitit.interceptor :as interceptor] - [reitit.core :as r]) + (:require [clojure.test :refer [are deftest is testing]] + [reitit.core :as r] + [reitit.interceptor :as interceptor]) #?(:clj (:import (clojure.lang ExceptionInfo)))) @@ -9,9 +9,9 @@ (defn execute [interceptors ctx] (as-> ctx $ - (reduce #(%2 %1) $ (keep :enter interceptors)) - (reduce #(%2 %1) $ (reverse (keep :leave interceptors))) - (:response $))) + (reduce #(%2 %1) $ (keep :enter interceptors)) + (reduce #(%2 %1) $ (reverse (keep :leave interceptors))) + (:response $))) (defn f [value ctx] (update ctx :request conj value)) @@ -41,8 +41,8 @@ (create interceptors nil)) ([interceptors opts] (let [chain (interceptor/chain - (conj interceptors handler) - :data opts)] + (conj interceptors handler) + :data opts)] (partial execute chain)))) (deftest expand-interceptor-test @@ -79,9 +79,9 @@ (testing "missing keyword" (is (thrown-with-msg? - ExceptionInfo - #"Interceptor :enter not found in registry" - (create [:enter])))) + ExceptionInfo + #"Interceptor :enter not found in registry" + (create [:enter])))) (testing "existing keyword, compiling to nil" (let [app (create [:enter] {::interceptor/registry {:enter {:compile (constantly nil)}}})] @@ -139,9 +139,9 @@ (testing "too deeply compiled interceptor fails" (binding [interceptor/*max-compile-depth* 2] (is (thrown? - ExceptionInfo - #"Too deep Interceptor compilation" - (create [[i3 :value]]))))) + ExceptionInfo + #"Too deep Interceptor compilation" + (create [[i3 :value]]))))) (testing "nil unmounts the interceptor" (let [app (create [{:compile (constantly nil)} @@ -160,11 +160,11 @@ (testing "interceptor-handler" (let [api-interceptor (interceptor :api) router (interceptor/router - [["/ping" handler] - ["/api" {:interceptors [api-interceptor]} - ["/ping" handler] - ["/admin" {:interceptors [[interceptor :admin]]} - ["/ping" handler]]]]) + [["/ping" handler] + ["/api" {:interceptors [api-interceptor]} + ["/ping" handler] + ["/admin" {:interceptors [[interceptor :admin]]} + ["/ping" handler]]]]) app (create-app router)] (testing "not found" @@ -184,8 +184,8 @@ i2 {:name ::i2, :compile (constantly nil)} i3 (interceptor ::i3) router (interceptor/router - ["/api" {:interceptors [i1 i2 i3 i2] - :handler handler}]) + ["/api" {:interceptors [i1 i2 i3 i2] + :handler handler}]) app (create-app router)] (is (= [::enter_i1 ::enter_i3 :ok ::leave_i3 ::leave_i1] (app "/api"))) @@ -226,12 +226,12 @@ (let [debug-i (enter ::debug) create (fn [options] (create-app - (interceptor/router - ["/ping" {:interceptors [(enter ::olipa) - (enter ::kerran) - (enter ::avaruus)] - :handler handler}] - options))) + (interceptor/router + ["/ping" {:interceptors [(enter ::olipa) + (enter ::kerran) + (enter ::avaruus)] + :handler handler}] + options))) inject-debug (interceptor/transform-butlast #(interleave % (repeat debug-i))) sort-interceptors (interceptor/transform-butlast (partial sort-by :name))] diff --git a/test/cljc/reitit/middleware_test.cljc b/test/cljc/reitit/middleware_test.cljc index 2bfc2f82..0b7bb05e 100644 --- a/test/cljc/reitit/middleware_test.cljc +++ b/test/cljc/reitit/middleware_test.cljc @@ -1,7 +1,7 @@ (ns reitit.middleware-test - (:require [clojure.test :refer [deftest testing is are]] - [reitit.middleware :as middleware] - [reitit.core :as r]) + (:require [clojure.test :refer [are deftest is testing]] + [reitit.core :as r] + [reitit.middleware :as middleware]) #?(:clj (:import (clojure.lang ExceptionInfo)))) @@ -15,10 +15,10 @@ (create middleware nil)) ([middleware opts] (middleware/chain - middleware - handler - :data - opts))) + middleware + handler + :data + opts))) (deftest expand-middleware-test @@ -61,9 +61,9 @@ (testing "missing keyword" (is (thrown-with-msg? - ExceptionInfo - #"Middleware :wrap not found in registry" - (create [:wrap])))) + ExceptionInfo + #"Middleware :wrap not found in registry" + (create [:wrap])))) (testing "existing keyword, compiling to nil" (let [app (create [:wrap] {::middleware/registry {:wrap {:compile (constantly nil)}}})] @@ -142,9 +142,9 @@ (testing "too deeply compiled Middleware fails" (binding [middleware/*max-compile-depth* 2] (is (thrown? - ExceptionInfo - #"Too deep Middleware compilation" - (create [[(middleware/map->Middleware mw3) :value]]))))) + ExceptionInfo + #"Too deep Middleware compilation" + (create [[(middleware/map->Middleware mw3) :value]]))))) (testing "nil unmounts the middleware" (let [app (create [{:compile (constantly nil)} @@ -162,9 +162,9 @@ (testing "all paths should have a handler" (is (thrown-with-msg? - ExceptionInfo - #"path \"/ping\" doesn't have a :handler defined" - (middleware/router ["/ping"])))) + ExceptionInfo + #"path \"/ping\" doesn't have a :handler defined" + (middleware/router ["/ping"])))) (testing "middleware-handler" (let [mw (fn [handler value] @@ -173,11 +173,11 @@ api-mw #(mw % :api) handler #(conj % :ok) router (middleware/router - [["/ping" handler] - ["/api" {:middleware [api-mw]} - ["/ping" handler] - ["/admin" {:middleware [[mw :admin]]} - ["/ping" handler]]]]) + [["/ping" handler] + ["/api" {:middleware [api-mw]} + ["/ping" handler] + ["/admin" {:middleware [[mw :admin]]} + ["/ping" handler]]]]) app (create-app router)] (testing "not found" @@ -197,9 +197,9 @@ mw2 {:name ::mw2, :compile (constantly nil)} mw3 {:name ::mw3, :wrap #(mw % ::mw3)} router (middleware/router - ["/api" {:name ::api - :middleware [mw1 mw2 mw3 mw2] - :handler handler}]) + ["/api" {:name ::api + :middleware [mw1 mw2 mw3 mw2] + :handler handler}]) app (create-app router)] (is (= [::mw1 ::mw3 :ok ::mw3 ::mw1] (app "/api"))) @@ -241,12 +241,12 @@ debug-mw {:name ::debug, :wrap #(wrap % ::debug)} create (fn [options] (create-app - (middleware/router - ["/ping" {:middleware [{:name ::olipa, :wrap #(wrap % ::olipa)} - {:name ::kerran, :wrap #(wrap % ::kerran)} - {:name ::avaruus, :wrap #(wrap % ::avaruus)}] - :handler #(conj % :ok)}] - options)))] + (middleware/router + ["/ping" {:middleware [{:name ::olipa, :wrap #(wrap % ::olipa)} + {:name ::kerran, :wrap #(wrap % ::kerran)} + {:name ::avaruus, :wrap #(wrap % ::avaruus)}] + :handler #(conj % :ok)}] + options)))] (testing "by default, all middleware are applied in order" (let [app (create nil)] diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index 06b5808c..fb9879bc 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -1,15 +1,19 @@ (ns reitit.ring-coercion-test - (:require [clojure.test :refer [deftest testing is]] - [schema.core :as s] - [spec-tools.data-spec :as ds] - [reitit.ring :as ring] - [reitit.ring.coercion :as rrc] - [reitit.coercion.spec :as spec] + (:require [clojure.test :refer [deftest is testing]] + [malli.experimental.lite :as l] + #?@(:clj [[muuntaja.middleware] + [jsonista.core :as j]]) + [malli.core :as m] + [malli.util :as mu] + [meta-merge.core :refer [meta-merge]] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] - #?@(:clj [[muuntaja.middleware] - [jsonista.core :as j]]) - [reitit.core :as r]) + [reitit.coercion.spec :as spec] + [reitit.core :as r] + [reitit.ring :as ring] + [reitit.ring.coercion :as rrc] + [schema.core :as s] + [spec-tools.data-spec :as ds]) #?(:clj (:import (clojure.lang ExceptionInfo) (java.io ByteArrayInputStream)))) @@ -77,19 +81,19 @@ (deftest spec-coercion-test (let [create (fn [middleware] (ring/ring-handler - (ring/router - ["/api" - ["/plus/:e" - {:get {:parameters {:query {(ds/opt :a) int?} - :body {:b int?} - :form {:c int?} - :header {:d int?} - :path {:e int?}} - :responses {200 {:body {:total pos-int?}} - 500 {:description "fail"}} - :handler handler}}]] - {:data {:middleware middleware - :coercion spec/coercion}})))] + (ring/router + ["/api" + ["/plus/:e" + {:get {:parameters {:query {(ds/opt :a) int?} + :body {:b int?} + :form {:c int?} + :header {:d int?} + :path {:e int?}} + :responses {200 {:body {:total pos-int?}} + 500 {:description "fail"}} + :handler handler}}]] + {:data {:middleware middleware + :coercion spec/coercion}})))] (testing "without exception handling" (let [app (create [rrc/coerce-request-middleware @@ -111,15 +115,15 @@ (testing "invalid request" (is (thrown-with-msg? - ExceptionInfo - #"Request coercion failed" - (app invalid-request1)))) + ExceptionInfo + #"Request coercion failed" + (app invalid-request1)))) (testing "invalid response" (is (thrown-with-msg? - ExceptionInfo - #"Response coercion failed" - (app invalid-request2)))))) + ExceptionInfo + #"Response coercion failed" + (app invalid-request2)))))) (testing "with exception handling" (let [app (create [rrc/coerce-exceptions-middleware @@ -144,19 +148,19 @@ (deftest schema-coercion-test (let [create (fn [middleware] (ring/ring-handler - (ring/router - ["/api" - ["/plus/:e" - {:get {:parameters {:query {(s/optional-key :a) s/Int} - :body {:b s/Int} - :form {:c s/Int} - :header {:d s/Int} - :path {:e s/Int}} - :responses {200 {:body {:total (s/constrained s/Int pos? 'positive)}} - 500 {:description "fail"}} - :handler handler}}]] - {:data {:middleware middleware - :coercion schema/coercion}})))] + (ring/router + ["/api" + ["/plus/:e" + {:get {:parameters {:query {(s/optional-key :a) s/Int} + :body {:b s/Int} + :form {:c s/Int} + :header {:d s/Int} + :path {:e s/Int}} + :responses {200 {:body {:total (s/constrained s/Int pos? 'positive)}} + 500 {:description "fail"}} + :handler handler}}]] + {:data {:middleware middleware + :coercion schema/coercion}})))] (testing "withut exception handling" (let [app (create [rrc/coerce-request-middleware @@ -175,19 +179,19 @@ (testing "invalid request" (is (thrown-with-msg? - ExceptionInfo - #"Request coercion failed" - (app invalid-request1))) + ExceptionInfo + #"Request coercion failed" + (app invalid-request1))) (is (thrown-with-msg? - ExceptionInfo - #"Request coercion failed" - (app valid-request3)))) + ExceptionInfo + #"Request coercion failed" + (app valid-request3)))) (testing "invalid response" (is (thrown-with-msg? - ExceptionInfo - #"Response coercion failed" - (app invalid-request2)))))) + ExceptionInfo + #"Response coercion failed" + (app invalid-request2)))))) (testing "with exception handling" (let [app (create [rrc/coerce-exceptions-middleware @@ -207,140 +211,218 @@ (let [{:keys [status]} (app invalid-request2)] (is (= 500 status)))))))) +(defn- custom-meta-merge-checking-schema + ([] {}) + ([left] left) + ([left right] + (cond + (and (map? left) (map? right)) + (merge-with custom-meta-merge-checking-schema left right) + + (and (m/schema? left) + (m/schema? right)) + (mu/merge left right) + + :else + (meta-merge left right))) + ([left right & more] + (reduce custom-meta-merge-checking-schema left (cons right more)))) + +(defn- custom-meta-merge-checking-parameters + ([] {}) + ([left] left) + ([left right] + (if (and (map? left) (map? right) + (contains? left :parameters) + (contains? right :parameters)) + (-> (merge-with custom-meta-merge-checking-parameters left right) + (assoc :parameters (merge-with mu/merge + (:parameters left) + (:parameters right)))) + (meta-merge left right))) + ([left right & more] + (reduce custom-meta-merge-checking-parameters left (cons right more)))) + (deftest malli-coercion-test - (let [create (fn [middleware] + (let [create (fn [middleware routes] (ring/ring-handler - (ring/router - ["/api" + (ring/router + routes + {:data {:middleware middleware + :coercion malli/coercion}})))] - ["/validate" {:summary "just validation" - :coercion (reitit.coercion.malli/create {:transformers {}}) - :post {:parameters {:body [:map [:x int?]]} - :responses {200 {:body [:map [:x int?]]}} - :handler (fn [req] - {:status 200 - :body (-> req :parameters :body)})}}] + (doseq [{:keys [style routes]} [{:style "malli" + :routes ["/api" + ["/validate" {:summary "just validation" + :coercion (reitit.coercion.malli/create {:transformers {}}) + :post {:parameters {:body [:map [:x int?]]} + :responses {200 {:body [:map [: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 [:map [:x int?]]} - :responses {200 {:body [:map [: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 [:map [:x int?]]} + :responses {200 {:body [:map [:x int?]]}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :body)})}}] - ["/skip" {:summary "skip" - :coercion (reitit.coercion.malli/create {:enabled false}) - :post {:parameters {:body [:map [:x int?]]} - :responses {200 {:body [:map [:x int?]]}} - :handler (fn [req] - {:status 200 - :body (-> req :parameters :body)})}}] + ["/skip" {:summary "skip" + :coercion (reitit.coercion.malli/create {:enabled false}) + :post {:parameters {:body [:map [:x int?]]} + :responses {200 {:body [:map [:x int?]]}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :body)})}}] - ["/or" {:post {:summary "accepts either of two map schemas" - :parameters {:body [:or [:map [:x int?]] [:map [:y int?]]]} - :responses {200 {:body [:map [:msg string?]]}} - :handler (fn [{{{:keys [x]} :body} :parameters}] - {:status 200 - :body {:msg (if x "you sent x" "you sent y")}})}}] + ["/or" {:post {:summary "accepts either of two map schemas" + :parameters {:body [:or [:map [:x int?]] [:map [:y int?]]]} + :responses {200 {:body [:map [: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 [:map [:a {:optional true} int?]] - :body [:map [:b int?]] - :form [:map [:c [int? {:default 3}]]] - :header [:map [:d int?]] - :path [:map [:e int?]]} - :responses {200 {:body [:map [:total pos-int?]]} - 500 {:description "fail"}} - :handler handler}}]] - {:data {:middleware middleware - :coercion malli/coercion}})))] + ["/plus/:e" {:get {:parameters {:query [:map [:a {:optional true} int?]] + :body [:map [:b int?]] + :form [:map [:c [int? {:default 3}]]] + :header [:map [:d int?]] + :path [:map [:e int?]]} + :responses {200 {:body [:map [:total pos-int?]]} + 500 {:description "fail"}} + :handler handler}}]]} + {:style "lite" + :routes ["/api" - (testing "without exception handling" - (let [app (create [rrc/coerce-request-middleware - rrc/coerce-response-middleware])] + ["/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)})}}] - (testing "all good" - (is (= {:status 200 - :body {:total 15}} - (app valid-request1))) - (is (= {:status 200 - :body {:total 115}} - (app valid-request2))) - (is (= {:status 200 - :body {:total 15}} - (app valid-request3))) - (testing "default values work" - (is (= {:status 200 - :body {:total 15}} - (app (update valid-request3 :form-params dissoc :c))))) - (is (= {:status 500 - :body {:evil true}} - (app (assoc-in valid-request1 [:query-params "a"] "666"))))) + ["/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)})}}] - (testing "invalid request" - (is (thrown-with-msg? - ExceptionInfo - #"Request coercion failed" - (app invalid-request1)))) + ["/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)})}}] - (testing "invalid response" - (is (thrown-with-msg? - ExceptionInfo - #"Response coercion failed" - (app invalid-request2)))))) + ["/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")}})}}] - (testing "with exception handling" - (let [app (create [rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware])] + ["/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 "just validation" - (is (= 400 (:status (app {:uri "/api/validate" - :request-method :post - :muuntaja/request {:format "application/edn"} - :body-params 123})))) - (is (= [:reitit.ring.coercion/coerce-exceptions - :reitit.ring.coercion/coerce-request - :reitit.ring.coercion/coerce-response] - (mounted-middleware app "/api/validate" :post)))) + (testing (str "malli with style " style) - (testing "no tranformation & validation" - (is (= 123 (:body (app {:uri "/api/no-op" - :request-method :post - :muuntaja/request {:format "application/edn"} - :body-params 123})))) - (is (= [:reitit.ring.coercion/coerce-exceptions - :reitit.ring.coercion/coerce-request - :reitit.ring.coercion/coerce-response] - (mounted-middleware app "/api/no-op" :post)))) + (testing "without exception handling" + (let [app (create [rrc/coerce-request-middleware + rrc/coerce-response-middleware] routes)] - (testing "skipping coercion" - (is (= nil (:body (app {:uri "/api/skip" - :request-method :post - :muuntaja/request {:format "application/edn"} - :body-params 123})))) - (is (= [:reitit.ring.coercion/coerce-exceptions] - (mounted-middleware app "/api/skip" :post)))) + (testing "all good" + (is (= {:status 200 + :body {:total 15}} + (app valid-request1))) + (is (= {:status 200 + :body {:total 115}} + (app valid-request2))) + (is (= {:status 200 + :body {:total 15}} + (app valid-request3))) + (testing "default values work" + (is (= {:status 200 + :body {:total 15}} + (app (update valid-request3 :form-params dissoc :c))))) + (is (= {:status 500 + :body {:evil true}} + (app (assoc-in valid-request1 [:query-params "a"] "666"))))) - (testing "or #407" - (is (= {:status 200 - :body {:msg "you sent x"}} - (app {:uri "/api/or" - :request-method :post - :body-params {:x 1}})))) + (testing "invalid request" + (is (thrown-with-msg? + ExceptionInfo + #"Request coercion failed" + (app invalid-request1)))) - (testing "all good" - (is (= {:status 200 - :body {:total 15}} - (app valid-request1)))) + (testing "invalid response" + (is (thrown-with-msg? + ExceptionInfo + #"Response coercion failed" + (app invalid-request2)))))) - (testing "invalid request" - (let [{:keys [status]} (app invalid-request1)] - (is (= 400 status)))) + (testing "with exception handling" + (let [app (create [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware] routes)] - (testing "invalid response" - (let [{:keys [status]} (app invalid-request2)] - (is (= 500 status)))))) + (testing "just validation" + (is (= 400 (:status (app {:uri "/api/validate" + :request-method :post + :muuntaja/request {:format "application/edn"} + :body-params 123})))) + (is (= [:reitit.ring.coercion/coerce-exceptions + :reitit.ring.coercion/coerce-request + :reitit.ring.coercion/coerce-response] + (mounted-middleware app "/api/validate" :post)))) + + (testing "no tranformation & validation" + (is (= 123 (:body (app {:uri "/api/no-op" + :request-method :post + :muuntaja/request {:format "application/edn"} + :body-params 123})))) + (is (= [:reitit.ring.coercion/coerce-exceptions + :reitit.ring.coercion/coerce-request + :reitit.ring.coercion/coerce-response] + (mounted-middleware app "/api/no-op" :post)))) + + (testing "skipping coercion" + (is (= nil (:body (app {:uri "/api/skip" + :request-method :post + :muuntaja/request {:format "application/edn"} + :body-params 123})))) + (is (= [:reitit.ring.coercion/coerce-exceptions] + (mounted-middleware app "/api/skip" :post)))) + + (testing "or #407" + (is (= {:status 200 + :body {:msg "you sent x"}} + (app {:uri "/api/or" + :request-method :post + :body-params {:x 1}})))) + + (testing "all good" + (is (= {:status 200 + :body {:total 15}} + (app valid-request1)))) + + (testing "invalid request" + (let [{:keys [status]} (app invalid-request1)] + (is (= 400 status)))) + + (testing "invalid response" + (let [{:keys [status]} (app invalid-request2)] + (is (= 500 status)))))))) (testing "open & closed schemas" (let [endpoint (fn [schema] @@ -350,15 +432,15 @@ {:status 200, :body (assoc body :response true)})}}) ->app (fn [options] (ring/ring-handler - (ring/router - ["/api" - ["/default" (endpoint [:map [:x int?]])] - ["/closed" (endpoint [:map {:closed true} [:x int?]])] - ["/open" (endpoint [:map {:closed false} [:x int?]])]] - {:data {:middleware [rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion (malli/create options)}}))) + (ring/router + ["/api" + ["/default" (endpoint [:map [:x int?]])] + ["/closed" (endpoint [:map {:closed true} [:x int?]])] + ["/open" (endpoint [:map {:closed false} [:x int?]])]] + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion (malli/create options)}}))) ->request (fn [uri] {:uri (str "/api/" uri) :request-method :get :muuntaja/request {:format "application/json"} @@ -414,22 +496,22 @@ (testing "sequence schemas" (let [app (ring/ring-handler - (ring/router - ["/ping" {:get {:parameters {:body [:vector [:map [:message string?]]]} - :responses {200 {:body [:vector [:map [:pong string?]]]} - 501 {:body [:vector [:map [:error string?]]]}} - :handler (fn [{{[{:keys [message]}] :body} :parameters :as req}] - (condp = message - "ping" {:status 200 - :body [{:pong message}]} - "fail" {:status 501 - :body [{:error "fail"}]} - {:status 200 - :body {:invalid "response"}}))}}] - {:data {:middleware [rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion malli/coercion}})) + (ring/router + ["/ping" {:get {:parameters {:body [:vector [:map [:message string?]]]} + :responses {200 {:body [:vector [:map [:pong string?]]]} + 501 {:body [:vector [:map [:error string?]]]}} + :handler (fn [{{[{:keys [message]}] :body} :parameters :as req}] + (condp = message + "ping" {:status 200 + :body [{:pong message}]} + "fail" {:status 501 + :body [{:error "fail"}]} + {:status 200 + :body {:invalid "response"}}))}}] + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion malli/coercion}})) ->request (fn [body] {:uri "/ping" :request-method :get @@ -454,15 +536,15 @@ (testing "encoding responses" (let [->app (fn [total-schema] (ring/ring-handler - (ring/router - ["/total" {:get {:parameters {:query [:map [:x :int]]} - :responses {200 {:body [:map [:total total-schema]]}} - :handler (fn [{{{:keys [x]} :query} :parameters}] - {:status 200 - :body {:total (* x x)}})}}] - {:data {:middleware [rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion malli/coercion}}))) + (ring/router + ["/total" {:get {:parameters {:query [:map [:x :int]]} + :responses {200 {:body [:map [:total total-schema]]}} + :handler (fn [{{{:keys [x]} :query} :parameters}] + {:status 200 + :body {:total (* x x)}})}}] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion malli/coercion}}))) call (fn [accept total-schema] ((->app total-schema) {:uri "/total" :request-method :get @@ -477,23 +559,44 @@ (is (= {:status 200, :body {:total -4}} (call "application/json" [:int {:encode/json -}])))) (testing "edn encoding (nada)" - (is (= {:status 200, :body {:total +4}} (call "application/edn" [:int {:encode/json -}])))))))) + (is (= {:status 200, :body {:total +4}} (call "application/edn" [:int {:encode/json -}])))))) + + (testing "using custom meta-merge function" + (let [->app (fn [schema-fn meta-merge-fn] + (ring/ring-handler + (ring/router + ["/merging-params/:foo" {:parameters {:path (schema-fn [:map [:foo :string]])}} + ["/:bar" {:parameters {:path (schema-fn [:map [:bar :string]])} + :get {:handler (fn [{{{:keys [foo bar]} :path} :parameters}] + {:status 200 + :body {:total (str "FOO: " foo ", " + "BAR: " bar)}})}}]] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion malli/coercion} + :meta-merge-fn meta-merge-fn}))) + call (fn [schema-fn meta-merge-fn] + ((->app schema-fn meta-merge-fn) {:uri "/merging-params/this/that" + :request-method :get}))] + + (is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call m/schema custom-meta-merge-checking-schema))) + (is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters))))))) #?(:clj (deftest muuntaja-test (let [app (ring/ring-handler - (ring/router - ["/api" - ["/plus" - {:post {:parameters {:body {:int int?, :keyword keyword?}} - :responses {200 {:body {:int int?, :keyword keyword?}}} - :handler (fn [{{:keys [body]} :parameters}] - {:status 200 - :body body})}}]] - {:data {:middleware [muuntaja.middleware/wrap-format - rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion spec/coercion}})) + (ring/router + ["/api" + ["/plus" + {:post {:parameters {:body {:int int?, :keyword keyword?}} + :responses {200 {:body {:int int?, :keyword keyword?}}} + :handler (fn [{{:keys [body]} :parameters}] + {:status 200 + :body body})}}]] + {:data {:middleware [muuntaja.middleware/wrap-format + rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion spec/coercion}})) request (fn [content-type body] (-> {:request-method :post :headers {"content-type" content-type, "accept" content-type} diff --git a/test/cljc/reitit/ring_spec_test.cljc b/test/cljc/reitit/ring_spec_test.cljc index 9a615efd..1112342b 100644 --- a/test/cljc/reitit/ring_spec_test.cljc +++ b/test/cljc/reitit/ring_spec_test.cljc @@ -1,11 +1,11 @@ (ns reitit.ring-spec-test - (:require [clojure.test :refer [deftest testing is]] - [reitit.ring :as ring] - [reitit.ring.spec :as rrs] - [reitit.ring.coercion :as rrc] + (:require [clojure.spec.alpha :as s] + [clojure.test :refer [deftest is testing]] [reitit.coercion.spec] - [clojure.spec.alpha :as s] - [reitit.core :as r]) + [reitit.core :as r] + [reitit.ring :as ring] + [reitit.ring.coercion :as rrc] + [reitit.ring.spec :as rrs]) #?(:clj (:import (clojure.lang ExceptionInfo)))) @@ -15,69 +15,69 @@ (deftest route-data-validation-test (testing "validation is turned off by default" (is (r/router? - (r/router - ["/api" {:handler "identity"}])))) + (r/router + ["/api" {:handler "identity"}])))) (testing "with default spec validates :name, :handler and :middleware" (is (thrown-with-msg? - ExceptionInfo - #"Invalid route data" - (ring/router - ["/api" {:handler "identity"}] - {:validate rrs/validate}))) + ExceptionInfo + #"Invalid route data" + (ring/router + ["/api" {:handler "identity"}] + {:validate rrs/validate}))) (is (thrown-with-msg? - ExceptionInfo - #"Invalid route data" - (ring/router - ["/api" {:handler identity - :name "kikka"}] - {:validate rrs/validate})))) + ExceptionInfo + #"Invalid route data" + (ring/router + ["/api" {:handler identity + :name "kikka"}] + {:validate rrs/validate})))) (testing "all endpoints are validated" (is (thrown-with-msg? - ExceptionInfo - #"Invalid route data" - (ring/router - ["/api" {:patch {:handler "identity"}}] - {:validate rrs/validate})))) + ExceptionInfo + #"Invalid route data" + (ring/router + ["/api" {:patch {:handler "identity"}}] + {:validate rrs/validate})))) (testing "spec can be overridden" (is (r/router? - (ring/router - ["/api" {:handler "identity"}] - {:spec (s/spec any?) - :validate rrs/validate}))) + (ring/router + ["/api" {:handler "identity"}] + {:spec (s/spec any?) + :validate rrs/validate}))) (testing "predicates are not allowed" (is (thrown-with-msg? - ExceptionInfo - #":reitit.ring.spec/invalid-specs" - (ring/router - ["/api" {:handler "identity"}] - {:spec any? - :validate rrs/validate}))))) + ExceptionInfo + #":reitit.ring.spec/invalid-specs" + (ring/router + ["/api" {:handler "identity"}] + {:spec any? + :validate rrs/validate}))))) (testing "middleware can contribute to specs" (is (r/router? - (ring/router - ["/api" {:get {:handler identity - :roles #{:admin}}}] - {:validate rrs/validate - :data {:middleware [{:spec (s/keys :opt-un [::roles]) - :wrap (fn [handler] - (fn [request] - (handler request)))}]}}))) + (ring/router + ["/api" {:get {:handler identity + :roles #{:admin}}}] + {:validate rrs/validate + :data {:middleware [{:spec (s/keys :opt-un [::roles]) + :wrap (fn [handler] + (fn [request] + (handler request)))}]}}))) (is (thrown-with-msg? - ExceptionInfo - #"Invalid route data" - (ring/router - ["/api" {:get {:handler identity - :roles #{:adminz}}}] - {:validate rrs/validate - :data {:middleware [{:spec (s/keys :opt-un [::roles]) - :wrap (fn [handler] - (fn [request] - (handler request)))}]}})))) + ExceptionInfo + #"Invalid route data" + (ring/router + ["/api" {:get {:handler identity + :roles #{:adminz}}}] + {:validate rrs/validate + :data {:middleware [{:spec (s/keys :opt-un [::roles]) + :wrap (fn [handler] + (fn [request] + (handler request)))}]}})))) (testing "middleware cannot be a list" (is (thrown-with-msg? ExceptionInfo @@ -89,46 +89,46 @@ (deftest coercion-spec-test (is (r/router? - (ring/router - ["/api" - ["/plus/:e" - {:get {:parameters {:query {:a string?} - :body {:b string?} - :form {:c string?} - :header {:d string?} - :path {:e string?}} - :responses {200 {:body {:total pos-int?}} - 400 {:description "fail"} - 500 {}} - :handler identity}}]] - {:data {:middleware [rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion reitit.coercion.spec/coercion} - :validate rrs/validate}))) + (ring/router + ["/api" + ["/plus/:e" + {:get {:parameters {:query {:a string?} + :body {:b string?} + :form {:c string?} + :header {:d string?} + :path {:e string?}} + :responses {200 {:body {:total pos-int?}} + 400 {:description "fail"} + 500 {}} + :handler identity}}]] + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion reitit.coercion.spec/coercion} + :validate rrs/validate}))) (is (r/router? - (ring/router - ["/api" - ["/plus/:e" - {:get {:parameters {:query (s/keys)} - :handler identity}}]] - {:data {:middleware [rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion reitit.coercion.spec/coercion} - :validate rrs/validate}))) + (ring/router + ["/api" + ["/plus/:e" + {:get {:parameters {:query (s/keys)} + :handler identity}}]] + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion reitit.coercion.spec/coercion} + :validate rrs/validate}))) (is (thrown-with-msg? - ExceptionInfo - #"Invalid route data" - (ring/router - ["/api" - ["/plus/:e" - {:get {:responses {"200" {}} - :handler identity}}]] - {:data {:middleware [rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion reitit.coercion.spec/coercion} - :validate rrs/validate})))) + ExceptionInfo + #"Invalid route data" + (ring/router + ["/api" + ["/plus/:e" + {:get {:responses {"200" {}} + :handler identity}}]] + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion reitit.coercion.spec/coercion} + :validate rrs/validate})))) diff --git a/test/cljc/reitit/ring_test.cljc b/test/cljc/reitit/ring_test.cljc index b057b401..d7b9f8f4 100644 --- a/test/cljc/reitit/ring_test.cljc +++ b/test/cljc/reitit/ring_test.cljc @@ -1,9 +1,9 @@ (ns reitit.ring-test - (:require [clojure.test :refer [deftest testing is]] - [clojure.set :as set] + (:require [clojure.set :as set] + [clojure.test :refer [deftest is testing]] + [reitit.core :as r] [reitit.middleware :as middleware] [reitit.ring :as ring] - [reitit.core :as r] [reitit.trie :as trie]) #?(:clj (:import (clojure.lang ExceptionInfo)))) @@ -28,35 +28,35 @@ (testing "nils are removed" (is (= 123 ((ring/routes - (constantly nil) - nil - (constantly 123)) + (constantly nil) + nil + (constantly 123)) ::irrelevant)))) (testing "can return nil" (is (= nil (ring/routes - nil - nil))))) + nil + nil))))) (deftest ring-router-test (testing "all paths should have a handler" (is (thrown-with-msg? - ExceptionInfo - #"path \"/ping\" doesn't have a :handler defined for :get" - (ring/router ["/ping" {:get {}}])))) + ExceptionInfo + #"path \"/ping\" doesn't have a :handler defined for :get" + (ring/router ["/ping" {:get {}}])))) (testing "ring-handler" (let [api-mw #(mw % :api) router (ring/router - ["/api" {:middleware [api-mw]} - ["/all" handler] - ["/get" {:get handler}] - ["/users" {:middleware [[mw :users]] - :get handler - :post {:handler handler - :middleware [[mw :post]]} - :handler handler}]]) + ["/api" {:middleware [api-mw]} + ["/all" handler] + ["/get" {:get handler}] + ["/users" {:middleware [[mw :users]] + :get handler + :post {:handler handler + :middleware [[mw :post]]} + :handler handler}]]) app (ring/ring-handler router)] (testing "router can be extracted" @@ -95,8 +95,8 @@ (testing "with top-level middleware" (let [router (ring/router - ["/api" {:middleware [[mw :api]]} - ["/get" {:get handler}]]) + ["/api" {:middleware [[mw :api]]} + ["/get" {:get handler}]]) app (ring/ring-handler router nil {:middleware [[mw :top]]})] (testing "router can be extracted" @@ -111,14 +111,14 @@ (testing "named routes" (let [router (ring/router - [["/api" - ["/all" {:handler handler :name ::all}] - ["/get" {:get {:handler handler :name ::HIDDEN} - :name ::get}] - ["/users" {:get handler - :post handler - :handler handler - :name ::users}]]]) + [["/api" + ["/all" {:handler handler :name ::all}] + ["/get" {:get {:handler handler :name ::HIDDEN} + :name ::get}] + ["/users" {:get handler + :post handler + :handler handler + :name ::users}]]]) app (ring/ring-handler router)] (testing "router can be extracted" @@ -141,21 +141,21 @@ (deftest mw-variadic-test (let [app (ring/ring-handler - (ring/router - ["/" {:middleware [[mw-variadic "kikka" "kakka" "kukka"]] - :handler handler}]))] + (ring/router + ["/" {:middleware [[mw-variadic "kikka" "kakka" "kukka"]] + :handler handler}]))] (is (= {:status 200, :body [:kikka_kakka_kukka :ok]} (app {:request-method :get, :uri "/"}))))) (deftest enforcing-data-rules-at-runtime-test (let [handler (constantly {:status 200, :body "ok"}) app (ring/ring-handler - (ring/router - [["/api" - ["/ping" handler] - ["/admin" {::roles #{:admin}} - ["/ping" handler]]]] - {:data {:middleware [wrap-enforce-roles]}}))] + (ring/router + [["/api" + ["/ping" handler] + ["/admin" {::roles #{:admin}} + ["/ping" handler]]]] + {:data {:middleware [wrap-enforce-roles]}}))] (testing "public handler" (is (= {:status 200, :body "ok"} @@ -175,8 +175,8 @@ (deftest default-handler-test (let [response {:status 200, :body "ok"} router (ring/router - [["/ping" {:get (constantly response)}] - ["/pong" (constantly nil)]]) + [["/ping" {:get (constantly response)}] + ["/pong" (constantly nil)]]) app (ring/ring-handler router)] (testing "match" @@ -202,9 +202,9 @@ (testing "with custom http responses" (let [app (ring/ring-handler router (ring/create-default-handler - {:not-found (constantly {:status -404}) - :method-not-allowed (constantly {:status -405}) - :not-acceptable (constantly {:status -406})}))] + {:not-found (constantly {:status -404}) + :method-not-allowed (constantly {:status -405}) + :not-acceptable (constantly {:status -406})}))] (testing "route doesn't match" (is (= -404 (:status (app {:request-method :get, :uri "/"}))))) (testing "method doesn't match" @@ -214,7 +214,7 @@ (testing "with some custom http responses" (let [app (ring/ring-handler router (ring/create-default-handler - {:not-found (constantly {:status -404})}))] + {:not-found (constantly {:status -404})}))] (testing "route doesn't match" (is (= 405 (:status (app {:request-method :post, :uri "/ping"})))))))))) @@ -227,11 +227,11 @@ (testing "with defaults" (let [app (ring/ring-handler - (ring/router - [["/get" {:get (constantly response) - :post (constantly response)}] - ["/options" {:options (constantly response)}] - ["/any" (constantly response)]]))] + (ring/router + [["/get" {:get (constantly response) + :post (constantly response)}] + ["/options" {:options (constantly response)}] + ["/any" (constantly response)]]))] (testing "endpoint with a non-options handler" (let [request {:request-method :options, :uri "/get"}] @@ -258,10 +258,10 @@ (constantly {:status 200, :body "ok"})]] (let [response {:status 200, :body "ok"} app (ring/ring-handler - (ring/router - ["/get" {:get (constantly response) - :post (constantly response)}] - {::ring/default-options-endpoint endpoint}))] + (ring/router + ["/get" {:get (constantly response) + :post (constantly response)}] + {::ring/default-options-endpoint endpoint}))] (testing "endpoint with a non-options handler" (let [request {:request-method :options, :uri "/get"}] @@ -270,11 +270,11 @@ (testing "disabled via options" (let [app (ring/ring-handler - (ring/router - [["/get" {:get (constantly response)}] - ["/options" {:options (constantly response)}] - ["/any" (constantly response)]] - {::ring/default-options-endpoint nil}))] + (ring/router + [["/get" {:get (constantly response)}] + ["/options" {:options (constantly response)}] + ["/any" (constantly response)]] + {::ring/default-options-endpoint nil}))] (testing "endpoint with a non-options handler" (is (= response (app {:request-method :get, :uri "/get"}))) @@ -297,8 +297,8 @@ :post (constantly ok)}]]] (testing "using :method :add" (let [app (ring/ring-handler - (ring/router routes) - (ring/redirect-trailing-slash-handler {:method :add}))] + (ring/router routes) + (ring/redirect-trailing-slash-handler {:method :add}))] (testing "exact matches work" (is (= ok (app {:request-method :get, :uri "/slash-less"}))) @@ -316,8 +316,8 @@ (testing "using :method :strip" (let [app (ring/ring-handler - (ring/router routes) - (ring/redirect-trailing-slash-handler {:method :strip}))] + (ring/router routes) + (ring/redirect-trailing-slash-handler {:method :strip}))] (testing "stripping to empty string doesn't match" (is (= nil (:status (app {:request-method :get, :uri "/"}))))) @@ -342,8 +342,8 @@ (testing "without option (equivalent to using :method :both)" (let [app (ring/ring-handler - (ring/router routes) - (ring/redirect-trailing-slash-handler))] + (ring/router routes) + (ring/redirect-trailing-slash-handler))] (testing "exact matches work" (is (= ok (app {:request-method :get, :uri "/slash-less"}))) @@ -370,10 +370,10 @@ ([x] (reset! value x)))) response {:status 200, :body "ok"} router (ring/router - [["/ping" {:get (fn [_ respond _] - (respond response))}] - ["/pong" (fn [_ respond _] - (respond nil))]]) + [["/ping" {:get (fn [_ respond _] + (respond response))}] + ["/pong" (fn [_ respond _] + (respond nil))]]) app (ring/ring-handler router)] (testing "match" @@ -436,12 +436,12 @@ request {:uri "/api/avaruus" :request-method :get} create (fn [options] (ring/ring-handler - (ring/router - ["/api" {:middleware [(middleware :olipa)]} - ["/avaruus" {:middleware [(middleware :kerran)] - :get {:handler handler - :middleware [(middleware :avaruus)]}}]] - options)))] + (ring/router + ["/api" {:middleware [(middleware :olipa)]} + ["/avaruus" {:middleware [(middleware :kerran)] + :get {:handler handler + :middleware [(middleware :avaruus)]}}]] + options)))] (testing "by default, all middleware are applied in order" (let [app (create nil)] @@ -510,9 +510,9 @@ (testing "from path" (let [app (ring/ring-handler - (ring/router - ["/files/*" (create nil)]) - (ring/create-default-handler)) + (ring/router + ["/files/*" (create nil)]) + (ring/create-default-handler)) request #(request (str "/files" %)) redirect #(redirect (str "/files" %))] (testing "different file-types" @@ -553,10 +553,10 @@ (testing "from root" (let [app (ring/ring-handler - (ring/router []) - (ring/routes - (create {:path "/" :not-found-handler (fn [x] {:status 404 :body "resource-handler"})}) - (ring/create-default-handler)))] + (ring/router []) + (ring/routes + (create {:path "/" :not-found-handler (fn [x] {:status 404 :body "resource-handler"})}) + (ring/create-default-handler)))] (testing "different file-types" (let [response (app (request "/hello.json"))] (is (= "application/json" (get-in response [:headers "Content-Type"]))) @@ -594,10 +594,10 @@ (testing "from path" (let [app (ring/ring-handler - (ring/router []) - (ring/routes - (create {:path "/files" :not-found-handler (fn [x] {:status 404 :body "resource-handler"})}) - (ring/create-default-handler))) + (ring/router []) + (ring/routes + (create {:path "/files" :not-found-handler (fn [x] {:status 404 :body "resource-handler"})}) + (ring/create-default-handler))) request #(request (str "/files" %)) redirect #(redirect (str "/files" %))] (testing "different file-types" @@ -638,12 +638,11 @@ (is (get-in @result [:headers "Last-Modified"])) (is (= "file\n" (slurp (:body @result)))))))))))))) - #?(:clj - (deftest file-resource-handler-not-found-test - (let [redirect (fn [uri] {:status 302, :body "", :headers {"Location" uri}}) - request (fn [uri] {:uri uri, :request-method :get}) - not-found-handler (fn [_] {:status 404, :body "not-found-handler"})] + (deftest file-resource-handler-not-found-test + (let [redirect (fn [uri] {:status 302, :body "", :headers {"Location" uri}}) + request (fn [uri] {:uri uri, :request-method :get}) + not-found-handler (fn [_] {:status 404, :body "not-found-handler"})] (doseq [[name create] [["resource-handler" ring/create-resource-handler] ["file-handler" #(ring/create-file-handler (assoc % :root "dev-resources/public"))]]] @@ -681,15 +680,15 @@ (deftest router-available-in-default-branch (testing "1-arity" ((ring/ring-handler - (ring/router []) - (fn [{::r/keys [router]}] - (is router))) + (ring/router []) + (fn [{::r/keys [router]}] + (is router))) {})) (testing "3-arity" ((ring/ring-handler - (ring/router []) - (fn [{::r/keys [router]} _ _] - (is router))) + (ring/router []) + (fn [{::r/keys [router]} _ _] + (is router))) {} ::respond ::raise))) #?(:clj @@ -697,11 +696,11 @@ (testing "in enough concurrent system, path-parameters can bleed" (doseq [compiler [trie/java-trie-compiler trie/clojure-trie-compiler]] (let [app (ring/ring-handler - (ring/router - ["/:id" (fn [request] - {:status 200 - :body (-> request :path-params :id)})]) - {::trie/trie-compiler compiler})] + (ring/router + ["/:id" (fn [request] + {:status 200 + :body (-> request :path-params :id)})]) + {::trie/trie-compiler compiler})] (dotimes [_ 10] (future (dotimes [n 100000] diff --git a/test/cljc/reitit/spec_test.cljc b/test/cljc/reitit/spec_test.cljc index f8506b6a..e2717989 100644 --- a/test/cljc/reitit/spec_test.cljc +++ b/test/cljc/reitit/spec_test.cljc @@ -1,7 +1,7 @@ (ns reitit.spec-test - (:require [clojure.test :refer [deftest testing is are use-fixtures]] - [#?(:clj clojure.spec.test.alpha :cljs cljs.spec.test.alpha) :as stest] + (:require [#?(:clj clojure.spec.test.alpha :cljs cljs.spec.test.alpha) :as stest] [clojure.spec.alpha :as s] + [clojure.test :refer [are deftest is testing use-fixtures]] [reitit.core :as r] [reitit.spec :as rs]) #?(:clj @@ -38,10 +38,10 @@ (testing "with invalid routes" (are [data] (is (thrown-with-msg? - ExceptionInfo - #"Call to #'reitit.core/router did not conform to spec" - (r/router - data))) + ExceptionInfo + #"Call to #'reitit.core/router did not conform to spec" + (r/router + data))) ;; path [:invalid {}] @@ -68,10 +68,10 @@ (are [opts] (is (thrown-with-msg? - ExceptionInfo - #"Call to #'reitit.core/router did not conform to spec" - (r/router - ["/api"] opts))) + ExceptionInfo + #"Call to #'reitit.core/router did not conform to spec" + (r/router + ["/api"] opts))) {:path :api} {:path nil} @@ -85,52 +85,52 @@ (deftest route-data-validation-test (testing "validation is turned off by default" (is (r/router? (r/router - ["/api" {:handler "identity"}])))) + ["/api" {:handler "identity"}])))) (testing "with default spec validates :name and :handler" (is (thrown-with-msg? - ExceptionInfo - #"Invalid route data" - (r/router - ["/api" {:handler "identity"}] - {:validate rs/validate}))) + ExceptionInfo + #"Invalid route data" + (r/router + ["/api" {:handler "identity"}] + {:validate rs/validate}))) (is (thrown-with-msg? - ExceptionInfo - #"Invalid route data" - (r/router - ["/api" {:name "kikka"}] - {:validate rs/validate})))) + ExceptionInfo + #"Invalid route data" + (r/router + ["/api" {:name "kikka"}] + {:validate rs/validate})))) (testing "spec can be overridden" (is (r/router? (r/router - ["/api" {:handler "identity"}] - {:spec any? - :validate rs/validate}))))) + ["/api" {:handler "identity"}] + {:spec any? + :validate rs/validate}))))) (deftest parameters-test (is (s/valid? - ::rs/parameters - {:parameters {:query {:a string?} - :body {:b string?} - :form {:c string?} - :header {:d string?} - :path {:e string?}}})) + ::rs/parameters + {:parameters {:query {:a string?} + :body {:b string?} + :form {:c string?} + :header {:d string?} + :path {:e string?}}})) (is (s/valid? - ::rs/parameters - {:parameters {:header (s/keys)}})) + ::rs/parameters + {:parameters {:header (s/keys)}})) (is (s/valid? - ::rs/responses - {:responses {200 {:description "ok", :body string?} - 400 {:description "fail"} - 500 {:body string?} - :default {}}})) + ::rs/responses + {:responses {200 {:description "ok", :body string?} + 400 {:description "fail"} + 500 {:body string?} + :default {}}})) (is (not (s/valid? - ::rs/responses - {:responses {"200" {:description "ok", :body string?}}}))) + ::rs/responses + {:responses {"200" {:description "ok", :body string?}}}))) (is (not (s/valid? - ::rs/responses - {:responses {200 {:description :ok, :body string?}}})))) + ::rs/responses + {:responses {200 {:description :ok, :body string?}}})))) diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index b87c695c..96dfed23 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -1,35 +1,37 @@ (ns reitit.swagger-test (:require [clojure.test :refer [deftest is testing]] - [reitit.ring :as ring] - [reitit.swagger :as swagger] - [reitit.swagger-ui :as swagger-ui] - [reitit.ring.coercion :as rrc] - [reitit.coercion.spec :as spec] + [muuntaja.core :as m] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] - [muuntaja.core :as m] - [spec-tools.data-spec :as ds] - [schema.core :as s])) + [reitit.coercion.spec :as spec] + [reitit.ring :as ring] + [reitit.ring.coercion :as rrc] + [reitit.swagger :as swagger] + [reitit.swagger-ui :as swagger-ui] + [schema.core :as s] + [spec-tools.data-spec :as ds])) (def app (ring/ring-handler - (ring/router - ["/api" - {:swagger {:id ::math}} + (ring/router + ["/api" + {:swagger {:id ::math}} - ["/swagger.json" - {:get {:no-doc true - :swagger {:info {:title "my-api"}} - :handler (swagger/create-swagger-handler)}}] + ["/swagger.json" + {:get {:no-doc true + :swagger {:info {:title "my-api"}} + :handler (swagger/create-swagger-handler)}}] ["/spec" {:coercion spec/coercion} ["/plus/:z" {:patch {:summary "patch" + :operationId "Patch" :handler (constantly {:status 200})} :options {:summary "options" :middleware [{:data {:swagger {:responses {200 {:description "200"}}}}}] :handler (constantly {:status 200})} :get {:summary "plus" + :operationId "GetPlus" :parameters {:query {:x int?, :y int?} :path {:z int?}} :swagger {:responses {400 {:schema {:type "string"} @@ -50,56 +52,56 @@ xs :body} :parameters}] {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] - ["/malli" {:coercion malli/coercion} - ["/plus/*z" - {:get {:summary "plus" - :parameters {:query [:map [:x int?] [:y int?]] - :path [:map [:z int?]]} - :swagger {:responses {400 {:schema {:type "string"} - :description "kosh"}}} - :responses {200 {:body [:map [:total int?]]} - 500 {:description "fail"}} - :handler (fn [{{{:keys [x y]} :query - {:keys [z]} :path} :parameters}] - {:status 200, :body {:total (+ x y z)}})} - :post {:summary "plus with body" - :parameters {:body [:maybe [:vector int?]] - :path [:map [:z int?]]} - :swagger {:responses {400 {:schema {:type "string"} - :description "kosh"}}} - :responses {200 {:body [:map [:total int?]]} - 500 {:description "fail"}} - :handler (fn [{{{:keys [z]} :path - xs :body} :parameters}] - {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] + ["/malli" {:coercion malli/coercion} + ["/plus/*z" + {:get {:summary "plus" + :parameters {:query [:map [:x int?] [:y int?]] + :path [:map [:z int?]]} + :swagger {:responses {400 {:schema {:type "string"} + :description "kosh"}}} + :responses {200 {:body [:map [:total int?]]} + 500 {:description "fail"}} + :handler (fn [{{{:keys [x y]} :query + {:keys [z]} :path} :parameters}] + {:status 200, :body {:total (+ x y z)}})} + :post {:summary "plus with body" + :parameters {:body [:maybe [:vector int?]] + :path [:map [:z int?]]} + :swagger {:responses {400 {:schema {:type "string"} + :description "kosh"}}} + :responses {200 {:body [:map [:total int?]]} + 500 {:description "fail"}} + :handler (fn [{{{:keys [z]} :path + xs :body} :parameters}] + {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] - ["/schema" {:coercion schema/coercion} - ["/plus/*z" - {:get {:summary "plus" - :parameters {:query {:x s/Int, :y s/Int} - :path {:z s/Int}} - :swagger {:responses {400 {:schema {:type "string"} - :description "kosh"}}} - :responses {200 {:body {:total s/Int}} - 500 {:description "fail"}} - :handler (fn [{{{:keys [x y]} :query - {:keys [z]} :path} :parameters}] - {:status 200, :body {:total (+ x y z)}})} - :post {:summary "plus with body" - :parameters {:body (s/maybe [s/Int]) - :path {:z s/Int}} - :swagger {:responses {400 {:schema {:type "string"} - :description "kosh"}}} - :responses {200 {:body {:total s/Int}} - 500 {:description "fail"}} - :handler (fn [{{{:keys [z]} :path - xs :body} :parameters}] - {:status 200, :body {:total (+ (reduce + xs) z)}})}}]]] + ["/schema" {:coercion schema/coercion} + ["/plus/*z" + {:get {:summary "plus" + :parameters {:query {:x s/Int, :y s/Int} + :path {:z s/Int}} + :swagger {:responses {400 {:schema {:type "string"} + :description "kosh"}}} + :responses {200 {:body {:total s/Int}} + 500 {:description "fail"}} + :handler (fn [{{{:keys [x y]} :query + {:keys [z]} :path} :parameters}] + {:status 200, :body {:total (+ x y z)}})} + :post {:summary "plus with body" + :parameters {:body (s/maybe [s/Int]) + :path {:z s/Int}} + :swagger {:responses {400 {:schema {:type "string"} + :description "kosh"}}} + :responses {200 {:body {:total s/Int}} + 500 {:description "fail"}} + :handler (fn [{{{:keys [z]} :path + xs :body} :parameters}] + {:status 200, :body {:total (+ (reduce + xs) z)}})}}]]] - {:data {:middleware [swagger/swagger-feature - rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware]}}))) + {:data {:middleware [swagger/swagger-feature + rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware]}}))) (require '[fipp.edn]) (deftest swagger-test @@ -118,6 +120,7 @@ (app {:request-method :get :uri "/api/schema/plus/3" :query-params {:x "2", :y "1"}}))))) + (testing "swagger-spec" (let [spec (:body (app {:request-method :get :uri "/api/swagger.json"})) @@ -126,6 +129,7 @@ :info {:title "my-api"} :paths {"/api/spec/plus/{z}" {:patch {:parameters [] :summary "patch" + :operationId "Patch" :responses {:default {:description ""}}} :options {:parameters [] :summary "options" @@ -156,7 +160,8 @@ 400 {:schema {:type "string"} :description "kosh"} 500 {:description "fail"}} - :summary "plus"} + :summary "plus" + :operationId "GetPlus"} :post {:parameters [{:in "body", :name "body", :description "", @@ -300,21 +305,21 @@ {:get {:no-doc true :handler (swagger/create-swagger-handler)}}] app (ring/ring-handler - (ring/router - [["/common" {:swagger {:id #{::one ::two}}} - ping-route] + (ring/router + [["/common" {:swagger {:id #{::one ::two}}} + ping-route] - ["/one" {:swagger {:id ::one}} - ping-route - spec-route] + ["/one" {:swagger {:id ::one}} + ping-route + spec-route] - ["/two" {:swagger {:id ::two}} - ping-route - spec-route - ["/deep" {:swagger {:id ::one}} - ping-route]] - ["/one-two" {:swagger {:id #{::one ::two}}} - spec-route]]))] + ["/two" {:swagger {:id ::two}} + ping-route + spec-route + ["/deep" {:swagger {:id ::one}} + ping-route]] + ["/one-two" {:swagger {:id #{::one ::two}}} + spec-route]]))] (is (= ["/common/ping" "/one/ping" "/two/deep/ping"] (spec-paths app "/one/swagger.json"))) (is (= ["/common/ping" "/two/ping"] @@ -324,8 +329,8 @@ (deftest swagger-ui-config-test (let [app (swagger-ui/create-swagger-ui-handler - {:path "/" - :config {:jsonEditor true}})] + {:path "/" + :config {:jsonEditor true}})] (is (= 302 (:status (app {:request-method :get, :uri "/"})))) (is (= 200 (:status (app {:request-method :get, :uri "/index.html"})))) (is (= {:jsonEditor true, :url "/swagger.json"} @@ -334,12 +339,12 @@ (deftest without-swagger-id-test (let [app (ring/ring-handler - (ring/router - [["/ping" - {:get (constantly "ping")}] - ["/swagger.json" - {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]]))] + (ring/router + [["/ping" + {:get (constantly "ping")}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]]))] (is (= ["/ping"] (spec-paths app "/swagger.json"))) (is (= #{::swagger/default} (-> {:request-method :get :uri "/swagger.json"} @@ -347,14 +352,14 @@ (deftest with-options-endpoint-test (let [app (ring/ring-handler - (ring/router - [["/ping" - {:options (constantly "options")}] - ["/pong" - (constantly "options")] - ["/swagger.json" - {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]]))] + (ring/router + [["/ping" + {:options (constantly "options")}] + ["/pong" + (constantly "options")] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]]))] (is (= ["/ping" "/pong"] (spec-paths app "/swagger.json"))) (is (= #{::swagger/default} (-> {:request-method :get :uri "/swagger.json"} @@ -362,18 +367,18 @@ (deftest all-parameter-types-test (let [app (ring/ring-handler - (ring/router - [["/parameters" - {:post {:coercion spec/coercion - :parameters {:query {:q string?} - :body {:b string?} - :form {:f string?} - :header {:h string?} - :path {:p string?}} - :handler identity}}] - ["/swagger.json" - {:get {:no-doc true - :handler (swagger/create-swagger-handler)}}]])) + (ring/router + [["/parameters" + {:post {:coercion spec/coercion + :parameters {:query {:q string?} + :body {:b string?} + :form {:f string?} + :header {:h string?} + :path {:p string?}} + :handler identity}}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]])) spec (:body (app {:request-method :get, :uri "/swagger.json"}))] (is (= ["query" "body" "formData" "header" "path"] (map :in (get-in spec [:paths "/parameters" :post :parameters])))))) diff --git a/test/cljc/reitit/trie_test.cljc b/test/cljc/reitit/trie_test.cljc index 983d52d0..7b252bc6 100644 --- a/test/cljc/reitit/trie_test.cljc +++ b/test/cljc/reitit/trie_test.cljc @@ -1,5 +1,5 @@ (ns reitit.trie-test - (:require [clojure.test :refer [deftest testing is are]] + (:require [clojure.test :refer [are deftest is testing]] [reitit.trie :as trie])) (deftest into-set-test diff --git a/test/cljs/reitit/frontend/core_test.cljs b/test/cljs/reitit/frontend/core_test.cljs index 22ac1db2..61c938d8 100644 --- a/test/cljs/reitit/frontend/core_test.cljs +++ b/test/cljs/reitit/frontend/core_test.cljs @@ -21,9 +21,11 @@ :data {:name ::frontpage} :path-params {} :query-params {} + :fragment-params {} :path "/" :parameters {:query {} - :path {}}}) + :path {} + :fragment {}}}) (rf/match-by-path router "/"))) (is (= "/" @@ -34,9 +36,11 @@ :data {:name ::foo} :path-params {} :query-params {} + :fragment-params {} :path "/foo" :parameters {:query {} - :path {}}}) + :path {} + :fragment {}}}) (rf/match-by-path router "/foo"))) (is (= (r/map->Match @@ -44,9 +48,11 @@ :data {:name ::foo} :path-params {} :query-params {:mode ["foo", "bar"]} + :fragment-params {} :path "/foo" :parameters {:query {:mode ["foo", "bar"]} - :path {}}}) + :path {} + :fragment {}}}) (rf/match-by-path router "/foo?mode=foo&mode=bar"))) (is (= "/foo" @@ -64,7 +70,12 @@ (let [router (r/router ["/" [":id" {:name ::foo :parameters {:path {:id s/Int} - :query {(s/optional-key :mode) s/Keyword}}}]] + :query {(s/optional-key :mode) s/Keyword} + :fragment {(s/optional-key :access_token) s/Str + (s/optional-key :refresh_token) s/Str + (s/optional-key :expires_in) s/Int + (s/optional-key :provider_token) s/Str + (s/optional-key :token_type) s/Str}}}]] {:compile rc/compile-request-coercers :data {:coercion rsc/coercion}})] @@ -72,36 +83,63 @@ {:template "/:id" :path-params {:id "5"} :query-params {} + :fragment-params {} :path "/5" :parameters {:query {} - :path {:id 5}}}) + :path {:id 5} + :fragment {}}}) (m (rf/match-by-path router "/5")))) (is (= "/5" (r/match->path (rf/match-by-name router ::foo {:id 5})))) + (testing "coercion error" + (testing "throws without options" + (is (thrown? js/Error (m (rf/match-by-path router "/a"))))) + + (testing "thows and calles on-coercion-error" + (let [exception (atom nil) + match (atom nil)] + (is (thrown? js/Error (m (rf/match-by-path router "/a" {:on-coercion-error (fn [m e] + (reset! match m) + (reset! exception e))})))) + (is (= {:id "a"} (-> @match :path-params))) + (is (= {:id "a"} (-> @exception (ex-data) :value)))))) + (testing "query param is read" (is (= (r/map->Match {:template "/:id" :path-params {:id "5"} :query-params {:mode "foo"} + :fragment-params {} :path "/5" :parameters {:path {:id 5} - :query {:mode :foo}}}) + :query {:mode :foo} + :fragment {}}}) (m (rf/match-by-path router "/5?mode=foo")))) (is (= "/5?mode=foo" (r/match->path (rf/match-by-name router ::foo {:id 5}) {:mode :foo})))) - (testing "fragment is ignored" + (testing "fragment is read" (is (= (r/map->Match {:template "/:id" :path-params {:id "5"} :query-params {:mode "foo"} + :fragment-params {:access_token "foo" + :refresh_token "bar" + :provider_token "baz" + :token_type "bearer" + :expires_in "3600"} :path "/5" :parameters {:path {:id 5} - :query {:mode :foo}}}) - (m (rf/match-by-path router "/5?mode=foo#fragment"))))) + :query {:mode :foo} + :fragment {:access_token "foo" + :refresh_token "bar" + :provider_token "baz" + :token_type "bearer" + :expires_in 3600}}}) + (m (rf/match-by-path router "/5?mode=foo#access_token=foo&refresh_token=bar&provider_token=baz&token_type=bearer&expires_in=3600"))))) (testing "console warning about missing params" (is (= [{:type :warn