Merge branch 'master' into into-interceptor-for-multimethods

This commit is contained in:
Tommi Reiman 2023-01-09 17:40:32 +02:00 committed by GitHub
commit f80271fac1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
138 changed files with 2827 additions and 2170 deletions

View file

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

View file

@ -0,0 +1 @@
{:config-paths ["../../../.clj-kondo"]}

3
.lsp/config.edn Normal file
View file

@ -0,0 +1,3 @@
{:cljfmt {:indents {for-all [[:inner 0]]
are [[:inner 0]]}}
:clean {:ns-inner-blocks-indentation :same-line}}

View file

@ -12,13 +12,50 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
## Unreleased
## 0.5.18 (2022-04-05)
**[compare](https://github.com/metosin/reitit/compare/0.5.15...master)**
* FIX [#334](https://github.com/metosin/reitit/pull/334) - Frontend: there is no way to catch the exception if coercion fails (via [#549](https://github.com/metosin/reitit/pull/549))
* Save three seq constructions [#537](https://github.com/metosin/reitit/pull/537)
* update jackson-databind for CVE-2020-36518 [#544](https://github.com/metosin/reitit/pull/544)
* Balance parenthesis in docs [#547](https://github.com/metosin/reitit/pull/547)
## 0.5.17 (2022-03-10)
* FIX match-by-path is broken if there are no non-conflicting wildcard routes [#538](https://github.com/metosin/reitit/issues/538)
## 0.5.16 (2022-02-15)
**[compare](https://github.com/metosin/reitit/compare/0.5.15...0.5.16)**
* Support for [Malli Lite Syntax](https://github.com/metosin/malli#lite) in coercion (enabled by default):
```clj
["/add/:id" {:post {:parameters {:path {:id int?}
:query {:a (l/optional int?)}
:body {:id int?
:data {:id (l/maybe int?)
:orders (l/map-of uuid? {:name string?})}}}
:responses {200 {:body {:total pos-int?}}
500 {:description "fail"}}}}]
```
* Improved Reitit-frontend function docstrings
* Allow multimethods to be interpreted as interceptors
* Updated deps:
```clj
[metosin/ring-swagger-ui "4.3.0"] is available but we use "3.46.0"
[metosin/jsonista "0.3.5"] is available but we use "0.3.3"
[metosin/malli "0.8.2"] is available but we use "0.5.1"
[com.fasterxml.jackson.core/jackson-core "2.13.1"] is available but we use "2.12.4"
[com.fasterxml.jackson.core/jackson-databind "2.13.1"] is available but we use "2.12.4"
[fipp "0.6.25"] is available but we use "0.6.24"
[expound "0.9.0"] is available but we use "0.8.9"
[ring/ring-core "1.9.5"] is available but we use "1.9.4"
```
## 0.5.15 (2021-08-05)
**[compare](https://github.com/metosin/reitit/compare/0.5.14...0.5.15)**

View file

@ -52,7 +52,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
All main modules bundled:
```clj
[metosin/reitit "0.5.15"]
[metosin/reitit "0.5.18"]
```
Optionally, the parts can be required separately.
@ -153,9 +153,13 @@ All examples are in https://github.com/metosin/reitit/tree/master/examples
## External resources
* Simple web application using Ring/Reitit and Integrant: https://github.com/PrestanceDesign/usermanager-reitit-integrant-example
* A simple [ClojureScript](https://clojurescript.org/) frontend and Clojure backend using Reitit, [JUXT Clip](https://github.com/juxt/clip), [next.jdbc](https://github.com/seancorfield/next-jdbc) and other bits and bobs...
* [startrek](https://git.sr.ht/~dharrigan/startrek)
* [startrek-ui](https://git.sr.ht/~dharrigan/startrek-ui)
* A simple Clojure backend using Reitit to serve up a RESTful API: [startrek](https://github.com/dharrigan/startrek). Technologies include:
* [Donut System](https://github.com/donut-party/system)
* [next-jdbc](https://github.com/seancorfield/next-jdbc)
* [JUXT Clip](https://github.com/juxt/clip)
* [Flyway](https://github.com/flyway/flyway)
* [HoneySQL](https://github.com/seancorfield/honeysql)
* [Babashka](https://babashka.org)
* https://www.learnreitit.com/
* Lipas, liikuntapalvelut: https://github.com/lipas-liikuntapaikat/lipas
* Implementation of the Todo-Backend API spec, using Clojure, Ring/Reitit and next-jdbc: https://github.com/PrestanceDesign/todo-backend-clojure-reitit

View file

@ -40,7 +40,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
All bundled:
```clj
[metosin/reitit "0.5.15"]
[metosin/reitit "0.5.18"]
```
Optionally, the parts can be required separately.
@ -139,7 +139,7 @@ Routing:
```clj
(app {:request-method :get, :uri "/api/admin/users"})
; {:status 200, :body "ok", :wrap (:api :admin}
; {:status 200, :body "ok", :wrap (:api :admin)}
(app {:request-method :put, :uri "/api/admin/users"})
; nil

View file

@ -4,15 +4,16 @@ Routers can be configured via options. The following options are available for t
| key | description
|--------------|-------------
| `:path` | Base-path for routes
| `:routes` | Initial resolved routes (default `[]`)
| `:data` | Initial route data (default `{}`)
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
| `:compile` | Function of `route opts => result` to compile a route handler
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
| `:router` | Function of `routes opts => router` to override the actual router implementation
| `:path` | Base-path for routes
| `:routes` | Initial resolved routes (default `[]`)
| `:data` | Initial route data (default `{}`)
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
| `:meta-merge-fn` | Function which follows the signature of `meta-merge.core/meta-merge`, useful for when you want to have more control over the meta merging
| `:compile` | Function of `route opts => result` to compile a route handler
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
| `:router` | Function of `routes opts => router` to override the actual router implementation

View file

@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
## Pretty Errors
```clj
[metosin/reitit-dev "0.5.15"]
[metosin/reitit-dev "0.5.18"]
```
For human-readable and developer-friendly exception messages, there is `reitit.dev.pretty/exception` (in the `reitit-dev` module). It is inspired by the lovely errors messages of [ELM](https://elm-lang.org/blog/compiler-errors-for-humans) and [ETA](https://twitter.com/jyothsnasrin/status/1037703436043603968) and uses [fipp](https://github.com/brandonbloom/fipp), [expound](https://github.com/bhb/expound) and [spell-spec](https://github.com/bhauman/spell-spec) for most of heavy lifting.

View file

@ -2,6 +2,10 @@
[Malli](https://github.com/metosin/malli) is data-driven Schema library for Clojure/Script.
## Default Syntax
By default, [Vector Syntax](https://github.com/metosin/malli#vector-syntax) is used:
```clj
(require '[reitit.coercion.malli])
(require '[reitit.coercion :as coercion])
@ -44,6 +48,20 @@ Failing coercion:
; => ExceptionInfo Request coercion failed...
```
## Lite Syntax
Same using [Lite Syntax](https://github.com/metosin/malli#lite):
```clj
(def router
(r/router
["/:company/users/:user-id" {:name ::user-view
:coercion reitit.coercion.malli/coercion
:parameters {:path {:company string?
:user-id int?}}}]
{:compile coercion/compile-request-coercers}))
```
## Configuring coercion
Using `create` with options to create the coercion instead of `coercion`:
@ -58,6 +76,8 @@ Using `create` with options to create the coercion instead of `coercion`:
:response {:default reitit.coercion.malli/default-transformer-provider}}
;; set of keys to include in error messages
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
;; support lite syntax?
:lite true
;; schema identity function (default: close all map schemas)
:compile mu/closed-schema
;; validate request & response

View file

@ -13,6 +13,13 @@
./scripts/test.sh cljs
```
## Formatting
```bash
clojure-lsp format
clojure-lsp clean-ns
```
## Documentation
The documentation lives under `doc` and it is hosted on [cljdoc](https://cljdoc.org). See their

View file

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

View file

@ -5,7 +5,7 @@ Reitit has also support for [interceptors](http://pedestal.io/reference/intercep
## Reitit-http
```clj
[metosin/reitit-http "0.5.15"]
[metosin/reitit-http "0.5.18"]
```
A module for http-routing using interceptors instead of middleware. Builds on top of the [`reitit-ring`](../ring/ring.md) module having all the same features.

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.
```clj
[metosin/reitit-pedestal "0.5.15"]
[metosin/reitit-pedestal "0.5.18"]
```
Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)?
@ -26,8 +26,8 @@ A minimalistic example on how to to swap the default-router with a reitit router
```clj
; [io.pedestal/pedestal.service "0.5.5"]
; [io.pedestal/pedestal.jetty "0.5.5"]
; [metosin/reitit-pedestal "0.5.15"]
; [metosin/reitit "0.5.15"]
; [metosin/reitit-pedestal "0.5.18"]
; [metosin/reitit "0.5.18"]
(require '[io.pedestal.http :as server])
(require '[reitit.pedestal :as pedestal])

View file

@ -1,7 +1,7 @@
# Sieppari
```clj
[metosin/reitit-sieppari "0.5.15"]
[metosin/reitit-sieppari "0.5.18"]
```
[Sieppari](https://github.com/metosin/sieppari) is a new and fast interceptor implementation for Clojure, with pluggable async supporting [core.async](https://github.com/clojure/core.async), [Manifold](https://github.com/ztellman/manifold) and [Promesa](http://funcool.github.io/promesa/latest).

View file

@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor
### Printing Context Diffs
```clj
[metosin/reitit-interceptors "0.5.15"]
[metosin/reitit-interceptors "0.5.18"]
```
Using `reitit.http.interceptors.dev/print-context-diffs` transformation, the context diffs between each interceptor are printed out to the console. To use it, add the following router option:

View file

@ -1,7 +1,7 @@
# Default Middleware
```clj
[metosin/reitit-middleware "0.5.15"]
[metosin/reitit-middleware "0.5.18"]
```
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.

View file

@ -1,7 +1,7 @@
# Exception Handling with Ring
```clj
[metosin/reitit-middleware "0.5.15"]
[metosin/reitit-middleware "0.5.18"]
```
Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practice is to have a top-level exception handler to log and format errors for clients.

View file

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

View file

@ -1,7 +1,7 @@
# Swagger Support
```
[metosin/reitit-swagger "0.5.15"]
[metosin/reitit-swagger "0.5.18"]
```
Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys.
@ -23,6 +23,7 @@ The following route data keys contribute to the generated swagger specification:
| :tags | optional set of string or keyword tags for an endpoint api docs
| :summary | optional short string summary of an endpoint
| :description | optional long description of an endpoint. Supports http://spec.commonmark.org/
| :operationId | optional string specifying the unique ID of an Operation
Coercion keys also contribute to the docs:
@ -44,7 +45,7 @@ If you need to post-process the generated spec, just wrap the handler with a cus
[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
```
[metosin/reitit-swagger-ui "0.5.15"]
[metosin/reitit-swagger-ui "0.5.18"]
```
`reitit.swagger-ui/create-swagger-ui-handler` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:

View file

@ -59,7 +59,7 @@ There is an extra option in ring-router (actually, in the underlying middleware-
### Printing Request Diffs
```clj
[metosin/reitit-middleware "0.5.15"]
[metosin/reitit-middleware "0.5.18"]
```
Using `reitit.ring.middleware.dev/print-request-diffs` transformation, the request diffs between each middleware are printed out to the console. To use it, add the following router option:

View file

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

View file

@ -10,9 +10,9 @@
[ring "1.7.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.439"]
[metosin/reitit "0.5.15"]
[metosin/reitit-schema "0.5.15"]
[metosin/reitit-frontend "0.5.15"]
[metosin/reitit "0.5.18"]
[metosin/reitit-schema "0.5.18"]
[metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match
[fipp "0.6.14"]]

View file

@ -10,9 +10,9 @@
[ring "1.7.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.439"]
[metosin/reitit "0.5.15"]
[metosin/reitit-schema "0.5.15"]
[metosin/reitit-frontend "0.5.15"]
[metosin/reitit "0.5.18"]
[metosin/reitit-schema "0.5.18"]
[metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match
[fipp "0.6.14"]]

View file

@ -10,9 +10,9 @@
[ring "1.7.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.5.15"]
[metosin/reitit-spec "0.5.15"]
[metosin/reitit-frontend "0.5.15"]
[metosin/reitit "0.5.18"]
[metosin/reitit-spec "0.5.18"]
[metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match
[fipp "0.6.14"]]

View file

@ -10,9 +10,9 @@
[ring "1.7.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.5.15"]
[metosin/reitit-spec "0.5.15"]
[metosin/reitit-frontend "0.5.15"]
[metosin/reitit "0.5.18"]
[metosin/reitit-spec "0.5.18"]
[metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match
[fipp "0.6.14"]]

View file

@ -1,7 +1,7 @@
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.5.15"]
[metosin/reitit "0.5.18"]
[reagent "0.8.1"]
[re-frame "0.10.6"]]

View file

@ -10,9 +10,9 @@
[ring "1.8.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.773"]
[metosin/reitit "0.5.15"]
[metosin/reitit-spec "0.5.15"]
[metosin/reitit-frontend "0.5.15"]
[metosin/reitit "0.5.18"]
[metosin/reitit-spec "0.5.18"]
[metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match
[fipp "0.6.23"]]

View file

@ -3,5 +3,5 @@
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"]
[aleph "0.4.7-alpha5"]
[metosin/reitit "0.5.15"]]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server})

View file

@ -5,5 +5,5 @@
[funcool/promesa "1.9.0"]
[manifold "0.1.8"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.15"]]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server})

View file

@ -2,4 +2,4 @@
:description "Reitit coercion with vanilla ring"
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.15"]])
[metosin/reitit "0.5.18"]])

View file

@ -0,0 +1,11 @@
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.hgignore
.hg/

View file

@ -0,0 +1,9 @@
(defproject pedestal-malli-swagger-example "0.1.0-SNAPSHOT"
:description "Reitit-http with pedestal"
:dependencies [[org.clojure/clojure "1.10.0"]
[io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-malli "0.5.18"]
[metosin/reitit-pedestal "0.5.18"]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns server})

View file

@ -0,0 +1,164 @@
(ns example.server
(:require [clojure.java.io :as io]
[io.pedestal.http.route]
[reitit.interceptor]
[reitit.dev.pretty :as pretty]
[reitit.coercion.malli]
[io.pedestal.http]
[reitit.ring]
[reitit.ring.malli]
[reitit.http]
[reitit.pedestal]
[reitit.swagger :as swagger]
[reitit.swagger-ui :as swagger-ui]
[reitit.http.coercion :as coercion]
[reitit.http.interceptors.parameters :as parameters]
[reitit.http.interceptors.muuntaja :as muuntaja]
[reitit.http.interceptors.multipart :as multipart]
[muuntaja.core]
[malli.util :as mu]))
(defn reitit-routes
[_config]
[["/swagger.json" {:get {:no-doc true
:swagger {:info {:title "my-api"
:description "with [malli](https://github.com/metosin/malli) and reitit-ring"}
:tags [{:name "files",
:description "file api"}
{:name "math",
:description "math api"}]}
:handler (swagger/create-swagger-handler)}}]
["/files" {:swagger {:tags ["files"]}}
["/upload"
{:post {:summary "upload a file"
:parameters {:multipart [:map [:file reitit.ring.malli/temp-file-part]]}
:responses {200 {:body [:map
[:name string?]
[:size int?]]}}
:handler (fn [{{{{:keys [filename
size]} :file}
:multipart}
:parameters}]
{:status 200
:body {:name filename
:size size}})}}]
["/download" {:get {:summary "downloads a file"
:swagger {:produces ["image/png"]}
:handler (fn [_]
{:status 200
:headers {"Content-Type" "image/png"}
:body (-> "reitit.png"
(io/resource)
(io/input-stream))})}}]]
["/math" {:swagger {:tags ["math"]}}
["/plus"
{:get {:summary "plus with malli query parameters"
:parameters {:query [:map
[:x
{:title "X parameter"
:description "Description for X parameter"
:json-schema/default 42}
int?]
[:y int?]]}
:responses {200 {:body [:map [:total int?]]}}
:handler (fn [{{{:keys [x
y]}
:query}
:parameters}]
{:status 200
:body {:total (+ x y)}})}
:post {:summary "plus with malli body parameters"
:parameters {:body [:map
[:x
{:title "X parameter"
:description "Description for X parameter"
:json-schema/default 42}
int?]
[:y int?]]}
:responses {200 {:body [:map [:total int?]]}}
:handler (fn [{{{:keys [x
y]}
:body}
:parameters}]
{:status 200
:body {:total (+ x y)}})}}]]])
(defn reitit-ring-routes
[_config]
[(swagger-ui/create-swagger-ui-handler
{:path "/"
:config {:validatorUrl nil
:operationsSorter "alpha"}})
(reitit.ring/create-resource-handler)
(reitit.ring/create-default-handler)])
(defn reitit-router-config
[_config]
{:exception pretty/exception
:data {:coercion (reitit.coercion.malli/create
{:error-keys #{:coercion
:in
:schema
:value
:errors
:humanized}
:compile mu/closed-schema
:strip-extra-keys true
:default-values true
:options nil})
:muuntaja muuntaja.core/instance
:interceptors [swagger/swagger-feature
(parameters/parameters-interceptor)
(muuntaja/format-negotiate-interceptor)
(muuntaja/format-response-interceptor)
(muuntaja/format-request-interceptor)
(coercion/coerce-response-interceptor)
(coercion/coerce-request-interceptor)
(multipart/multipart-interceptor)]}})
(def config
{:env :dev
:io.pedestal.http/routes []
:io.pedestal.http/type :jetty
:io.pedestal.http/port 3000
:io.pedestal.http/join? false
:io.pedestal.http/secure-headers {:content-security-policy-settings
{:default-src "'self'"
:style-src "'self' 'unsafe-inline'"
:script-src "'self' 'unsafe-inline'"}}
::reitit-routes reitit-routes
::reitit-ring-routes reitit-ring-routes
::reitit-router-config reitit-router-config})
(defn reitit-http-router
[{::keys [reitit-routes
reitit-ring-routes
reitit-router-config]
:as config}]
(reitit.pedestal/routing-interceptor
(reitit.http/router
(reitit-routes config)
(reitit-router-config config))
(->> config
reitit-ring-routes
(apply reitit.ring/routes))))
(defonce server (atom nil))
(defn start
[server
config]
(when @server
(io.pedestal.http/stop @server)
(println "server stopped"))
(-> config
io.pedestal.http/default-interceptors
(reitit.pedestal/replace-last-interceptor (reitit-http-router config))
io.pedestal.http/dev-interceptors
io.pedestal.http/create-server
io.pedestal.http/start
(->> (reset! server)))
(println "server running in port 3000"))
#_(start server config)

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.10.0"]
[io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-pedestal "0.5.15"]
[metosin/reitit "0.5.15"]]
[metosin/reitit-pedestal "0.5.18"]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server})

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.10.0"]
[io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-pedestal "0.5.15"]
[metosin/reitit "0.5.15"]]
[metosin/reitit-pedestal "0.5.18"]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server})

View file

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

View file

@ -2,7 +2,7 @@
:description "Reitit Ring App with Integrant"
:dependencies [[org.clojure/clojure "1.10.1"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.15"]
[metosin/reitit "0.5.18"]
[integrant "0.7.0"]]
:main example.server
:repl-options {:init-ns user}

View file

@ -0,0 +1,11 @@
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.hgignore
.hg/

View file

@ -0,0 +1,23 @@
# reitit-ring, malli, swagger
## Usage
```clj
> lein repl
(start)
```
To test the endpoints using [httpie](https://httpie.org/):
```bash
http GET :3000/math/plus x==1 y==20
http POST :3000/math/plus x:=1 y:=20
http GET :3000/swagger.json
```
<img src="https://raw.githubusercontent.com/metosin/reitit/master/examples/ring-spec-swagger/swagger.png" />
## License
Copyright © 2017-2019 Metosin Oy

View file

@ -0,0 +1,8 @@
(defproject ring-example "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.10.0"]
[metosin/jsonista "0.2.6"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

View file

@ -0,0 +1,123 @@
(ns example.server
(:require [reitit.ring :as ring]
[reitit.coercion.malli]
[reitit.ring.malli]
[reitit.swagger :as swagger]
[reitit.swagger-ui :as swagger-ui]
[reitit.ring.coercion :as coercion]
[reitit.dev.pretty :as pretty]
[reitit.ring.middleware.muuntaja :as muuntaja]
[reitit.ring.middleware.exception :as exception]
[reitit.ring.middleware.multipart :as multipart]
[reitit.ring.middleware.parameters :as parameters]
; [reitit.ring.middleware.dev :as dev]
; [reitit.ring.spec :as spec]
; [spec-tools.spell :as spell]
[ring.adapter.jetty :as jetty]
[muuntaja.core :as m]
[clojure.java.io :as io]
[malli.util :as mu]))
(def app
(ring/ring-handler
(ring/router
[["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "my-api"
:description "with [malli](https://github.com/metosin/malli) and reitit-ring"}
:tags [{:name "files", :description "file api"}
{:name "math", :description "math api"}]}
:handler (swagger/create-swagger-handler)}}]
["/files"
{:swagger {:tags ["files"]}}
["/upload"
{:post {:summary "upload a file"
:parameters {:multipart {:file reitit.ring.malli/temp-file-part}}
:responses {200 {:body {:name :string, :size :int}}}
:handler (fn [{{{:keys [file]} :multipart} :parameters}]
{:status 200
:body {:name (:filename file)
:size (:size file)}})}}]
["/download"
{:get {:summary "downloads a file"
:swagger {:produces ["image/png"]}
:handler (fn [_]
{:status 200
:headers {"Content-Type" "image/png"}
:body (-> "reitit.png"
(io/resource)
(io/input-stream))})}}]]
["/math"
{:swagger {:tags ["math"]}}
["/plus"
{:get {:summary "plus with malli query parameters"
:parameters {:query {:x [:int {:title "X parameter"
:description "Description for X parameter"
:json-schema/default 42}]
:y :int}}
:responses {200 {:body {:total :int}}}
:handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200
:body {:total (+ x y)}})}
:post {:summary "plus with malli body parameters"
:parameters {:body {:x [:int {:title "X parameter"
:description "Description for X parameter"
:json-schema/default 42}]
:y :int}}
:responses {200 {:body {:total :int}}}
:handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200
:body {:total (+ x y)}})}}]]]
{;;:reitit.middleware/transform dev/print-request-diffs ;; pretty diffs
;;:validate spec/validate ;; enable spec validation for route data
;;:reitit.spec/wrap spell/closed ;; strict top-level validation
:exception pretty/exception
:data {:coercion (reitit.coercion.malli/create
{;; set of keys to include in error messages
:error-keys #{#_:type :coercion :in :schema :value :errors :humanized #_:transformed}
;; schema identity function (default: close all map schemas)
:compile mu/closed-schema
;; strip-extra-keys (effects only predefined transformers)
:strip-extra-keys true
;; add/set default values
:default-values true
;; malli options
:options nil})
:muuntaja m/instance
:middleware [;; swagger feature
swagger/swagger-feature
;; query-params & form-params
parameters/parameters-middleware
;; content-negotiation
muuntaja/format-negotiate-middleware
;; encoding response body
muuntaja/format-response-middleware
;; exception handling
exception/exception-middleware
;; decoding request body
muuntaja/format-request-middleware
;; coercing response bodys
coercion/coerce-response-middleware
;; coercing request parameters
coercion/coerce-request-middleware
;; multipart
multipart/multipart-middleware]}})
(ring/routes
(swagger-ui/create-swagger-ui-handler
{:path "/"
:config {:validatorUrl nil
:operationsSorter "alpha"}})
(ring/create-default-handler))))
(defn start []
(jetty/run-jetty #'app {:port 3000, :join? false})
(println "server running in port 3000"))
(comment
(start))

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

View file

@ -0,0 +1,38 @@
(ns example.server-test
(:require [clojure.test :refer [deftest testing is]]
[example.server :refer [app]]
[ring.mock.request :refer [request json-body]]))
(deftest example-server
(testing "GET"
(is (= (-> (request :get "/math/plus?x=20&y=3")
app :body slurp)
(-> {:request-method :get :uri "/math/plus" :query-string "x=20&y=3"}
app :body slurp)
(-> {:request-method :get :uri "/math/plus" :query-params {:x 20 :y 3}}
app :body slurp)
"{\"total\":23}")))
(testing "POST"
(is (= (-> (request :post "/math/plus") (json-body {:x 40 :y 2})
app :body slurp)
(-> {:request-method :post :uri "/math/plus" :body-params {:x 40 :y 2}}
app :body slurp)
"{\"total\":42}")))
(testing "Download"
(is (= (-> {:request-method :get :uri "/files/download"}
app :body (#(slurp % :encoding "ascii")) count) ;; binary
(.length (clojure.java.io/file "resources/reitit.png"))
506325)))
(testing "Upload"
(let [file (clojure.java.io/file "resources/reitit.png")
multipart-temp-file-part {:tempfile file
:size (.length file)
:filename (.getName file)
:content-type "image/png;"}]
(is (= (-> {:request-method :post :uri "/files/upload" :multipart-params {:file multipart-temp-file-part}}
app :body slurp)
"{\"name\":\"reitit.png\",\"size\":506325}")))))

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.10.0"]
[metosin/jsonista "0.2.6"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.15"]]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})

View file

@ -2,6 +2,6 @@
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.15"]]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})

View file

@ -2,5 +2,5 @@
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.15"]]
[metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server})

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -39,38 +39,47 @@
:body (->ParameterCoercion :body-params :body false false)
:form (->ParameterCoercion :form-params :string true true)
:header (->ParameterCoercion :headers :string true true)
:path (->ParameterCoercion :path-params :string true true)})
:path (->ParameterCoercion :path-params :string true true)
:fragment (->ParameterCoercion :fragment-params :string true true)})
(defn ^:no-doc request-coercion-failed! [result coercion value in request]
(defn ^:no-doc request-coercion-failed! [result coercion value in request serialize-failed-result]
(throw
(ex-info
(ex-info
(if serialize-failed-result
(str "Request coercion failed: " (pr-str result))
(merge
(into {} result)
{:type ::request-coercion
:coercion coercion
:value value
:in [:request in]
:request request}))))
"Request coercion failed")
(-> {}
transient
(as-> $ (reduce conj! $ result))
(assoc! :type ::request-coercion)
(assoc! :coercion coercion)
(assoc! :value value)
(assoc! :in [:request in])
(assoc! :request request)
persistent!))))
(defn ^:no-doc response-coercion-failed! [result coercion value request response]
(defn ^:no-doc response-coercion-failed! [result coercion value request response serialize-failed-result]
(throw
(ex-info
(ex-info
(if serialize-failed-result
(str "Response coercion failed: " (pr-str result))
(merge
(into {} result)
{:type ::response-coercion
:coercion coercion
:value value
:in [:response :body]
:request request
:response response}))))
"Response coercion failed")
(-> {}
transient
(as-> $ (reduce conj! $ result))
(assoc! :type ::response-coercion)
(assoc! :coercion coercion)
(assoc! :value value)
(assoc! :in [:response :body])
(assoc! :request request)
(assoc! :response response)
persistent!))))
(defn extract-request-format-default [request]
(-> request :muuntaja/request :format))
;; TODO: support faster key walking, walk/keywordize-keys is quite slow...
(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion]
(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result]
:or {extract-request-format extract-request-format-default
parameter-coercion default-parameter-coercion}}]
(if coercion
@ -83,13 +92,13 @@
format (extract-request-format request)
result (coercer value format)]
(if (error? result)
(request-coercion-failed! result coercion value in request)
(request-coercion-failed! result coercion value in request serialize-failed-result)
result))))))))
(defn extract-response-format-default [request _]
(-> request :muuntaja/response :format))
(defn response-coercer [coercion body {:keys [extract-response-format]
(defn response-coercer [coercion body {:keys [extract-response-format serialize-failed-result]
:or {extract-response-format extract-response-format-default}}]
(if coercion
(if-let [coercer (-response-coercer coercion body)]
@ -98,7 +107,7 @@
value (:body response)
result (coercer value format)]
(if (error? result)
(response-coercion-failed! result coercion value request response)
(response-coercion-failed! result coercion value request response serialize-failed-result)
result))))))
(defn encode-error [data]
@ -109,9 +118,9 @@
(defn coerce-request [coercers request]
(reduce-kv
(fn [acc k coercer]
(impl/fast-assoc acc k (coercer request)))
{} coercers))
(fn [acc k coercer]
(impl/fast-assoc acc k (coercer request)))
{} coercers))
(defn coerce-response [coercers request response]
(if response
@ -147,13 +156,13 @@
:multipart :formData}]
(case specification
:swagger (->> (update
data
:parameters
(fn [parameters]
(->> parameters
(map (fn [[k v]] [(swagger-parameter k) v]))
(filter first)
(into {}))))
data
:parameters
(fn [parameters]
(->> parameters
(map (fn [[k v]] [(swagger-parameter k) v]))
(filter first)
(into {}))))
(-get-apidocs coercion specification)))))
;;

View file

@ -1,6 +1,6 @@
(ns reitit.core
(:require [reitit.impl :as impl]
[reitit.exception :as exception]
(:require [reitit.exception :as exception]
[reitit.impl :as impl]
[reitit.trie :as trie]))
;;
@ -61,7 +61,7 @@
(if-not (partial-match? match)
match
(impl/throw-on-missing-path-params
(:template match) (:required match) path-params)))))
(:template match) (:required match) path-params)))))
(defn match->path
([match]
@ -87,15 +87,15 @@
(let [compiler (::trie/trie-compiler opts (trie/compiler))
names (impl/find-names compiled-routes opts)
[pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p opts)
f #(if-let [path (impl/path-for route %)]
(->Match p data result (impl/url-decode-coll %) path)
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
[(conj pl (-> (trie/insert nil p (->Match p data result nil nil) opts) (trie/compile)))
(if name (assoc nl name f) nl)]))
[[] {}]
compiled-routes)
(fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p opts)
f #(if-let [path (impl/path-for route %)]
(->Match p data result (impl/url-decode-coll %) path)
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
[(conj pl (-> (trie/insert nil p (->Match p data result nil nil) opts) (trie/compile)))
(if name (assoc nl name f) nl)]))
[[] {}]
compiled-routes)
lookup (impl/fast-map nl)
matcher (trie/linear-matcher compiler pl true)
match-by-path (trie/path-matcher matcher compiler)
@ -103,16 +103,11 @@
^{:type ::router}
(reify
Router
(router-name [_]
:linear-router)
(routes [_]
routes)
(compiled-routes [_]
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(router-name [_] :linear-router)
(routes [_] routes)
(compiled-routes [_] compiled-routes)
(options [_] opts)
(route-names [_] names)
(match-by-path [_ path]
(if-let [match (match-by-path path)]
(-> (:data match)
@ -133,33 +128,28 @@
([compiled-routes opts]
(when-let [wilds (seq (filter (impl/->wild-route? opts) compiled-routes))]
(exception/fail!
(str "can't create :lookup-router with wildcard routes: " wilds)
{:wilds wilds
:routes compiled-routes}))
(str "can't create :lookup-router with wildcard routes: " wilds)
{:wilds wilds
:routes compiled-routes}))
(let [names (impl/find-names compiled-routes opts)
[pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]]
[(assoc pl p (->Match p data result {} p))
(if name
(assoc nl name #(->Match p data result % p))
nl)])
[{} {}]
compiled-routes)
(fn [[pl nl] [p {:keys [name] :as data} result]]
[(assoc pl p (->Match p data result {} p))
(if name
(assoc nl name #(->Match p data result % p))
nl)])
[{} {}]
compiled-routes)
data (impl/fast-map pl)
lookup (impl/fast-map nl)
routes (impl/uncompile-routes compiled-routes)]
^{:type ::router}
(reify Router
(router-name [_]
:lookup-router)
(routes [_]
routes)
(compiled-routes [_]
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(router-name [_] :lookup-router)
(routes [_] routes)
(compiled-routes [_] compiled-routes)
(options [_] opts)
(route-names [_] names)
(match-by-path [_ path]
(impl/fast-get data path))
(match-by-name [_ name]
@ -183,34 +173,29 @@
(let [compiler (::trie/trie-compiler opts (trie/compiler))
names (impl/find-names compiled-routes opts)
[pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p opts)
f #(if-let [path (impl/path-for route %)]
(->Match p data result (impl/url-decode-coll %) path)
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
[(trie/insert pl p (->Match p data result nil nil) opts)
(if name (assoc nl name f) nl)]))
[nil {}]
compiled-routes)
(fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p opts)
f #(if-let [path (impl/path-for route %)]
(->Match p data result (impl/url-decode-coll %) path)
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
[(trie/insert pl p (->Match p data result nil nil) opts)
(if name (assoc nl name f) nl)]))
[nil {}]
compiled-routes)
matcher (trie/compile pl compiler)
match-by-path (trie/path-matcher matcher compiler)
match-by-path (if matcher (trie/path-matcher matcher compiler))
lookup (impl/fast-map nl)
routes (impl/uncompile-routes compiled-routes)]
^{:type ::router}
(reify
Router
(router-name [_]
:trie-router)
(routes [_]
routes)
(compiled-routes [_]
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(router-name [_] :trie-router)
(routes [_] routes)
(compiled-routes [_] compiled-routes)
(options [_] opts)
(route-names [_] names)
(match-by-path [_ path]
(if-let [match (match-by-path path)]
(if-let [match (and match-by-path (match-by-path path))]
(-> (:data match)
(assoc :path-params (:params match))
(assoc :path path))))
@ -229,8 +214,8 @@
([compiled-routes opts]
(when (or (not= (count compiled-routes) 1) (some (impl/->wild-route? opts) compiled-routes))
(exception/fail!
(str ":single-static-path-router requires exactly 1 static route: " compiled-routes)
{:routes compiled-routes}))
(str ":single-static-path-router requires exactly 1 static route: " compiled-routes)
{:routes compiled-routes}))
(let [[n :as names] (impl/find-names compiled-routes opts)
[[p data result]] compiled-routes
p #?(:clj (.intern ^String p) :cljs p)
@ -238,25 +223,17 @@
routes (impl/uncompile-routes compiled-routes)]
^{:type ::router}
(reify Router
(router-name [_]
:single-static-path-router)
(routes [_]
routes)
(compiled-routes [_]
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(router-name [_] :single-static-path-router)
(routes [_] routes)
(compiled-routes [_] compiled-routes)
(options [_] opts)
(route-names [_] names)
(match-by-path [_ path]
(if (#?(:clj .equals :cljs =) p path)
match))
(if (#?(:clj .equals :cljs =) p path) match))
(match-by-name [_ name]
(if (= n name)
match))
(if (= n name) match))
(match-by-name [_ name path-params]
(if (= n name)
(impl/fast-assoc match :path-params (impl/path-params path-params))))))))
(if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params))))))))
(defn mixed-router
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
@ -274,16 +251,11 @@
routes (impl/uncompile-routes compiled-routes)]
^{:type ::router}
(reify Router
(router-name [_]
:mixed-router)
(routes [_]
routes)
(compiled-routes [_]
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(router-name [_] :mixed-router)
(routes [_] routes)
(compiled-routes [_] compiled-routes)
(options [_] opts)
(route-names [_] names)
(match-by-path [_ path]
(or (match-by-path static-router path)
(match-by-path wildcard-router path)))
@ -310,16 +282,11 @@
routes (impl/uncompile-routes compiled-routes)]
^{:type ::router}
(reify Router
(router-name [_]
:quarantine-router)
(routes [_]
routes)
(compiled-routes [_]
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(router-name [_] :quarantine-router)
(routes [_] routes)
(compiled-routes [_] compiled-routes)
(options [_] opts)
(route-names [_] names)
(match-by-path [_ path]
(or (match-by-path mixed-router path)
(match-by-path linear-router path)))

View file

@ -10,8 +10,8 @@
(map (fn [provide]
(when (contains? acc provide)
(exception/fail!
(str "multiple providers for: " provide)
{::multiple-providers provide}))
(str "multiple providers for: " provide)
{::multiple-providers provide}))
[provide dependent]))
(get-provides dependent)))
{} nodes))
@ -22,8 +22,8 @@
(if (contains? providers k)
(get providers k)
(exception/fail!
(str "provider missing for dependency: " k)
{::missing-provider k})))
(str "provider missing for dependency: " k)
{::missing-provider k})))
(defn post-order
"Put `nodes` in post-order. Can also be described as a reverse topological sort.

View file

@ -31,20 +31,20 @@
path " " (not-empty (select-keys route-data [:conflicting]))))]
(apply str "Router contains conflicting route paths:\n\n"
(mapv
(fn [[[path route-data] vals]]
(str (resolve-str path route-data)
"\n"
(str/join "\n" (mapv (fn [[path route-data]]
(resolve-str path route-data)) vals))
"\n\n"))
conflicts))))
(fn [[[path route-data] vals]]
(str (resolve-str path route-data)
"\n"
(str/join "\n" (mapv (fn [[path route-data]]
(resolve-str path route-data)) vals))
"\n\n"))
conflicts))))
(defmethod format-exception :name-conflicts [_ _ conflicts]
(apply str "Router contains conflicting route names:\n\n"
(mapv
(fn [[name vals]]
(str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n"))
conflicts)))
(fn [[name vals]]
(str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n"))
conflicts)))
(defmethod format-exception :reitit.impl/merge-data [_ _ data]
(str "Error merging route-data\n\n" (pr-str data)))

View file

@ -1,13 +1,13 @@
(ns ^:no-doc reitit.impl
#?(:cljs (:require-macros [reitit.impl]))
(:require [clojure.string :as str]
[clojure.set :as set]
(:require [clojure.set :as set]
[clojure.string :as str]
[meta-merge.core :as mm]
[reitit.trie :as trie]
[reitit.exception :as ex])
[reitit.exception :as ex]
[reitit.trie :as trie])
#?(:clj
(:import (java.util HashMap Map)
(java.net URLEncoder URLDecoder))))
(:import (java.net URLEncoder URLDecoder)
(java.util HashMap Map))))
(defn parse [path opts]
(let [path #?(:clj (.intern ^String (trie/normalize path opts)) :cljs (trie/normalize path opts))
@ -28,59 +28,59 @@
Also works on vectors. Maintains key for maps, order for vectors."
[f coll]
(reduce-kv
(fn [coll k v]
(if-some [v' (f v)]
(assoc coll k v')
coll))
coll
coll))
(fn [coll k v]
(if-some [v' (f v)]
(assoc coll k v')
coll))
coll
coll))
(defn walk [raw-routes {:keys [path data routes expand]
:or {data [], routes []}
:as opts}]
(letfn
[(walk-many [p m r]
(reduce #(into %1 (walk-one p m %2)) [] r))
(walk-one [pacc macc routes]
(if (vector? (first routes))
(walk-many pacc macc routes)
(when (string? (first routes))
(let [[path & [maybe-arg :as args]] routes
[data childs] (if (or (vector? maybe-arg)
(and (sequential? maybe-arg)
(sequential? (first maybe-arg)))
(nil? maybe-arg))
[{} args]
[maybe-arg (rest args)])
macc (into macc (expand data opts))
child-routes (walk-many (str pacc path) macc (keep identity childs))]
(if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))]
[(walk-many [p m r]
(reduce #(into %1 (walk-one p m %2)) [] r))
(walk-one [pacc macc routes]
(if (vector? (first routes))
(walk-many pacc macc routes)
(when (string? (first routes))
(let [[path & [maybe-arg :as args]] routes
[data childs] (if (or (vector? maybe-arg)
(and (sequential? maybe-arg)
(sequential? (first maybe-arg)))
(nil? maybe-arg))
[{} args]
[maybe-arg (rest args)])
macc (into macc (expand data opts))
child-routes (walk-many (str pacc path) macc (keep identity childs))]
(if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))]
(walk-one path (mapv identity data) raw-routes)))
(defn map-data [f routes]
(mapv (fn [[p ds]] [p (f p ds)]) routes))
(defn merge-data [p x]
(defn merge-data [{:keys [meta-merge-fn] :as g} p x]
(reduce
(fn [acc [k v]]
(try
(mm/meta-merge acc {k v})
(catch #?(:clj Exception, :cljs js/Error) e
(ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e}))))
{} x))
(fn [acc [k v]]
(try
((or meta-merge-fn mm/meta-merge) acc {k v})
(catch #?(:clj Exception, :cljs js/Error) e
(ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e}))))
{} x))
(defn resolve-routes [raw-routes {:keys [coerce] :as opts}]
(cond->> (->> (walk raw-routes opts) (map-data merge-data))
coerce (into [] (keep #(coerce % opts)))))
(cond->> (->> (walk raw-routes opts) (map-data #(merge-data opts %1 %2)))
coerce (into [] (keep #(coerce % opts)))))
(defn path-conflicting-routes [routes opts]
(let [parts-and-routes (mapv (fn [[s :as r]] [(trie/split-path s opts) r]) routes)]
(-> (into {} (comp (map-indexed (fn [index [p r]]
[r (reduce
(fn [acc [p' r']]
(if (trie/conflicting-parts? p p')
(conj acc r') acc))
#{} (subvec parts-and-routes (inc index)))]))
(fn [acc [p' r']]
(if (trie/conflicting-parts? p p')
(conj acc r') acc))
#{} (subvec parts-and-routes (inc index)))]))
(filter (comp seq second))) parts-and-routes)
(not-empty))))
@ -123,13 +123,13 @@
(defn path-for [route path-params]
(if (:path-params route)
(if-let [parts (reduce
(fn [acc part]
(if (string? part)
(conj acc part)
(if-let [p (get path-params (:value part))]
(conj acc p)
(reduced nil))))
[] (:path-parts route))]
(fn [acc part]
(if (string? part)
(conj acc part)
(if-let [p (get path-params (:value part))]
(conj acc p)
(reduced nil))))
[] (:path-parts route))]
(apply str parts))
(:path route)))
@ -138,8 +138,8 @@
(let [defined (-> path-params keys set)
missing (set/difference required defined)]
(ex/fail!
(str "missing path-params for route " template " -> " missing)
{:path-params path-params, :required required}))))
(str "missing path-params for route " template " -> " missing)
{:path-params path-params, :required required}))))
(defn fast-assoc
#?@(:clj [[^clojure.lang.Associative a k v] (.assoc a k v)]
@ -178,10 +178,10 @@
(if s
#?(:clj (if (.contains ^String s "%")
(URLDecoder/decode
(if (.contains ^String s "+")
(.replace ^String s "+" "%2B")
^String s)
"UTF-8"))
(if (.contains ^String s "+")
(.replace ^String s "+" "%2B")
^String s)
"UTF-8"))
:cljs (js/decodeURIComponent s))))
(defn url-decode [s]
@ -249,6 +249,10 @@
(->> params
(map (fn [[k v]]
(if (or (sequential? v) (set? v))
(str/join "&" (map query-parameter (repeat k) v))
(if (seq v)
(str/join "&" (map query-parameter (repeat k) v))
;; Empty seq results in single & character in the query string.
;; Handle as empty string to behave similarly as when the value is nil.
(query-parameter k ""))
(query-parameter k v))))
(str/join "&")))

View file

@ -1,9 +1,9 @@
(ns reitit.interceptor
(:require [meta-merge.core :refer [meta-merge]]
[clojure.pprint :as pprint]
(:require [clojure.pprint :as pprint]
[meta-merge.core :refer [meta-merge]]
[reitit.core :as r]
[reitit.impl :as impl]
[reitit.exception :as exception]))
[reitit.exception :as exception]
[reitit.impl :as impl]))
(defprotocol IntoInterceptor
(into-interceptor [this data opts]))
@ -42,25 +42,25 @@
(if-let [interceptor (if registry (registry this))]
(into-interceptor interceptor data opts)
(throw
(ex-info
(str
"Interceptor " this " not found in registry.\n\n"
(if (seq registry)
(str
"Available interceptors in registry:\n"
(with-out-str
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
"see [reitit.interceptor/router] on how to add interceptor to the registry.\n") "\n")
{:id this
:registry registry}))))
(ex-info
(str
"Interceptor " this " not found in registry.\n\n"
(if (seq registry)
(str
"Available interceptors in registry:\n"
(with-out-str
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
"see [reitit.interceptor/router] on how to add interceptor to the registry.\n") "\n")
{:id this
:registry registry}))))
#?(:clj clojure.lang.APersistentVector
:cljs cljs.core.PersistentVector)
(into-interceptor [[f & args :as form] data opts]
(when (and (seq args) (not (fn? f)))
(exception/fail!
(str "Invalid Interceptor form: " form "")
{:form form}))
(str "Invalid Interceptor form: " form "")
{:form form}))
(into-interceptor (apply f args) data opts))
#?(:clj clojure.lang.Fn
@ -91,13 +91,13 @@
opts (assoc opts ::compiled (inc ^long compiled))]
(when (>= ^long compiled ^long *max-compile-depth*)
(exception/fail!
(str "Too deep Interceptor compilation - " compiled)
{:this this, :data data, :opts opts}))
(str "Too deep Interceptor compilation - " compiled)
{:this this, :data data, :opts opts}))
(if-let [interceptor (into-interceptor (compile data opts) data opts)]
(map->Interceptor
(merge
(dissoc this :compile)
(impl/strip-nils interceptor)))))))
(merge
(dissoc this :compile)
(impl/strip-nils interceptor)))))))
nil
(into-interceptor [_ _ _]))
@ -127,9 +127,9 @@
([[_ {:keys [interceptors handler] :as data}] {::keys [queue] :as opts} _]
(let [chain (chain (into (vec interceptors) [handler]) data opts)]
(map->Endpoint
{:interceptors chain
:queue ((or queue identity) chain)
:data data}))))
{:interceptors chain
:queue ((or queue identity) chain)
:data data}))))
(defn transform-butlast
"Returns a function to that takes a interceptor transformation function and
@ -137,8 +137,8 @@
[f]
(fn [interceptors]
(concat
(f (butlast interceptors))
[(last interceptors)])))
(f (butlast interceptors))
[(last interceptors)])))
(defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
@ -160,8 +160,8 @@
:handler get-user}]])"
([data]
(router data nil))
([data opts]
(let [opts (meta-merge {:compile compile-result} opts)]
([data {:keys [meta-merge-fn] :as opts}]
(let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
(r/router data opts))))
(defn interceptor-handler [router]

View file

@ -1,9 +1,9 @@
(ns reitit.middleware
(:require [meta-merge.core :refer [meta-merge]]
[clojure.pprint :as pprint]
(:require [clojure.pprint :as pprint]
[meta-merge.core :refer [meta-merge]]
[reitit.core :as r]
[reitit.impl :as impl]
[reitit.exception :as exception]))
[reitit.exception :as exception]
[reitit.impl :as impl]))
(defprotocol IntoMiddleware
(into-middleware [this data opts]))
@ -21,17 +21,17 @@
(if-let [middleware (if registry (registry this))]
(into-middleware middleware data opts)
(throw
(ex-info
(str
"Middleware " this " not found in registry.\n\n"
(if (seq registry)
(str
"Available middleware in registry:\n"
(with-out-str
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
"see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n")
{:id this
:registry registry}))))
(ex-info
(str
"Middleware " this " not found in registry.\n\n"
(if (seq registry)
(str
"Available middleware in registry:\n"
(with-out-str
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
"see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n")
{:id this
:registry registry}))))
#?(:clj clojure.lang.APersistentVector
:cljs cljs.core.PersistentVector)
@ -43,7 +43,7 @@
:cljs function)
(into-middleware [this _ _]
(map->Middleware
{:wrap this}))
{:wrap this}))
#?(:clj clojure.lang.PersistentArrayMap
:cljs cljs.core.PersistentArrayMap)
@ -63,13 +63,13 @@
opts (assoc opts ::compiled (inc ^long compiled))]
(when (>= ^long compiled ^long *max-compile-depth*)
(exception/fail!
(str "Too deep Middleware compilation - " compiled)
{:this this, :data data, :opts opts}))
(str "Too deep Middleware compilation - " compiled)
{:this this, :data data, :opts opts}))
(if-let [middeware (into-middleware (compile data opts) data opts)]
(map->Middleware
(merge
(dissoc this :compile)
(impl/strip-nils middeware)))))))
(merge
(dissoc this :compile)
(impl/strip-nils middeware)))))))
nil
(into-middleware [_ _ _]))
@ -77,10 +77,10 @@
(defn- ensure-handler! [path data scope]
(when-not (:handler data)
(exception/fail!
(str "path \"" path "\" doesn't have a :handler defined"
(if scope (str " for " scope)))
(merge {:path path, :data data}
(if scope {:scope scope})))))
(str "path \"" path "\" doesn't have a :handler defined"
(if scope (str " for " scope)))
(merge {:path path, :data data}
(if scope {:scope scope})))))
(defn- expand-and-transform
[middleware data {::keys [transform] :or {transform identity} :as opts}]
@ -116,9 +116,9 @@
(ensure-handler! path data scope)
(let [middleware (expand-and-transform middleware data opts)]
(map->Endpoint
{:handler (compile-handler middleware handler)
:middleware middleware
:data data}))))
{:handler (compile-handler middleware handler)
:middleware middleware
:data data}))))
(defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
@ -138,8 +138,8 @@
:handler get-user}]])"
([data]
(router data nil))
([data opts]
(let [opts (meta-merge {:compile compile-result} opts)]
([data {:keys [meta-merge-fn] :as opts}]
(let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
(r/router data opts))))
(defn middleware-handler [router]

View file

@ -1,8 +1,8 @@
(ns reitit.spec
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]
[reitit.exception :as exception]
[reitit.core :as r]))
[reitit.core :as r]
[reitit.exception :as exception]))
;;
;; routes
@ -16,9 +16,9 @@
(s/def ::raw-route
(s/nilable
(s/cat :path ::path
:arg (s/? ::arg)
:childs (s/* (s/and (s/nilable ::raw-routes))))))
(s/cat :path ::path
:arg (s/? ::arg)
:childs (s/* (s/and (s/nilable ::raw-routes))))))
(s/def ::raw-routes
(s/or :route ::raw-route
@ -60,19 +60,19 @@
(s/def ::opts
(s/nilable
(s/keys :opt-un [:reitit.router/path
:reitit.router/routes
:reitit.router/data
:reitit.router/expand
:reitit.router/coerce
:reitit.router/compile
:reitit.router/conflicts
:reitit.router/router])))
(s/keys :opt-un [:reitit.router/path
:reitit.router/routes
:reitit.router/data
:reitit.router/expand
:reitit.router/coerce
:reitit.router/compile
:reitit.router/conflicts
:reitit.router/router])))
(s/fdef r/router
:args (s/or :1arity (s/cat :data (s/spec ::raw-routes))
:2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts))
:ret ::router)
:args (s/or :1arity (s/cat :data (s/spec ::raw-routes))
:2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts))
:ret ::router)
;;
;; coercion
@ -119,24 +119,25 @@
(defrecord Problem [path scope data spec problems])
(defn validate-route-data [routes wrap spec]
(let [spec (wrap spec)]
(some->> (for [[p d _] routes]
(when-let [problems (and spec (s/explain-data spec d))]
(->Problem p nil d spec problems)))
(keep identity) (seq) (vec))))
(let [spec (wrap spec)
spec-explain (fn [[p d _]]
(when-let [problems (and spec (s/explain-data spec d))]
(->Problem p nil d spec problems)))
errors (into [] (keep spec-explain) routes)]
(when (pos? (count errors)) errors)))
(defn validate [routes {:keys [spec] ::keys [wrap] :or {spec ::default-data, wrap identity}}]
(when-let [problems (validate-route-data routes wrap spec)]
(exception/fail!
::invalid-route-data
{:problems problems})))
::invalid-route-data
{:problems problems})))
(defmethod exception/format-exception :reitit.spec/invalid-route-data [_ _ {:keys [problems]}]
(apply str "Invalid route data:\n\n"
(mapv
(fn [{:keys [path scope data spec]}]
(str "-- On route -----------------------\n\n"
(pr-str path) (if scope (str " " (pr-str scope))) "\n\n"
(pr-str data) "\n\n"
(s/explain-str spec data) "\n"))
problems)))
(fn [{:keys [path scope data spec]}]
(str "-- On route -----------------------\n\n"
(pr-str path) (if scope (str " " (pr-str scope))) "\n\n"
(pr-str data) "\n\n"
(s/explain-str spec data) "\n"))
problems)))

View file

@ -2,8 +2,8 @@
(:refer-clojure :exclude [compile])
(:require [clojure.string :as str]
[reitit.exception :as ex])
#?(:clj (:import [reitit Trie Trie$Match Trie$Matcher]
(java.net URLDecoder))))
#?(:clj (:import (java.net URLDecoder)
[reitit Trie Trie$Match Trie$Matcher])))
(defn ^:no-doc into-set [x]
(cond
@ -90,12 +90,12 @@
(defn join-path [xs]
(reduce
(fn [s x]
(str s (cond
(string? x) x
(instance? Wild x) (str "{" (-> x :value str (subs 1)) "}")
(instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}"))))
"" xs))
(fn [s x]
(str s (cond
(string? x) x
(instance? Wild x) (str "{" (-> x :value str (subs 1)) "}")
(instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}"))))
"" xs))
(defn normalize [s opts]
(-> s (split-path opts) (join-path)))
@ -172,25 +172,25 @@
:else
(or
(reduce
(fn [_ [p n]]
(if-let [cp (common-prefix p path)]
(if (= cp p)
(reduce
(fn [_ [p n]]
(if-let [cp (common-prefix p path)]
(if (= cp p)
;; insert into child node
(let [n' (-insert n (conj ps (subs path (count p))) fp params data)]
(reduced (assoc-in node [:children p] n')))
(let [n' (-insert n (conj ps (subs path (count p))) fp params data)]
(reduced (assoc-in node [:children p] n')))
;; split child node
(let [rp (subs p (count cp))
rp' (subs path (count cp))
n' (-insert (-node {}) ps fp params data)
n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)]
(reduced (update node :children (fn [children]
(-> children
(dissoc p)
(assoc cp n'')))))))))
nil (:children node))
(let [rp (subs p (count cp))
rp' (subs path (count cp))
n' (-insert (-node {}) ps fp params data)
n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)]
(reduced (update node :children (fn [children]
(-> children
(dissoc p)
(assoc cp n'')))))))))
nil (:children node))
;; new child node
(assoc-in node [:children path] (-insert (-node {}) ps fp params data))))]
(assoc-in node [:children path] (-insert (-node {}) ps fp params data))))]
(if-let [child (get-in node' [:children ""])]
;; optimize by removing empty paths
(-> (merge-with merge (dissoc node' :data) child)
@ -202,10 +202,10 @@
(if percent?
#?(:cljs (js/decodeURIComponent param)
:clj (URLDecoder/decode
(if (.contains ^String param "+")
(.replace ^String param "+" "%2B")
param)
"UTF-8"))
(if (.contains ^String param "+")
(.replace ^String param "+" "%2B")
param)
"UTF-8"))
param)))
;;
@ -313,13 +313,13 @@
(def record-parameters
"Memoized function to transform parameters into runtime generated Record."
(memoize
(fn [keys]
(if (some qualified-keyword? keys)
(map-parameters keys)
(let [sym (gensym "PathParams")
ctor (symbol (str "map->" sym))]
(binding [*ns* (find-ns 'user)]
(eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {}))))))))))
(fn [keys]
(if (some qualified-keyword? keys)
(map-parameters keys)
(let [sym (gensym "PathParams")
ctor (symbol (str "map->" sym))]
(binding [*ns* (find-ns 'user)]
(eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {}))))))))))
(defn insert
"Returns a trie with routes added to it."
@ -327,9 +327,9 @@
(insert nil routes))
([node routes]
(reduce
(fn [acc [p d]]
(insert acc p d))
node routes))
(fn [acc [p d]]
(insert acc p d))
node routes))
([node path data]
(insert node path data nil))
([node path data {::keys [parameters] :or {parameters map-parameters} :as opts}]
@ -355,17 +355,16 @@
(cond-> data (conj (data-matcher compiler params data)))
(into (for [[p c] children] (static-matcher compiler p (compile c compiler (conj cp p)))))
(into
(for [[p c] wilds]
(let [pv (:value p)
ends (ends c)]
(if (next ends)
(ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))})
(wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv)))))))
(for [[p c] wilds]
(let [pv (:value p)
ends (ends c)]
(if (next ends)
(ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))})
(wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv)))))))
(into (for [[p c] catch-all] (catch-all-matcher compiler (:value p) params (:data c)))))]
(cond
(> (count matchers) 1) (linear-matcher compiler matchers false)
(= (count matchers) 1) (first matchers)
:else (data-matcher compiler {} nil)))))
(= (count matchers) 1) (first matchers)))))
(defn pretty
"Returns a simplified EDN structure of a compiled trie for printing purposes."
@ -387,61 +386,61 @@
(comment
(->
[["/v2/whoami" 1]
["/v2/users/:user-id/datasets" 2]
["/v2/public/projects/:project-id/datasets" 3]
["/v1/public/topics/:topic" 4]
["/v1/users/:user-id/orgs/:org-id" 5]
["/v1/search/topics/:term" 6]
["/v1/users/:user-id/invitations" 7]
["/v1/users/:user-id/topics" 9]
["/v1/users/:user-id/bookmarks/followers" 10]
["/v2/datasets/:dataset-id" 11]
["/v1/orgs/:org-id/usage-stats" 12]
["/v1/orgs/:org-id/devices/:client-id" 13]
["/v1/messages/user/:user-id" 14]
["/v1/users/:user-id/devices" 15]
["/v1/public/users/:user-id" 16]
["/v1/orgs/:org-id/errors" 17]
["/v1/public/orgs/:org-id" 18]
["/v1/orgs/:org-id/invitations" 19]
["/v1/users/:user-id/device-errors" 22]
["/v2/login" 23]
["/v1/users/:user-id/usage-stats" 24]
["/v2/users/:user-id/devices" 25]
["/v1/users/:user-id/claim-device/:client-id" 26]
["/v2/public/projects/:project-id" 27]
["/v2/public/datasets/:dataset-id" 28]
["/v2/users/:user-id/topics/bulk" 29]
["/v1/messages/device/:client-id" 30]
["/v1/users/:user-id/owned-orgs" 31]
["/v1/topics/:topic" 32]
["/v1/users/:user-id/bookmark/:topic" 33]
["/v1/orgs/:org-id/members/:user-id" 34]
["/v1/users/:user-id/devices/:client-id" 35]
["/v1/users/:user-id" 36]
["/v1/orgs/:org-id/devices" 37]
["/v1/orgs/:org-id/members" 38]
["/v2/orgs/:org-id/topics" 40]
["/v1/whoami" 41]
["/v1/orgs/:org-id" 42]
["/v1/users/:user-id/api-key" 43]
["/v2/schemas" 44]
["/v2/users/:user-id/topics" 45]
["/v1/orgs/:org-id/confirm-membership/:token" 46]
["/v2/topics/:topic" 47]
["/v1/messages/topic/:topic" 48]
["/v1/users/:user-id/devices/:client-id/reset-password" 49]
["/v2/topics" 50]
["/v1/login" 51]
["/v1/users/:user-id/orgs" 52]
["/v2/public/messages/dataset/:dataset-id" 53]
["/v1/topics" 54]
["/v1/orgs" 55]
["/v1/users/:user-id/bookmarks" 56]
["/v1/orgs/:org-id/topics" 57]
["/command1 {arg1} {arg2}" ::cmd1]
["/command2 {arg1} {arg2} {arg3}" ::cmd2]]
(insert)
(compile)
(pretty)))
[["/v2/whoami" 1]
["/v2/users/:user-id/datasets" 2]
["/v2/public/projects/:project-id/datasets" 3]
["/v1/public/topics/:topic" 4]
["/v1/users/:user-id/orgs/:org-id" 5]
["/v1/search/topics/:term" 6]
["/v1/users/:user-id/invitations" 7]
["/v1/users/:user-id/topics" 9]
["/v1/users/:user-id/bookmarks/followers" 10]
["/v2/datasets/:dataset-id" 11]
["/v1/orgs/:org-id/usage-stats" 12]
["/v1/orgs/:org-id/devices/:client-id" 13]
["/v1/messages/user/:user-id" 14]
["/v1/users/:user-id/devices" 15]
["/v1/public/users/:user-id" 16]
["/v1/orgs/:org-id/errors" 17]
["/v1/public/orgs/:org-id" 18]
["/v1/orgs/:org-id/invitations" 19]
["/v1/users/:user-id/device-errors" 22]
["/v2/login" 23]
["/v1/users/:user-id/usage-stats" 24]
["/v2/users/:user-id/devices" 25]
["/v1/users/:user-id/claim-device/:client-id" 26]
["/v2/public/projects/:project-id" 27]
["/v2/public/datasets/:dataset-id" 28]
["/v2/users/:user-id/topics/bulk" 29]
["/v1/messages/device/:client-id" 30]
["/v1/users/:user-id/owned-orgs" 31]
["/v1/topics/:topic" 32]
["/v1/users/:user-id/bookmark/:topic" 33]
["/v1/orgs/:org-id/members/:user-id" 34]
["/v1/users/:user-id/devices/:client-id" 35]
["/v1/users/:user-id" 36]
["/v1/orgs/:org-id/devices" 37]
["/v1/orgs/:org-id/members" 38]
["/v2/orgs/:org-id/topics" 40]
["/v1/whoami" 41]
["/v1/orgs/:org-id" 42]
["/v1/users/:user-id/api-key" 43]
["/v2/schemas" 44]
["/v2/users/:user-id/topics" 45]
["/v1/orgs/:org-id/confirm-membership/:token" 46]
["/v2/topics/:topic" 47]
["/v1/messages/topic/:topic" 48]
["/v1/users/:user-id/devices/:client-id/reset-password" 49]
["/v2/topics" 50]
["/v1/login" 51]
["/v1/users/:user-id/orgs" 52]
["/v2/public/messages/dataset/:dataset-id" 53]
["/v1/topics" 54]
["/v1/orgs" 55]
["/v1/users/:user-id/bookmarks" 56]
["/v1/orgs/:org-id/topics" 57]
["/command1 {arg1} {arg2}" ::cmd1]
["/command2 {arg1} {arg2} {arg3}" ::cmd2]]
(insert)
(compile)
(pretty)))

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -1,18 +1,16 @@
(ns reitit.dev.pretty
(:require [clojure.string :as str]
(:require [arrangement.core] ;; spell-spec
[clojure.spec.alpha :as s]
[reitit.exception :as exception]
[arrangement.core]
;; spell-spec
[spell-spec.expound]
;; expound
[clojure.string :as str]
[expound.alpha] ;; fipp
[expound.ansi]
[expound.alpha]
;; fipp
[fipp.visit]
[fipp.edn]
[fipp.ednize]
[fipp.engine]))
[fipp.engine]
[fipp.visit]
[reitit.exception :as exception]
[spell-spec.expound] ;; expound
))
;;
;; colors
@ -152,13 +150,13 @@
(printer nil))
([options]
(map->EdnPrinter
(merge
{:width 80
:symbols {}
:print-length *print-length*
:print-level *print-level*
:print-meta *print-meta*}
options))))
(merge
{:width 80
:symbols {}
:print-length *print-length*
:print-level *print-level*
:print-meta *print-meta*}
options))))
(defn pprint
([x] (pprint x {}))
@ -209,13 +207,13 @@
(defn exception-str [message source printer]
(with-out-str
(print-doc
[:group
(title "Router creation failed" source printer)
[:break] [:break]
message
[:break]
(footer printer)]
printer)))
[:group
(title "Router creation failed" source printer)
[:break] [:break]
message
[:break]
(footer printer)]
printer)))
(defmulti format-exception (fn [type _ _] type))
@ -231,11 +229,11 @@
(defn de-expound-colors [^String s mappings]
(let [s' (reduce
(fn [s [from to]]
(.replace ^String s
^String (expound.ansi/esc [from])
^String (-start (colors to))))
s mappings)]
(fn [s [from to]]
(.replace ^String s
^String (expound.ansi/esc [from])
^String (-start (colors to))))
s mappings)]
(.replace ^String s'
^String (expound.ansi/esc [:none])
(str (expound.ansi/esc [:none]) (-start (colors :text))))))
@ -254,9 +252,9 @@
(def expound-printer
(expound.alpha/custom-printer
{:theme :figwheel-theme
:show-valid-values? false
:print-specs? false}))
{:theme :figwheel-theme
:show-valid-values? false
:print-specs? false}))
;;
;; Formatters
@ -276,18 +274,18 @@
" ")
(edn (not-empty (select-keys route-data [:conflicting])))])]
(into
[:group]
(mapv
(fn [[[path route-data] vals]]
[:group
(path-report path route-data)
(into
[:group]
(map
(fn [[path route-data]] (path-report path route-data))
vals))
[:break]])
conflicts)))
[:group]
(mapv
(fn [[[path route-data] vals]]
[:group
(path-report path route-data)
(into
[:group]
(map
(fn [[path route-data]] (path-report path route-data))
vals))
[:break]])
conflicts)))
[:span (text "Either fix the conflicting paths or disable the conflict resolution")
[:break] (text "by setting route data for conflicting route: ") [:break] [:break]
(edn {:conflicting true} {:margin 3})
@ -302,19 +300,19 @@
(text "Router contains conflicting route names:")
[:break] [:break]
(into
[:group]
(mapv
(fn [[name vals]]
[:group
[:span (text name)]
[:break]
(into
[:group]
(map
(fn [p] [:span (color :grey "-> " p) [:break]])
(mapv first vals)))
[:break]])
conflicts))
[:group]
(mapv
(fn [[name vals]]
[:group
[:span (text name)]
[:break]
(into
[:group]
(map
(fn [p] [:span (color :grey "-> " p) [:break]])
(mapv first vals)))
[:break]])
conflicts))
(color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts")
[:break]])
@ -323,22 +321,22 @@
(text "Invalid route data:")
[:break] [:break]
(into
[:group]
(map
(fn [{:keys [data path spec scope]}]
[:group
[:span (color :grey "-- On route -----------------------")]
[:break]
[:break]
(text path) (if scope [:span " " (text scope)])
[:break]
[:break]
(-> (s/explain-data spec data)
(expound-printer)
(with-out-str)
(fippify))
[:break]])
problems))
[:group]
(map
(fn [{:keys [data path spec scope]}]
[:group
[:span (color :grey "-- On route -----------------------")]
[:break]
[:break]
(text path) (if scope [:span " " (text scope)])
[:break]
[:break]
(-> (s/explain-data spec data)
(expound-printer)
(with-out-str)
(fippify))
[:break]])
problems))
(color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-data-validation")
[:break]])

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -1,5 +1,6 @@
(ns reitit.frontend
(:require [clojure.set :as set]
[clojure.string :as str]
[reitit.coercion :as coercion]
[reitit.core :as r])
(:import goog.Uri
@ -20,20 +21,45 @@
(map (juxt keyword #(query-param q %)))
(into {}))))
(defn fragment-params
"Given goog.Uri, read fragment parameters into Clojure map."
[^Uri uri]
(let [fp (.getFragment uri)]
(if-not (seq fp)
{}
(into {}
(comp
(map #(str/split % #"="))
(map (fn [[k v]]
[(keyword k) v])))
(str/split fp #"&")))))
(defn match-by-path
"Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found."
[router path]
(let [uri (.parse Uri path)]
(if-let [match (r/match-by-path router (.getPath uri))]
(let [q (query-params uri)
match (assoc match :query-params q)
;; Return uncoerced values if coercion is not enabled - so
;; that tha parameters are always accessible from same property.
parameters (or (coercion/coerce! match)
{:path (:path-params match)
:query q})]
(assoc match :parameters parameters)))))
coerced parameters. Return nil if no match found.
:on-coercion-error - a sideeffecting fn of `match exception -> nil`"
([router path] (match-by-path router path nil))
([router path {:keys [on-coercion-error]}]
(let [uri (.parse Uri path)
coerce! (if on-coercion-error
(fn [match]
(try (coercion/coerce! match)
(catch js/Error e
(on-coercion-error match e)
(throw e))))
coercion/coerce!)]
(if-let [match (r/match-by-path router (.getPath uri))]
(let [q (query-params uri)
fp (fragment-params uri)
match (assoc match :query-params q :fragment-params fp)
;; Return uncoerced values if coercion is not enabled - so
;; that tha parameters are always accessible from same property.
parameters (or (coerce! match)
{:path (:path-params match)
:query q
:fragment fp})]
(assoc match :parameters parameters))))))
(defn match-by-name
"Given a router, route name and optionally path-parameters,
@ -64,11 +90,11 @@
(let [defined (-> path-params keys set)
missing (set/difference (:required match) defined)]
(js/console.warn
"missing path-params for route" name
{:template (:template match)
:missing missing
:path-params path-params
:required (:required match)})
"missing path-params for route" name
{:template (:template match)
:missing missing
:path-params path-params
:required (:required match)})
nil))
match)
(do (js/console.warn "missing route" name)

View file

@ -38,7 +38,7 @@
(when (nil? @history)
(reset! history this))
(on-navigate m this))
opts))
opts))
(defn
^{:see-also ["reitit.frontend.history/href"]}

View file

@ -1,9 +1,9 @@
(ns reitit.frontend.history
"Provides integration to hash-change or HTML5 History
events."
(:require [reitit.core :as reitit]
[reitit.frontend :as rf]
[goog.events :as gevents])
(:require [goog.events :as gevents]
[reitit.core :as reitit]
[reitit.frontend :as rf])
(:import goog.Uri))
(defprotocol History
@ -40,7 +40,7 @@
nil)
(-on-navigate [this path]
(reset! last-fragment path)
(on-navigate (rf/match-by-path router path) this))
(on-navigate (rf/match-by-path router path this) this))
(-get-path [this]
;; Remove #
;; "" or "#" should be same as "#/"
@ -125,7 +125,7 @@
(-on-navigate this (-get-path this))
this))
(-on-navigate [this path]
(on-navigate (rf/match-by-path router path) this))
(on-navigate (rf/match-by-path router path this) this))
(-stop [this]
(gevents/unlistenByKey listen-key)
(gevents/unlistenByKey click-listen-key)

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -1,24 +1,24 @@
(ns reitit.http
(:require [meta-merge.core :refer [meta-merge]]
[reitit.interceptor :as interceptor]
[reitit.core :as r]
[reitit.exception :as ex]
[reitit.ring :as ring]
[reitit.core :as r]))
[reitit.interceptor :as interceptor]
[reitit.ring :as ring]))
(defrecord Endpoint [data interceptors queue handler path method])
(defn coerce-handler [[path data] {:keys [expand] :as opts}]
[path (reduce
(fn [acc method]
(if (contains? acc method)
(update acc method expand opts)
acc)) data ring/http-methods)])
(fn [acc method]
(if (contains? acc method)
(update acc method expand opts)
acc)) data ring/http-methods)])
(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}]
(defn compile-result [[path data] {:keys [::default-options-endpoint expand meta-merge-fn] :as opts}]
(let [[top childs] (ring/group-keys data)
childs (cond-> childs
(and (not (:options childs)) (not (:handler top)) default-options-endpoint)
(assoc :options (expand default-options-endpoint opts)))
(and (not (:options childs)) (not (:handler top)) default-options-endpoint)
(assoc :options (expand default-options-endpoint opts)))
compile (fn [[path data] opts scope]
(interceptor/compile-result [path data] opts scope))
->endpoint (fn [p d m s]
@ -29,19 +29,19 @@
(assoc :method m))))
->methods (fn [any? data]
(reduce
(fn [acc method]
(cond-> acc
any? (assoc method (->endpoint path data method nil))))
(ring/map->Methods {})
ring/http-methods))]
(fn [acc method]
(cond-> acc
any? (assoc method (->endpoint path data method nil))))
(ring/map->Methods {})
ring/http-methods))]
(if-not (seq childs)
(->methods true top)
(reduce-kv
(fn [acc method data]
(let [data (meta-merge top data)]
(assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data)
childs))))
(fn [acc method data]
(let [data ((or meta-merge-fn meta-merge) top data)]
(assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data)
childs))))
(defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
@ -133,7 +133,7 @@
(assoc ::interceptor/queue (partial interceptor/queue executor))
(dissoc :data) ; data is already merged into routes
(cond-> (seq interceptors)
(update-in [:data :interceptors] (partial into (vec interceptors)))))
(update-in [:data :interceptors] (partial into (vec interceptors)))))
router (reitit.http/router (r/routes router) router-opts) ;; will re-compile the interceptors
enrich-request (ring/create-enrich-request inject-match? inject-router?)
enrich-default-request (ring/create-enrich-default-request inject-router?)]

View file

@ -1,7 +1,7 @@
(ns reitit.http.coercion
(:require [reitit.coercion :as coercion]
[reitit.spec :as rs]
[reitit.impl :as impl]))
[reitit.impl :as impl]
[reitit.spec :as rs]))
(defn coerce-request-interceptor
"Interceptor for pluggable request coercion.

View file

@ -1,8 +1,8 @@
(ns reitit.http.spec
(:require [clojure.spec.alpha :as s]
[reitit.ring.spec :as rrs]
[reitit.interceptor :as interceptor]
[reitit.exception :as exception]
[reitit.interceptor :as interceptor]
[reitit.ring.spec :as rrs]
[reitit.spec :as rs]))
;;
@ -22,5 +22,5 @@
[routes {:keys [spec ::rs/wrap] :or {spec ::data, wrap identity}}]
(when-let [problems (rrs/validate-route-data routes :interceptors wrap spec)]
(exception/fail!
::rs/invalid-route-data
{:problems problems})))
::rs/invalid-route-data
{:problems problems})))

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -20,7 +20,9 @@
(defn- polish [ctx]
(-> ctx
(dissoc ::original ::previous :stack :queue)
(dissoc ::original ::previous :stack :queue
:io.pedestal.interceptor.chain/stack
:io.pedestal.interceptor.chain/queue)
(update :request dissoc ::r/match ::r/router)))
(defn- handle [name stage]
@ -36,16 +38,16 @@
[stages {:keys [enter leave error name] :as interceptor}]
(if (->> (select-keys interceptor stages) (vals) (keep identity) (seq))
(cond-> {:name ::diff}
(and enter (stages :enter)) (assoc :enter (handle name :enter))
(and leave (stages :leave)) (assoc :leave (handle name :leave))
(and error (stages :error)) (assoc :error (handle name :error)))))
(and enter (stages :enter)) (assoc :enter (handle name :enter))
(and leave (stages :leave)) (assoc :leave (handle name :leave))
(and error (stages :error)) (assoc :error (handle name :error)))))
(defn print-context-diffs
"A interceptor chain transformer that adds a context diff printer between all interceptors"
[interceptors]
(reduce
(fn [chain interceptor]
(into chain (keep identity [(diff-interceptor #{:leave :error} interceptor)
interceptor
(diff-interceptor #{:enter} interceptor)])))
[(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors))
(fn [chain interceptor]
(into chain (keep identity [(diff-interceptor #{:leave :error} interceptor)
interceptor
(diff-interceptor #{:enter} interceptor)])))
[(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors))

View file

@ -1,10 +1,10 @@
(ns reitit.http.interceptors.exception
(:require [reitit.coercion :as coercion]
[reitit.ring :as ring]
[clojure.spec.alpha :as s]
[clojure.string :as str])
(:import (java.time Instant)
(java.io PrintWriter Writer)))
(:require [clojure.spec.alpha :as s]
[clojure.string :as str]
[reitit.coercion :as coercion]
[reitit.ring :as ring])
(:import (java.io PrintWriter Writer)
(java.time Instant)))
(s/def ::handlers (s/map-of any? fn?))
(s/def ::spec (s/keys :opt-un [::handlers]))
@ -25,11 +25,11 @@
error-handler (or (get handlers type)
(get handlers ex-class)
(some
(partial get handlers)
(descendants type))
(partial get handlers)
(descendants type))
(some
(partial get handlers)
(super-classes ex-class))
(partial get handlers)
(super-classes ex-class))
(get handlers ::default))]
(if-let [wrap (get handlers ::wrap)]
(wrap error-handler error request)

View file

@ -1,8 +1,8 @@
(ns reitit.http.interceptors.multipart
(:require [reitit.coercion :as coercion]
(:require [clojure.spec.alpha :as s]
[reitit.coercion :as coercion]
[reitit.spec]
[ring.middleware.multipart-params :as multipart-params]
[clojure.spec.alpha :as s]
[spec-tools.core :as st])
(:import (java.io File)))
@ -18,14 +18,14 @@
(def temp-file-part
"Spec for file param created by ring.middleware.multipart-params.temp-file store."
(st/spec
{:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size])
:swagger/type "file"}))
{:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size])
:swagger/type "file"}))
(def bytes-part
"Spec for file param created by ring.middleware.multipart-params.byte-array store."
(st/spec
{:spec (s/keys :req-un [::filename ::content-type ::bytes])
:swagger/type "file"}))
{:spec (s/keys :req-un [::filename ::content-type ::bytes])
:swagger/type "file"}))
(defn- coerced-request [request coercers]
(if-let [coerced (if coercers (coercion/coerce-request coercers request))]
@ -49,7 +49,7 @@
:compile (fn [{:keys [parameters coercion]} opts]
(if-let [multipart (:multipart parameters)]
(let [parameter-coercion {:multipart (coercion/->ParameterCoercion
:multipart-params :string true true)}
:multipart-params :string true true)}
opts (assoc opts ::coercion/parameter-coercion parameter-coercion)
coercers (if multipart (coercion/request-coercers coercion parameters opts))]
{:data {:swagger {:consumes ^:replace #{"multipart/form-data"}}}

View file

@ -1,7 +1,7 @@
(ns reitit.http.interceptors.muuntaja
(:require [muuntaja.core :as m]
[muuntaja.interceptor]
[clojure.spec.alpha :as s]))
(:require [clojure.spec.alpha :as s]
[muuntaja.core :as m]
[muuntaja.interceptor]))
(s/def ::muuntaja m/muuntaja?)
(s/def ::spec (s/keys :opt-un [::muuntaja]))
@ -40,10 +40,10 @@
:compile (fn [{:keys [muuntaja parameters]} _]
(if-let [muuntaja (or muuntaja default-muuntaja)]
(merge
(stripped (muuntaja.interceptor/format-interceptor muuntaja))
(if (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))
:consumes (displace (m/decodes muuntaja))}}}))))}))
(stripped (muuntaja.interceptor/format-interceptor muuntaja))
(if (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))
:consumes (displace (m/decodes muuntaja))}}}))))}))
(defn format-negotiate-interceptor
"Interceptor for content-negotiation.
@ -87,9 +87,9 @@
:compile (fn [{:keys [muuntaja parameters]} _]
(if-let [muuntaja (or muuntaja default-muuntaja)]
(merge
(stripped (muuntaja.interceptor/format-request-interceptor muuntaja))
(when (publish-swagger-data? parameters)
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))}))
(stripped (muuntaja.interceptor/format-request-interceptor muuntaja))
(when (publish-swagger-data? parameters)
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))}))
(defn format-response-interceptor
"Interceptor for response formatting.
@ -112,6 +112,6 @@
:compile (fn [{:keys [muuntaja parameters]} _]
(if-let [muuntaja (or muuntaja default-muuntaja)]
(merge
(stripped (muuntaja.interceptor/format-response-interceptor muuntaja))
(when (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))}))
(stripped (muuntaja.interceptor/format-response-interceptor muuntaja))
(when (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))}))

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -1,13 +1,14 @@
(ns reitit.coercion.malli
(:require [reitit.coercion :as coercion]
[malli.transform :as mt]
(:require [clojure.set :as set]
[clojure.walk :as walk]
[malli.core :as m]
[malli.edn :as edn]
[malli.error :as me]
[malli.util :as mu]
[malli.experimental.lite :as l]
[malli.swagger :as swagger]
[malli.core :as m]
[clojure.set :as set]
[clojure.walk :as walk]))
[malli.transform :as mt]
[malli.util :as mu]
[reitit.coercion :as coercion]))
;;
;; coercion
@ -26,9 +27,9 @@
(reify TransformationProvider
(-transformer [_ {:keys [strip-extra-keys default-values]}]
(mt/transformer
(if strip-extra-keys (mt/strip-extra-keys-transformer))
transformer
(if default-values (mt/default-value-transformer))))))
(if strip-extra-keys (mt/strip-extra-keys-transformer))
transformer
(if default-values (mt/default-value-transformer))))))
(def string-transformer-provider (-provider (mt/string-transformer)))
(def json-transformer-provider (-provider (mt/json-transformer)))
@ -61,7 +62,7 @@
transformed
(let [error (-explain coercer transformed)]
(coercion/map->CoercionError
(assoc error :transformed transformed)))))
(assoc error :transformed transformed)))))
value))
;; encode: decode -> validate -> encode
(fn [value format]
@ -71,7 +72,7 @@
(-encode coercer transformed)
(let [error (-explain coercer transformed)]
(coercion/map->CoercionError
(assoc error :transformed transformed))))
(assoc error :transformed transformed))))
value))))))))
;;
@ -91,15 +92,15 @@
(defmethod extract-parameter :default [in schema options]
(let [{:keys [properties required]} (swagger/transform schema (merge options {:in in, :type :parameter}))]
(mapv
(fn [[k {:keys [type] :as schema}]]
(merge
{:in (name in)
:name k
:description (:description schema "")
:type type
:required (contains? (set required) k)}
schema))
properties)))
(fn [[k {:keys [type] :as schema}]]
(merge
{:in (name in)
:name k
:description (:description schema "")
:type type
:required (contains? (set required) k)}
schema))
properties)))
;;
;; public api
@ -113,7 +114,9 @@
:response {:default default-transformer-provider
:formats {"application/json" json-transformer-provider}}}
;; set of keys to include in error messages
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
:error-keys #{:type :coercion :in #_:schema :value #_:errors :humanized #_:transformed}
;; support lite syntax?
:lite true
;; schema identity function (default: close all map schemas)
:compile mu/closed-schema
;; validate request & response
@ -133,9 +136,11 @@
([]
(create nil))
([opts]
(let [{:keys [transformers compile options error-keys encode-error] :as opts} (merge default-options opts)
(let [{:keys [transformers lite compile options error-keys encode-error] :as opts} (merge default-options opts)
show? (fn [key] (contains? error-keys key))
transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers)]
transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers)
compile (if lite (fn [schema options] (compile (binding [l/*options* options] (l/schema schema)) options))
compile)]
^{:type ::coercion/coercion}
(reify coercion/Coercion
(-get-name [_] :malli)
@ -143,39 +148,39 @@
(-get-apidocs [_ specification {:keys [parameters responses]}]
(case specification
:swagger (merge
(if parameters
{:parameters
(->> (for [[in schema] parameters
parameter (extract-parameter in (compile schema options) options)]
parameter)
(into []))})
(if responses
{:responses
(into
(empty responses)
(for [[status response] responses]
[status (as-> response $
(set/rename-keys $ {:body :schema})
(update $ :description (fnil identity ""))
(if (:schema $)
(-> $
(update :schema compile options)
(update :schema swagger/transform {:type :schema}))
$))]))}))
(if parameters
{:parameters
(->> (for [[in schema] parameters
parameter (extract-parameter in (compile schema options) options)]
parameter)
(into []))})
(if responses
{:responses
(into
(empty responses)
(for [[status response] responses]
[status (as-> response $
(set/rename-keys $ {:body :schema})
(update $ :description (fnil identity ""))
(if (:schema $)
(-> $
(update :schema compile options)
(update :schema swagger/transform {:type :schema}))
$))]))}))
(throw
(ex-info
(str "Can't produce Schema apidocs for " specification)
{:type specification, :coercion :schema}))))
(ex-info
(str "Can't produce Schema apidocs for " specification)
{:type specification, :coercion :schema}))))
(-compile-model [_ model _] (compile model options))
(-open-model [_ schema] schema)
(-encode-error [_ error]
(cond-> error
(show? :humanized) (assoc :humanized (me/humanize error {:wrap :message}))
(show? :schema) (update :schema edn/write-string opts)
(show? :errors) (-> (me/with-error-messages opts)
(update :errors (partial map #(update % :schema edn/write-string opts))))
(seq error-keys) (select-keys error-keys)
encode-error (encode-error)))
(show? :humanized) (assoc :humanized (me/humanize error {:wrap :message}))
(show? :schema) (update :schema edn/write-string opts)
(show? :errors) (-> (me/with-error-messages opts)
(update :errors (partial map #(update % :schema edn/write-string opts))))
(seq error-keys) (select-keys error-keys)
encode-error (encode-error)))
(-request-coercer [_ type schema]
(-coercer (compile schema options) type transformers :decode opts))
(-response-coercer [_ schema]

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -48,6 +48,6 @@
printer between all middleware."
[chain]
(reduce
(fn [chain mw]
(into chain [mw (print-diff-middleware (select-keys mw [:name]))]))
[(print-diff-middleware)] chain))
(fn [chain mw]
(into chain [mw (print-diff-middleware (select-keys mw [:name]))]))
[(print-diff-middleware)] chain))

View file

@ -1,10 +1,10 @@
(ns reitit.ring.middleware.exception
(:require [reitit.coercion :as coercion]
[reitit.ring :as ring]
[clojure.spec.alpha :as s]
[clojure.string :as str])
(:import (java.time Instant)
(java.io Writer PrintWriter)))
(:require [clojure.spec.alpha :as s]
[clojure.string :as str]
[reitit.coercion :as coercion]
[reitit.ring :as ring])
(:import (java.io Writer PrintWriter)
(java.time Instant)))
(s/def ::handlers (s/map-of any? fn?))
(s/def ::spec (s/keys :opt-un [::handlers]))
@ -25,11 +25,11 @@
error-handler (or (get handlers type)
(get handlers ex-class)
(some
(partial get handlers)
(descendants type))
(partial get handlers)
(descendants type))
(some
(partial get handlers)
(super-classes ex-class))
(partial get handlers)
(super-classes ex-class))
(get handlers ::default))]
(if-let [wrap (get handlers ::wrap)]
(wrap error-handler error request)

View file

@ -1,8 +1,8 @@
(ns reitit.ring.middleware.multipart
(:refer-clojure :exclude [compile])
(:require [reitit.coercion :as coercion]
(:require [clojure.spec.alpha :as s]
[reitit.coercion :as coercion]
[ring.middleware.multipart-params :as multipart-params]
[clojure.spec.alpha :as s]
[spec-tools.core :as st])
(:import (java.io File)))
@ -15,14 +15,14 @@
(def temp-file-part
"Spec for file param created by ring.middleware.multipart-params.temp-file store."
(st/spec
{:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size])
:swagger/type "file"}))
{:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size])
:swagger/type "file"}))
(def bytes-part
"Spec for file param created by ring.middleware.multipart-params.byte-array store."
(st/spec
{:spec (s/keys :req-un [::filename ::content-type ::bytes])
:swagger/type "file"}))
{:spec (s/keys :req-un [::filename ::content-type ::bytes])
:swagger/type "file"}))
(defn- coerced-request [request coercers]
(if-let [coerced (if coercers (coercion/coerce-request coercers request))]
@ -33,7 +33,7 @@
(fn [{:keys [parameters coercion]} opts]
(if-let [multipart (:multipart parameters)]
(let [parameter-coercion {:multipart (coercion/->ParameterCoercion
:multipart-params :string true true)}
:multipart-params :string true true)}
opts (assoc opts ::coercion/parameter-coercion parameter-coercion)
coercers (if multipart (coercion/request-coercers coercion parameters opts))]
{:data {:swagger {:consumes ^:replace #{"multipart/form-data"}}}

View file

@ -1,7 +1,7 @@
(ns reitit.ring.middleware.muuntaja
(:require [muuntaja.core :as m]
[muuntaja.middleware]
[clojure.spec.alpha :as s]))
(:require [clojure.spec.alpha :as s]
[muuntaja.core :as m]
[muuntaja.middleware]))
(s/def ::muuntaja m/muuntaja?)
(s/def ::spec (s/keys :opt-un [::muuntaja]))
@ -34,10 +34,10 @@
:compile (fn [{:keys [muuntaja parameters]} _]
(if muuntaja
(merge
(if (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))
:consumes (displace (m/decodes muuntaja))}}})
{:wrap #(muuntaja.middleware/wrap-format % muuntaja)})))})
(if (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))
:consumes (displace (m/decodes muuntaja))}}})
{:wrap #(muuntaja.middleware/wrap-format % muuntaja)})))})
(def format-negotiate-middleware
"Middleware for content-negotiation.
@ -71,9 +71,9 @@
:compile (fn [{:keys [muuntaja parameters]} _]
(if muuntaja
(merge
(when (publish-swagger-data? parameters)
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}})
{:wrap #(muuntaja.middleware/wrap-format-request % muuntaja)})))})
(when (publish-swagger-data? parameters)
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}})
{:wrap #(muuntaja.middleware/wrap-format-request % muuntaja)})))})
(def format-response-middleware
"Middleware for response formatting.
@ -91,6 +91,6 @@
:compile (fn [{:keys [muuntaja parameters]} _]
(if muuntaja
(merge
(when (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}})
{:wrap #(muuntaja.middleware/wrap-format-response % muuntaja)})))})
(when (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}})
{:wrap #(muuntaja.middleware/wrap-format-response % muuntaja)})))})

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -1,11 +1,11 @@
(ns reitit.pedestal
(:require [io.pedestal.interceptor.chain :as chain]
(:require [io.pedestal.http :as http]
[io.pedestal.interceptor :as interceptor]
[io.pedestal.http :as http]
[reitit.interceptor]
[reitit.http])
(:import (reitit.interceptor Executor)
(java.lang.reflect Method)))
[io.pedestal.interceptor.chain :as chain]
[reitit.http]
[reitit.interceptor])
(:import (java.lang.reflect Method)
(reitit.interceptor Executor)))
;; TODO: variadic
(defn- arities [f]
@ -36,9 +36,9 @@
interceptor
(->> (select-keys interceptor [:enter :leave :error]) (vals) (keep identity) (seq))
(interceptor/interceptor
(if (error-without-arity-2? interceptor)
(wrap-error-arity-2->1 interceptor)
interceptor))))
(if (error-without-arity-2? interceptor)
(wrap-error-arity-2->1 interceptor)
interceptor))))
;;
;; Public API
@ -62,11 +62,11 @@
(routing-interceptor router default-handler nil))
([router default-handler {:keys [interceptors]}]
(interceptor/interceptor
(reitit.http/routing-interceptor
router
default-handler
{:executor pedestal-executor
:interceptors interceptors}))))
(reitit.http/routing-interceptor
router
default-handler
{:executor pedestal-executor
:interceptors interceptors}))))
(defn replace-last-interceptor [service-map interceptor]
(-> service-map

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -1,12 +1,12 @@
(ns reitit.ring
(:require [meta-merge.core :refer [meta-merge]]
[reitit.middleware :as middleware]
[reitit.exception :as ex]
[reitit.core :as r]
[reitit.impl :as impl]
(:require [clojure.string :as str]
[meta-merge.core :refer [meta-merge]]
#?@(:clj [[ring.util.mime-type :as mime-type]
[ring.util.response :as response]])
[clojure.string :as str]))
[ring.util.response :as response]])
[reitit.core :as r]
[reitit.exception :as ex]
[reitit.impl :as impl]
[reitit.middleware :as middleware]))
(declare get-match)
(declare get-router)
@ -17,23 +17,23 @@
(defn ^:no-wiki group-keys [data]
(reduce-kv
(fn [[top childs] k v]
(if (http-methods k)
[top (assoc childs k v)]
[(assoc top k v) childs])) [{} {}] data))
(fn [[top childs] k v]
(if (http-methods k)
[top (assoc childs k v)]
[(assoc top k v) childs])) [{} {}] data))
(defn coerce-handler [[path data] {:keys [expand] :as opts}]
[path (reduce
(fn [acc method]
(if (contains? acc method)
(update acc method expand opts)
acc)) data http-methods)])
(fn [acc method]
(if (contains? acc method)
(update acc method expand opts)
acc)) data http-methods)])
(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}]
(defn compile-result [[path data] {:keys [::default-options-endpoint expand meta-merge-fn] :as opts}]
(let [[top childs] (group-keys data)
childs (cond-> childs
(and (not (:options childs)) (not (:handler top)) default-options-endpoint)
(assoc :options (expand default-options-endpoint opts)))
(and (not (:options childs)) (not (:handler top)) default-options-endpoint)
(assoc :options (expand default-options-endpoint opts)))
->endpoint (fn [p d m s]
(-> (middleware/compile-result [p d] opts s)
(map->Endpoint)
@ -41,30 +41,30 @@
(assoc :method m)))
->methods (fn [any? data]
(reduce
(fn [acc method]
(cond-> acc
any? (assoc method (->endpoint path data method nil))))
(map->Methods {})
http-methods))]
(fn [acc method]
(cond-> acc
any? (assoc method (->endpoint path data method nil))))
(map->Methods {})
http-methods))]
(if-not (seq childs)
(->methods true top)
(reduce-kv
(fn [acc method data]
(let [data (meta-merge top data)]
(assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data)
childs))))
(fn [acc method data]
(let [data ((or meta-merge-fn meta-merge) top data)]
(assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data)
childs))))
(def default-options-handler
(let [handle (fn [request]
(let [methods (->> request get-match :result (keep (fn [[k v]] (if v k))))
allow (->> methods (map (comp str/upper-case name)) (str/join ","))]
{:status 200, :body "", :headers {"Allow" allow}}))]
(let [handler (fn [request]
(let [methods (->> request get-match :result (keep (fn [[k v]] (if v k))))
allow (->> methods (map (comp str/upper-case name)) (str/join ","))]
{:status 200, :body "", :headers {"Allow" allow}}))]
(fn
([request]
(handle request))
(handler request))
([request respond _]
(respond (handle request))))))
(respond (handler request))))))
(def default-options-endpoint
{:no-doc true
@ -318,26 +318,26 @@
enrich-default-request (create-enrich-default-request inject-router?)]
(with-meta
(wrap
(fn
([request]
(if-let [match (r/match-by-path router (:uri request))]
(let [method (:request-method request)
path-params (:path-params match)
result (:result match)
handler (-> result method :handler (or default-handler))
request (enrich-request request path-params match router)]
(or (handler request) (default-handler request)))
(default-handler (enrich-default-request request router))))
([request respond raise]
(if-let [match (r/match-by-path router (:uri request))]
(let [method (:request-method request)
path-params (:path-params match)
result (:result match)
handler (-> result method :handler (or default-handler))
request (enrich-request request path-params match router)]
((routes handler default-handler) request respond raise))
(default-handler (enrich-default-request request router) respond raise))
nil)))
(fn
([request]
(if-let [match (r/match-by-path router (:uri request))]
(let [method (:request-method request)
path-params (:path-params match)
result (:result match)
handler (-> result method :handler (or default-handler))
request (enrich-request request path-params match router)]
(or (handler request) (default-handler request)))
(default-handler (enrich-default-request request router))))
([request respond raise]
(if-let [match (r/match-by-path router (:uri request))]
(let [method (:request-method request)
path-params (:path-params match)
result (:result match)
handler (-> result method :handler (or default-handler))
request (enrich-request request path-params match router)]
((routes handler default-handler) request respond raise))
(default-handler (enrich-default-request request router) respond raise))
nil)))
{::r/router router}))))
(defn get-router [handler]

View file

@ -1,7 +1,7 @@
(ns reitit.ring.coercion
(:require [reitit.coercion :as coercion]
[reitit.spec :as rs]
[reitit.impl :as impl]))
[reitit.impl :as impl]
[reitit.spec :as rs]))
(defn handle-coercion-exception [e respond raise]
(let [data (ex-data e)]
@ -10,8 +10,8 @@
::coercion/response-coercion 500
nil)]
(respond
{:status status
:body (coercion/encode-error data)})
{:status status
:body (coercion/encode-error data)})
(raise e))))
;;

View file

@ -1,8 +1,8 @@
(ns reitit.ring.spec
(:require [clojure.spec.alpha :as s]
[reitit.exception :as exception]
[reitit.middleware :as middleware]
[reitit.spec :as rs]
[reitit.exception :as exception]))
[reitit.spec :as rs]))
;;
;; Specs
@ -19,7 +19,6 @@
(s/def ::trace map?)
(s/def ::patch map?)
(s/def ::data
(s/keys :opt-un [::rs/handler ::rs/name ::rs/no-doc ::middleware]))
@ -30,9 +29,9 @@
(defn merge-specs [specs]
(when-let [non-specs (seq (remove #(or (s/spec? %) (s/get-spec %)) specs))]
(exception/fail!
::invalid-specs
{:specs specs
:invalid non-specs}))
::invalid-specs
{:specs specs
:invalid non-specs}))
(s/merge-spec-impl (vec specs) (vec specs) nil))
(defn validate-route-data [routes key wrap spec]
@ -51,5 +50,5 @@
[routes {:keys [spec ::rs/wrap] :or {spec ::data, wrap identity}}]
(when-let [problems (validate-route-data routes :middleware wrap spec)]
(exception/fail!
::rs/invalid-route-data
{:problems problems})))
::rs/invalid-route-data
{:problems problems})))

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

View file

@ -1,13 +1,13 @@
(ns reitit.coercion.schema
(:require [clojure.walk :as walk]
[schema.core :as s]
[schema-tools.core :as st]
[schema.coerce :as sc]
[schema.utils :as su]
[schema-tools.coerce :as stc]
[schema-tools.swagger.core :as swagger]
(:require [clojure.set :as set]
[clojure.walk :as walk]
[reitit.coercion :as coercion]
[clojure.set :as set]))
[schema-tools.coerce :as stc]
[schema-tools.core :as st]
[schema-tools.swagger.core :as swagger]
[schema.coerce :as sc]
[schema.core :as s]
[schema.utils :as su]))
(def string-coercion-matcher
stc/string-coercion-matcher)
@ -23,16 +23,16 @@
(defn stringify [schema]
(walk/prewalk
(fn [x]
(cond
#?@(:clj [(class? x) (.getName ^Class x)])
(instance? schema.core.OptionalKey x) (pr-str (list 'opt (:k x)))
(instance? schema.core.RequiredKey x) (pr-str (list 'req (:k x)))
(and (satisfies? s/Schema x) (record? x)) (try (pr-str (s/explain x)) (catch #?(:clj Exception :cljs js/Error) _ x))
(instance? schema.utils.ValidationError x) (str (su/validation-error-explain x))
(instance? schema.utils.NamedError x) (str (su/named-error-explain x))
:else x))
schema))
(fn [x]
(cond
#?@(:clj [(class? x) (.getName ^Class x)])
(instance? schema.core.OptionalKey x) (pr-str (list 'opt (:k x)))
(instance? schema.core.RequiredKey x) (pr-str (list 'req (:k x)))
(and (satisfies? s/Schema x) (record? x)) (try (pr-str (s/explain x)) (catch #?(:clj Exception :cljs js/Error) _ x))
(instance? schema.utils.ValidationError x) (str (su/validation-error-explain x))
(instance? schema.utils.NamedError x) (str (su/named-error-explain x))
:else x))
schema))
(def default-options
{:coerce-response? coerce-response?
@ -50,27 +50,27 @@
;; TODO: this looks identical to spec, refactor when schema is done.
(case specification
:swagger (swagger/swagger-spec
(merge
(if parameters
{::swagger/parameters
(into
(empty parameters)
(for [[k v] parameters]
[k (coercion/-compile-model this v nil)]))})
(if responses
{::swagger/responses
(into
(empty responses)
(for [[k response] responses]
[k (as-> response $
(set/rename-keys $ {:body :schema})
(if (:schema $)
(update $ :schema #(coercion/-compile-model this % nil))
$))]))})))
(merge
(if parameters
{::swagger/parameters
(into
(empty parameters)
(for [[k v] parameters]
[k (coercion/-compile-model this v nil)]))})
(if responses
{::swagger/responses
(into
(empty responses)
(for [[k response] responses]
[k (as-> response $
(set/rename-keys $ {:body :schema})
(if (:schema $)
(update $ :schema #(coercion/-compile-model this % nil))
$))]))})))
(throw
(ex-info
(str "Can't produce Schema apidocs for " specification)
{:type specification, :coercion :schema}))))
(ex-info
(str "Can't produce Schema apidocs for " specification)
{:type specification, :coercion :schema}))))
(-compile-model [_ model _] model)
(-open-model [_ schema] (st/open-schema schema))
(-encode-error [_ error]
@ -88,8 +88,8 @@
coerced (coercer value)]
(if-let [error (su/error-val coerced)]
(coercion/map->CoercionError
{:schema schema
:errors error})
{:schema schema
:errors error})
coerced))
value))))
(-response-coercer [this schema]

View file

@ -1,6 +1,6 @@
(ns reitit.ring.schema
(:require [schema.core :as s]
[schema-tools.swagger.core :as swagger])
(:require [schema-tools.swagger.core :as swagger]
[schema.core :as s])
#?(:clj (:import (java.io File))))
(defrecord Upload [m]

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

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

Some files were not shown because too many files have changed in this diff Show more