Compare commits

..

No commits in common. "master" and "0.9.2" have entirely different histories.

87 changed files with 397 additions and 775 deletions

View file

@ -1,6 +1,5 @@
{;;:skip-comments true {;;:skip-comments true
:lint-as {potemkin/def-derived-map clojure.core/defrecord :lint-as {potemkin/def-derived-map clojure.core/defrecord}
clojure.test.check.clojure-test/defspec clojure.test/deftest}
:linters {:missing-else-branch {:level :off} :linters {:missing-else-branch {:level :off}
:unused-binding {:level :off} :unused-binding {:level :off}
:unused-referred-var {:exclude {clojure.test [deftest testing is are] :unused-referred-var {:exclude {clojure.test [deftest testing is are]

View file

@ -2,19 +2,16 @@ name: testsuite
on: on:
push: push:
branches: [master]
pull_request: pull_request:
branches: [master]
jobs: jobs:
build-clj: build-clj:
strategy: strategy:
matrix: matrix:
# Supported Java versions: LTS releases and latest # Supported Java versions: LTS releases and latest
jdk: [11, 17, 21, 25] jdk: [11, 17, 21]
clojure: [11, 12]
name: Clojure ${{ matrix.clojure }} (Java ${{ matrix.jdk }}) name: Clojure (Java ${{ matrix.jdk }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -39,18 +36,14 @@ jobs:
uses: DeLaGuardo/setup-clojure@13.1 uses: DeLaGuardo/setup-clojure@13.1
with: with:
lein: 2.9.5 lein: 2.9.5
clj-kondo: 2025.12.23
# Install openapi-schema-validator for openapi-tests # Install openapi-schema-validator for openapi-tests
# Uses node version from the ubuntu-latest # Uses node version from the ubuntu-latest
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Run Linter
run: ./lint.sh
- name: Run tests - name: Run tests
run: ./scripts/test.sh clj${{ matrix.clojure }} run: ./scripts/test.sh clj
build-cljs: build-cljs:
name: ClojureScript name: ClojureScript

View file

@ -12,27 +12,6 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md [breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
## UNRELEASED
* **FIX** redirect-trailing-slash-handler won't make external redirects. [#776](https://github.com/metosin/reitit/pull/776)
* Allow colons in bracket parameter syntax. [#770](https://github.com/metosin/reitit/pull/770)
* Add `url-encode?` option to `match-by-name`. [#778](https://github.com/metosin/reitit/pull/778)
## 0.10.0 (2026-01-09)
* Improve & document how response schemas get picked in per-content-type coercion. See [docs](./doc/ring/coercion.md#per-content-type-coercion). [#745](https://github.com/metosin/reitit/issues/745).
* **BREAKING** Remove unused `reitit.dependency` ns. [#763](https://github.com/metosin/reitit/pull/763)
* Support passing options to malli humanize. See [docs](./doc/coercion/malli_coercion.md). [#467](https://github.com/metosin/reitit/issues/467)
* **FIX** Handling of ex-type keyword hierarchies in create-exception-middleware. [#768](https://github.com/metosin/reitit/issues/768)
* Updated dependencies:
```
[metosin/malli "0.20.0"] is available but we use "0.19.2"
[com.fasterxml.jackson.core/jackson-core "2.20.1"] is available but we use "2.20.0"
[com.fasterxml.jackson.core/jackson-databind "2.20.1"] is available but we use "2.20.0"
[org.clojure/core.rrb-vector "0.2.1"] is available but we use "0.2.0"
```
## 0.9.2 (2025-10-28) ## 0.9.2 (2025-10-28)
* Allow multimethods as handlers when validating [#755](https://github.com/metosin/reitit/pull/755) * Allow multimethods as handlers when validating [#755](https://github.com/metosin/reitit/pull/755)

View file

@ -66,14 +66,14 @@ modules will continue to be released under `metosin` for compatibility purposes.
All main modules bundled: All main modules bundled:
```clj ```clj
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
``` ```
Optionally, the parts can be required separately. Optionally, the parts can be required separately.
Reitit requires Clojure 1.11 and Java 11. Reitit requires Clojure 1.11 and Java 11.
Reitit is tested with the LTS releases Java 11, 17, 21 and 25 Reitit is tested with the LTS releases Java 11, 17 and 21.
## Quick start ## Quick start

View file

@ -41,7 +41,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
All bundled: All bundled:
```clj ```clj
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
``` ```
Optionally, the parts can be required separately. Optionally, the parts can be required separately.

View file

@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
## Pretty Errors ## Pretty Errors
```clj ```clj
[metosin/reitit-dev "0.10.0"] [metosin/reitit-dev "0.9.2"]
``` ```
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. 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.

View file

@ -75,17 +75,6 @@ Path-parameters are automatically coerced into strings, with the help of (curren
; :path-params {:id "1"}} ; :path-params {:id "1"}}
``` ```
In case you want to do something like generate a template path for documentation, you can disable url-encoding:
```clj
(r/match-by-name router ::user {:id "<id goes here>"} {:url-encode? false})
; #reitit.core.Match{:template "/api/user/:id"
; :data {:name :user/user}
; :path "/api/user/<id goes here>"
; :result nil
; :path-params {:id "<id goes here>"}}
```
There is also an exception throwing version: There is also an exception throwing version:
```clj ```clj
@ -108,5 +97,5 @@ It can take an optional map of query-parameters too:
(-> router (-> router
(r/match-by-name ::user {:id 1}) (r/match-by-name ::user {:id 1})
(r/match->path {:iso "möly"})) (r/match->path {:iso "möly"}))
; "/api/user/1?iso=m%C3%B6ly" ; "/api/user/1?iso=m%C3%B6ly"
``` ```

View file

@ -96,29 +96,3 @@ Using `create` with options to create the coercion instead of `coercion`:
;; malli options ;; malli options
:options nil}) :options nil})
``` ```
## Configuring humanize error messages
Malli humanized error messages can be configured using `:options :errors`:
```clj
(reitit.coercion.malli/create
{:options
{:errors (assoc malli.error/default-errors
:malli.core/missing-key {:error/message {:en "MISSING"}})}})
```
See the malli docs for more info.
## Custom registry
Malli registry can be configured conveniently via `:options :registry`:
```clj
(require '[malli.core :as m])
(reitit.coercion.malli/create
{:options
{:registry {:registry (merge (m/default-schemas)
{:my-type :string})}}})
```

View file

@ -1,7 +1,7 @@
# Default Interceptors # Default Interceptors
```clj ```clj
[metosin/reitit-interceptors "0.10.0"] [metosin/reitit-interceptors "0.9.2"]
``` ```
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors. Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.

View file

@ -5,7 +5,7 @@ Reitit has also support for [interceptors](http://pedestal.io/reference/intercep
## Reitit-http ## Reitit-http
```clj ```clj
[metosin/reitit-http "0.10.0"] [metosin/reitit-http "0.9.2"]
``` ```
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. 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.

View file

@ -3,7 +3,7 @@
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal. [Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
```clj ```clj
[metosin/reitit-pedestal "0.10.0"] [metosin/reitit-pedestal "0.9.2"]
``` ```
Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)? 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 ```clj
; [io.pedestal/pedestal.service "0.5.5"] ; [io.pedestal/pedestal.service "0.5.5"]
; [io.pedestal/pedestal.jetty "0.5.5"] ; [io.pedestal/pedestal.jetty "0.5.5"]
; [metosin/reitit-pedestal "0.10.0"] ; [metosin/reitit-pedestal "0.9.2"]
; [metosin/reitit "0.10.0"] ; [metosin/reitit "0.9.2"]
(require '[io.pedestal.http :as server]) (require '[io.pedestal.http :as server])
(require '[reitit.pedestal :as pedestal]) (require '[reitit.pedestal :as pedestal])

View file

@ -1,7 +1,7 @@
# Sieppari # Sieppari
```clj ```clj
[metosin/reitit-sieppari "0.10.0"] [metosin/reitit-sieppari "0.9.2"]
``` ```
[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). [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).

View file

@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor
### Printing Context Diffs ### Printing Context Diffs
```clj ```clj
[metosin/reitit-interceptors "0.10.0"] [metosin/reitit-interceptors "0.9.2"]
``` ```
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: 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:

View file

@ -63,6 +63,8 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
{:status 200 {:status 200
:body {:total total}}))}) :body {:total total}}))})
``` ```
### Nested parameter definitions ### Nested parameter definitions
Parameters are accumulated recursively along the route tree, just like Parameters are accumulated recursively along the route tree, just like
@ -92,26 +94,6 @@ handling for merging eg. malli `:map` schemas.
; [:task-id :int]]} ; [:task-id :int]]}
``` ```
### Differences in behaviour for different parameters
All parameter coercions *except* `:body`:
1. Allow keys outside the schema (by opening up the schema using eg. `malli.util/open-schema`)
2. Keywordize the keys (ie. header & query parameter names) of the input before coercing
In contrast, the `:body` coercion:
1. Uses the specified schema
* depending on the coercion, it can be configured as open or closed, see specific coercion docs for details
2. Does not keywordize the keys of the input before coercion
* however, coercions like malli might do the keywordization when coercing json bodies, depending on configuration
This admittedly confusing behaviour is retained currently due to
backwards compatibility reasons. It can be configured by passing
option `:reitit.coercion/parameter-coercion` to `reitit.ring/router`
or `reitit.coercion/compile-request-coercers`. See also:
`reitit.coercion/default-parameter-coercion`.
## Coercion Middleware ## Coercion Middleware
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`: Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`:
@ -220,11 +202,9 @@ is:
"application/edn" {:schema {:x s/Int}} "application/edn" {:schema {:x s/Int}}
:default {:schema {:ww s/Int}}}}} :default {:schema {:ww s/Int}}}}}
:handler ...}}]] :handler ...}}]]
{:data {:muuntaja muuntaja.core/instance {:data {:middleware [rrc/coerce-exceptions-middleware
:middleware [reitit.ring.middleware.muuntaja/format-middleware rrc/coerce-request-middleware
reitit.ring.coercion/coerce-exceptions-middleware rrc/coerce-response-middleware]}})))
reitit.ring.coercion/coerce-request-middleware
reitit.ring.coercion/coerce-response-middleware]}})))
``` ```
The resolution logic for response coercers is: The resolution logic for response coercers is:
@ -235,17 +215,6 @@ The resolution logic for response coercers is:
3. `:body` 3. `:body`
3. If nothing was found, do not coerce 3. If nothing was found, do not coerce
To select the response content-type, you can either:
1. Let muuntaja pick the content-type based on things like the request Accept header
- This is what most users want
2. Set `:muuntaja/content-type` in the response to pick an explicit content type
3. Set the `"Content-Type"` header in the response
- This disables muuntaja, so you need to encode your response body in some other way!
- This is not compatible with response schema checking, since coercion won't know what to do with the already-encoded response body.
4. Use the `:extract-response-format` option to inject your own logic. See `reitit.coercion/extract-response-format-default` for the default.
See also the [muuntaja content negotiation](./content_negotiation.md) docs.
## Pretty printing spec errors ## Pretty printing spec errors
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: 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:

View file

@ -1,7 +1,7 @@
# Default Middleware # Default Middleware
```clj ```clj
[metosin/reitit-middleware "0.10.0"] [metosin/reitit-middleware "0.9.2"]
``` ```
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.

View file

@ -1,7 +1,7 @@
# Exception Handling with Ring # Exception Handling with Ring
```clj ```clj
[metosin/reitit-middleware "0.10.0"] [metosin/reitit-middleware "0.9.2"]
``` ```
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. 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.

View file

@ -5,7 +5,7 @@
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts). Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
```clj ```clj
[metosin/reitit-ring "0.10.0"] [metosin/reitit-ring "0.9.2"]
``` ```
## `reitit.ring/router` ## `reitit.ring/router`

View file

@ -1,7 +1,7 @@
# Swagger Support # Swagger Support
``` ```
[metosin/reitit-swagger "0.10.0"] [metosin/reitit-swagger "0.9.2"]
``` ```
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. 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.
@ -47,7 +47,7 @@ If you need to post-process the generated spec, just wrap the handler with a cus
[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module. [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.10.0"] [metosin/reitit-swagger-ui "0.9.2"]
``` ```
`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: `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:

View file

@ -59,7 +59,7 @@ There is an extra option in the Ring router (actually, in the underlying middlew
### Printing Request Diffs ### Printing Request Diffs
```clj ```clj
[metosin/reitit-middleware "0.10.0"] [metosin/reitit-middleware "0.9.2"]
``` ```
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: 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:

View file

@ -2,6 +2,6 @@
:description "Reitit Buddy Auth App" :description "Reitit Buddy Auth App"
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[buddy "2.0.0"]] [buddy "2.0.0"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -10,9 +10,9 @@
[ring "1.12.1"] [ring "1.12.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"] [org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/reitit-schema "0.10.0"] [metosin/reitit-schema "0.9.2"]
[metosin/reitit-frontend "0.10.0"] [metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"] [cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"] [cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match ;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"] [ring "1.12.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"] [org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/reitit-schema "0.10.0"] [metosin/reitit-schema "0.9.2"]
[metosin/reitit-frontend "0.10.0"] [metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"] [cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"] [cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match ;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"] [ring "1.12.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"] [org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/reitit-spec "0.10.0"] [metosin/reitit-spec "0.9.2"]
[metosin/reitit-frontend "0.10.0"] [metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"] [cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"] [cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match ;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"] [ring "1.12.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"] [org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/reitit-malli "0.10.0"] [metosin/reitit-malli "0.9.2"]
[metosin/reitit-frontend "0.10.0"] [metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"] [cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"] [cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match ;; Just for pretty printting the match

View file

@ -10,9 +10,9 @@
[ring "1.12.1"] [ring "1.12.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"] [org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/reitit-spec "0.10.0"] [metosin/reitit-spec "0.9.2"]
[metosin/reitit-frontend "0.10.0"] [metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"] [cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"] [cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match ;; Just for pretty printting the match

View file

@ -1,7 +1,7 @@
(defproject frontend-re-frame "0.1.0-SNAPSHOT" (defproject frontend-re-frame "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[org.clojure/clojurescript "1.11.132"] [org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[reagent "1.2.0"] [reagent "1.2.0"]
[re-frame "0.10.6"] [re-frame "0.10.6"]
[cljsjs/react "17.0.2-0"] [cljsjs/react "17.0.2-0"]

View file

@ -1,5 +1,6 @@
(ns frontend-re-frame.core (ns frontend-re-frame.core
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[reagent.dom :as rd] [reagent.dom :as rd]
[reitit.core :as r] [reitit.core :as r]
[reitit.coercion.spec :as rss] [reitit.coercion.spec :as rss]

View file

@ -10,9 +10,9 @@
[ring "1.12.1"] [ring "1.12.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.11.132"] [org.clojure/clojurescript "1.11.132"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/reitit-spec "0.10.0"] [metosin/reitit-spec "0.9.2"]
[metosin/reitit-frontend "0.10.0"] [metosin/reitit-frontend "0.9.2"]
[cljsjs/react "17.0.2-0"] [cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"] [cljsjs/react-dom "17.0.2-0"]
;; Just for pretty printting the match ;; Just for pretty printting the match

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[aleph "0.7.1"] [aleph "0.7.1"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"]] [metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -5,5 +5,5 @@
[funcool/promesa "11.0.678"] [funcool/promesa "11.0.678"]
[manifold "0.4.2"] [manifold "0.4.2"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"]] [metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -2,4 +2,4 @@
:description "Reitit coercion with vanilla ring" :description "Reitit coercion with vanilla ring"
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"]]) [metosin/reitit "0.9.2"]])

View file

@ -3,8 +3,7 @@
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"] [metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"] [metosin/ring-swagger-ui "5.9.0"]]
[org.slf4j/slf4j-simple "2.0.9"]]
:repl-options {:init-ns example.server} :repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}}) :profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -12,6 +12,7 @@
[reitit.ring.middleware.multipart :as multipart] [reitit.ring.middleware.multipart :as multipart]
[reitit.ring.middleware.parameters :as parameters] [reitit.ring.middleware.parameters :as parameters]
[ring.adapter.jetty :as jetty] [ring.adapter.jetty :as jetty]
[malli.core :as malli]
[muuntaja.core :as m])) [muuntaja.core :as m]))
(def Transaction (def Transaction
@ -51,34 +52,23 @@
{:get {:summary "Fetch a pizza | Multiple content-types, multiple examples" {:get {:summary "Fetch a pizza | Multiple content-types, multiple examples"
:responses {200 {:description "Fetch a pizza as json or EDN" :responses {200 {:description "Fetch a pizza as json or EDN"
:content {"application/json" {:schema [:map :content {"application/json" {:schema [:map
[:format [:enum :json]]
[:color :keyword] [:color :keyword]
[:pineapple :boolean]] [:pineapple :boolean]]
:examples {:white {:description "White pizza with pineapple" :examples {:white {:description "White pizza with pineapple"
:value {:format :json :value {:color :white
:color :white
:pineapple true}} :pineapple true}}
:red {:description "Red pizza" :red {:description "Red pizza"
:value {:format :json :value {:color :red
:color :red
:pineapple false}}}} :pineapple false}}}}
"application/edn" {:schema [:map "application/edn" {:schema [:map
[:format [:enum :edn]]
[:color :keyword] [:color :keyword]
[:pineapple :boolean]] [:pineapple :boolean]]
:examples {:red {:description "Red pizza with pineapple" :examples {:red {:description "Red pizza with pineapple"
:value (pr-str {:format :edn :color :red :pineapple true})}}}}}} :value (pr-str {:color :red :pineapple true})}}}}}}
:handler (fn [_request] :handler (fn [_request]
(rand-nth [{:status 200 {:status 200
:muuntaja/content-type "application/json" :body {:color :red
:body {:format :json :pineapple true}})}
:color :red
:pineapple true}}
{:status 200
:muuntaja/content-type "application/edn"
:body {:format :edn
:color :red
:pineapple true}}]))}
:post {:summary "Create a pizza | Multiple content-types, multiple examples | Default response schema" :post {:summary "Create a pizza | Multiple content-types, multiple examples | Default response schema"
:request {:description "Create a pizza using json or EDN" :request {:description "Create a pizza using json or EDN"
:content {"application/json" {:schema [:map :content {"application/json" {:schema [:map
@ -156,22 +146,22 @@
[:regex [:re "[0-9]+"]] [:regex [:re "[0-9]+"]]
[:enum [:enum 1 3 5 42]] [:enum [:enum 1 3 5 42]]
[:multi [:multi {:dispatch :type} [:multi [:multi {:dispatch :type}
["literal" [:map [:literal [:map
[:type [:= "literal"]] [:type [:= :literal]]
[:value [:or :int :string]]]] [:value [:or :int :string]]]]
["reference" [:map [:reference [:map
[:type [:= "reference"]] [:type [:= :reference]]
[:description :string] [:description :string]
[:ref :uuid]]]]]] [:ref :uuid]]]]]]
:example {:vector-of-tuples [["a" 1] ["b" 2]] :example {:vector-of-tuples [["a" 1] ["b" 2]]
:regex "01234" :regex "01234"
:enum 5 :enum 5
:multi {:type "literal" :multi {:type :literal
:value "x"}}}}} :value "x"}}}}}
:responses {200 {:content {:default {:schema [:map-of :keyword :any]}}}} :responses {200 {:content {:default {:schema [:map]}}}}
:handler (fn [request] :handler (fn [request]
{:status 200 {:status 200
:body (get-in request [:parameters :request])})}}] :body (:body request)})}}]
["/secure" ["/secure"
{:tags #{"secure"} {:tags #{"secure"}

View file

@ -3,7 +3,7 @@
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[io.pedestal/pedestal.service "0.6.3"] [io.pedestal/pedestal.service "0.6.3"]
[io.pedestal/pedestal.jetty "0.6.3"] [io.pedestal/pedestal.jetty "0.6.3"]
[metosin/reitit-malli "0.10.0"] [metosin/reitit-malli "0.9.2"]
[metosin/reitit-pedestal "0.10.0"] [metosin/reitit-pedestal "0.9.2"]
[metosin/reitit "0.10.0"]] [metosin/reitit "0.9.2"]]
:repl-options {:init-ns server}) :repl-options {:init-ns server})

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[io.pedestal/pedestal.service "0.6.3"] [io.pedestal/pedestal.service "0.6.3"]
[io.pedestal/pedestal.jetty "0.6.3"] [io.pedestal/pedestal.jetty "0.6.3"]
[metosin/reitit-pedestal "0.10.0"] [metosin/reitit-pedestal "0.9.2"]
[metosin/reitit "0.10.0"]] [metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[io.pedestal/pedestal.service "0.6.3"] [io.pedestal/pedestal.service "0.6.3"]
[io.pedestal/pedestal.jetty "0.6.3"] [io.pedestal/pedestal.jetty "0.6.3"]
[metosin/reitit-pedestal "0.10.0"] [metosin/reitit-pedestal "0.9.2"]
[metosin/reitit "0.10.0"]] [metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -2,5 +2,5 @@
:description "Reitit Ring App" :description "Reitit Ring App"
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"]] [metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -2,7 +2,7 @@
:description "Reitit Ring App with Integrant" :description "Reitit Ring App with Integrant"
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[integrant "0.8.1"]] [integrant "0.8.1"]]
:main example.server :main example.server
:repl-options {:init-ns user} :repl-options {:init-ns user}

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"] [metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"]] [metosin/reitit "0.9.2"]]
:repl-options {:init-ns example.server} :repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}}) :profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -3,7 +3,7 @@
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[metosin/jsonista "0.3.8"] [metosin/jsonista "0.3.8"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"]] [metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server} :repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}}) :profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -2,7 +2,7 @@
:description "Reitit Ring App with Swagger" :description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.11.2"] :dependencies [[org.clojure/clojure "1.11.2"]
[ring/ring-jetty-adapter "1.12.1"] [ring/ring-jetty-adapter "1.12.1"]
[metosin/reitit "0.10.0"] [metosin/reitit "0.9.2"]
[metosin/ring-swagger-ui "5.9.0"]] [metosin/ring-swagger-ui "5.9.0"]]
:repl-options {:init-ns example.server} :repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}}) :profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-core "0.10.0" (defproject metosin/reitit-core "0.9.2"
:description "Snappy data-driven router for Clojure(Script)" :description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -152,10 +152,8 @@
rcs (request-coercers coercion parameters (cond-> opts route-request (assoc ::skip #{:body})))] rcs (request-coercers coercion parameters (cond-> opts route-request (assoc ::skip #{:body})))]
(if (and crc rcs) (into crc (vec rcs)) (or crc rcs))))) (if (and crc rcs) (into crc (vec rcs)) (or crc rcs)))))
(defn extract-response-format-default [request response] (defn extract-response-format-default [request _]
(or (get-in response [:headers "Content-Type"]) (-> request :muuntaja/response :format))
(:muuntaja/content-type response)
(-> request :muuntaja/response :format)))
(defn -format->coercer [coercion {:keys [content body]} _opts] (defn -format->coercer [coercion {:keys [content body]} _opts]
(->> (concat (when body (->> (concat (when body
@ -244,7 +242,7 @@
(if coercer (if coercer
(let [result (coercer query-params :default)] (let [result (coercer query-params :default)]
(if (error? result) (if (error? result)
(throw (ex-info "Query parameters coercion failed" (throw (ex-info (str "Query parameters coercion failed")
result)) result))
result)) result))
query-params)))) query-params))))

View file

@ -46,7 +46,7 @@
(options [this]) (options [this])
(route-names [this]) (route-names [this])
(match-by-path [this path]) (match-by-path [this path])
(match-by-name [this name] [this name path-params] [this name path-params opts])) (match-by-name [this name] [this name path-params]))
(defn router? [x] (defn router? [x]
(satisfies? Router x)) (satisfies? Router x))
@ -122,11 +122,9 @@
(match-by-name [_ name] (match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match nil))) (match nil)))
(match-by-name [r name path-params] (match-by-name [_ name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match (impl/path-params path-params opts)))))))) (match (impl/path-params path-params))))))))
(defn lookup-router (defn lookup-router
"Creates a lookup-router from resolved routes and optional "Creates a lookup-router from resolved routes and optional
@ -163,11 +161,9 @@
(match-by-name [_ name] (match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match nil))) (match nil)))
(match-by-name [r name path-params] (match-by-name [_ name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match (impl/path-params path-params opts)))))))) (match (impl/path-params path-params))))))))
(defn trie-router (defn trie-router
"Creates a special prefix-tree router from resolved routes and optional "Creates a special prefix-tree router from resolved routes and optional
@ -212,11 +208,9 @@
(match-by-name [_ name] (match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match nil))) (match nil)))
(match-by-name [r name path-params] (match-by-name [_ name path-params]
(match-by-name r name path-params nil))
(match-by-name [_ name path-params opts]
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match (impl/path-params path-params opts)))))))) (match (impl/path-params path-params))))))))
(defn single-static-path-router (defn single-static-path-router
"Creates a fast router of 1 static route(s) and optional "Creates a fast router of 1 static route(s) and optional
@ -244,10 +238,8 @@
(if (#?(:clj .equals :cljs =) p path) match)) (if (#?(:clj .equals :cljs =) p path) match))
(match-by-name [_ name] (match-by-name [_ name]
(if (= n name) match)) (if (= n name) match))
(match-by-name [r name path-params] (match-by-name [_ name path-params]
(match-by-name r name path-params nil)) (if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params))))))))
(match-by-name [_ name path-params opts]
(if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params opts))))))))
(defn mixed-router (defn mixed-router
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for "Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
@ -276,11 +268,9 @@
(match-by-name [_ name] (match-by-name [_ name]
(or (match-by-name static-router name) (or (match-by-name static-router name)
(match-by-name wildcard-router name))) (match-by-name wildcard-router name)))
(match-by-name [r name path-params] (match-by-name [_ name path-params]
(match-by-name r name path-params nil)) (or (match-by-name static-router name path-params)
(match-by-name [_ name path-params opts] (match-by-name wildcard-router name path-params)))))))
(or (match-by-name static-router name path-params opts)
(match-by-name wildcard-router name path-params opts)))))))
(defn quarantine-router (defn quarantine-router
"Creates two routers: [[mixed-router]] for non-conflicting routes "Creates two routers: [[mixed-router]] for non-conflicting routes
@ -309,11 +299,9 @@
(match-by-name [_ name] (match-by-name [_ name]
(or (match-by-name mixed-router name) (or (match-by-name mixed-router name)
(match-by-name linear-router name))) (match-by-name linear-router name)))
(match-by-name [r name path-params] (match-by-name [_ name path-params]
(match-by-name r name path-params nil)) (or (match-by-name mixed-router name path-params)
(match-by-name [_ name path-params opts] (match-by-name linear-router name path-params)))))))
(or (match-by-name mixed-router name path-params opts)
(match-by-name linear-router name path-params opts)))))))
;; ;;
;; Creating Routers ;; Creating Routers

View file

@ -0,0 +1,52 @@
(ns reitit.dependency
"Dependency resolution for middleware/interceptors."
(:require [reitit.exception :as exception]))
(defn- providers
"Map from provision key to provider. `get-provides` should return the provision keys of a dependent."
[get-provides nodes]
(reduce (fn [acc dependent]
(into acc
(map (fn [provide]
(when (contains? acc provide)
(exception/fail!
(str "multiple providers for: " provide)
{::multiple-providers provide}))
[provide dependent]))
(get-provides dependent)))
{} nodes))
(defn- get-provider
"Get the provider for `k`, throw if no provider can be found for it."
[providers k]
(if (contains? providers k)
(get providers k)
(exception/fail!
(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.
`get-provides` and `get-requires` are callbacks that you can provide to compute the provide and depend
key sets of nodes, the defaults are `:provides` and `:requires`."
([nodes] (post-order :provides :requires nodes))
([get-provides get-requires nodes]
(let [providers-by-key (providers get-provides nodes)]
(letfn [(toposort [node path colors]
(case (get colors node)
:white (let [requires (get-requires node)
[nodes* colors] (toposort-seq (map (partial get-provider providers-by-key) requires)
(conj path node)
(assoc colors node :grey))]
[(conj nodes* node)
(assoc colors node :black)])
:grey (exception/fail! "circular dependency" {:cycle (drop-while #(not= % node) (conj path node))})
:black [() colors]))
(toposort-seq [nodes path colors]
(reduce (fn [[nodes* colors] node]
(let [[nodes** colors] (toposort node path colors)]
[(into nodes* nodes**) colors]))
[[] colors] nodes))]
(first (toposort-seq nodes [] (zipmap nodes (repeat :white))))))))

View file

@ -7,10 +7,6 @@
([type data] ([type data]
(throw (ex-info (str type) {:type type, :data data})))) (throw (ex-info (str type) {:type type, :data data}))))
(defn unsupported-protocol-method!
[method]
(fail! :unsupported-protocol-method {:method method}))
(defn get-message [e] (defn get-message [e]
#?(:clj (.getMessage ^Exception e) :cljs (ex-message e))) #?(:clj (.getMessage ^Exception e) :cljs (ex-message e)))

View file

@ -1,6 +1,7 @@
(ns ^:no-doc reitit.impl (ns ^:no-doc reitit.impl
#?(:cljs (:require-macros [reitit.impl])) #?(:cljs (:require-macros [reitit.impl]))
(:require [clojure.string :as str] (:require [clojure.set :as set]
[clojure.string :as str]
[meta-merge.core :as mm] [meta-merge.core :as mm]
[reitit.exception :as ex] [reitit.exception :as ex]
[reitit.trie :as trie]) [reitit.trie :as trie])
@ -297,11 +298,8 @@
(defn path-params (defn path-params
"Convert parameters' values into URL-encoded strings, suitable for URL paths" "Convert parameters' values into URL-encoded strings, suitable for URL paths"
([params] (path-params params nil)) [params]
([params {:keys [url-encode?] :or {url-encode? true}}] (maybe-map-values #(url-encode (into-string %)) params))
(if url-encode?
(maybe-map-values #(url-encode (into-string %)) params)
(maybe-map-values #(into-string %) params))))
(defn- query-parameter [k v] (defn- query-parameter [k v]
(str (form-encode (into-string k)) (str (form-encode (into-string k))

View file

@ -71,17 +71,11 @@
(and bracket? (= \{ c)) (and bracket? (= \{ c))
(let [^long to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))] (let [^long to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))]
(cond (if (= \* (get s (inc to)))
(= \* (get s (inc to)))
(recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to'))) (recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to')))
(= \: (get s (inc to)))
(recur (concat ss (-static from to) (-wild (inc to) to')) (long (inc to')) (long (inc to')))
:else
(recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to'))))) (recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to')))))
(and colon? (= \: c) (not= \{ (get s (dec to)))) (and colon? (= \: c))
(let [^long to' (or (str/index-of s "/" to) (count s))] (let [^long to' (or (str/index-of s "/" to) (count s))]
(if (= 1 (- to' to)) (if (= 1 (- to' to))
(recur ss from (inc to)) (recur ss from (inc to))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-dev "0.10.0" (defproject metosin/reitit-dev "0.9.2"
:description "Snappy data-driven router for Clojure(Script)" :description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -174,7 +174,7 @@
;; TODO: this is hack, but seems to work and is safe. ;; TODO: this is hack, but seems to work and is safe.
(defn source-str [[target _ file line]] (defn source-str [[target _ file line]]
(try (try
(if (not= 1 line) (if (and (not= 1 line))
(let [file-name (str/replace file #"(.*?)\.\S[^\.]+" "$1") (let [file-name (str/replace file #"(.*?)\.\S[^\.]+" "$1")
target-name (name target) target-name (name target)
ns (str (subs target-name 0 (or (str/index-of target-name (str file-name "$")) 0)) file-name)] ns (str (subs target-name 0 (or (str/index-of target-name (str file-name "$")) 0)) file-name)]
@ -190,7 +190,7 @@
(color :title message " ") (color :title message " ")
(color :title-dark (repeat-str "-" between) " ") (color :title-dark (repeat-str "-" between) " ")
(color :title source) " " (color :title source) " "
(color :title-dark "--")])) (color :title-dark (str "--"))]))
(defn footer [{:keys [width]}] (defn footer [{:keys [width]}]
(color :title-dark (repeat-str "-" width))) (color :title-dark (repeat-str "-" width)))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-frontend "0.10.0" (defproject metosin/reitit-frontend "0.9.2"
:description "Reitit: Clojurescript frontend routing core" :description "Reitit: Clojurescript frontend routing core"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-http "0.10.0" (defproject metosin/reitit-http "0.9.2"
:description "Reitit: HTTP routing with interceptors" :description "Reitit: HTTP routing with interceptors"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -22,12 +22,12 @@
compile (fn [[path data] opts scope] compile (fn [[path data] opts scope]
(interceptor/compile-result [path data] opts scope)) (interceptor/compile-result [path data] opts scope))
->endpoint (fn [p d m s] ->endpoint (fn [p d m s]
(let [d (ring/-compile-coercion d) (let [d (ring/-compile-coercion d)]
compiled (compile [p d] opts s)] (let [compiled (compile [p d] opts s)]
(-> compiled (-> compiled
(map->Endpoint) (map->Endpoint)
(assoc :path p) (assoc :path p)
(assoc :method m)))) (assoc :method m)))))
->methods (fn [any? data] ->methods (fn [any? data]
(reduce (reduce
(fn [acc method] (fn [acc method]

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-interceptors "0.10.0" (defproject metosin/reitit-interceptors "0.9.2"
:description "Reitit, common interceptors bundled" :description "Reitit, common interceptors bundled"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-malli "0.10.0" (defproject metosin/reitit-malli "0.9.2"
:description "Reitit: Malli coercion" :description "Reitit: Malli coercion"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -188,8 +188,7 @@
(-open-model [_ schema] schema) (-open-model [_ schema] schema)
(-encode-error [_ error] (-encode-error [_ error]
(cond-> error (cond-> error
(show? :humanized) (assoc :humanized (me/humanize error (cond-> {:wrap :message} (show? :humanized) (assoc :humanized (me/humanize error {:wrap :message}))
options (merge options))))
(show? :schema) (update :schema edn/write-string opts) (show? :schema) (update :schema edn/write-string opts)
(show? :errors) (-> (me/with-error-messages opts) (show? :errors) (-> (me/with-error-messages opts)
(update :errors (partial map #(update % :schema edn/write-string opts)))) (update :errors (partial map #(update % :schema edn/write-string opts))))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-middleware "0.10.0" (defproject metosin/reitit-middleware "0.9.2"
:description "Reitit, common middleware bundled" :description "Reitit, common middleware bundled"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -19,17 +19,17 @@
(recur (.getSuperclass sk) (conj ks sk)) (recur (.getSuperclass sk) (conj ks sk))
ks))) ks)))
(defn- find-closest-ancestor [val m] (defn- descendants-safe [type]
(or (get m val) (when-not (class? type) (descendants type)))
(some #(find-closest-ancestor % m) (parents val))))
(defn- call-error-handler [handlers error request] (defn- call-error-handler [handlers error request]
(let [type (:type (ex-data error)) (let [type (:type (ex-data error))
ex-class (class error) ex-class (class error)
error-handler (or (get handlers type) error-handler (or (get handlers type)
(get handlers ex-class) (get handlers ex-class)
(when-not (class? type) (some
(find-closest-ancestor type handlers)) (partial get handlers)
(descendants-safe type))
(some (some
(partial get handlers) (partial get handlers)
(super-classes ex-class)) (super-classes ex-class))
@ -143,9 +143,6 @@
4) Super Classes of exception 4) Super Classes of exception
5) The ::default handler 5) The ::default handler
Note! If the closest ancestor for `:type` is not unique, an
arbitrary one is picked.
Example: Example:
(require '[reitit.ring.middleware.exception :as exception]) (require '[reitit.ring.middleware.exception :as exception])

View file

@ -1,4 +1,4 @@
(defproject fi.metosin/reitit-openapi "0.10.0" (defproject fi.metosin/reitit-openapi "0.9.2"
:description "Reitit: OpenAPI-support" :description "Reitit: OpenAPI-support"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-pedestal "0.10.0" (defproject metosin/reitit-pedestal "0.9.2"
:description "Reitit + Pedestal Integration" :description "Reitit + Pedestal Integration"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -2,10 +2,10 @@
(:require [io.pedestal.http :as http] (:require [io.pedestal.http :as http]
[io.pedestal.interceptor :as interceptor] [io.pedestal.interceptor :as interceptor]
[io.pedestal.interceptor.chain :as chain] [io.pedestal.interceptor.chain :as chain]
[reitit.exception :as ex]
[reitit.http] [reitit.http]
[reitit.interceptor]) [reitit.interceptor])
(:import (java.lang.reflect Method))) (:import (java.lang.reflect Method)
(reitit.interceptor Executor)))
;; TODO: variadic ;; TODO: variadic
(defn- arities [f] (defn- arities [f]
@ -46,16 +46,12 @@
(def pedestal-executor (def pedestal-executor
(reify (reify
reitit.interceptor/Executor Executor
(queue [_ interceptors] (queue [_ interceptors]
(->> interceptors (->> interceptors
(map (fn [{::interceptor/keys [handler] :as interceptor}] (map (fn [{::interceptor/keys [handler] :as interceptor}]
(or handler interceptor))) (or handler interceptor)))
(keep ->interceptor))) (keep ->interceptor)))
(execute [_ _ _]
(ex/unsupported-protocol-method! 'reitit.interceptor/execute))
(execute [_ _ _ _ _]
(ex/unsupported-protocol-method! 'reitit.interceptor/execute))
(enqueue [_ context interceptors] (enqueue [_ context interceptors]
(chain/enqueue context interceptors)))) (chain/enqueue context interceptors))))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-ring "0.10.0" (defproject metosin/reitit-ring "0.9.2"
:description "Reitit: Ring routing" :description "Reitit: Ring routing"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -173,8 +173,7 @@
(letfn [(maybe-redirect [{:keys [query-string] :as request} path] (letfn [(maybe-redirect [{:keys [query-string] :as request} path]
(if (and (seq path) (r/match-by-path (::r/router request) path)) (if (and (seq path) (r/match-by-path (::r/router request) path))
{:status (if (= (:request-method request) :get) 301 308) {:status (if (= (:request-method request) :get) 301 308)
:headers {"Location" (let [path (str/replace-first path #"^/+" "/")] ; Locations starting with // redirect to another hostname. Avoid these due to security implications. :headers {"Location" (if query-string (str path "?" query-string) path)}
(if query-string (str path "?" query-string) path))}
:body ""})) :body ""}))
(redirect-handler [request] (redirect-handler [request]
(let [uri (:uri request)] (let [uri (:uri request)]
@ -299,8 +298,7 @@
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly | :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash) | :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found) | :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
| :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types | :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types"
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to false"
([] ([]
(create-resource-handler nil)) (create-resource-handler nil))
([opts] ([opts]
@ -320,8 +318,7 @@
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly | :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash) | :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found) | :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
| :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types | :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types"
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to false"
([] ([]
(create-file-handler nil)) (create-file-handler nil))
([opts] ([opts]

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-schema "0.10.0" (defproject metosin/reitit-schema "0.9.2"
:description "Reitit: Plumatic Schema coercion" :description "Reitit: Plumatic Schema coercion"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-sieppari "0.10.0" (defproject metosin/reitit-sieppari "0.9.2"
:description "Reitit: Sieppari Interceptors" :description "Reitit: Sieppari Interceptors"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,6 +1,5 @@
(ns reitit.interceptor.sieppari (ns reitit.interceptor.sieppari
(:require [reitit.exception :as ex] (:require [reitit.interceptor :as interceptor]
[reitit.interceptor :as interceptor]
[sieppari.core :as sieppari] [sieppari.core :as sieppari]
[sieppari.queue :as queue])) [sieppari.queue :as queue]))
@ -16,6 +15,4 @@
(execute [_ interceptors request] (execute [_ interceptors request]
(sieppari/execute interceptors request)) (sieppari/execute interceptors request))
(execute [_ interceptors request respond raise] (execute [_ interceptors request respond raise]
(sieppari/execute interceptors request respond raise)) (sieppari/execute interceptors request respond raise))))
(enqueue [_ _ _]
(ex/unsupported-protocol-method! 'reitit.interceptor/enqueue))))

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-spec "0.10.0" (defproject metosin/reitit-spec "0.9.2"
:description "Reitit: clojure.spec coercion" :description "Reitit: clojure.spec coercion"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-swagger-ui "0.10.0" (defproject metosin/reitit-swagger-ui "0.9.2"
:description "Reitit: Swagger-ui support" :description "Reitit: Swagger-ui support"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-swagger "0.10.0" (defproject metosin/reitit-swagger "0.9.2"
:description "Reitit: Swagger-support" :description "Reitit: Swagger-support"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit "0.10.0" (defproject metosin/reitit "0.9.2"
:description "Snappy data-driven router for Clojure(Script)" :description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -80,15 +80,12 @@
(defrecord NoOpCoercion [] (defrecord NoOpCoercion []
coercion/Coercion coercion/Coercion
(-get-name [_] :no-op) (-get-name [_] :no-op)
(-get-options [_])
(-get-apidocs [_ _ {:keys [parameters responses] :as info}]) (-get-apidocs [_ _ {:keys [parameters responses] :as info}])
(-get-model-apidocs [_ _ _ _])
(-compile-model [_ model _] model) (-compile-model [_ model _] model)
(-open-model [_ spec] spec) (-open-model [_ spec] spec)
(-encode-error [_ error] error) (-encode-error [_ error] error)
(-request-coercer [_ type spec] (fn [value format] value)) (-request-coercer [_ type spec] (fn [value format] value))
(-response-coercer [this spec] (coercion/request-coercer this :response spec {})) (-response-coercer [this spec] (coercion/request-coercer this :response spec {})))
(-query-string-coercer [_ _]))
(comment (comment
(doseq [coercion [nil (->NoOpCoercion) spec/coercion]] (doseq [coercion [nil (->NoOpCoercion) spec/coercion]]

View file

@ -53,27 +53,27 @@
request (json-request data) request (json-request data)
request! (request-stream request)]] request! (request-stream request)]]
;; # 10b "10b"
;; 38µs (c-api 1.x) ;; 38µs (c-api 1.x)
;; 14µs (c-api 2.0.0-alpha21) ;; 14µs (c-api 2.0.0-alpha21)
;; 6µs ;; 6µs
;; # 100b "100b"
;; 74µs (c-api 1.x) ;; 74µs (c-api 1.x)
;; 16µs (c-api 2.0.0-alpha21) ;; 16µs (c-api 2.0.0-alpha21)
;; 8µs ;; 8µs
;; # 1k "1k"
;; 322µs (c-api 1.x) ;; 322µs (c-api 1.x)
;; 24µs (c-api 2.0.0-alpha21) ;; 24µs (c-api 2.0.0-alpha21)
;; 16µs ;; 16µs
;; # 10k "10k"
;; 3300µs (c-api 1.x) ;; 3300µs (c-api 1.x)
;; 120µs (c-api 2.0.0-alpha21) ;; 120µs (c-api 2.0.0-alpha21)
;; 110µs ;; 110µs
;; # 100k "100k"
;; 10600µs (c-api 1.x) ;; 10600µs (c-api 1.x)
;; 1100µs (c-api 2.0.0-alpha21) ;; 1100µs (c-api 2.0.0-alpha21)
;; 1100µs ;; 1100µs

View file

@ -37,40 +37,40 @@
(defn routing-test [] (defn routing-test []
;; 21385 / 14337 ;; 21385 / 14337
;; "barista" "barista"
;; 26259 / 25571 ;; 26259 / 25571
;; "choreographer" "choreographer"
;; 24277 / 19174 ;; 24277 / 19174
;; "clutch" "clutch"
;; 26158 / 25584 ;; 26158 / 25584
;; "connect" "connect"
;; 24614 / 25413 ;; 24614 / 25413
;; "escort" "escort"
;; 21979 / 18595 ;; 21979 / 18595
;; "express" "express"
;; 23123 / 25405 ;; 23123 / 25405
;; "find-my-way" "find-my-way"
;; 24798 / 25286 ;; 24798 / 25286
;; "http-hash" "http-hash"
;; 24215 / 23670 ;; 24215 / 23670
;; "i40" "i40"
;; 23561 / 26278 ;; 23561 / 26278
;; "light-router" "light-router"
;; 28362 / 30056 ;; 28362 / 30056
;; "http-raw" "http-raw"
;; 25310 / 25126 ;; 25310 / 25126
;; "regex" "regex"
;; 112719 / 113959 ;; 112719 / 113959
(title "reitit") (title "reitit")

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-parent "0.10.0" (defproject metosin/reitit-parent "0.9.2"
:description "Snappy data-driven router for Clojure(Script)" :description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"
@ -18,38 +18,38 @@
:url "https://github.com/metosin/reitit"} :url "https://github.com/metosin/reitit"}
;; Ring 1.13.1 drops support for Java 1.8 so lets target 11 ;; Ring 1.13.1 drops support for Java 1.8 so lets target 11
:javac-options ["-Xlint:unchecked" "-target" "11" "-source" "11"] :javac-options ["-Xlint:unchecked" "-target" "11" "-source" "11"]
:managed-dependencies [[metosin/reitit "0.10.0"] :managed-dependencies [[metosin/reitit "0.9.2"]
[metosin/reitit-core "0.10.0"] [metosin/reitit-core "0.9.2"]
[metosin/reitit-dev "0.10.0"] [metosin/reitit-dev "0.9.2"]
[metosin/reitit-spec "0.10.0"] [metosin/reitit-spec "0.9.2"]
[metosin/reitit-malli "0.10.0"] [metosin/reitit-malli "0.9.2"]
[metosin/reitit-schema "0.10.0"] [metosin/reitit-schema "0.9.2"]
[metosin/reitit-ring "0.10.0"] [metosin/reitit-ring "0.9.2"]
[metosin/reitit-middleware "0.10.0"] [metosin/reitit-middleware "0.9.2"]
[metosin/reitit-http "0.10.0"] [metosin/reitit-http "0.9.2"]
[metosin/reitit-interceptors "0.10.0"] [metosin/reitit-interceptors "0.9.2"]
[metosin/reitit-swagger "0.10.0"] [metosin/reitit-swagger "0.9.2"]
[fi.metosin/reitit-openapi "0.10.0"] [fi.metosin/reitit-openapi "0.9.2"]
[metosin/reitit-swagger-ui "0.10.0"] [metosin/reitit-swagger-ui "0.9.2"]
[metosin/reitit-frontend "0.10.0"] [metosin/reitit-frontend "0.9.2"]
[metosin/reitit-sieppari "0.10.0"] [metosin/reitit-sieppari "0.9.2"]
[metosin/reitit-pedestal "0.10.0"] [metosin/reitit-pedestal "0.9.2"]
[metosin/ring-swagger-ui "5.20.0"] [metosin/ring-swagger-ui "5.20.0"]
[metosin/spec-tools "0.10.8"] [metosin/spec-tools "0.10.7"]
[metosin/schema-tools "0.13.1"] [metosin/schema-tools "0.13.1"]
[metosin/muuntaja "0.6.11"] [metosin/muuntaja "0.6.11"]
[metosin/jsonista "0.3.13"] [metosin/jsonista "0.3.13"]
[metosin/sieppari "0.0.0-alpha13"] [metosin/sieppari "0.0.0-alpha13"]
[metosin/malli "0.20.0"] [metosin/malli "0.19.2"]
;; https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111 ;; https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111
[com.fasterxml.jackson.core/jackson-core "2.20.1"] [com.fasterxml.jackson.core/jackson-core "2.20.0"]
[com.fasterxml.jackson.core/jackson-databind "2.20.1"] [com.fasterxml.jackson.core/jackson-databind "2.20.0"]
[meta-merge "1.0.0"] [meta-merge "1.0.0"]
[fipp "0.6.29" :exclusions [org.clojure/core.rrb-vector]] [fipp "0.6.29" :exclusions [org.clojure/core.rrb-vector]]
;; Deep-diff uses this version, override olders versiom from fipp. ;; Deep-diff uses this version, override olders versiom from fipp.
[org.clojure/core.rrb-vector "0.2.1"] [org.clojure/core.rrb-vector "0.2.0"]
[expound "0.9.0"] [expound "0.9.0"]
[lambdaisland/deep-diff "0.0-47"] [lambdaisland/deep-diff "0.0-47"]
[com.bhauman/spell-spec "0.1.2"] [com.bhauman/spell-spec "0.1.2"]
@ -67,8 +67,7 @@
[lein-codox "0.10.8"] [lein-codox "0.10.8"]
[metosin/bat-test "0.4.4"]] [metosin/bat-test "0.4.4"]]
:profiles {:clj11 {:dependencies [[org.clojure/clojure "1.11.4"]]} :profiles {:dev {:jvm-opts ^:replace ["-server"]
:dev {:jvm-opts ^:replace ["-server"]
;; all module sources for development ;; all module sources for development
:source-paths ["modules/reitit/src" :source-paths ["modules/reitit/src"
@ -90,17 +89,17 @@
:java-source-paths ["modules/reitit-core/java-src"] :java-source-paths ["modules/reitit-core/java-src"]
:dependencies [[org.clojure/clojure "1.12.4"] :dependencies [[org.clojure/clojure "1.11.4"]
[thheller/shadow-cljs "3.3.4"] [thheller/shadow-cljs "3.2.1"]
[org.clojure/clojurescript "1.12.134"] [org.clojure/clojurescript "1.12.42"]
;; modules dependencies ;; modules dependencies
[metosin/schema-tools "0.13.1"] [metosin/schema-tools "0.13.1"]
[metosin/spec-tools "0.10.8"] [metosin/spec-tools "0.10.7"]
[metosin/muuntaja "0.6.11"] [metosin/muuntaja "0.6.11"]
[metosin/sieppari "0.0.0-alpha13"] [metosin/sieppari "0.0.0-alpha13"]
[metosin/jsonista "0.3.13"] [metosin/jsonista "0.3.13"]
[metosin/malli "0.20.0"] [metosin/malli "0.19.2"]
[lambdaisland/deep-diff "0.0-47"] [lambdaisland/deep-diff "0.0-47"]
[meta-merge "1.0.0"] [meta-merge "1.0.0"]
[com.bhauman/spell-spec "0.1.2"] [com.bhauman/spell-spec "0.1.2"]
@ -113,11 +112,11 @@
[ikitommi/immutant-web "3.0.0-alpha1"] [ikitommi/immutant-web "3.0.0-alpha1"]
[metosin/ring-http-response "0.9.5"] [metosin/ring-http-response "0.9.5"]
[metosin/ring-swagger-ui "5.20.0"] [metosin/ring-swagger-ui "5.20.0"]
[org.clojure/tools.analyzer "1.2.1"] [org.clojure/tools.analyzer "1.2.0"]
[criterium "0.4.6"] [criterium "0.4.6"]
[org.clojure/test.check "1.1.3"] [org.clojure/test.check "1.1.1"]
[org.clojure/tools.namespace "1.5.1"] [org.clojure/tools.namespace "1.5.0"]
[com.gfredericks/test.chuck "0.2.15"] [com.gfredericks/test.chuck "0.2.15"]
[nubank/matcher-combinators "3.9.2"] [nubank/matcher-combinators "3.9.2"]
@ -125,10 +124,10 @@
[io.pedestal/pedestal.service "0.6.4" :upgrade false] [io.pedestal/pedestal.service "0.6.4" :upgrade false]
[org.clojure/core.async "1.8.741"] [org.clojure/core.async "1.8.741"]
[manifold "0.5.0"] [manifold "0.4.3"]
[funcool/promesa "11.0.678"] [funcool/promesa "11.0.678"]
[com.clojure-goes-fast/clj-async-profiler "1.7.0"] [com.clojure-goes-fast/clj-async-profiler "1.6.2"]
[ring-cors "0.1.13"] [ring-cors "0.1.13"]
[com.bhauman/rebel-readline "0.1.5"]]} [com.bhauman/rebel-readline "0.1.5"]]}
@ -144,7 +143,7 @@
[io.pedestal/pedestal.jetty "0.6.4" :upgrade false] [io.pedestal/pedestal.jetty "0.6.4" :upgrade false]
[calfpath "0.8.1"] [calfpath "0.8.1"]
[org.clojure/core.async "1.8.741"] [org.clojure/core.async "1.8.741"]
[manifold "0.5.0"] [manifold "0.4.3"]
[funcool/promesa "11.0.678"] [funcool/promesa "11.0.678"]
[metosin/sieppari] [metosin/sieppari]
[yada "1.2.16"] [yada "1.2.16"]
@ -160,7 +159,6 @@
:aliases {"all" ["with-profile" "dev,default"] :aliases {"all" ["with-profile" "dev,default"]
"perf" ["with-profile" "default,dev,perf"] "perf" ["with-profile" "default,dev,perf"]
"test-clj" ["all" "do" ["bat-test"] ["check"]] "test-clj" ["all" "do" ["bat-test"] ["check"]]
"test-clj11" ["with-profile" "dev,default,clj11" "do" ["bat-test"] ["check"]]
;; NOTE: These are deprecated, kept around for ensuring shadow-cljs works ;; NOTE: These are deprecated, kept around for ensuring shadow-cljs works
;; the same way. ;; the same way.
"test-browser" ["doo" "chrome-headless" "test"] "test-browser" ["doo" "chrome-headless" "test"]

View file

@ -13,14 +13,11 @@ case $1 in
npx shadow-cljs release karma npx shadow-cljs release karma
npx karma start --single-run npx karma start --single-run
;; ;;
clj11) clj)
lein test-clj11
;;
clj12)
lein test-clj lein test-clj
;; ;;
*) *)
echo "Please select [clj11|clj12|cljs]" echo "Please select [clj|cljs]"
exit 1 exit 1
;; ;;
esac esac

View file

@ -308,10 +308,10 @@
["/ping" {:get {:interceptors [{:enter #(a/go %)}] ["/ping" {:get {:interceptors [{:enter #(a/go %)}]
:handler (fn [_] (a/go response))}}]) :handler (fn [_] (a/go response))}}])
(ring/create-default-handler) (ring/create-default-handler)
{:executor sieppari/executor}) {:executor sieppari/executor})]
respond (promise)] (let [respond (promise)]
(app {:request-method :get, :uri "/ping"} respond ::irrelevant) (app {:request-method :get, :uri "/ping"} respond ::irrelevant)
(is (= response (deref respond 100 ::timeout)))))) (is (= response (deref respond 100 ::timeout)))))))
(defrecord MyAsyncContext []) (defrecord MyAsyncContext [])

View file

@ -11,7 +11,7 @@
(:import (clojure.lang ExceptionInfo) (:import (clojure.lang ExceptionInfo)
(java.sql SQLException SQLWarning))) (java.sql SQLException SQLWarning)))
(derive ::kukka ::kikka) (derive ::kikka ::kukka)
(deftest exception-test (deftest exception-test
(letfn [(create (letfn [(create
@ -147,55 +147,6 @@
(is (= status 500)) (is (= status 500))
(is (= body "too many tries"))))))) (is (= body "too many tries")))))))
(derive ::table ::object)
(derive ::living ::object)
(derive ::plant ::living)
(derive ::animal ::living)
(derive ::dog ::animal)
(derive ::cat ::animal)
(derive ::garfield ::cat)
(deftest exception-hierarchy-test
(letfn [(create [f]
(ring/ring-handler
(ring/router
[["/defaults"
{:handler f}]]
{:data {:middleware [(exception/create-exception-middleware
(merge
exception/default-handlers
{::object (constantly (http-response/bad-request "object"))
::living (constantly (http-response/bad-request "living"))
::animal (constantly (http-response/bad-request "animal"))
::cat (constantly (http-response/bad-request "cat"))}))]}})))
(call [ex-typ]
(let [app (create (fn [_] (throw (ex-info "fail" {:type ex-typ}))))]
(app {:request-method :get, :uri "/defaults"})))]
(let [{:keys [status body]} (call ::object)]
(is (= status 400))
(is (= body "object")))
(let [{:keys [status body]} (call ::table)]
(is (= status 400))
(is (= body "object")))
(let [{:keys [status body]} (call ::living)]
(is (= status 400))
(is (= body "living")))
(let [{:keys [status body]} (call ::plant)]
(is (= status 400))
(is (= body "living")))
(let [{:keys [status body]} (call ::animal)]
(is (= status 400))
(is (= body "animal")))
(let [{:keys [status body]} (call ::dog)]
(is (= status 400))
(is (= body "animal")))
(let [{:keys [status body]} (call ::cat)]
(is (= status 400))
(is (= body "cat")))
(let [{:keys [status body]} (call ::garfield)]
(is (= status 400))
(is (= body "cat")))))
(deftest spec-coercion-exception-test (deftest spec-coercion-exception-test
(let [app (ring/ring-handler (let [app (ring/ring-handler
(ring/router (ring/router

View file

@ -2,6 +2,7 @@
(:require [clojure.spec.alpha :as cs] (:require [clojure.spec.alpha :as cs]
[clojure.string :as str] [clojure.string :as str]
[clojure.test :refer [deftest is testing]] [clojure.test :refer [deftest is testing]]
[malli.core :as m]
[malli.experimental.lite :as l] [malli.experimental.lite :as l]
[reitit.coercion :as coercion] [reitit.coercion :as coercion]
[reitit.coercion.malli] [reitit.coercion.malli]
@ -9,7 +10,8 @@
[reitit.coercion.spec] [reitit.coercion.spec]
[reitit.core :as r] [reitit.core :as r]
[schema.core :as s] [schema.core :as s]
[spec-tools.data-spec :as ds]) [spec-tools.data-spec :as ds]
[malli.transform :as mt])
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo)))) (:import (clojure.lang ExceptionInfo))))
@ -108,6 +110,7 @@
(testing "spec-coercion (shallow)" (testing "spec-coercion (shallow)"
(testing "succeeds" (testing "succeeds"
(let [m (r/match-by-path r "/spec-shallow/1/abba")] (let [m (r/match-by-path r "/spec-shallow/1/abba")]
(def MATCH m)
(is (= {:path {:keyword :abba, :number 1}, :query nil} (is (= {:path {:keyword :abba, :number 1}, :query nil}
(coercion/coerce! m)))) (coercion/coerce! m))))
(let [m (r/match-by-path r "/spec-shallow/1/abba")] (let [m (r/match-by-path r "/spec-shallow/1/abba")]

View file

@ -1,9 +1,10 @@
(ns reitit.core-test (ns reitit.core-test
(:require [clojure.test :refer [are deftest is testing]] (:require [clojure.test :refer [are deftest is testing]]
[reitit.core :as r] [reitit.core :as r #?@(:cljs [:refer [Router]])]
[reitit.impl :as impl]) [reitit.impl :as impl])
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo)))) (:import (clojure.lang ExceptionInfo)
(reitit.core Router))))
(defn- var-handler [& _] (defn- var-handler [& _]
"var-handler") "var-handler")
@ -33,12 +34,6 @@
:path "/api/ipa/large" :path "/api/ipa/large"
:path-params {:size "large"}}) :path-params {:size "large"}})
(r/match-by-name router ::beer {: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"}})
(r/match-by-name router ::beer {:size ":large"} {:url-encode? false})))
(is (= (r/map->Match (is (= (r/map->Match
{:template "/api/ipa/:size" {:template "/api/ipa/:size"
:data {:name ::beer} :data {:name ::beer}
@ -266,12 +261,10 @@
(is (= #'var-handler result)))))) (is (= #'var-handler result))))))
(testing "custom router" (testing "custom router"
(let [router (r/router (let [router (r/router ["/ping"] {:router (fn [_ _]
["/ping"] (reify Router
{:router (fn [_ _] (r/router-name [_]
#_{:clj-kondo/ignore [:missing-protocol-method]} ::custom)))})]
(reify r/Router
(router-name [_] ::custom)))})]
(is (= ::custom (r/router-name router))))) (is (= ::custom (r/router-name router)))))
(testing "bide sample" (testing "bide sample"

View file

@ -0,0 +1,33 @@
(ns reitit.dependency-test
(:require [clojure.test :refer [are deftest is testing]]
[reitit.dependency :as rc])
#?(:clj (:import [clojure.lang ExceptionInfo])))
(deftest post-order-test
(let [base-middlewares [{:name ::bar, :provides #{:bar}, :requires #{:foo}, :wrap identity}
{:name ::baz, :provides #{:baz}, :requires #{:bar :foo}, :wrap identity}
{:name ::foo, :provides #{:foo}, :requires #{}, :wrap identity}]]
(testing "happy cases"
(testing "default ordering works"
(is (= (rc/post-order base-middlewares)
(into (vec (drop 2 base-middlewares)) (take 2 base-middlewares)))))
(testing "custom provides and requires work"
(is (= (rc/post-order (comp hash-set :name)
(fn [node] (into #{} (map (fn [k] (keyword "reitit.dependency-test" (name k))))
(:requires node)))
base-middlewares)
(into (vec (drop 2 base-middlewares)) (take 2 base-middlewares))))))
(testing "errors"
(testing "missing dependency detection"
(is (thrown-with-msg? ExceptionInfo #"missing"
(rc/post-order (drop 1 base-middlewares)))))
(testing "ambiguous dependency detection"
(is (thrown-with-msg? ExceptionInfo #"multiple providers"
(rc/post-order (update-in base-middlewares [0 :provides] conj :foo)))))
(testing "circular dependency detection"
(is (thrown-with-msg? ExceptionInfo #"circular"
(rc/post-order (assoc-in base-middlewares [2 :requires] #{:baz}))))))))

View file

@ -41,33 +41,7 @@
:u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e" :u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e"
:k :kikka :k :kikka
:qk ::kikka :qk ::kikka
:nil nil}))) :nil nil}))))
(is (= {:n "1"
:n1 "-1"
:n2 "1"
:n3 "1"
:n4 "1"
:n5 "1"
:d "2.2"
:b "true"
:s "kikka"
:u "c2541900-17a7-4353-9024-db8ac258ba4e"
:k "kikka"
:qk "reitit.impl-test/kikka"
:nil nil}
(impl/path-params {:n 1
:n1 -1
:n2 (long 1)
:n3 (int 1)
:n4 (short 1)
:n5 (byte 1)
:d 2.2
:b true
:s "kikka"
:u #uuid "c2541900-17a7-4353-9024-db8ac258ba4e"
:k :kikka
:qk ::kikka
:nil nil} {:url-encode? false}))))
(deftest query-params-test (deftest query-params-test
(are [x y] (are [x y]

View file

@ -4,6 +4,7 @@
[jsonista.core :as j] [jsonista.core :as j]
[malli.core :as mc] [malli.core :as mc]
[matcher-combinators.test :refer [match?]] [matcher-combinators.test :refer [match?]]
[matcher-combinators.matchers :as matchers]
[muuntaja.core :as m] [muuntaja.core :as m]
[reitit.coercion.malli :as malli] [reitit.coercion.malli :as malli]
[reitit.coercion.schema :as schema] [reitit.coercion.schema :as schema]
@ -17,7 +18,6 @@
[reitit.swagger-ui :as swagger-ui] [reitit.swagger-ui :as swagger-ui]
[schema.core :as s] [schema.core :as s]
[schema-tools.core] [schema-tools.core]
[clojure.spec.alpha :as sp]
[spec-tools.core :as st] [spec-tools.core :as st]
[spec-tools.data-spec :as ds])) [spec-tools.data-spec :as ds]))
@ -1027,36 +1027,3 @@
"reitit.openapi-test.Y" {:type "integer"}}}} "reitit.openapi-test.Y" {:type "integer"}}}}
spec)) spec))
(is (nil? (validate spec)))))) (is (nil? (validate spec))))))
(sp/def ::address string?)
(sp/def ::zip int?)
(sp/def ::city string?)
(sp/def ::street string?)
(sp/def ::or-and-schema (sp/keys :req-un [(or (and ::address ::zip) (and ::city ::street))]))
(deftest openapi-spec-tests
(testing "s/keys + or maps to :anyOf"
(let [app (ring/ring-handler
(ring/router
[["/openapi.json"
{:get {:no-doc true
:openapi {:info {:title "" :version "0.0.1"}}
:handler (openapi/create-openapi-handler)}}]
["/spec" {:coercion spec/coercion
:post {:summary "or-and-schema"
:request {:content {"application/json" {:schema ::or-and-schema}}}
:handler identity}}]]
{:validate reitit.ring.spec/validate
:data {:middleware [openapi/openapi-feature]}}))
spec (:body (app {:request-method :get :uri "/openapi.json"}))]
(is (nil? (validate spec)))
(is (= {:title "reitit.openapi-test/or-and-schema"
:type "object"
:properties {"address" {:type "string"}
"zip" {:type "integer" :format "int64"}
"city" {:type "string"}
"street" {:type "string"}}
:anyOf [{:required ["address" "zip"]}
{:required ["city" "street"]}]}
(get-in spec [:paths "/spec" :post :requestBody :content "application/json" :schema]))))))

View file

@ -1,21 +1,19 @@
(ns reitit.ring-coercion-test (ns reitit.ring-coercion-test
(:require [clojure.test :refer [deftest is testing]] (:require [clojure.test :refer [deftest is testing]]
[malli.experimental.lite :as l] [malli.experimental.lite :as l]
#?@(:clj [[muuntaja.core] #?@(:clj [[muuntaja.middleware]
[muuntaja.middleware] [jsonista.core :as j]])
[jsonista.core :as j]
[reitit.coercion.schema :as schema]
[reitit.ring.middleware.muuntaja]
[schema.core :as s]])
[malli.core :as m] [malli.core :as m]
[malli.util :as mu] [malli.util :as mu]
[meta-merge.core :refer [meta-merge]] [meta-merge.core :refer [meta-merge]]
[reitit.coercion.malli :as malli] [reitit.coercion.malli :as malli]
[reitit.coercion.schema :as schema]
[reitit.coercion.spec :as spec] [reitit.coercion.spec :as spec]
[reitit.core :as r] [reitit.core :as r]
[reitit.ring :as ring] [reitit.ring :as ring]
[reitit.ring.spec] [reitit.ring.spec]
[reitit.ring.coercion :as rrc] [reitit.ring.coercion :as rrc]
[schema.core :as s]
[clojure.spec.alpha] [clojure.spec.alpha]
[spec-tools.data-spec :as ds]) [spec-tools.data-spec :as ds])
#?(:clj #?(:clj
@ -583,168 +581,103 @@
:request-method :get}))] :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 m/schema custom-meta-merge-checking-schema)))
(is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters))))) (is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters)))))))
(testing "malli options"
(let [->app (fn [options]
(ring/ring-handler
(ring/router
["/api" {:get {:parameters {:body [:map
[:i :int]
[:x :string]]}
:handler (fn [{{:keys [body]} :parameters}]
{:status 200 :body body})}}]
{:data {:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion (malli/create options)}})))
request {:uri "/api"
:request-method :get
:muuntaja/request {:format "application/json"}}]
(testing "humanize options"
(is (= {:i ["should be an integer"] :x ["missing required key"]}
(-> ((->app nil) (assoc request :body-params {:i "x"}))
:body
:humanized)))
(is (= {:i ["SHOULD INT"] :x ["MISSING"]}
(-> ((->app {:options {:errors {:int {:error/message {:en "SHOULD INT"}}
:malli.core/missing-key {:error/message {:en "MISSING"}}}}})
(assoc request :body-params {:i "x"}))
:body
:humanized))))
(testing "overriding registry"
(is (= {:body {:i "x" :x "x"} :status 200}
(-> ((->app {:options {:registry (merge (m/default-schemas)
{:int :string})}})
(assoc request :body-params {:i "x" :x "x"}))))))))))
#?(:clj #?(:clj
(deftest per-content-type-test (deftest per-content-type-test
(let [normalize-json (fn [resp] (doseq [[coercion json-request edn-request default-request json-response edn-response default-response]
(update resp :body #(-> % j/write-value-as-string (j/read-value j/keyword-keys-object-mapper))))] [[malli/coercion
(doseq [[coercion json-request edn-request default-request json-response edn-response default-response] [:map [:request [:enum :json]] [:response any?]]
[[malli/coercion [:map [:request [:enum :edn]] [:response any?]]
[:map [:request [:enum :json]] [:response any?]] [:map [:request [:enum :default]] [:response any?]]
[:map [:request [:enum :edn]] [:response any?]] [:map [:request any?] [:response [:enum :json]]]
[:map [:request [:enum :default]] [:response any?]] [:map [:request any?] [:response [:enum :edn]]]
[:map [:request any?] [:response [:enum :json]]] [:map [:request any?] [:response [:enum :default]]]]
[:map [:request any?] [:response [:enum :edn]]] [schema/coercion
[:map [:request any?] [:response [:enum :default]]]] {:request (s/eq :json) :response s/Any}
[schema/coercion {:request (s/eq :edn) :response s/Any}
{:request (s/eq :json) :response s/Any} {:request (s/eq :default) :response s/Any}
{:request (s/eq :edn) :response s/Any} {:request s/Any :response (s/eq :json)}
{:request (s/eq :default) :response s/Any} {:request s/Any :response (s/eq :edn)}
{:request s/Any :response (s/eq :json)} {:request s/Any :response (s/eq :default)}]
{:request s/Any :response (s/eq :edn)} [spec/coercion
{:request s/Any :response (s/eq :default)}] {:request (clojure.spec.alpha/spec #{:json}) :response any?}
[spec/coercion {:request (clojure.spec.alpha/spec #{:edn}) :response any?}
{:request (clojure.spec.alpha/spec #{:json}) :response any?} {:request (clojure.spec.alpha/spec #{:default}) :response any?}
{:request (clojure.spec.alpha/spec #{:edn}) :response any?} {:request any? :response (clojure.spec.alpha/spec #{:json})}
{:request (clojure.spec.alpha/spec #{:default}) :response any?} {:request any? :response (clojure.spec.alpha/spec #{:end})}
{:request any? :response (clojure.spec.alpha/spec #{:json})} {:request any? :response (clojure.spec.alpha/spec #{:default})}]]]
{:request any? :response (clojure.spec.alpha/spec #{:end})} (testing (str coercion)
{:request any? :response (clojure.spec.alpha/spec #{:default})}]]] (doseq [{:keys [name app]}
(testing (str coercion) [{:name "using top-level :body"
(doseq [{:keys [name app]} :app (ring/ring-handler
[{:name "using top-level :body" (ring/router
:app (ring/ring-handler ["/foo" {:post {:request {:content {"application/json" {:schema json-request}
(ring/router "application/edn" {:schema edn-request}}
["/foo" {:post {:request {:content {"application/json" {:schema json-request} :body default-request}
"application/edn" {:schema edn-request}} :responses {200 {:content {"application/json" {:schema json-response}
:body default-request} "application/edn" {:schema edn-response}}
:responses {200 {:content {"application/json" {:schema json-response} :body default-response}}
"application/edn" {:schema edn-response}} :handler (fn [req]
:body default-response}} {:status 200
:handler (fn [req] :body (-> req :parameters :request)})}}]
{:status 200 {:validate reitit.ring.spec/validate
:body (-> req :parameters :request)})}}] :data {:middleware [rrc/coerce-request-middleware
{:validate reitit.ring.spec/validate rrc/coerce-response-middleware]
:data {:middleware [rrc/coerce-request-middleware :coercion coercion}}))}
rrc/coerce-response-middleware] {:name "using :default content"
:coercion coercion}}))} :app (ring/ring-handler
{:name "using :default content" (ring/router
:app (ring/ring-handler ["/foo" {:post {:request {:content {"application/json" {:schema json-request}
(ring/router "application/edn" {:schema edn-request}
["/foo" {:post {:request {:content {"application/json" {:schema json-request} :default {:schema default-request}}
"application/edn" {:schema edn-request} :body json-request} ;; not applied as :default exists
:default {:schema default-request}} :responses {200 {:content {"application/json" {:schema json-response}
:body json-request} ;; not applied as :default exists "application/edn" {:schema edn-response}
:responses {200 {:content {"application/json" {:schema json-response} :default {:schema default-response}}
"application/edn" {:schema edn-response} :body json-response}} ;; not applied as :default exists
:default {:schema default-response}} :handler (fn [req]
:body json-response}} ;; not applied as :default exists {:status 200
:handler (fn [req] :body (-> req :parameters :request)})}}]
{:status 200 {:validate reitit.ring.spec/validate
:body (-> req :parameters :request)})}}] :data {:middleware [rrc/coerce-request-middleware
{:validate reitit.ring.spec/validate rrc/coerce-response-middleware]
:data {:middleware [rrc/coerce-request-middleware :coercion coercion}}))}]]
rrc/coerce-response-middleware] (testing name
:coercion coercion}}))}]] (let [call (fn [request]
(testing name
(let [call (fn [request]
(try
(app request)
(catch ExceptionInfo e
(select-keys (ex-data e) [:type :in]))))
request (fn [request-format response-format body]
{:request-method :post
:uri "/foo"
:muuntaja/request {:format request-format}
:muuntaja/response {:format response-format}
:body-params body})]
(testing "succesful call"
(is (= {:status 200 :body {:request "json", :response "json"}}
(normalize-json (call (request "application/json" "application/json" {:request :json :response :json})))))
(is (= {:status 200 :body {:request "edn", :response "json"}}
(normalize-json (call (request "application/edn" "application/json" {:request :edn :response :json})))))
(is (= {:status 200 :body {:request :default, :response :default}}
(call (request "application/transit" "application/transit" {:request :default :response :default})))))
(testing "request validation fails"
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
(call (request "application/edn" "application/json" {:request :json :response :json}))))
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
(call (request "application/json" "application/json" {:request :edn :response :json}))))
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
(call (request "application/transit" "application/json" {:request :edn :response :json})))))
(testing "response validation fails"
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
(call (request "application/json" "application/json" {:request :json :response :edn}))))
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
(call (request "application/json" "application/edn" {:request :json :response :json}))))
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
(call (request "application/json" "application/transit" {:request :json :response :json}))))))))
(testing "explicit response content type"
(let [response (atom nil)
app (ring/ring-handler
(ring/router
["/foo" {:post {:responses {200 {:content {"application/json" {:schema json-response}
"application/edn" {:schema edn-response}
:default {:schema default-response}}}}
:handler (fn [req]
@response)}}]
{:validate reitit.ring.spec/validate
:data {:middleware [rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion coercion}}))
call (fn [request]
(try (try
(app request) (app request)
(catch ExceptionInfo e (catch ExceptionInfo e
#_(ex-data e)
(select-keys (ex-data e) [:type :in])))) (select-keys (ex-data e) [:type :in]))))
request (fn [request-format body resp] request (fn [request-format response-format body]
(reset! response resp)
{:request-method :post {:request-method :post
:uri "/foo" :uri "/foo"
:muuntaja/request {:format request-format} :muuntaja/request {:format request-format}
:body-params body})] :muuntaja/response {:format response-format}
(testing "via :muuntaja/content-type" :body-params body})
(is (= {:status 200 :body {:request "json" :response "json"} :muuntaja/content-type "application/json"} normalize-json (fn[body]
(normalize-json (call (request "application/json" {:request :json :response :json} {:status 200 :body {:request :json :response :json} :muuntaja/content-type "application/json"})))) (-> body j/write-value-as-string (j/read-value j/keyword-keys-object-mapper)))]
"valid reponse") (testing "succesful call"
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion} (is (= {:status 200 :body {:request "json", :response "json"}}
(call (request "application/json" {:request :json :response :json} {:status 200 :body {:request :json :response :invalid} :muuntaja/content-type "application/json"}))) (normalize-json (call (request "application/json" "application/json" {:request :json :response :json})))))
"invalid reponse"))))))))) (is (= {:status 200 :body {:request "edn", :response "json"}}
(normalize-json (call (request "application/edn" "application/json" {:request :edn :response :json})))))
(is (= {:status 200 :body {:request :default, :response :default}}
(call (request "application/transit" "application/transit" {:request :default :response :default})))))
(testing "request validation fails"
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
(call (request "application/edn" "application/json" {:request :json :response :json}))))
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
(call (request "application/json" "application/json" {:request :edn :response :json}))))
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
(call (request "application/transit" "application/json" {:request :edn :response :json})))))
(testing "response validation fails"
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
(call (request "application/json" "application/json" {:request :json :response :edn}))))
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
(call (request "application/json" "application/edn" {:request :json :response :json}))))
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
(call (request "application/json" "application/transit" {:request :json :response :json}))))))))))))
#?(:clj #?(:clj
@ -868,100 +801,3 @@
(app) :body slurp (read-string))] (app) :body slurp (read-string))]
(is (= data-edn (e2e (assoc data-edn :EXTRA "VALUE")))) (is (= data-edn (e2e (assoc data-edn :EXTRA "VALUE"))))
(is (thrown? ExceptionInfo (e2e data-json)))))))) (is (thrown? ExceptionInfo (e2e data-json))))))))
#?(:clj
(deftest muuntaja-per-content-type-coercion-test
;; Test integration between per-content-type coercion and muuntaja.
;; Malli-only for now.
(let [response (atom nil)
app (ring/ring-handler
(ring/router
["/foo" {:post {:request {:content {"application/json" {:schema [:map [:request [:enum :json]]]}
"application/edn" {:schema [:map [:request [:enum :edn]]]}
:default {:schema [:map [:request [:enum :default]]]}}}
:responses {200 {:content {"application/json" {:schema [:map [:response [:enum :json]]]}
"application/edn" {:schema [:map [:response [:enum :edn]]]}
:default {}}}}
:handler (fn [req] @response)}}]
{:data {:middleware [reitit.ring.middleware.muuntaja/format-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:muuntaja muuntaja.core/instance
:coercion malli/coercion}}))
maybe-slurp #(if (instance? java.io.InputStream %)
(slurp %)
%)
call (fn [request resp]
(reset! response resp)
(try
(-> (merge {:request-method :post :uri "/foo"} request)
(update :body #(ByteArrayInputStream. (.getBytes % "UTF-8")))
(app))
(catch ExceptionInfo e
#_(ex-data e)
(select-keys (ex-data e) [:in :type]))))
read-json #(j/read-value % (j/object-mapper {:decode-key-fn true}))
json-response? (fn [resp]
(and (.startsWith (get-in resp [:headers "Content-Type"]) "application/json") ;; ignore the ;charset=utf-8 part
(= {:response "json"} (read-json (maybe-slurp (:body resp))))))
edn-response? (fn [resp]
(and (.startsWith (get-in resp [:headers "Content-Type"]) "application/edn") ;; ignore the ;charset=utf-8 part
(= {:response :edn} (read-string (maybe-slurp (:body resp))))))
custom-response? (fn [resp]
(and (= (get-in resp [:headers "Content-Type"]) "application/custom")
(= "custom data" (maybe-slurp (:body resp)))))]
(testing "response content-type defaults to json"
(is (json-response?
(call {:headers {"content-type" "application/json"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:body {:response :json}})))
(is (json-response?
(call {:headers {"content-type" "application/edn"}
:body (pr-str {:request :edn})}
{:status 200
:body {:response :json}})))
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion}
(call {:headers {"content-type" "application/json"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:body {:response :invalid}}))
"invalid response"))
(testing "response content-type negotiated via accept header"
(is (json-response?
(call {:headers {"content-type" "application/json" "accept" "application/json"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:body {:response :json}})))
(is (edn-response?
(call {:headers {"content-type" "application/json" "accept" "application/edn"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:body {:response :edn}})))
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion}
(call {:headers {"content-type" "application/json" "accept" "application/edn"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:body {:response :invalid}}))
"invalid response"))
(testing "response content-type set via :muuntaja/content-type"
(is (edn-response?
(call {:headers {"content-type" "application/json" "accept" "application/json"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:muuntaja/content-type "application/edn"
:body {:response :edn}})))
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion}
(call {:headers {"content-type" "application/json" "accept" "application/json"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:muuntaja/content-type "application/edn"
:body {:response :invalid}}))
"invalid response"))
(testing "response content-type set via Content-Type header. muuntaja disabled for response."
(is (custom-response?
(call {:headers {"content-type" "application/json" "accept" "application/json"}
:body (j/write-value-as-string {:request :json})}
{:status 200
:headers {"Content-Type" "application/custom"}
:body "custom data"})))))))

View file

@ -4,8 +4,7 @@
[reitit.core :as r] [reitit.core :as r]
[reitit.middleware :as middleware] [reitit.middleware :as middleware]
[reitit.ring :as ring] [reitit.ring :as ring]
#?(:clj [reitit.trie :as trie] [reitit.trie :as trie])
:cljs [reitit.trie :as-alias trie]))
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo)))) (:import (clojure.lang ExceptionInfo))))
@ -416,31 +415,7 @@
:get "/slash-less//" "/slash-less?kikka=kukka" :get "/slash-less//" "/slash-less?kikka=kukka"
:post "/with-slash" "/with-slash/?kikka=kukka" :post "/with-slash" "/with-slash/?kikka=kukka"
:post "/slash-less/" "/slash-less?kikka=kukka" :post "/slash-less/" "/slash-less?kikka=kukka"
:post "/slash-less//" "/slash-less?kikka=kukka")))) :post "/slash-less//" "/slash-less?kikka=kukka"))))))
;; See issue #337
(testing "Avoid external redirects"
(let [app (ring/ring-handler
(ring/router [["*" {:get (constantly nil)}]])
(ring/redirect-trailing-slash-handler))
resp (fn [uri & [query-string]]
(let [r (app {:request-method :get :uri uri :query-string query-string})]
{:status (:status r)
:Location (get-in r [:headers "Location"])}))]
(testing "without query params"
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "//malicious.com/foo")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo/")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo//")))
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "///malicious.com/foo")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo/")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo//"))))
(testing "with query params"
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "//malicious.com/foo" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo/" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo//" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "///malicious.com/foo" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo/" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo//" "bar=quux"))))))))
(deftest async-ring-test (deftest async-ring-test
(let [promise #(let [value (atom ::nil)] (let [promise #(let [value (atom ::nil)]
@ -872,22 +847,23 @@
(let [body (:body (app {:request-method :get, :uri (str "/" n)}))] (let [body (:body (app {:request-method :get, :uri (str "/" n)}))]
(is (= body (str n)))))))))))) (is (= body (str n))))))))))))
(def routes (atom nil)) (declare routes)
(deftest reloading-ring-handler-test (deftest reloading-ring-handler-test
(let [r (fn [body] {:status 200, :body body})] (let [r (fn [body] {:status 200, :body body})]
(reset! routes ["/" (constantly (r "1"))]) ;; initial value (def routes ["/" (constantly (r "1"))]) ;; initial value
(let [create-handler (fn [] (ring/ring-handler (ring/router @routes)))]
(let [create-handler (fn [] (ring/ring-handler (ring/router routes)))]
(testing "static ring handler does not see underlying route changes" (testing "static ring handler does not see underlying route changes"
(let [app (create-handler)] (let [app (create-handler)]
(is (= (r "1") (app {:uri "/", :request-method :get}))) (is (= (r "1") (app {:uri "/", :request-method :get})))
(reset! routes ["/" (constantly (r "2"))]) ;; redefine (def routes ["/" (constantly (r "2"))]) ;; redefine
(is (= (r "1") (app {:uri "/", :request-method :get}))))) (is (= (r "1") (app {:uri "/", :request-method :get})))))
(testing "reloading ring handler sees underlying route changes" (testing "reloading ring handler sees underlying route changes"
(let [app (ring/reloading-ring-handler create-handler)] (let [app (ring/reloading-ring-handler create-handler)]
(is (= (r "2") (app {:uri "/", :request-method :get}))) (is (= (r "2") (app {:uri "/", :request-method :get})))
(reset! routes ["/" (constantly (r "3"))]) ;; redefine again (def routes ["/" (constantly (r "3"))]) ;; redefine again
(is (= (r "3") (app {:uri "/", :request-method :get})))))))) (is (= (r "3") (app {:uri "/", :request-method :get}))))))))
(defrecord FooTest [a b]) (defrecord FooTest [a b])

View file

@ -41,9 +41,7 @@
"/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] "/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
"/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"]
"/olipa/{:kerran}/avaruus", ["/olipa/{:kerran}/avaruus"]
"/olipa/{a.b/c}/avaruus", ["/olipa/{a.b/c}/avaruus"] "/olipa/{a.b/c}/avaruus", ["/olipa/{a.b/c}/avaruus"]
"/olipa/{:a.b/c}/avaruus", ["/olipa/{:a.b/c}/avaruus"]
"/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] "/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "avaruus}"))] "/olipa/kerran/{*avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "avaruus}"))]
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "valtavan.suuri/avaruus}"))]))) "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{" (trie/->CatchAll (keyword "valtavan.suuri/avaruus}"))])))
@ -55,9 +53,7 @@
"/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"] "/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"]
"/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
"/olipa/{:kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
"/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"] "/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
"/olipa/{:a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
"/olipa/kerran/*avaruus", ["/olipa/kerran/*avaruus"] "/olipa/kerran/*avaruus", ["/olipa/kerran/*avaruus"]
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] "/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)]))) "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)])))
@ -69,9 +65,7 @@
"/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] "/olipa/:kerran/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
"/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
"/olipa/{:kerran}/avaruus", ["/olipa/" (trie/->Wild :kerran) "/avaruus"]
"/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"] "/olipa/{a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
"/olipa/{:a.b/c}/avaruus", ["/olipa/" (trie/->Wild :a.b/c) "/avaruus"]
"/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] "/olipa/kerran/*avaruus", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)] "/olipa/kerran/{*avaruus}", ["/olipa/kerran/" (trie/->CatchAll :avaruus)]
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)]))) "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/" (trie/->CatchAll :valtavan.suuri/avaruus)])))
@ -83,9 +77,7 @@
"/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"] "/olipa/:kerran/avaruus", ["/olipa/:kerran/avaruus"]
"/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"] "/olipa/{kerran}/avaruus", ["/olipa/{kerran}/avaruus"]
"/olipa/{:kerran}/avaruus", ["/olipa/{:kerran}/avaruus"]
"/olipa/{a.b/c}/avaruus", ["/olipa/{a.b/c}/avaruus"] "/olipa/{a.b/c}/avaruus", ["/olipa/{a.b/c}/avaruus"]
"/olipa/{:a.b/c}/avaruus", ["/olipa/{:a.b/c}/avaruus"]
"/olipa/kerran/*avaruus", ["/olipa/kerran/*avaruus"] "/olipa/kerran/*avaruus", ["/olipa/kerran/*avaruus"]
"/olipa/kerran/{*avaruus}", ["/olipa/kerran/{*avaruus}"] "/olipa/kerran/{*avaruus}", ["/olipa/kerran/{*avaruus}"]
"/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{*valtavan.suuri/avaruus}"])))) "/olipa/kerran/{*valtavan.suuri/avaruus}", ["/olipa/kerran/{*valtavan.suuri/avaruus}"]))))