diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 00ab4f3d..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,86 +0,0 @@ -version: 2.1 - -jobs: - test-clj: - working_directory: ~/test - parameters: - image-name: - type: string - docker: - - image: << parameters.image-name >> - steps: - - checkout - - restore_cache: - keys: - - 'v1-clj-{{ checksum "project.clj" }}' - - 'v1-clj-' - - 'v1-test-' - - run: - name: Install modules - command: ./scripts/lein-modules install - - run: - name: Run tests - command: ./scripts/test.sh clj - - run: - name: Install curl if missing - command: apt update && apt install -y curl - - run: - name: Verify cljdoc.edn - command: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn - - store_test_results: - # path must be a directory under which there a subdirectories that - # contain the JUnit XML files. - path: ~/test/target/results -# - run: -# name: Run coverage -# command: ./scripts/submit-to-coveralls.sh clj - - save_cache: - key: 'v1-clj-{{ checksum "project.clj" }}' - paths: - - ~/.m2 - - ~/.cljs/.aot_cache - - test-cljs: - working_directory: ~/test - docker: - - image: circleci/clojure:lein-2.8.1-node-browsers - steps: - - checkout - - restore_cache: - keys: - - 'v1-cljs-{{ checksum "project.clj" }}-{{ checksum "package.json" }}' - - 'v1-cljs-' - - run: - name: Install npm dependencies - command: npm install - - run: - name: Install modules - command: ./scripts/lein-modules install - - run: - name: Run tests - command: ./scripts/test.sh cljs - - store_test_results: - path: ~/test/target/results - - save_cache: - key: 'v1-cljs-{{ checksum "project.clj" }}-{{ checksum "package.json" }}' - paths: - - ~/.m2 - - ~/test/node_modules - -workflows: - version: 2 - test-and-build-docs: - jobs: - - test-clj: - name: jdk8 - image-name: clojure:openjdk-8-lein-2.9.1 - - test-clj: - name: jdk11 - image-name: clojure:openjdk-11-lein-2.9.1 - - test-clj: - name: jdk13 - image-name: clojure:openjdk-13-lein-2.9.1 - - test-clj: - name: jdk14 - image-name: clojure:openjdk-14-lein-2.9.1 - - test-cljs 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/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml new file mode 100644 index 00000000..85e86658 --- /dev/null +++ b/.github/workflows/testsuite.yml @@ -0,0 +1,78 @@ +name: testsuite + +on: + push: + pull_request: + +jobs: + build-clj: + strategy: + matrix: + # Supported Java versions: LTS releases 8 and 11 and the latest release + jdk: [8, 11, 15] + + name: Clojure (Java ${{ matrix.jdk }}) + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-clj-${{ hashFiles('**/project.clj') }} + restore-keys: | + ${{ runner.os }}-clj- + - name: Setup Java ${{ matrix.jdk }} + uses: actions/setup-java@v1.4.3 + with: + java-version: ${{ matrix.jdk }} + - name: Setup Clojure + uses: DeLaGuardo/setup-clojure@3.1 + with: + lein: 2.9.5 + - name: Run tests + run: ./scripts/test.sh clj + + build-cljs: + name: ClojureScript + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: | + ~/.m2/repository + **/node_modules + key: ${{ runner.os }}-cljs-${{ hashFiles('**/project.clj', '**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-cljs- + - name: Setup Java 11 + uses: actions/setup-java@v1.4.3 + with: + java-version: 11 + - name: Setup Clojure + uses: DeLaGuardo/setup-clojure@3.1 + with: + lein: 2.9.5 + - name: Setup Node.js + uses: actions/setup-node@v2.1.2 + with: + node-version: 12 + - name: Install dependencies + run: | + npm ci + - name: Install modules + run: ./scripts/lein-modules install + - name: Run tests + run: ./scripts/test.sh cljs + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Verify cljdoc.edn + run: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn 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 c9b21dfd..d77091f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,138 @@ We use [Break Versioning][breakver]. The version numbers follow a `.. match :data :router)] (let [subpath (subs path (str/last-index-of (:template match) "/"))] - (if-let [submatch (recursive-match-by-path subrouter subpath)] + (when-let [submatch (recursive-match-by-path subrouter subpath)] (cons match submatch))) (list match)))) ``` @@ -206,10 +206,10 @@ First, we need to modify our matching function to support router references: (deref x) x)) (defn recursive-match-by-path [router path] - (if-let [match (r/match-by-path (<< router) path)] + (when-let [match (r/match-by-path (<< router) path)] (if-let [subrouter (-> match :data :router <<)] (let [subpath (subs path (str/last-index-of (:template match) "/"))] - (if-let [submatch (recursive-match-by-path subrouter subpath)] + (when-let [submatch (recursive-match-by-path subrouter subpath)] (cons match submatch))) (list match)))) ``` diff --git a/doc/advanced/configuring_routers.md b/doc/advanced/configuring_routers.md index 4cc30be9..35601c94 100644 --- a/doc/advanced/configuring_routers.md +++ b/doc/advanced/configuring_routers.md @@ -4,15 +4,16 @@ Routers can be configured via options. The following options are available for t | key | description |--------------|------------- -| `: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` -| `: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/advanced/different_routers.md b/doc/advanced/different_routers.md index aa7b78ae..49adebbd 100644 --- a/doc/advanced/different_routers.md +++ b/doc/advanced/different_routers.md @@ -1,6 +1,6 @@ # Different Routers -Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` function selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](advanced/configuring_routers.md). +Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` function selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](configuring_routers.md). | router | description | | ------------------------------|-------------| diff --git a/doc/basics/error_messages.md b/doc/basics/error_messages.md index 68a38916..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.10"] +[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. @@ -51,4 +51,4 @@ See the [validating route data](route_data_validation.md) page. ## Runtime Exception -See [Exception Handling with Ring](exceptions.md). +See [Exception Handling with Ring](../ring/exceptions.md). diff --git a/doc/basics/route_data.md b/doc/basics/route_data.md index 2ea2f50e..c5df80dd 100644 --- a/doc/basics/route_data.md +++ b/doc/basics/route_data.md @@ -79,7 +79,7 @@ Resolved route tree: ; :name :user/ping}] ; ["/api/admin/users" {:interceptors [::api] ; :roles #{:admin} -; :name ::users} nil] +; :name ::users}] ; ["/api/admin/db" {:interceptors [::api ::db] ; :roles #{:db-admin}}]] ``` diff --git a/doc/basics/route_syntax.md b/doc/basics/route_syntax.md index f7481b50..a91ec097 100644 --- a/doc/basics/route_syntax.md +++ b/doc/basics/route_syntax.md @@ -132,7 +132,7 @@ Routes are just data, so it's easy to create them programmatically: ### Explicit path-parameter syntax -Router options `:syntax` allows the path-parameter syntax to be explicitely defined. It takes a keyword or set of keywords as a value. Valid values are `:colon` and `:bracket`. Default value is `#{:colon :bracket}`. +Router options `:syntax` allows the path-parameter syntax to be explicitly defined. It takes a keyword or set of keywords as a value. Valid values are `:colon` and `:bracket`. Default value is `#{:colon :bracket}`. With defaults: diff --git a/doc/basics/router.md b/doc/basics/router.md index 00348ac7..dba18dd6 100644 --- a/doc/basics/router.md +++ b/doc/basics/router.md @@ -63,14 +63,6 @@ Route names: ; [:user/ping :user/user] ``` -The compiled route tree: - -```clj -(r/routes router) -; [["/api/ping" {:name :user/ping} nil] -; ["/api/user/:id" {:name :user/user} nil]] -``` - ### Composing As routes are defined as plain data, it's easy to merge multiple route trees into a single router @@ -85,9 +77,10 @@ As routes are defined as plain data, it's easy to merge multiple route trees int ["/ping" ::ping] ["/db" ::db]]) -(r/router - [admin-routes - user-routes]) +(def router + (r/router + [admin-routes + user-routes])) ``` Merged route tree: @@ -109,6 +102,6 @@ When router is created, the following steps are done: * route arguments are expanded (via `:expand` option) * routes are coerced (via `:coerce` options) * route tree is compiled (via `:compile` options) -* [route conflicts](advanced/route_conflicts.md) are resolved (via `:conflicts` options) +* [route conflicts](route_conflicts.md) are resolved (via `:conflicts` options) * optionally, route data is validated (via `:validate` options) * [router implementation](../advanced/different_routers.md) is automatically selected (or forced via `:router` options) and created diff --git a/doc/coercion/malli_coercion.md b/doc/coercion/malli_coercion.md index 7c041d98..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]) @@ -13,7 +17,7 @@ :coercion reitit.coercion.malli/coercion :parameters {:path [:map [:company string?] - [:user-id int?]]}] + [:user-id int?]]}}] {:compile coercion/compile-request-coercers})) (defn match-by-path-and-coerce! [path] @@ -44,18 +48,36 @@ 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`: ```clj +(require '[malli.util :as mu]) + (reitit.coercion.malli/create - {:transformers {:body {:default default-transformer-provider - :formats {"application/json" json-transformer-provider}} - :string {:default string-transformer-provider} - :response {:default default-transformer-provider}} + {:transformers {:body {:default reitit.coercion.malli/default-transformer-provider + :formats {"application/json" reitit.coercion.malli/json-transformer-provider}} + :string {:default reitit.coercion.malli/string-transformer-provider} + :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 d57abae6..b0721609 100644 --- a/doc/development.md +++ b/doc/development.md @@ -13,25 +13,48 @@ ./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 documentation for [library authors](https://github.com/cljdoc/cljdoc/blob/master/doc/userguide/for-library-authors.adoc) -## To bump up version: +## Making a release We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes! [breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md ```bash +# Check that you're using Java 8! Making the release with a newer Java version +# means that it is broken when used with Java 8. +java -version + # new version ./scripts/set-version "1.0.0" -./scripts/lein-modules install + +# create a release commit and a tag +git add -u +git commit -m "Release 1.0.0" +git tag 1.0.0 # works +./scripts/lein-modules install lein test # deploy to clojars ./scripts/lein-modules do clean, deploy clojars + +# push the commit and the tag +git push +git push --tags ``` + +* Remembor to update the changelog! +* Announce the release at least on #reitit in Clojurians. diff --git a/doc/http/default_interceptors.md b/doc/http/default_interceptors.md index 7956ad6c..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.10"] +[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 09c2ee1d..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.10"] +[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 02117902..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.10"] +[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.10"] -; [metosin/reitit "0.5.10"] +; [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 ae3ff5ab..f886efb2 100644 --- a/doc/http/sieppari.md +++ b/doc/http/sieppari.md @@ -1,7 +1,7 @@ # Sieppari ```clj -[metosin/reitit-sieppari "0.5.10"] +[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 e521f9b7..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.10"] +[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/performance.md b/doc/performance.md index 38098f80..3609e05e 100644 --- a/doc/performance.md +++ b/doc/performance.md @@ -76,7 +76,7 @@ The routing sample taken from [bide](https://github.com/funcool/bide) README: (r/match-by-path routes "/workspace/1/1"))) ``` -Based on the [perf tests](https://github.com/metosin/reitit/tree/master/perf-test/clj/reitit/perf/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 18-110x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal). +Based on the [perf tests](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 18-110x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal). But, the example is too simple for any real benchmark. Also, some of the libraries always match on the `:request-method` too and by doing so, do more work than just match by path. Compojure does most work also by invoking the handler. @@ -146,6 +146,6 @@ Few things that have an effect on performance: * Wildcard-routes are an order of magnitude slower than static routes * Conflicting routes are served with LinearRouter, which is the slowest implementation. -* It's ok to mix non-wildcard, wildcard or even conflicting routes in a same routing tree. Reitit will create an hierarchy of routers to serve all the routes with best possible implementation. +* It's ok to mix non-wildcard, wildcard or even conflicting routes in a same routing tree. Reitit will create an hierarchy of routers to serve all the routes with best possible implementation. * Move computation from request processing time into creation time, using by compiling [middleware](ring/compiling_middleware.md), [interceptors](http/interceptors.md) and [route data](advanced/configuring_routers.md). * Unmounted middleware (or interceptor) is infinitely faster than a mounted one effectively doing nothing. diff --git a/doc/ring/RESTful_form_methods.md b/doc/ring/RESTful_form_methods.md index 6ab793b5..61f6bfd7 100644 --- a/doc/ring/RESTful_form_methods.md +++ b/doc/ring/RESTful_form_methods.md @@ -1,36 +1,37 @@ # RESTful form methods -When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms. +When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms. There is a pattern to solve this (pioneered by Rails) using a hidden "_method" field in the form and swapping out the "POST" method for whatever is in that field. -We can do this with middleware in reitit like this: +We can do this with middleware in reitit like this: ```clj (defn- hidden-method [request] - (keyword - (or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params - (get-in request [:multipart-params "_method"])))) ;; or in :multipart-params + (some-> (or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params + (get-in request [:multipart-params "_method"])) ;; or in :multipart-params + clojure.string/lower-case + keyword)) (def wrap-hidden-method {:name ::wrap-hidden-method :wrap (fn [handler] (fn [request] (if-let [fm (and (= :post (:request-method request)) ;; if this is a :post request - (hidden-method request))] ;; and there is a "_method" field + (hidden-method request))] ;; and there is a "_method" field (handler (assoc request :request-method fm)) ;; replace :request-method (handler request))))}) ``` -And apply the middleware like this: +And apply the middleware like this: ```clj (reitit.ring/ring-handler (reitit.ring/router ...) (reitit.ring/create-default-handler) - {:middleware + {:middleware [reitit.ring.middleware.parameters/parameters-middleware ;; needed to have :form-params in the request map reitit.ring.middleware.multipart/multipart-middleware ;; needed to have :multipart-params in the request map wrap-hidden-method]}) ;; our hidden method wrapper ``` -(NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`. +(NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`. This is so that our middleware is applied before reitit matches the request with a specific handler using the wrong method.) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index a8ddae6f..faed3a8b 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -39,7 +39,7 @@ Responses are defined in route data under `:responses` key. It's value should be Below is an example with [Plumatic Schema](https://github.com/plumatic/schema). It defines schemas for `:query`, `:body` and `:path` parameters and for http 200 response `:body`. -Handler can access the coerced parameters can be read under `:parameters` key in the request. +Handlers can access the coerced parameters via the `:parameters` key in the request. ```clj (require '[reitit.coercion.schema]) @@ -71,7 +71,7 @@ Defining a coercion for a route data doesn't do anything, as it's just data. We ### Full example -Here's an full example for applying coercion with Reitit, Ring and Schema: +Here is a full example for applying coercion with Reitit, Ring and Schema: ```clj (require '[reitit.ring.coercion :as rrc]) @@ -150,7 +150,7 @@ Invalid response: ## Pretty printing spec errors -Spec problems are exposed as-is into request & response coercion errors, enabling pretty-printers like [expound](https://github.com/bhb/expound) to be used: +Spec problems are exposed as is in request & response coercion errors. Pretty-printers like [expound](https://github.com/bhb/expound) can be enabled like this: ```clj (require '[reitit.ring :as ring]) @@ -216,7 +216,7 @@ Spec problems are exposed as-is into request & response coercion errors, enablin ### Optimizations -The coercion middleware are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined. +The coercion middlewares are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined. We can query the compiled middleware chain for the routes: diff --git a/doc/ring/compiling_middleware.md b/doc/ring/compiling_middleware.md index 1e100f3b..f9404cc9 100644 --- a/doc/ring/compiling_middleware.md +++ b/doc/ring/compiling_middleware.md @@ -1,12 +1,12 @@ # Compiling Middleware -The [dynamic extensions](dynamic_extensions.md) is a easy way to extend the system. To enable fast lookups into route data, we can compile them into any shape (records, functions etc.) we want, enabling fast access at request-time. +The [dynamic extensions](dynamic_extensions.md) are an easy way to extend the system. To enable fast lookup of route data, we can compile them into any shape (records, functions etc.), enabling fast access at request-time. -But, we can do much better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. Middleware can also decide not to mount itself by returning `nil`. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it? +But, we can do much better. As we know the exact route that a middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: Extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. A middleware can also decide not to mount itself by returning `nil`. (E.g. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?) To enable this we use [middleware records](data_driven_middleware.md) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?IntoMiddleware`. -To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:compile`. +To demonstrate the two approaches, below is the response coercion middleware written as normal ring middleware function and as middleware record with `:compile`. ## Normal Middleware diff --git a/doc/ring/content_negotiation.md b/doc/ring/content_negotiation.md index 90605ad7..fb696c10 100644 --- a/doc/ring/content_negotiation.md +++ b/doc/ring/content_negotiation.md @@ -1,8 +1,8 @@ # Content Negotiation -Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content-negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emit's [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration. +Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emits [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration. -Negotiates a request body based on `Content-Type` header and response body based on `Accept`, `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request. +Negotiates a request body based on `Content-Type` header and response body based on `Accept` and `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request. Decodes the request body into `:body-params` using the `:muuntaja/request` key in request if the `:body-params` doesn't already exist. @@ -87,7 +87,7 @@ Server: Jetty(9.2.21.v20170120) ## Changing default parameters -The current JSON formatter used by `reitit` already have the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista) +The current JSON formatter used by `reitit` already has the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista) ```clj @@ -102,7 +102,7 @@ The current JSON formatter used by `reitit` already have the option to parse key Now you should change the `m/instance` installed in the router with the `new-muuntaja-instance`. -You can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and [EDN]. +Here you can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and EDN. ## Adding custom encoder @@ -125,14 +125,14 @@ The example below is from `muuntaja` explaining how to add a custom encoder to p ``` -## Adding all together +## Putting it all together -If you inspect `m/default-options` it's only a map, therefore you can compose your new muuntaja instance with as many options as you need it. +If you inspect `m/default-options` you'll find it's only a map. This means you can compose your new muuntaja instance with as many options as you need. ```clj (def new-muuntaja (m/create (-> m/default-options (assoc-in [:formats "application/json" :decoder-opts :bigdecimals] true) - (assoc-in [:formats "application/json" :encoder-opts :data-format] "yyyy-MM-dd")))) + (assoc-in [:formats "application/json" :encoder-opts :date-format] "yyyy-MM-dd")))) ``` diff --git a/doc/ring/data_driven_middleware.md b/doc/ring/data_driven_middleware.md index e11be838..2d310ff4 100644 --- a/doc/ring/data_driven_middleware.md +++ b/doc/ring/data_driven_middleware.md @@ -1,19 +1,19 @@ # Data-driven Middleware -Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It's relatively easy to understand and enables good performance. Downside is that the middleware-chain is just a opaque function, making things like debugging and composition hard. It's too easy to apply the middleware in wrong order. +Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It is relatively easy to understand and allows for good performance. A downside is that the middleware chain is just a opaque function, making things like debugging and composition hard. It is too easy to apply the middlewares in wrong order. Reitit defines middleware as data: -1. Middleware can be defined as first-class data entries -2. Middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware) -4. Middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint -3. Middleware chain can be transformed by the router +1. A middleware can be defined as first-class data entries +2. A middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middlewares) +4. A middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint +3. A middleware chain can be transformed by the router ## Middleware as data -All values in the `:middleware` vector in the route data are expanded into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed. +All values in the `:middleware` vector of route data are expanded into `reitit.middleware/Middleware` Records by using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed. -Records can have arbitrary keys, but the following keys have a special purpose: +Records can have arbitrary keys, but the following keys have special purpose: | key | description | | ---------------|-------------| @@ -22,13 +22,13 @@ Records can have arbitrary keys, but the following keys have a special purpose: | `:wrap` | The actual middleware function of `handler & args => request => response` | `:compile` | Middleware compilation function, see [compiling middleware](compiling_middleware.md). -Middleware Records are accessible in their raw form in the compiled route results, thus available for inventories, creating api-docs etc. +Middleware Records are accessible in their raw form in the compiled route results, and thus are available for inventories, creating api-docs, etc. For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty. ### Creating Middleware -The following produce identical middleware runtime function. +The following examples produce identical middleware runtime functions. ### Function @@ -77,7 +77,7 @@ The following produce identical middleware runtime function. :handler handler}}]]))) ``` -All the middleware are applied correctly: +All the middlewares are applied correctly: ```clj (app {:request-method :get, :uri "/api/ping"}) @@ -86,7 +86,7 @@ All the middleware are applied correctly: ## Compiling middleware -Middleware can be optimized against an endpoint using [middleware compilation](compiling_middleware.md). +Middlewares can be optimized against an endpoint using [middleware compilation](compiling_middleware.md). ## Ideas for the future diff --git a/doc/ring/default_handler.md b/doc/ring/default_handler.md index 40fda56b..890dcb9c 100644 --- a/doc/ring/default_handler.md +++ b/doc/ring/default_handler.md @@ -1,6 +1,6 @@ # Default handler -By default, if no routes match, `nil` is returned, which is not valid response in Ring: +By default, if no routes match, `nil` is returned, which is not a valid response in Ring: ```clj (require '[reitit.ring :as ring]) diff --git a/doc/ring/default_middleware.md b/doc/ring/default_middleware.md index fda4aa42..ea9274aa 100644 --- a/doc/ring/default_middleware.md +++ b/doc/ring/default_middleware.md @@ -1,10 +1,10 @@ # Default Middleware ```clj -[metosin/reitit-middleware "0.5.10"] +[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. +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. * [Parameter Handling](#parameters-handling) * [Exception Handling](#exception-handling) @@ -17,7 +17,7 @@ Any Ring middleware can be used with `reitit-ring`, but using data-driven middle `reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps `ring.middleware.params/wrap-params`. -**NOTE**: will be factored into two parts: a query-parameters middleware and a Muuntaja format responsible for the the `application/x-www-form-urlencoded` body format. +**NOTE**: This middleware will be factored into two parts: a query-parameters middleware and a Muuntaja format responsible for the the `application/x-www-form-urlencoded` body format. cf. https://github.com/metosin/reitit/issues/134 ## Exception Handling @@ -32,7 +32,7 @@ See [Content Negotiation](content_negotiation.md). Wrapper for [Ring Multipart Middleware](https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj). Emits swagger `:consumes` definitions automatically. Expected route data: - + | key | description | | -------------|-------------| | `[:parameters :multipart]` | mounts only if defined for a route. diff --git a/doc/ring/dynamic_extensions.md b/doc/ring/dynamic_extensions.md index 3f922317..1308dde1 100644 --- a/doc/ring/dynamic_extensions.md +++ b/doc/ring/dynamic_extensions.md @@ -1,8 +1,8 @@ # Dynamic Extensions -`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad-hoc extensions to the system. +`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad hoc extensions to the system. -Example middleware to guard routes based on user roles: +This example shows a middleware to guard routes based on user roles: ```clj (require '[reitit.ring :as ring]) diff --git a/doc/ring/exceptions.md b/doc/ring/exceptions.md index 916ddf9c..0ba1321b 100644 --- a/doc/ring/exceptions.md +++ b/doc/ring/exceptions.md @@ -1,10 +1,10 @@ # Exception Handling with Ring ```clj -[metosin/reitit-middleware "0.5.10"] +[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 practise is a have an top-level exception handler to log and format the errors for clients. +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. ```clj (require '[reitit.ring.middleware.exception :as exception]) @@ -36,7 +36,7 @@ A preconfigured middleware using `exception/default-handlers`. Catches: ### `exception/create-exception-middleware` -Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or a Exception Class. +Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or an Exception Class. The following handlers are available by default: @@ -55,7 +55,7 @@ The handler is selected from the options map by exception identifier in the foll 2) Class of exception 3) `:type` ancestors of exception ex-data 4) Super Classes of exception -5) The ::default handler +5) The `::default` handler ```clj ;; type hierarchy @@ -94,7 +94,7 @@ The handler is selected from the options map by exception identifier in the foll (def app (ring/ring-handler (ring/router - ["/fail" (fn [_] (throw (ex-info "fail" {:type ::failue})))] + ["/fail" (fn [_] (throw (ex-info "fail" {:type ::failure})))] {:data {:middleware [exception-middleware]}}))) (app {:request-method :get, :uri "/fail"}) @@ -102,6 +102,6 @@ The handler is selected from the options map by exception identifier in the foll ; => {:status 500, ; :body {:message "default" ; :exception clojure.lang.ExceptionInfo -; :data {:type :user/failue} +; :data {:type :user/failure} ; :uri "/fail"}} ``` diff --git a/doc/ring/ring.md b/doc/ring/ring.md index d7f2bf57..daf4b0a6 100644 --- a/doc/ring/ring.md +++ b/doc/ring/ring.md @@ -5,14 +5,14 @@ Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts). ```clj -[metosin/reitit-ring "0.5.10"] +[metosin/reitit-ring "0.5.18"] ``` ## `reitit.ring/ring-router` `ring-router` is a higher order router, which adds support for `:request-method` based routing, [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers) and [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware). - - It accepts the following options: + +It accepts the following options: | key | description | | ----------------------------------------|-------------| @@ -53,7 +53,7 @@ Given a `ring-router`, optional default-handler & options, `ring-handler` functi | key | description | | ------------------|-------------| -| `:middleware` | Optional sequence of middleware that wrap the ring-handler" +| `:middleware` | Optional sequence of middlewares that wrap the ring-handler | `:inject-match?` | Boolean to inject `match` into request under `:reitit.core/match` key (default true) | `:inject-router?` | Boolean to inject `router` into request under `:reitit.core/router` key (default true) @@ -91,7 +91,7 @@ The router can be accessed via `get-router`: # Request-method based routing -Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found. +Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found. By default, the `:options` route is generated for all paths - to enable thing like [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing). @@ -196,7 +196,7 @@ Top-level middleware, applied before any routing is done: (ring/router ["/api" {:middleware [[mw :api]]} ["/get" {:get handler}]]) - nil + nil {:middleware [[mw :top]]})) (app {:request-method :get, :uri "/api/get"}) diff --git a/doc/ring/static.md b/doc/ring/static.md index 10a7b03c..058f3199 100644 --- a/doc/ring/static.md +++ b/doc/ring/static.md @@ -1,8 +1,12 @@ # Static Resources (Clojure Only) -Static resources can be served using `reitit.ring/create-resource-handler`. It takes optionally an options map and returns a ring handler to serve files from Classpath. +Static resources can be served by using the following two functions: -There are two options to serve the files. +* `reitit.ring/create-resource-handler`, which returns a Ring handler that serves files from classpath, and +* `reitit.ring/create-file-handler`, which returns a Ring handler that servers files from file system + +There are two ways to mount the handlers. +The examples below use `reitit.ring/create-resource-handler`, but `reitit.ring/create-file-handler` works the same way. ## Internal routes @@ -33,7 +37,9 @@ To serve static files with conflicting routes, e.g. `"/*"`, one needs to disable ## External routes -A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve them from the default-handler. One can compose multiple default locations using `ring-handler`. This way, they are only served if none of the actual routes have matched. +A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve them from the default-handler. +One can compose multiple default locations using `reitit.ring/ring-handler`. +This way, they are only served if none of the actual routes have matched. ```clj (ring/ring-handler @@ -46,21 +52,19 @@ A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve the ## Configuration -`reitit.ring/create-resource-handler` takes optionally an options map to configure how the files are being served. +`reitit.ring/create-file-handler` and `reitit.ring/create-resource-handler` take optionally an options map to configure how the files are being served. -| key | description | -| -----------------|-------------| +| key | description | +| -------------------|-------------| | :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:` | :root | optional resource root, defaults to `\"public\"` -| :path | optional path to mount the handler to. Works only if mounted outside of a router. +| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router. | :loader | optional class loader to resolve the resources | :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]` | :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found) - ### TODO * support for things like `:cache`, `:etag`, `:last-modified?`, and `:gzip` * support for ClojureScript -* serve from file-system diff --git a/doc/ring/swagger.md b/doc/ring/swagger.md index d5767445..719a0611 100644 --- a/doc/ring/swagger.md +++ b/doc/ring/swagger.md @@ -1,7 +1,7 @@ # Swagger Support ``` -[metosin/reitit-swagger "0.5.10"] +[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. @@ -45,10 +45,10 @@ 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.10"] +[metosin/reitit-swagger-ui "0.5.18"] ``` -`reitit.swagger-ui/create-swagger-ui-hander` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options: +`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: | key | description | | -----------------|-------------| diff --git a/doc/ring/transforming_middleware_chain.md b/doc/ring/transforming_middleware_chain.md index 2e2f630f..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.10"] +[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/README.md b/examples/README.md new file mode 100644 index 00000000..27134eb3 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,50 @@ +# Examples + +## buddy-auth +## frontend-auth +## frontend-controllers +## frontend-links +## frontend-prompt +## frontend-re-frame +## frontend +## http-swagger + +Coercion with Spec and Swagger generation. + +Same as ring-spec-swagger? + +Async examples as extra. + +## http + +Async example. + +## just-coercion-with-ring + +Bad name. + +Coercion example for spec, data-spec, Schema. +No Swagger generation or Malli! + +Same as ring-example? + +## pedestal-swagger +## pedestal +## ring-example + +Coercion example for spec, data-spec, Schema. +No Swagger generation or Malli! + +## ring-integrant + +## ring-malli-swagger + +Coercion with Malli and Swagger generation. + +## ring-spec-swagger + +Coercion with Spec and Swagger generation. + +## ring-swagger + +Coercion with Spec and Swagger generation. Same as previous! diff --git a/examples/buddy-auth/project.clj b/examples/buddy-auth/project.clj index bf138da6..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.10"] + [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 9530b9eb..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.10"] - [metosin/reitit-schema "0.5.10"] - [metosin/reitit-frontend "0.5.10"] + [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 9530b9eb..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.10"] - [metosin/reitit-schema "0.5.10"] - [metosin/reitit-frontend "0.5.10"] + [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 5a6c27d1..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.10"] - [metosin/reitit-spec "0.5.10"] - [metosin/reitit-frontend "0.5.10"] + [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 5a6c27d1..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.10"] - [metosin/reitit-spec "0.5.10"] - [metosin/reitit-frontend "0.5.10"] + [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 d9ec8190..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.10"] + [metosin/reitit "0.5.18"] [reagent "0.8.1"] [re-frame "0.10.6"]] diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs index 49d2c12e..dcd3774a 100644 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs @@ -7,6 +7,14 @@ [reitit.frontend.controllers :as rfc] [reitit.frontend.easy :as rfe])) +;;; Effects ;;; + +;; Triggering navigation from events. + +(re-frame/reg-fx :push-state + (fn [route] + (apply rfe/push-state route))) + ;;; Events ;;; (re-frame/reg-event-db ::initialize-db @@ -16,7 +24,7 @@ {:current-route nil}))) (re-frame/reg-event-fx ::push-state - (fn [db [_ & route]] + (fn [_ [_ & route]] {:push-state route})) (re-frame/reg-event-db ::navigated @@ -49,14 +57,6 @@ [:div [:h1 "This is sub-page 2"]]) -;;; Effects ;;; - -;; Triggering navigation from events. - -(re-frame/reg-fx :push-state - (fn [route] - (apply rfe/push-state route))) - ;;; Routes ;;; (defn href diff --git a/examples/frontend/project.clj b/examples/frontend/project.clj index 5a60dfdd..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.10"] - [metosin/reitit-spec "0.5.10"] - [metosin/reitit-frontend "0.5.10"] + [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 25057cdd..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.10"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/examples/http/project.clj b/examples/http/project.clj index b4e22fa4..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.10"]] + [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 2155066d..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.10"]]) + [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 f9110d2b..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.10"] - [metosin/reitit "0.5.10"]] + [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 f9110d2b..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.10"] - [metosin/reitit "0.5.10"]] + [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 fb69bb1e..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.10"]] + [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 1bec6a4a..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.10"] + [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 4fe31d1b..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.10"]] + [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-swagger/src/example/server.clj b/examples/ring-malli-swagger/src/example/server.clj index 1a5d7d89..bc9c3861 100644 --- a/examples/ring-malli-swagger/src/example/server.clj +++ b/examples/ring-malli-swagger/src/example/server.clj @@ -56,13 +56,25 @@ ["/plus" {:get {:summary "plus with malli query parameters" - :parameters {:query [:map [:x int?] [:y int?]]} + :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 int?] [:y int?]]} + :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 diff --git a/examples/ring-spec-swagger/project.clj b/examples/ring-spec-swagger/project.clj index b68e1205..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.10"]] + [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/src/example/server.clj b/examples/ring-spec-swagger/src/example/server.clj index 69615800..31ac2bc0 100644 --- a/examples/ring-spec-swagger/src/example/server.clj +++ b/examples/ring-spec-swagger/src/example/server.clj @@ -13,6 +13,7 @@ ; [reitit.ring.middleware.dev :as dev] ; [reitit.ring.spec :as spec] ; [spec-tools.spell :as spell] + [spec-tools.core :as st] [ring.adapter.jetty :as jetty] [muuntaja.core :as m] [clojure.spec.alpha :as s] @@ -25,7 +26,12 @@ (s/def ::size int?) (s/def ::file-response (s/keys :req-un [::name ::size])) -(s/def ::x int?) +;; Use data-specs to provide extra JSON-Schema properties: +;; https://github.com/metosin/spec-tools/blob/master/docs/04_json_schema.md#annotated-specs +(s/def ::x (st/spec {:spec int? + :name "X parameter" + :description "Description for X parameter" + :json-schema/default 42})) (s/def ::y int?) (s/def ::total int?) (s/def ::math-request (s/keys :req-un [::x ::y])) diff --git a/examples/ring-swagger/project.clj b/examples/ring-swagger/project.clj index 20bbd75e..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.10"]] + [metosin/reitit "0.5.18"]] :repl-options {:init-ns example.server}) diff --git a/examples/ring-swagger/src/example/server.clj b/examples/ring-swagger/src/example/server.clj index 8416c2b4..58d01810 100644 --- a/examples/ring-swagger/src/example/server.clj +++ b/examples/ring-swagger/src/example/server.clj @@ -11,8 +11,6 @@ [reitit.ring.middleware.parameters :as parameters] ;; Uncomment to use ; [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])) @@ -53,13 +51,15 @@ ["/plus" {:get {:summary "plus with spec query parameters" - :parameters {:query {:x int?, :y int?}} + :parameters {:query {:x int? + :y int?}} :responses {200 {:body {:total int?}}} :handler (fn [{{{:keys [x y]} :query} :parameters}] {:status 200 :body {:total (+ x y)}})} :post {:summary "plus with spec body parameters" - :parameters {:body {:x int?, :y int?}} + :parameters {:body {:x int? + :y int?}} :responses {200 {:body {:total int?}}} :handler (fn [{{{:keys [x y]} :body} :parameters}] {:status 200 @@ -80,7 +80,8 @@ ;; encoding response body muuntaja/format-response-middleware ;; exception handling - exception/exception-middleware + (exception/create-exception-middleware + {::exception/default (partial exception/wrap-log-to-console exception/default-handler)}) ;; decoding request body muuntaja/format-request-middleware ;; coercing response bodys 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 f48602c3..9903a3c7 100644 --- a/modules/reitit-core/project.clj +++ b/modules/reitit-core/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-core "0.5.10" +(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..4436214c 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -41,36 +41,44 @@ :header (->ParameterCoercion :headers :string true true) :path (->ParameterCoercion :path-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 +91,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 +106,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 +117,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 +155,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 cc902295..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,18 +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 "&"))) - -(defmacro goog-extend [type base-type ctor & methods] - `(do - (def ~type (fn ~@ctor)) - - (goog/inherits ~type ~base-type) - - ~@(map - (fn [method] - `(set! (.. ~type -prototype ~(symbol (str "-" (first method)))) - (fn ~@(rest method)))) - methods))) diff --git a/modules/reitit-core/src/reitit/interceptor.cljc b/modules/reitit-core/src/reitit/interceptor.cljc index b6bfade9..9b544638 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])) @@ -37,36 +37,36 @@ (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 :cljs function) (into-interceptor [this data opts] (into-interceptor - {:name ::handler - ::handler this - :enter (fn [ctx] - (assoc ctx :response (this (:request ctx))))} - data opts)) + {:name ::handler + ::handler this + :enter (fn [ctx] + (assoc ctx :response (this (:request ctx))))} + data opts)) #?(:clj clojure.lang.PersistentArrayMap :cljs cljs.core.PersistentArrayMap) @@ -86,13 +86,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 [_ _ _])) @@ -122,9 +122,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 @@ -132,8 +132,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 @@ -155,8 +155,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 bbd1aded..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))) @@ -167,30 +167,30 @@ (instance? CatchAll path) (assoc-in node [:catch-all path] (-node {:params params, :data data})) - (str/blank? path) + (empty? path) (-insert node ps fp params data) :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,59 +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]] - (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 2e472cf3..65da7e02 100644 --- a/modules/reitit-dev/project.clj +++ b/modules/reitit-dev/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-dev "0.5.10" +(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 36fb5cd6..9860511f 100644 --- a/modules/reitit-frontend/project.clj +++ b/modules/reitit-frontend/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-frontend "0.5.10" +(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..d1e9c50a 100644 --- a/modules/reitit-frontend/src/reitit/frontend.cljs +++ b/modules/reitit-frontend/src/reitit/frontend.cljs @@ -22,18 +22,28 @@ (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) + 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 (coerce! match) + {:path (:path-params match) + :query q})] + (assoc match :parameters parameters)))))) (defn match-by-name "Given a router, route name and optionally path-parameters, @@ -64,11 +74,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 94a1789a..8a76b900 100644 --- a/modules/reitit-frontend/src/reitit/frontend/easy.cljs +++ b/modules/reitit-frontend/src/reitit/frontend/easy.cljs @@ -10,7 +10,8 @@ ;; Differences: ;; This one automatically removes previous event listeners. -(defn start! +(defn ^{:see-also ["reitit.frontend.history/start!"]} + start! "This registers event listeners on HTML5 history and hashchange events. Automatically removes previous event listeners so it is safe to call this repeatedly, for example when using @@ -37,30 +38,66 @@ (when (nil? @history) (reset! history this)) (on-navigate m this)) - opts)) + opts)) -(defn href - ([k] - (rfh/href @history k nil nil)) - ([k params] - (rfh/href @history k params nil)) - ([k params query] - (rfh/href @history k params query))) +(defn + ^{:see-also ["reitit.frontend.history/href"]} + href + "Generate a URL for a route defined by name, with given path-params and query-params. -(defn push-state - "Sets the new route, leaving previous route in history." - ([k] - (rfh/push-state @history k nil nil)) - ([k params] - (rfh/push-state @history k params nil)) - ([k params query] - (rfh/push-state @history k params query))) + The URL is formatted using Reitit frontend history handler, so using it with + anchor element href will correctly trigger route change event. -(defn replace-state - "Replaces current route. I.e. current route is not left on history." - ([k] - (rfh/replace-state @history k nil nil)) - ([k params] - (rfh/replace-state @history k params nil)) - ([k params query] - (rfh/replace-state @history k params query))) + Note: currently collections in query-parameters are encoded as field-value + pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them + differently, convert the collections to strings first." + ([name] + (rfh/href @history name nil nil)) + ([name path-params] + (rfh/href @history name path-params nil)) + ([name path-params query-params] + (rfh/href @history name path-params query-params))) + +(defn + ^{:see-also ["reitit.frontend.history/push-state"]} + push-state + "Updates the browser location and pushes new entry to the history stack using + URL built from a route defined by name, with given path-params and + query-params. + + Will also trigger on-navigate callback on Reitit frontend History handler. + + Note: currently collections in query parameters are encoded as field-value + pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them + differently, convert the collections to strings first. + + See also: + https://developer.mozilla.org/en-US/docs/Web/API/History/pushState" + ([name] + (rfh/push-state @history name nil nil)) + ([name path-params] + (rfh/push-state @history name path-params nil)) + ([name path-params query-params] + (rfh/push-state @history name path-params query-params))) + +(defn + ^{:see-also ["reitit.frontend.history/replace-state"]} + replace-state + "Updates the browser location and replaces latest entry in the history stack + using URL built from a route defined by name, with given path-params and + query-params. + + Will also trigger on-navigate callback on Reitit frontend History handler. + + Note: currently collections in query-parameters are encoded as field-value + pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them + differently, convert the collections to strings first. + + See also: + https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState" + ([name] + (rfh/replace-state @history name nil nil)) + ([name path-params] + (rfh/replace-state @history name path-params nil)) + ([name path-params query-params] + (rfh/replace-state @history name path-params query-params))) diff --git a/modules/reitit-frontend/src/reitit/frontend/history.cljs b/modules/reitit-frontend/src/reitit/frontend/history.cljs index 84ec5141..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) @@ -171,40 +171,73 @@ (map->FragmentHistory opts) (map->Html5History opts)))))) -(defn stop! [history] +(defn stop! + "Stops the given history handler, removing the event handlers." + [history] (if history (-stop history))) (defn href - ([history k] - (href history k nil)) - ([history k params] - (href history k params nil)) - ([history k params query] - (let [match (rf/match-by-name! (:router history) k params)] - (-href history (reitit/match->path match query))))) + "Generate a URL for a route defined by name, with given path-params and query-params. -(defn push-state - "Sets the new route, leaving previous route in history." - ([history k] - (push-state history k nil nil)) - ([history k params] - (push-state history k params nil)) - ([history k params query] - (let [match (rf/match-by-name! (:router history) k params) - path (reitit/match->path match query)] + The URL is formatted using Reitit frontend history handler, so using it with + anchor element href will correctly trigger route change event. + + Note: currently collections in query parameters are encoded as field-value + pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them + differently, convert the collections to strings first." + ([history name] + (href history name nil)) + ([history name path-params] + (href history name path-params nil)) + ([history name path-params query-params] + (let [match (rf/match-by-name! (:router history) name path-params)] + (-href history (reitit/match->path match query-params))))) + +(defn + ^{:see-also ["reitit.core/match->path"]} + push-state + "Updates the browser URL and pushes new entry to the history stack using + a route defined by name, with given path-params and query-params. + + Will also trigger on-navigate callback on Reitit frontend History handler. + + Note: currently collections in query-parameters are encoded as field-value + pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them + differently, convert the collections to strings first. + + See also: + https://developer.mozilla.org/en-US/docs/Web/API/History/pushState" + ([history name] + (push-state history name nil nil)) + ([history name path-params] + (push-state history name path-params nil)) + ([history name path-params query-params] + (let [match (rf/match-by-name! (:router history) name path-params) + path (reitit/match->path match query-params)] ;; pushState and replaceState don't trigger popstate event so call on-navigate manually (.pushState js/window.history nil "" (-href history path)) (-on-navigate history path)))) (defn replace-state - "Replaces current route. I.e. current route is not left on history." - ([history k] - (replace-state history k nil nil)) - ([history k params] - (replace-state history k params nil)) - ([history k params query] - (let [match (rf/match-by-name! (:router history) k params) - path (reitit/match->path match query)] + "Updates the browser location and replaces latest entry in the history stack + using URL built from a route defined by name, with given path-params and + query-params. + + Will also trigger on-navigate callback on Reitit frontend History handler. + + Note: currently collections in query-parameters are encoded as field-value + pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them + differently, convert the collections to strings first. + + See also: + https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState" + ([history name] + (replace-state history name nil nil)) + ([history name path-params] + (replace-state history name path-params nil)) + ([history name path-params query-params] + (let [match (rf/match-by-name! (:router history) name path-params) + path (reitit/match->path match query-params)] (.replaceState js/window.history nil "" (-href history path)) (-on-navigate history path)))) 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 e70e7f4b..968d74fa 100644 --- a/modules/reitit-http/project.clj +++ b/modules/reitit-http/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-http "0.5.10" +(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 fa4c26fc..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 @@ -89,7 +89,8 @@ default-interceptors (->> interceptors (map #(interceptor/into-interceptor % nil (r/options router)))) default-queue (interceptor/queue executor default-interceptors) - enrich-request (ring/create-enrich-request inject-match? inject-router?)] + enrich-request (ring/create-enrich-request inject-match? inject-router?) + enrich-default-request (ring/create-enrich-default-request inject-router?)] {:name ::router :enter (fn [{:keys [request] :as context}] (if-let [match (r/match-by-path router (:uri request))] @@ -101,7 +102,9 @@ context (assoc context :request request) queue (interceptor/queue executor (concat default-interceptors interceptors))] (interceptor/enqueue executor context queue)) - (interceptor/enqueue executor context default-queue))) + (let [request (enrich-default-request request router) + context (assoc context :request request)] + (interceptor/enqueue executor context default-queue)))) :leave (fn [context] (if-not (:response context) (assoc context :response (default-handler (:request context))) @@ -130,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 53a1f0b3..a4cbab4e 100644 --- a/modules/reitit-interceptors/project.clj +++ b/modules/reitit-interceptors/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-interceptors "0.5.10" +(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 f0a47a1d..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))) + (: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,17 +25,17 @@ 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) (error-handler error request)))) -(defn print! [^PrintWriter writer & more] +(defn print! [^Writer writer & more] (.write writer (str (str/join " " more) "\n"))) ;; @@ -68,7 +68,7 @@ (defn wrap-log-to-console [handler ^Throwable e {:keys [uri request-method] :as req}] (print! *out* (Instant/now) request-method (pr-str uri) "=>" (.getMessage e)) - (.printStackTrace e ^PrintWriter *out*) + (.printStackTrace e (PrintWriter. ^Writer *out*)) (handler e req)) ;; 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 8422ba04..049c01eb 100644 --- a/modules/reitit-malli/project.clj +++ b/modules/reitit-malli/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-malli "0.5.10" +(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 23ec65b9..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,15 +27,15 @@ (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))) (def default-transformer-provider (-provider nil)) -(defn- -coercer [schema type transformers f encoder {:keys [validate enabled options]}] +(defn- -coercer [schema type transformers f {:keys [validate enabled options]}] (if schema (let [->coercer (fn [t] (let [decoder (if t (m/decoder schema options t) identity) @@ -48,7 +49,6 @@ (-explain [_ value] (explainer value))))) {:keys [formats default]} (transformers type) default-coercer (->coercer default) - encode (or encoder (fn [value _format] value)) format-coercers (some->> (for [[f t] formats] [f (->coercer t)]) (filter second) (seq) (into {})) get-coercer (cond format-coercers (fn [format] (or (get format-coercers format) default-coercer)) default-coercer (constantly default-coercer))] @@ -62,18 +62,18 @@ 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] - (if-let [coercer (get-coercer format)] - (let [transformed (-decode coercer value)] + (let [transformed (-decode default-coercer value)] + (if-let [coercer (get-coercer format)] (if (-validate coercer transformed) - (encode transformed format) + (-encode coercer transformed) (let [error (-explain coercer transformed)] (coercion/map->CoercionError - (assoc error :transformed transformed))))) - value))))))) + (assoc error :transformed transformed)))) + value)))))))) ;; ;; swagger @@ -92,27 +92,31 @@ (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 ;; +;; TODO: this is much too compöex (def default-options {:transformers {:body {:default default-transformer-provider :formats {"application/json" json-transformer-provider}} :string {:default string-transformer-provider} - :response {:default default-transformer-provider}} + :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 @@ -132,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) @@ -142,44 +148,42 @@ (-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 nil opts)) + (-coercer (compile schema options) type transformers :decode opts)) (-response-coercer [_ schema] - (let [schema (compile schema options) - encoder (-coercer schema :body transformers :encode nil opts)] - (-coercer schema :response transformers :encode encoder opts))))))) + (-coercer (compile schema options) :response transformers :encode opts)))))) (def coercion (create default-options)) 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 94da4030..c28fd24b 100644 --- a/modules/reitit-middleware/project.clj +++ b/modules/reitit-middleware/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-middleware "0.5.10" +(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 92bbdec5..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 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) @@ -55,7 +55,7 @@ (catch Throwable e (on-exception handlers e request respond raise))))))) -(defn print! [^PrintWriter writer & more] +(defn print! [^Writer writer & more] (.write writer (str (str/join " " more) "\n"))) ;; @@ -88,7 +88,7 @@ (defn wrap-log-to-console [handler ^Throwable e {:keys [uri request-method] :as req}] (print! *out* (Instant/now) request-method (pr-str uri) "=>" (.getMessage e)) - (.printStackTrace e ^PrintWriter *out*) + (.printStackTrace e (PrintWriter. ^Writer *out*)) (handler e req)) ;; 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 182c97ab..fc2b0ee1 100644 --- a/modules/reitit-pedestal/project.clj +++ b/modules/reitit-pedestal/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-pedestal "0.5.10" +(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 e281e118..64b5cda2 100644 --- a/modules/reitit-ring/project.clj +++ b/modules/reitit-ring/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-ring "0.5.10" +(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 727536b4..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 @@ -195,7 +195,9 @@ root "public" index-files ["index.html"] paths (constantly nil) - not-found-handler (constantly {:status 404, :body "", :headers {}})}}] + not-found-handler (if path + (constantly nil) + (constantly {:status 404, :body "", :headers {}}))}}] (let [options {:root root :loader loader :index-files? false @@ -220,11 +222,12 @@ (recur files)))))) handler (if path (fn [request] - (let [uri (:uri request)] - (if (.startsWith uri path) - (path-or-index-response (subs uri path-size) uri)))) + (let [uri (impl/url-decode (:uri request))] + (if (str/starts-with? uri path) + (or (path-or-index-response (subs uri path-size) uri) + (not-found-handler request))))) (fn [request] - (let [uri (:uri request) + (let [uri (impl/url-decode (:uri request)) path (-> request :path-params parameter)] (or (path-or-index-response path uri) (not-found-handler request)))))] @@ -238,7 +241,7 @@ | -------------------|-------------| | :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:` | :root | optional resource root, defaults to `\"public\"` - | :path | optional path to mount the handler to. Works only if mounted outside of a router. + | :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router. | :loader | optional class loader to resolve the resources | :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]` | :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)" @@ -255,7 +258,7 @@ | -------------------|-------------| | :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:` | :root | optional resource root, defaults to `\"public\"` - | :path | optional path to mount the handler to. Works only if mounted outside of a router. + | :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router. | :loader | optional class loader to resolve the resources | :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]` | :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)" @@ -315,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 46f4b778..de6f0d29 100644 --- a/modules/reitit-schema/project.clj +++ b/modules/reitit-schema/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-schema "0.5.10" +(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 1cc444df..bab5da48 100644 --- a/modules/reitit-sieppari/project.clj +++ b/modules/reitit-sieppari/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-sieppari "0.5.10" +(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 9d77d30d..602cf25c 100644 --- a/modules/reitit-spec/project.clj +++ b/modules/reitit-spec/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-spec "0.5.10" +(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 3cb49728..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.10" +(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 52ee5856..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 @@ -12,7 +12,7 @@ | :root | optional resource root, defaults to `\"swagger-ui\"` | :url | path to swagger endpoint, defaults to `/swagger.json` | :path | optional path to mount the handler to. Works only if mounted outside of a router. - | :config | parameters passed to swaggger-ui as-is. + | :config | parameters passed to swagger-ui as-is. See https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md for all available :config options. @@ -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 8edcc18f..06f59705 100644 --- a/modules/reitit-swagger/project.clj +++ b/modules/reitit-swagger/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-swagger "0.5.10" +(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 4a2a5319..0286565a 100644 --- a/modules/reitit-swagger/src/reitit/swagger.cljc +++ b/modules/reitit-swagger/src/reitit/swagger.cljc @@ -1,15 +1,16 @@ (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?) @@ -53,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?}} 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 7efe4136..632819e9 100644 --- a/modules/reitit/project.clj +++ b/modules/reitit/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit "0.5.10" +(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 a9b76084..edfe3e1b 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject metosin/reitit-parent "0.5.10" +(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.10"] - [metosin/reitit-core "0.5.10"] - [metosin/reitit-dev "0.5.10"] - [metosin/reitit-spec "0.5.10"] - [metosin/reitit-malli "0.5.10"] - [metosin/reitit-schema "0.5.10"] - [metosin/reitit-ring "0.5.10"] - [metosin/reitit-middleware "0.5.10"] - [metosin/reitit-http "0.5.10"] - [metosin/reitit-interceptors "0.5.10"] - [metosin/reitit-swagger "0.5.10"] - [metosin/reitit-swagger-ui "0.5.10"] - [metosin/reitit-frontend "0.5.10"] - [metosin/reitit-sieppari "0.5.10"] - [metosin/reitit-pedestal "0.5.10"] - [metosin/ring-swagger-ui "3.25.3"] - [metosin/spec-tools "0.10.4"] - [metosin/schema-tools "0.12.2"] - [metosin/muuntaja "0.6.7"] - [metosin/jsonista "0.2.7"] + :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.5"] [metosin/sieppari "0.0.0-alpha13"] - [metosin/malli "0.2.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.11.3"] - [com.fasterxml.jackson.core/jackson-databind "2.11.3"] + [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.23" :exclusions [org.clojure/core.rrb-vector]] - [expound "0.8.6"] + [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.8.2"] + [ring/ring-core "1.9.5"] - [io.pedestal/pedestal.service "0.5.8"]] + [io.pedestal/pedestal.service "0.5.10"]] - :plugins [[jonase/eastwood "0.3.11"] + :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.1"] - [lein-codox "0.10.7"] + [lein-cloverage "1.2.2"] + [lein-codox "0.10.8"] [metosin/bat-test "0.4.4"]] :profiles {:dev {:jvm-opts ^:replace ["-server"] @@ -77,41 +77,41 @@ :java-source-paths ["modules/reitit-core/java-src"] - :dependencies [[org.clojure/clojure "1.10.1"] - [org.clojure/clojurescript "1.10.597"] + :dependencies [[org.clojure/clojure "1.10.2"] + [org.clojure/clojurescript "1.10.773"] ;; modules dependencies - [metosin/schema-tools "0.12.2"] - [metosin/spec-tools "0.10.4"] - [metosin/muuntaja "0.6.7"] + [metosin/schema-tools "0.12.3"] + [metosin/spec-tools "0.10.5"] + [metosin/muuntaja "0.6.8"] [metosin/sieppari "0.0.0-alpha13"] - [metosin/jsonista "0.2.7"] - [metosin/malli "0.2.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.6"] - [fipp "0.6.23"] + [expound "0.9.0"] + [fipp "0.6.25"] - [orchestra "2020.09.18-1"] + [orchestra "2021.01.01-1"] - [ring "1.8.2"] + [ring "1.9.5"] [ikitommi/immutant-web "3.0.0-alpha1"] - [metosin/ring-http-response "0.9.1"] - [metosin/ring-swagger-ui "3.25.3"] + [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.0.0"] - [com.gfredericks/test.chuck "0.2.10"] + [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.8"] + [io.pedestal/pedestal.service "0.5.10"] - [org.clojure/core.async "1.3.610"] - [manifold "0.1.8"] - [funcool/promesa "6.0.0"] + [org.clojure/core.async "1.5.648"] + [manifold "0.2.3"] + [funcool/promesa "6.1.434"] - [com.clojure-goes-fast/clj-async-profiler "0.4.1"] + [com.clojure-goes-fast/clj-async-profiler "0.5.1"] [ring-cors "0.1.13"] [com.bhauman/rebel-readline "0.1.4"]]} @@ -121,18 +121,18 @@ "-Dclojure.compiler.direct-linking=true"] :test-paths ["perf-test/clj"] :dependencies [[compojure "1.6.2"] - [ring/ring-defaults "0.3.2"] + [ring/ring-defaults "0.3.3"] [ikitommi/immutant-web "3.0.0-alpha1"] - [io.pedestal/pedestal.service "0.5.8"] - [io.pedestal/pedestal.jetty "0.5.8"] - [calfpath "0.7.2"] - [org.clojure/core.async "1.3.610"] - [manifold "0.1.8"] - [funcool/promesa "6.0.0"] + [io.pedestal/pedestal.service "0.5.10"] + [io.pedestal/pedestal.jetty "0.5.10"] + [calfpath "0.8.1"] + [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"] - [ring/ring-defaults "0.3.2"] + [ring/ring-defaults "0.3.3"] [ataraxy "0.4.2"] [bidi "2.1.6"] [janus "1.3.2"]]} 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 86211927..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) @@ -41,3 +41,25 @@ (:io.pedestal.http/service-fn))] (is (= "ok" (:body (io.pedestal.test/response-for service :get "/ok")))) (is (= 500 (:status (io.pedestal.test/response-for service :get "/fail")))))) + +(deftest pedestal-inject-router-test + (let [check-router (fn [r] (when-not (:reitit.core/router r) + (throw (ex-info "Missing :reitit.core/router!" {})))) + interceptor {:name ::needs-router + :enter (fn [{:as context :keys [request]}] + (check-router request) + context)} + router (pedestal/routing-interceptor + (http/router + ["" + ["/ok" (fn [r] (check-router r) {:status 200, :body "ok"})]]) + nil + {:interceptors [interceptor]}) + service (-> {:io.pedestal.http/request-logger nil + :io.pedestal.http/routes []} + (io.pedestal.http/default-interceptors) + (pedestal/replace-last-interceptor router) + (io.pedestal.http/create-servlet) + (:io.pedestal.http/service-fn))] + (is (= "ok" (:body (io.pedestal.test/response-for service :get "/ok")))) + (is (= "Not Found" (:body (io.pedestal.test/response-for service :get "/not-existing")))))) 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 c1161931..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"))) @@ -121,6 +121,7 @@ :foo/bar "ei/toista/kertaa"}] (by-path "/olipa/kerran/avaruus/vaan/ei/toista/kertaa"))) (is (= [::html {:name "10"}] (by-path "/files/file-10.html"))) + (is (= [nil nil] (by-path "/files/file-..html"))) (is (= [::loru {:eskon "viitan", :saum "aa"}] (by-path "/viitan/aa/pium\u2215paum"))) (is (= [nil nil] (by-path "/ei/osu/pium/paum"))) (is (= [::emoji {:🌈 "brackets"}] (by-path "/brackets🤔/🎈"))) @@ -129,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/"))) @@ -168,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 @@ -215,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 @@ -280,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 @@ -333,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)] @@ -363,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" @@ -374,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])] @@ -405,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 @@ -421,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 11f32964..185db2db 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)) @@ -36,8 +36,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 @@ -74,9 +74,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)}}})] @@ -134,9 +134,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)} @@ -155,11 +155,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" @@ -179,8 +179,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"))) @@ -221,12 +221,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 3e38029e..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"} @@ -397,7 +479,7 @@ (testing "encoding errors" (let [app (->app {:encode-error (fn [error] {:errors (:humanized error)})})] (is (= {:status 400, :body {:errors {:x ["missing required key"]}}} - (app (assoc (->request "closed") :body-params {})))))) + (app (assoc (->request "closed") :body-params {})))))) (testing "when schemas are not closed and extra keys are not stripped" (let [app (->app {:compile (fn [v _] v) :strip-extra-keys false})] @@ -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 @@ -449,23 +531,72 @@ (testing "failed response" (let [{:keys [status body]} (app (->request [{:message "kosh"}]))] (is (= 500 status)) - (is (= :reitit.coercion/response-coercion (:type body)))))))))) + (is (= :reitit.coercion/response-coercion (:type body)))))))) + + (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}}))) + call (fn [accept total-schema] + ((->app total-schema) {:uri "/total" + :request-method :get + :muuntaja/request {:format "application/json"} + :muuntaja/response {:format accept} + :query-params {"x" "2"}}))] + + (testing "no encoding" + (is (= {:status 200, :body {:total +4}} (call "application/json" :int)))) + + (testing "json encoding" + (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 -}])))))) + + (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 5d3e872a..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)] @@ -471,164 +471,224 @@ (testing "from root" (let [app (ring/ring-handler - (ring/router - ["/*" (create nil)]) - (ring/create-default-handler))] - (testing test - (testing "different file-types" - (let [response (app (request "/hello.json"))] - (is (= "application/json" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) - (let [response (app (request "/hello.xml"))] - (is (= "text/xml" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body response)))))) + (ring/router + ["/*" (create nil)]) + (ring/create-default-handler))] + (testing "different file-types" + (let [response (app (request "/hello.json"))] + (is (= "application/json" (get-in response [:headers "Content-Type"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) + (let [response (app (request "/hello.xml"))] + (is (= "text/xml" (get-in response [:headers "Content-Type"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "file\n" (slurp (:body response)))))) - (testing "index-files" - (let [response (app (request "/docs"))] - (is (= (redirect "/docs/index.html") response))) - (let [response (app (request "/docs/"))] - (is (= (redirect "/docs/index.html") response)))) + (testing "with url decoding" + (let [response (app (request "/with%20space.txt"))] + (is (= 200 (:status response))) + (is (= "hello\n" (slurp (:body response)))))) - (testing "not found" - (let [response (app (request "/not-found"))] - (is (= 404 (:status response))))) + (testing "index-files" + (let [response (app (request "/docs"))] + (is (= (redirect "/docs/index.html") response))) + (let [response (app (request "/docs/"))] + (is (= (redirect "/docs/index.html") response)))) - (testing "3-arity" - (let [result (atom nil) - respond (partial reset! result) - raise ::not-called] - (app (request "/hello.xml") respond raise) - (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) - (is (get-in @result [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body @result))))))))) + (testing "not found" + (let [response (app (request "/not-found"))] + (is (= 404 (:status response))))) + + (testing "3-arity" + (let [result (atom nil) + respond (partial reset! result) + raise ::not-called] + (app (request "/hello.xml") respond raise) + (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) + (is (get-in @result [:headers "Last-Modified"])) + (is (= "file\n" (slurp (:body @result)))))))) (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 test - (testing "different file-types" - (let [response (app (request "/hello.json"))] - (is (= "application/json" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) - (let [response (app (request "/hello.xml"))] - (is (= "text/xml" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body response)))))) + (testing "different file-types" + (let [response (app (request "/hello.json"))] + (is (= "application/json" (get-in response [:headers "Content-Type"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) + (let [response (app (request "/hello.xml"))] + (is (= "text/xml" (get-in response [:headers "Content-Type"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "file\n" (slurp (:body response)))))) - (testing "index-files" - (let [response (app (request "/docs"))] - (is (= (redirect "/docs/index.html") response))) - (let [response (app (request "/docs/"))] - (is (= (redirect "/docs/index.html") response)))) + (testing "with url decoding" + (let [response (app (request "/with%20space.txt"))] + (is (= 200 (:status response))) + (is (= "hello\n" (slurp (:body response)))))) - (testing "not found" - (let [response (app (request "/not-found"))] - (is (= 404 (:status response))))) + (testing "index-files" + (let [response (app (request "/docs"))] + (is (= (redirect "/docs/index.html") response))) + (let [response (app (request "/docs/"))] + (is (= (redirect "/docs/index.html") response)))) - (testing "3-arity" - (let [result (atom nil) - respond (partial reset! result) - raise ::not-called] - (app (request "/hello.xml") respond raise) - (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) - (is (get-in @result [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body @result)))))))))) + (testing "not found" + (let [response (app (request "/not-found"))] + (is (= 404 (:status response))))) + + (testing "3-arity" + (let [result (atom nil) + respond (partial reset! result) + raise ::not-called] + (app (request "/hello.xml") respond raise) + (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) + (is (get-in @result [:headers "Last-Modified"])) + (is (= "file\n" (slurp (:body @result))))))))) (testing "outside a router" (testing "from root" (let [app (ring/ring-handler - (ring/router []) - (ring/routes - (create {:path "/"}) - (ring/create-default-handler)))] - (testing test - (testing "different file-types" - (let [response (app (request "/hello.json"))] - (is (= "application/json" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) - (let [response (app (request "/hello.xml"))] - (is (= "text/xml" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body response)))))) + (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"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) + (let [response (app (request "/hello.xml"))] + (is (= "text/xml" (get-in response [:headers "Content-Type"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "file\n" (slurp (:body response)))))) - (testing "index-files" - (let [response (app (request "/docs"))] - (is (= (redirect "/docs/index.html") response))) - (let [response (app (request "/docs/"))] - (is (= (redirect "/docs/index.html") response)))) + (testing "with url decoding" + (let [response (app (request "/with%20space.txt"))] + (is (= 200 (:status response))) + (is (= "hello\n" (slurp (:body response)))))) - (testing "not found" - (let [response (app (request "/not-found"))] - (is (= 404 (:status response))))) + (testing "index-files" + (let [response (app (request "/docs"))] + (is (= (redirect "/docs/index.html") response))) + (let [response (app (request "/docs/"))] + (is (= (redirect "/docs/index.html") response)))) - (testing "3-arity" - (let [result (atom nil) - respond (partial reset! result) - raise ::not-called] - (app (request "/hello.xml") respond raise) - (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) - (is (get-in @result [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body @result))))))))) + (testing "not found" + (let [response (app (request "/not-found"))] + (is (= 404 (:status response))) + (is (= "resource-handler" (:body response))))) + + (testing "3-arity" + (let [result (atom nil) + respond (partial reset! result) + raise ::not-called] + (app (request "/hello.xml") respond raise) + (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) + (is (get-in @result [:headers "Last-Modified"])) + (is (= "file\n" (slurp (:body @result)))))))) (testing "from path" (let [app (ring/ring-handler - (ring/router []) - (ring/routes - (create {:path "/files"}) - (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 test - (testing "different file-types" - (let [response (app (request "/hello.json"))] - (is (= "application/json" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) - (let [response (app (request "/hello.xml"))] - (is (= "text/xml" (get-in response [:headers "Content-Type"]))) - (is (get-in response [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body response)))))) + (testing "different file-types" + (let [response (app (request "/hello.json"))] + (is (= "application/json" (get-in response [:headers "Content-Type"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "{\"hello\": \"file\"}" (slurp (:body response))))) + (let [response (app (request "/hello.xml"))] + (is (= "text/xml" (get-in response [:headers "Content-Type"]))) + (is (get-in response [:headers "Last-Modified"])) + (is (= "file\n" (slurp (:body response)))))) - (testing "index-files" - (let [response (app (request "/docs"))] - (is (= (redirect "/docs/index.html") response))) - (let [response (app (request "/docs/"))] - (is (= (redirect "/docs/index.html") response)))) + (testing "with url decoding" + (let [response (app (request "/with%20space.txt"))] + (is (= 200 (:status response))) + (is (= "hello\n" (slurp (:body response)))))) - (testing "not found" - (let [response (app (request "/not-found"))] - (is (= 404 (:status response))))) + (testing "index-files" + (let [response (app (request "/docs"))] + (is (= (redirect "/docs/index.html") response))) + (let [response (app (request "/docs/"))] + (is (= (redirect "/docs/index.html") response)))) - (testing "3-arity" - (let [result (atom nil) - respond (partial reset! result) - raise ::not-called] - (app (request "/hello.xml") respond raise) - (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) - (is (get-in @result [:headers "Last-Modified"])) - (is (= "file\n" (slurp (:body @result))))))))))))))) + (testing "not found" + (let [response (app {:uri "/not-found" :request-method :get})] + (is (= 404 (:status response))) + (is (= "" (:body response)))) + (let [response (app {:uri "/files/not-found" :request-method :get})] + (is (= 404 (:status response))) + (is (= "resource-handler" (:body response))))) + + (testing "3-arity" + (let [result (atom nil) + respond (partial reset! result) + raise ::not-called] + (app (request "/hello.xml") respond raise) + (is (= "text/xml" (get-in @result [:headers "Content-Type"]))) + (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"})] + + (doseq [[name create] [["resource-handler" ring/create-resource-handler] + ["file-handler" #(ring/create-file-handler (assoc % :root "dev-resources/public"))]]] + (testing (str "for " name) + (testing "inside a router" + (let [create-app (fn [handler] + (ring/ring-handler + (ring/router + ["/files/*" handler])))] + (testing "not-found-handler not set" + (let [app (create-app (create nil))] + (is (nil? (app (request "/not-found")))) + (is (= "" (:body (app (request "/files/not-found"))))))) + + (testing "not-found-handler set" + (let [app (create-app (create {:not-found-handler not-found-handler}))] + (is (nil? (app (request "/not-found")))) + (is (= "not-found-handler" (:body (app (request "/files/not-found"))))))))) + + (testing "outside a router" + (let [create-app (fn [handler] + (ring/ring-handler + (ring/router []) + handler))] + (testing "not-found-handler not set" + (let [app (create-app (create {:path "/files"}))] + (is (nil? (app (request "/not-found")))) + (is (nil? (app (request "/files/not-found")))))) + + (testing "not-found-handler set" + (let [app (create-app (create {:path "/files" :not-found-handler not-found-handler}))] + (is (nil? (app (request "/not-found")))) + (is (= "not-found-handler" (:body (app (request "/files/not-found")))))))))))))) (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 @@ -636,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 c141a489..96dfed23 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -1,26 +1,26 @@ (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" @@ -52,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 @@ -305,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"] @@ -329,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"} @@ -339,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"} @@ -352,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"} @@ -367,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 36ba52e4..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 @@ -121,4 +121,11 @@ (trie/compile) (trie/path-matcher)) "/a")] (is (record? (:params match))) - (is (= (trie/->Match {:a "a"} {:a 1}) (update match :params (partial into {}))))))) + (is (= (trie/->Match {:a "a"} {:a 1}) (update match :params (partial into {})))))) + + (testing "space as separator" + (is (= (trie/->Match {:arg1 "say" :arg2 "hello"} ::cmd1) + ((-> (trie/insert nil "/command1 {arg1} {arg2}" ::cmd1) + (trie/insert "/command2 {arg1} {arg2} {arg3}" ::cmd2) + (trie/compile) + (trie/path-matcher)) "/command1 say hello"))))) diff --git a/test/cljs/reitit/frontend/core_test.cljs b/test/cljs/reitit/frontend/core_test.cljs index 22ac1db2..e5c1d13a 100644 --- a/test/cljs/reitit/frontend/core_test.cljs +++ b/test/cljs/reitit/frontend/core_test.cljs @@ -80,6 +80,19 @@ (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"