mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 08:21:11 +00:00
commit
3890e68100
49 changed files with 1164 additions and 63 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -1,3 +1,18 @@
|
||||||
|
## 0.2.2-SNAPSHOT
|
||||||
|
|
||||||
|
* better documentation for interceptors
|
||||||
|
* sample apps:
|
||||||
|
* [Sieppari, reitit-http & swagger](https://github.com/metosin/reitit/blob/master/examples/http-swagger/src/example/server.clj)
|
||||||
|
* [Pedestal, reitit-http & swagger](https://github.com/metosin/reitit/blob/master/examples/pedestal-swagger/src/example/server.clj)
|
||||||
|
|
||||||
|
## `reitit-middleware`
|
||||||
|
|
||||||
|
* new middleware `reitit.ring.middleware.parameters/parameters-middleware` to wrap query & form params.
|
||||||
|
|
||||||
|
## `reitit-interceptors`
|
||||||
|
|
||||||
|
* new module like `reitit-middleware` but for interceptors. See the [Docs](https://metosin.github.io/reitit/http/default_interceptors.html).
|
||||||
|
|
||||||
## 0.2.1 (2018-09-04)
|
## 0.2.1 (2018-09-04)
|
||||||
|
|
||||||
## `reitit-schema`
|
## `reitit-schema`
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -32,8 +32,8 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
||||||
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
||||||
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui)
|
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui)
|
||||||
* `reitit-frontend` Tools for [frontend routing]((https://metosin.github.io/reitit/frontend/basics.html))
|
* `reitit-frontend` Tools for [frontend routing]((https://metosin.github.io/reitit/frontend/basics.html))
|
||||||
* `reitit-http` http-routing with Pedestal-style Interceptors (WIP)
|
* `reitit-http` http-routing with Pedestal-style Interceptors
|
||||||
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors (WIP)
|
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors
|
||||||
|
|
||||||
## Latest version
|
## Latest version
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ Optionally, the parts can be required separately:
|
||||||
;; frontend helpers
|
;; frontend helpers
|
||||||
[metosin/reitit-frontend "0.2.1"]
|
[metosin/reitit-frontend "0.2.1"]
|
||||||
|
|
||||||
;; http with interceptors (WIP)
|
;; http with interceptors
|
||||||
[metosin/reitit-http "0.2.1"]
|
[metosin/reitit-http "0.2.1"]
|
||||||
[metosin/reitit-sieppari "0.2.1"]
|
[metosin/reitit-sieppari "0.2.1"]
|
||||||
```
|
```
|
||||||
|
|
@ -147,10 +147,6 @@ Invalid request:
|
||||||
; :in [:request :query-params]}}
|
; :in [:request :query-params]}}
|
||||||
```
|
```
|
||||||
|
|
||||||
**NOTE**: Reitit is not a batteries included web-stack. You should also include at least:
|
|
||||||
* content negotiation library like [Muuntaja](https://github.com/metosin/muuntaja)
|
|
||||||
* some default Ring-middleware like `ring.middleware.params/wrap-params`
|
|
||||||
|
|
||||||
## More examples
|
## More examples
|
||||||
|
|
||||||
* [`reitit-ring` with coercion, swagger and default middleware](https://github.com/metosin/reitit/blob/master/examples/ring-swagger/src/example/server.clj)
|
* [`reitit-ring` with coercion, swagger and default middleware](https://github.com/metosin/reitit/blob/master/examples/ring-swagger/src/example/server.clj)
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ Modules:
|
||||||
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
||||||
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
|
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
|
||||||
* `reitit-frontend` Tools for [frontend routing](frontend/basics.md)
|
* `reitit-frontend` Tools for [frontend routing](frontend/basics.md)
|
||||||
* `reitit-http` http-routing with Pedestal-style Interceptors (WIP)
|
* `reitit-http` http-routing with Pedestal-style Interceptors
|
||||||
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors (WIP)
|
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors
|
||||||
|
|
||||||
## Latest version
|
## Latest version
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ Optionally, the parts can be required separately:
|
||||||
;; frontend helpers
|
;; frontend helpers
|
||||||
[metosin/reitit-frontend "0.2.1"]
|
[metosin/reitit-frontend "0.2.1"]
|
||||||
|
|
||||||
;; http with interceptors (WIP)
|
;; http with interceptors
|
||||||
[metosin/reitit-http "0.2.1"]
|
[metosin/reitit-http "0.2.1"]
|
||||||
[metosin/reitit-sieppari "0.2.1"]
|
[metosin/reitit-sieppari "0.2.1"]
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,18 @@
|
||||||
* [Compiling Middleware](ring/compiling_middleware.md)
|
* [Compiling Middleware](ring/compiling_middleware.md)
|
||||||
* [Swagger Support](ring/swagger.md)
|
* [Swagger Support](ring/swagger.md)
|
||||||
|
|
||||||
|
## HTTP
|
||||||
|
|
||||||
|
* [Interceptors](http/interceptors.md)
|
||||||
|
* [Pedestal](http/pedestal.md)
|
||||||
|
* [Sieppari](http/sieppari.md)
|
||||||
|
* [Default Interceptors](http/default_interceptors.md)
|
||||||
|
|
||||||
## Frontend
|
## Frontend
|
||||||
|
|
||||||
* [Basics](frontend/basics.md)
|
* [Basics](frontend/basics.md)
|
||||||
* [Browser integration](frontend/browser.md)
|
* [Browser integration](frontend/browser.md)
|
||||||
* [Controllers (WIP)](frontend/controllers.md)
|
* [Controllers](frontend/controllers.md)
|
||||||
|
|
||||||
## HTTP
|
|
||||||
|
|
||||||
* [Interceptors](http/interceptors.md)
|
|
||||||
|
|
||||||
## Advanced
|
## Advanced
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,15 @@
|
||||||
["Route Data Validation" {:file "doc/ring/route_data_validation.md"}]
|
["Route Data Validation" {:file "doc/ring/route_data_validation.md"}]
|
||||||
["Compiling Middleware" {:file "doc/ring/compiling_middleware.md"}]
|
["Compiling Middleware" {:file "doc/ring/compiling_middleware.md"}]
|
||||||
["Swagger Support" {:file "doc/ring/swagger.md"}]]
|
["Swagger Support" {:file "doc/ring/swagger.md"}]]
|
||||||
|
["HTTP" {}
|
||||||
|
["Interceptors" {:file "doc/http/interceptors.md"}]
|
||||||
|
["Pedestal" {:file "doc/http/pedestal.md"}]
|
||||||
|
["Sieppari" {:file "doc/http/sieppar.md"}]
|
||||||
|
["Default Interceptors" {:file "doc/http/default_interceptors.md"}]]
|
||||||
["Frontend" {}
|
["Frontend" {}
|
||||||
["Basics" {:file "doc/frontend/basics.md"}]
|
["Basics" {:file "doc/frontend/basics.md"}]
|
||||||
["Browser integration" {:file "doc/frontend/browser.md"}]
|
["Browser integration" {:file "doc/frontend/browser.md"}]
|
||||||
["Controllers (WIP)" {:file "doc/frontend/controllers.md"}]]
|
["Controllers" {:file "doc/frontend/controllers.md"}]]
|
||||||
["HTTP" {}
|
|
||||||
["Interceptors" {:file "doc/http/interceptors.md"}]]
|
|
||||||
["Advanced" {}
|
["Advanced" {}
|
||||||
["Configuring Routers" {:file "doc/advanced/configuring_routers.md"}]
|
["Configuring Routers" {:file "doc/advanced/configuring_routers.md"}]
|
||||||
["Composing Routers" {:file "doc/advanced/composing_routers.md"}]
|
["Composing Routers" {:file "doc/advanced/composing_routers.md"}]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Controllers (WIP)
|
# Controllers
|
||||||
|
|
||||||
* https://github.com/metosin/reitit/tree/master/examples/frontend-controllers
|
* https://github.com/metosin/reitit/tree/master/examples/frontend-controllers
|
||||||
|
|
||||||
|
|
|
||||||
26
doc/http/default_interceptors.md
Normal file
26
doc/http/default_interceptors.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Default Interceptors
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/reitit-interceptors "0.2.1"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors. The default interceptors are:
|
||||||
|
|
||||||
|
### Parameters handling
|
||||||
|
* `reitit.http.interceptors.parameters/parameters-interceptor`
|
||||||
|
|
||||||
|
### Exception handling
|
||||||
|
* `reitit.http.interceptors.exception/exception-interceptor`
|
||||||
|
|
||||||
|
### Content Negotiation
|
||||||
|
* `reitit.http.interceptors.muuntaja/format-interceptor`
|
||||||
|
* `reitit.http.interceptors.muuntaja/format-negotiate-interceptor`
|
||||||
|
* `reitit.http.interceptors.muuntaja/format-request-interceptor`
|
||||||
|
* `reitit.http.interceptors.muuntaja/format-response-interceptor`
|
||||||
|
|
||||||
|
### Multipart request handling
|
||||||
|
* `reitit.http.interceptors.multipart/multipart-interceptor`
|
||||||
|
|
||||||
|
## Example app
|
||||||
|
|
||||||
|
See an example app with the default interceptors in action: https://github.com/metosin/reitit/blob/master/examples/http-swagger/src/example/server.clj.
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
# Interceptors (WIP)
|
# Interceptors
|
||||||
|
|
||||||
Reitit also support for [Pedestal](pedestal.io)-style [interceptors](http://pedestal.io/reference/interceptors) as an alternative to using middleware. Basic interceptor handling is implemented in `reitit.interceptor` package. There is no interceptor executor shipped, but you can use libraries like [Pedestal Interceptor](https://github.com/pedestal/pedestal/tree/master/interceptor) or [Sieppari](https://github.com/metosin/sieppari) to execute the chains.
|
Reitit also support for [interceptors](http://pedestal.io/reference/interceptors) as an alternative to using middleware. Basic interceptor handling is implemented in `reitit.interceptor` package. There is no interceptor executor shipped, but you can use libraries like [Pedestal Interceptor](https://github.com/pedestal/pedestal/tree/master/interceptor) or [Sieppari](https://github.com/metosin/sieppari) to execute the chains.
|
||||||
|
|
||||||
## Current Status
|
|
||||||
|
|
||||||
Work-in-progress and considered alpha quality.
|
|
||||||
|
|
||||||
## Reitit-http
|
## Reitit-http
|
||||||
|
|
||||||
|
|
@ -12,18 +8,15 @@ Work-in-progress and considered alpha quality.
|
||||||
[metosin/reitit-http "0.2.1"]
|
[metosin/reitit-http "0.2.1"]
|
||||||
```
|
```
|
||||||
|
|
||||||
An module for http-routing using interceptors instead of middleware. Builds on top of the [`reitit-ring`](../ring/ring.md) module. The differences:
|
An 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.
|
||||||
|
|
||||||
|
The differences:
|
||||||
|
|
||||||
* instead of `:middleware`, uses `:interceptors`
|
* instead of `:middleware`, uses `:interceptors`
|
||||||
* compared to `reitit.http/http-router` takes an extra options map with mandatory key `:executor` (of type `reitit.interceptor/Executor`) and optional top level `:interceptors` - wrapping both routes and default handler.
|
* compared to `reitit.ring/ring-router`, the `reitit.http/http-router` takes an extra options map with mandatory key `:executor` (of type `reitit.interceptor/Executor`) and optional top level `:interceptors` - wrapping both routes and default handler.
|
||||||
* optional entry poitn `reitit.http/routing-interceptor` to provide a routing interceptor, to be used with Pedestal.
|
* instead of creating a ring-handler, apps can be wrapped into a routing interceptor that enqueues the matched interceptors into the context. For this, there is `reitit.http/routing-interceptor`.
|
||||||
|
|
||||||
## Examples
|
## Why interceptors?
|
||||||
|
|
||||||
### Sieppari
|
* https://quanttype.net/posts/2018-08-03-why-interceptors.html
|
||||||
|
* https://www.reddit.com/r/Clojure/comments/9csmty/why_interceptors/
|
||||||
See code at: https://github.com/metosin/reitit/tree/master/examples/http
|
|
||||||
|
|
||||||
### Pedestal
|
|
||||||
|
|
||||||
See example at: https://github.com/metosin/reitit/tree/master/examples/pedestal
|
|
||||||
|
|
|
||||||
25
doc/http/pedestal.md
Normal file
25
doc/http/pedestal.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Pedestal
|
||||||
|
|
||||||
|
[Pedestal](http://pedestal.io/) is a well known interceptor implmementation for Clojure. To use `reitit-http` with it, we need to change the default routing interceptor into a new one. Currently, there isn't a separate Pedestal-module in reitit, but the examples have the example code how to do this.
|
||||||
|
|
||||||
|
## Caveat
|
||||||
|
|
||||||
|
`reitit-http` defines Interceptors as `reitit.interceptor/Interceptor`. Compared to Pedestal (2-arity), reitit uses a simplified (1-arity) model for handling errors, described in the [Sieppari README](https://github.com/metosin/sieppari#differences-to-pedestal).
|
||||||
|
|
||||||
|
* you can use any [pedestal-style interceptor](http://pedestal.io/reference/interceptors) within reitit router (as Pedestal is executing those anyway)
|
||||||
|
* you can use any reitit-style interceptor that doesn't have `:error`-stage defined
|
||||||
|
* using a reitit-style interceptor with `:error` defined will cause `ArityException` if invoked
|
||||||
|
|
||||||
|
See the [error handling guide](http://pedestal.io/reference/error-handling) on how to handle errors with Pedestal.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple
|
||||||
|
|
||||||
|
* simple example, with both sync & async code:
|
||||||
|
* https://github.com/metosin/reitit/tree/master/examples/pedestal
|
||||||
|
|
||||||
|
### With batteries
|
||||||
|
|
||||||
|
* with [default interceptors](default_interceptors.md), [coercion](../coercion/coercion.md) and [swagger](../ring/swagger.md)-support (note: exception handling is disabled):
|
||||||
|
* https://github.com/metosin/reitit/tree/master/examples/pedestal-swagger
|
||||||
71
doc/http/sieppari.md
Normal file
71
doc/http/sieppari.md
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Sieppari
|
||||||
|
|
||||||
|
```clj
|
||||||
|
[metosin/reitit-sieppari "0.2.1"]
|
||||||
|
```
|
||||||
|
|
||||||
|
[Sieppari](https://github.com/metosin/sieppari) is a new and fast interceptor implementation with pluggable async ([core.async](https://github.com/clojure/core.async), [Manifold](https://github.com/ztellman/manifold) and [Promesa](http://funcool.github.io/promesa/latest)).
|
||||||
|
|
||||||
|
To use Sieppari with `reitit-http`, there is `reitit-sieppari` module, which has an `reitit.interceptor.Executor` implementation for Sieppari. All reitit interceptors use the Sieppari Interceptor model, so they work seamlesly together.
|
||||||
|
|
||||||
|
Synchronous Ring:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[reitit.http :as http])
|
||||||
|
(require '[reitit.interceptor.sieppari :as sieppari])
|
||||||
|
|
||||||
|
(defn i [x]
|
||||||
|
{:enter (fn [ctx] (println "enter " x) ctx)
|
||||||
|
:leave (fn [ctx] (println "leave " x) ctx)})
|
||||||
|
|
||||||
|
(defn handler [_]
|
||||||
|
(future {:status 200, :body "pong"}))
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(http/ring-handler
|
||||||
|
(http/router
|
||||||
|
["/api"
|
||||||
|
{:interceptors [(i :api)]}
|
||||||
|
|
||||||
|
["/ping"
|
||||||
|
{:interceptors [(i :ping)]
|
||||||
|
:get {:interceptors [(i :get)]
|
||||||
|
:handler handler}}]])
|
||||||
|
{:executor sieppari/executor}))
|
||||||
|
|
||||||
|
(app {:request-method :get, :uri "/api/ping"})
|
||||||
|
;enter :api
|
||||||
|
;enter :ping
|
||||||
|
;enter :get
|
||||||
|
;leave :get
|
||||||
|
;leave :ping
|
||||||
|
;leave :api
|
||||||
|
;=> {:status 200, :body "pong"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ring-async:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(let [respond (promise)]
|
||||||
|
(app {:request-method :get, :uri "/api/ping"} respond nil)
|
||||||
|
(deref respond 1000 ::timeout))
|
||||||
|
;enter :api
|
||||||
|
;enter :ping
|
||||||
|
;enter :get
|
||||||
|
;leave :get
|
||||||
|
;leave :ping
|
||||||
|
;leave :api
|
||||||
|
;=> {:status 200, :body "pong"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple
|
||||||
|
|
||||||
|
* simple example, with both sync & async code:
|
||||||
|
* https://github.com/metosin/reitit/tree/master/examples/http
|
||||||
|
|
||||||
|
### With batteries
|
||||||
|
|
||||||
|
* with [default interceptors](default_interceptors.md), [coercion](../coercion/coercion.md) and [swagger](../ring/swagger.md)-support:
|
||||||
|
* https://github.com/metosin/reitit/tree/master/examples/http-swagger
|
||||||
|
|
@ -6,10 +6,18 @@
|
||||||
|
|
||||||
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases, yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.
|
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases, yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.
|
||||||
|
|
||||||
|
* [Parameter handling](#parameters-handling)
|
||||||
* [Exception handling](#exception-handling)
|
* [Exception handling](#exception-handling)
|
||||||
* [Content negotiation](#content-negotiation)
|
* [Content negotiation](#content-negotiation)
|
||||||
* [Multipart request handling](#multipart-request-handling)
|
* [Multipart request handling](#multipart-request-handling)
|
||||||
|
|
||||||
|
## Parameters handling
|
||||||
|
|
||||||
|
`reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps
|
||||||
|
`ring.middleware.params/wrap-params`.
|
||||||
|
|
||||||
|
**NOTE**: will be factored into two parts: a query-parameters middleware and a Muuntaja format responsible for the the `application/x-www-form-urlencoded` body format.
|
||||||
|
|
||||||
## Exception handling
|
## Exception handling
|
||||||
|
|
||||||
A polished version of [compojure-api](https://github.com/metosin/compojure-api) exception handling. Catches all exceptions and invokes configured exception handler.
|
A polished version of [compojure-api](https://github.com/metosin/compojure-api) exception handling. Catches all exceptions and invokes configured exception handler.
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ Whole example project is in [`/examples/ring-swagger`](https://github.com/metosi
|
||||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
[reitit.ring.middleware.exception :as exception]
|
[reitit.ring.middleware.exception :as exception]
|
||||||
[reitit.ring.middleware.multipart :as multipart]
|
[reitit.ring.middleware.multipart :as multipart]
|
||||||
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
[ring.middleware.params :as params]
|
[ring.middleware.params :as params]
|
||||||
[ring.adapter.jetty :as jetty]
|
[ring.adapter.jetty :as jetty]
|
||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
|
|
@ -180,7 +181,7 @@ Whole example project is in [`/examples/ring-swagger`](https://github.com/metosi
|
||||||
{:data {:coercion reitit.coercion.spec/coercion
|
{:data {:coercion reitit.coercion.spec/coercion
|
||||||
:muuntaja m/instance
|
:muuntaja m/instance
|
||||||
:middleware [;; query-params & form-params
|
:middleware [;; query-params & form-params
|
||||||
params/wrap-params
|
parameters/parameters-middleware
|
||||||
;; content-negotiation
|
;; content-negotiation
|
||||||
muuntaja/format-negotiate-middleware
|
muuntaja/format-negotiate-middleware
|
||||||
;; encoding response body
|
;; encoding response body
|
||||||
|
|
|
||||||
11
examples/http-swagger/.gitignore
vendored
Normal file
11
examples/http-swagger/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.hgignore
|
||||||
|
.hg/
|
||||||
23
examples/http-swagger/README.md
Normal file
23
examples/http-swagger/README.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Http with Swagger example
|
||||||
|
|
||||||
|
## 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/spec/plus x:=1 y:=20
|
||||||
|
|
||||||
|
http GET :3000/swagger.json
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/metosin/reitit/master/examples/ring-swagger/swagger.png" />
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2018 Metosin Oy
|
||||||
6
examples/http-swagger/project.clj
Normal file
6
examples/http-swagger/project.clj
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||||
|
:description "Reitit Http App with Swagger"
|
||||||
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
|
[ring/ring-jetty-adapter "1.7.0-RC2"]
|
||||||
|
[metosin/reitit "0.2.1"]]
|
||||||
|
:repl-options {:init-ns example.server})
|
||||||
BIN
examples/http-swagger/resources/reitit.png
Normal file
BIN
examples/http-swagger/resources/reitit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
94
examples/http-swagger/src/example/server.clj
Normal file
94
examples/http-swagger/src/example/server.clj
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
(ns example.server
|
||||||
|
(:require [reitit.ring :as ring]
|
||||||
|
[reitit.http :as http]
|
||||||
|
[reitit.swagger :as swagger]
|
||||||
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
[reitit.http.coercion :as coercion]
|
||||||
|
[reitit.coercion.spec :as spec-coercion]
|
||||||
|
[reitit.http.interceptors.parameters :as parameters]
|
||||||
|
[reitit.http.interceptors.muuntaja :as muuntaja]
|
||||||
|
[reitit.http.interceptors.exception :as exception]
|
||||||
|
[reitit.http.interceptors.multipart :as multipart]
|
||||||
|
[reitit.interceptor.sieppari :as sieppari]
|
||||||
|
[ring.adapter.jetty :as jetty]
|
||||||
|
[muuntaja.core :as m]
|
||||||
|
[clojure.java.io :as io]))
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(http/ring-handler
|
||||||
|
(http/router
|
||||||
|
[["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:swagger {:info {:title "my-api"
|
||||||
|
:description "with reitit-http"}}
|
||||||
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
|
||||||
|
["/files"
|
||||||
|
{:swagger {:tags ["files"]}}
|
||||||
|
|
||||||
|
["/upload"
|
||||||
|
{:post {:summary "upload a file"
|
||||||
|
:parameters {:multipart {:file multipart/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 (io/input-stream
|
||||||
|
(io/resource "reitit.png"))})}}]]
|
||||||
|
|
||||||
|
["/math"
|
||||||
|
{:swagger {:tags ["math"]}}
|
||||||
|
|
||||||
|
["/plus"
|
||||||
|
{:get {:summary "plus with spec query parameters"
|
||||||
|
:parameters {:query {:x int?, :y int?}}
|
||||||
|
:responses {200 {:body {:total int?}}}
|
||||||
|
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:total (+ x y)}})}
|
||||||
|
:post {:summary "plus with spec body parameters"
|
||||||
|
:parameters {:body {:x int?, :y int?}}
|
||||||
|
:responses {200 {:body {:total int?}}}
|
||||||
|
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:total (+ x y)}})}}]]]
|
||||||
|
|
||||||
|
{:data {:coercion spec-coercion/coercion
|
||||||
|
:muuntaja m/instance
|
||||||
|
:interceptors [;; query-params & form-params
|
||||||
|
(parameters/parameters-interceptor)
|
||||||
|
;; content-negotiation
|
||||||
|
(muuntaja/format-negotiate-interceptor)
|
||||||
|
;; encoding response body
|
||||||
|
(muuntaja/format-response-interceptor)
|
||||||
|
;; exception handling
|
||||||
|
(exception/exception-interceptor)
|
||||||
|
;; decoding request body
|
||||||
|
(muuntaja/format-request-interceptor)
|
||||||
|
;; coercing response bodys
|
||||||
|
(coercion/coerce-response-interceptor)
|
||||||
|
;; coercing request parameters
|
||||||
|
(coercion/coerce-request-interceptor)
|
||||||
|
;; multipart
|
||||||
|
(multipart/multipart-interceptor)]}})
|
||||||
|
(ring/routes
|
||||||
|
(swagger-ui/create-swagger-ui-handler
|
||||||
|
{:path "/"
|
||||||
|
:config {:validatorUrl nil}})
|
||||||
|
(ring/create-default-handler))
|
||||||
|
{:executor sieppari/executor}))
|
||||||
|
|
||||||
|
(defn start []
|
||||||
|
(jetty/run-jetty #'app {:port 3000, :join? false})
|
||||||
|
(println "server running in port 3000"))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(start))
|
||||||
BIN
examples/http-swagger/swagger.png
Normal file
BIN
examples/http-swagger/swagger.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 228 KiB |
|
|
@ -4,6 +4,6 @@
|
||||||
[org.clojure/core.async "0.4.474"]
|
[org.clojure/core.async "0.4.474"]
|
||||||
[funcool/promesa "1.9.0"]
|
[funcool/promesa "1.9.0"]
|
||||||
[manifold "0.1.8"]
|
[manifold "0.1.8"]
|
||||||
[ring "1.6.3"]
|
[ring/ring-jetty-adapter "1.7.0-RC2"]
|
||||||
[metosin/reitit "0.2.1"]]
|
[metosin/reitit "0.2.1"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(defproject just-coercion-with-ring "0.1.0-SNAPSHOT"
|
(defproject just-coercion-with-ring "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit coercion with vanilla ring"
|
:description "Reitit coercion with vanilla ring"
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring/ring-jetty-adapter "1.7.0-RC2"]
|
||||||
[metosin/muuntaja "0.4.1"]
|
[metosin/muuntaja "0.4.1"]
|
||||||
[metosin/reitit "0.2.1"]])
|
[metosin/reitit "0.2.1"]])
|
||||||
|
|
|
||||||
11
examples/pedestal-swagger/.gitignore
vendored
Normal file
11
examples/pedestal-swagger/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.hgignore
|
||||||
|
.hg/
|
||||||
23
examples/pedestal-swagger/README.md
Normal file
23
examples/pedestal-swagger/README.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Pedestal with reitit-http & Swagger example
|
||||||
|
|
||||||
|
## 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/spec/plus x:=1 y:=20
|
||||||
|
|
||||||
|
http GET :3000/swagger.json
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/metosin/reitit/master/examples/pedestal-swagger/swagger.png" />
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2018 Metosin Oy
|
||||||
7
examples/pedestal-swagger/project.clj
Normal file
7
examples/pedestal-swagger/project.clj
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||||
|
:description "Reitit-http with pedestal"
|
||||||
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
|
[io.pedestal/pedestal.service "0.5.4"]
|
||||||
|
[io.pedestal/pedestal.jetty "0.5.4"]
|
||||||
|
[metosin/reitit "0.2.1"]]
|
||||||
|
:repl-options {:init-ns example.server})
|
||||||
BIN
examples/pedestal-swagger/resources/reitit.png
Normal file
BIN
examples/pedestal-swagger/resources/reitit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
112
examples/pedestal-swagger/src/example/server.clj
Normal file
112
examples/pedestal-swagger/src/example/server.clj
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
(ns example.server
|
||||||
|
(:require [io.pedestal.http]
|
||||||
|
[reitit.interceptor.pedestal :as pedestal]
|
||||||
|
[reitit.ring :as ring]
|
||||||
|
[reitit.http :as http]
|
||||||
|
[reitit.swagger :as swagger]
|
||||||
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
[reitit.http.coercion :as coercion]
|
||||||
|
[reitit.coercion.spec :as spec-coercion]
|
||||||
|
[reitit.http.interceptors.parameters :as parameters]
|
||||||
|
[reitit.http.interceptors.muuntaja :as muuntaja]
|
||||||
|
#_[reitit.http.interceptors.exception :as exception]
|
||||||
|
[reitit.http.interceptors.multipart :as multipart]
|
||||||
|
[muuntaja.core :as m]
|
||||||
|
[clojure.java.io :as io]))
|
||||||
|
|
||||||
|
(def routing-interceptor
|
||||||
|
(pedestal/routing-interceptor
|
||||||
|
(http/router
|
||||||
|
[["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:swagger {:info {:title "my-api"
|
||||||
|
:description "with pedestal & reitit-http"}}
|
||||||
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
|
||||||
|
["/files"
|
||||||
|
{:swagger {:tags ["files"]}}
|
||||||
|
|
||||||
|
["/upload"
|
||||||
|
{:post {:summary "upload a file"
|
||||||
|
:parameters {:multipart {:file multipart/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 (io/input-stream
|
||||||
|
(io/resource "reitit.png"))})}}]]
|
||||||
|
|
||||||
|
["/math"
|
||||||
|
{:swagger {:tags ["math"]}}
|
||||||
|
|
||||||
|
["/plus"
|
||||||
|
{:get {:summary "plus with spec query parameters"
|
||||||
|
:parameters {:query {:x int?, :y int?}}
|
||||||
|
:responses {200 {:body {:total int?}}}
|
||||||
|
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:total (+ x y)}})}
|
||||||
|
:post {:summary "plus with spec body parameters"
|
||||||
|
:parameters {:body {:x int?, :y int?}}
|
||||||
|
:responses {200 {:body {:total int?}}}
|
||||||
|
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||||
|
{:status 200
|
||||||
|
:body {:total (+ x y)}})}}]]]
|
||||||
|
|
||||||
|
{:data {:coercion spec-coercion/coercion
|
||||||
|
:muuntaja m/instance
|
||||||
|
:interceptors [;; query-params & form-params
|
||||||
|
(parameters/parameters-interceptor)
|
||||||
|
;; content-negotiation
|
||||||
|
(muuntaja/format-negotiate-interceptor)
|
||||||
|
;; encoding response body
|
||||||
|
(muuntaja/format-response-interceptor)
|
||||||
|
;; exception handling - doesn't work
|
||||||
|
;;(exception/exception-interceptor)
|
||||||
|
;; decoding request body
|
||||||
|
(muuntaja/format-request-interceptor)
|
||||||
|
;; coercing response bodys
|
||||||
|
(coercion/coerce-response-interceptor)
|
||||||
|
;; coercing request parameters
|
||||||
|
(coercion/coerce-request-interceptor)
|
||||||
|
;; multipart
|
||||||
|
(multipart/multipart-interceptor)]}})
|
||||||
|
|
||||||
|
;; optional default ring handler (if no routes have matched)
|
||||||
|
(ring/routes
|
||||||
|
(swagger-ui/create-swagger-ui-handler
|
||||||
|
{:path "/"
|
||||||
|
:config {:validatorUrl nil}})
|
||||||
|
(ring/create-default-handler))))
|
||||||
|
|
||||||
|
(defonce server (atom nil))
|
||||||
|
|
||||||
|
(defn start []
|
||||||
|
(when @server
|
||||||
|
(io.pedestal.http/stop @server)
|
||||||
|
(println "server stopped"))
|
||||||
|
(-> {:env :prod
|
||||||
|
:io.pedestal.http/routes []
|
||||||
|
:io.pedestal.http/resource-path "/public"
|
||||||
|
:io.pedestal.http/type :jetty
|
||||||
|
:io.pedestal.http/port 3000}
|
||||||
|
(merge {:env :dev
|
||||||
|
:io.pedestal.http/join? false
|
||||||
|
:io.pedestal.http/allowed-origins {:creds true :allowed-origins (constantly true)}})
|
||||||
|
(pedestal/default-interceptors routing-interceptor)
|
||||||
|
io.pedestal.http/dev-interceptors
|
||||||
|
io.pedestal.http/create-server
|
||||||
|
io.pedestal.http/start
|
||||||
|
(->> (reset! server)))
|
||||||
|
(println "server running in port 3000"))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(start))
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
(ns reitit.interceptor.pedestal
|
||||||
|
(:require [io.pedestal.interceptor.chain :as chain]
|
||||||
|
[io.pedestal.interceptor :as interceptor]
|
||||||
|
[io.pedestal.http :as http]
|
||||||
|
[reitit.interceptor]
|
||||||
|
[reitit.http])
|
||||||
|
(:import (reitit.interceptor Executor)))
|
||||||
|
|
||||||
|
(def pedestal-executor
|
||||||
|
(reify
|
||||||
|
Executor
|
||||||
|
(queue [_ interceptors]
|
||||||
|
(->> interceptors
|
||||||
|
(map (fn [{:keys [::interceptor/handler] :as interceptor}]
|
||||||
|
(or handler interceptor)))
|
||||||
|
(map interceptor/interceptor)))
|
||||||
|
(enqueue [_ context interceptors]
|
||||||
|
(chain/enqueue context interceptors))))
|
||||||
|
|
||||||
|
(defn routing-interceptor
|
||||||
|
([router]
|
||||||
|
(routing-interceptor router nil))
|
||||||
|
([router default-handler]
|
||||||
|
(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}))))
|
||||||
|
|
||||||
|
(defn default-interceptors [spec router]
|
||||||
|
(-> spec
|
||||||
|
(assoc ::http/routes [])
|
||||||
|
(http/default-interceptors)
|
||||||
|
(update ::http/interceptors (comp vec butlast))
|
||||||
|
(update ::http/interceptors conj router)))
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit Ring App"
|
:description "Reitit Ring App"
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring/ring-jetty-adapter "1.7.0-RC2"]
|
||||||
[metosin/reitit "0.2.1"]]
|
[metosin/reitit "0.2.1"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit Ring App with Swagger"
|
:description "Reitit Ring App with Swagger"
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring/ring-jetty-adapter "1.7.0-RC2"]
|
||||||
[metosin/reitit "0.2.1"]]
|
[metosin/reitit "0.2.1"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
[reitit.ring.middleware.exception :as exception]
|
[reitit.ring.middleware.exception :as exception]
|
||||||
[reitit.ring.middleware.multipart :as multipart]
|
[reitit.ring.middleware.multipart :as multipart]
|
||||||
[ring.middleware.params :as params]
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
[ring.adapter.jetty :as jetty]
|
[ring.adapter.jetty :as jetty]
|
||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
{:data {:coercion reitit.coercion.spec/coercion
|
{:data {:coercion reitit.coercion.spec/coercion
|
||||||
:muuntaja m/instance
|
:muuntaja m/instance
|
||||||
:middleware [;; query-params & form-params
|
:middleware [;; query-params & form-params
|
||||||
params/wrap-params
|
parameters/parameters-middleware
|
||||||
;; content-negotiation
|
;; content-negotiation
|
||||||
muuntaja/format-negotiate-middleware
|
muuntaja/format-negotiate-middleware
|
||||||
;; encoding response body
|
;; encoding response body
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(defproject ring-example "0.1.0-SNAPSHOT"
|
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||||
:description "Reitit Ring App with Swagger"
|
:description "Reitit Ring App with Swagger"
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring/ring-jetty-adapter "1.7.0-RC2"]
|
||||||
[metosin/reitit "0.2.1"]]
|
[metosin/reitit "0.2.1"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||||
[reitit.ring.middleware.exception :as exception]
|
[reitit.ring.middleware.exception :as exception]
|
||||||
[reitit.ring.middleware.multipart :as multipart]
|
[reitit.ring.middleware.multipart :as multipart]
|
||||||
[ring.middleware.params :as params]
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
[ring.adapter.jetty :as jetty]
|
[ring.adapter.jetty :as jetty]
|
||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
[clojure.java.io :as io]))
|
[clojure.java.io :as io]))
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
(ring/router
|
(ring/router
|
||||||
[["/swagger.json"
|
[["/swagger.json"
|
||||||
{:get {:no-doc true
|
{:get {:no-doc true
|
||||||
:swagger {:info {:title "my-api"}}
|
:swagger {:info {:title "my-api"
|
||||||
|
:description "with reitit-ring"}}
|
||||||
:handler (swagger/create-swagger-handler)}}]
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
|
||||||
["/files"
|
["/files"
|
||||||
|
|
@ -38,8 +39,9 @@
|
||||||
:handler (fn [_]
|
:handler (fn [_]
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"Content-Type" "image/png"}
|
:headers {"Content-Type" "image/png"}
|
||||||
:body (io/input-stream
|
:body (-> "reitit.png"
|
||||||
(io/resource "reitit.png"))})}}]]
|
(io/resource)
|
||||||
|
(io/input-stream))})}}]]
|
||||||
|
|
||||||
["/math"
|
["/math"
|
||||||
{:swagger {:tags ["math"]}}
|
{:swagger {:tags ["math"]}}
|
||||||
|
|
@ -61,7 +63,7 @@
|
||||||
{:data {:coercion reitit.coercion.spec/coercion
|
{:data {:coercion reitit.coercion.spec/coercion
|
||||||
:muuntaja m/instance
|
:muuntaja m/instance
|
||||||
:middleware [;; query-params & form-params
|
:middleware [;; query-params & form-params
|
||||||
params/wrap-params
|
parameters/parameters-middleware
|
||||||
;; content-negotiation
|
;; content-negotiation
|
||||||
muuntaja/format-negotiate-middleware
|
muuntaja/format-negotiate-middleware
|
||||||
;; encoding response body
|
;; encoding response body
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
(if-let [interceptor (into-interceptor (compile data opts) data opts)]
|
(if-let [interceptor (into-interceptor (compile data opts) data opts)]
|
||||||
(map->Interceptor
|
(map->Interceptor
|
||||||
(merge
|
(merge
|
||||||
(dissoc this :create)
|
(dissoc this :compile)
|
||||||
(impl/strip-nils interceptor)))))))
|
(impl/strip-nils interceptor)))))))
|
||||||
|
|
||||||
nil
|
nil
|
||||||
|
|
|
||||||
12
modules/reitit-interceptors/project.clj
Normal file
12
modules/reitit-interceptors/project.clj
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
(defproject metosin/reitit-interceptors "0.2.1"
|
||||||
|
:description "Reitit, common interceptors bundled"
|
||||||
|
:url "https://github.com/metosin/reitit"
|
||||||
|
:license {:name "Eclipse Public License"
|
||||||
|
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||||
|
:scm {:name "git"
|
||||||
|
:url "https://github.com/metosin/reitit"}
|
||||||
|
:plugins [[lein-parent "0.3.2"]]
|
||||||
|
:parent-project {:path "../../project.clj"
|
||||||
|
:inherit [:deploy-repositories :managed-dependencies]}
|
||||||
|
:dependencies [[metosin/reitit-ring]
|
||||||
|
[metosin/muuntaja]])
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
(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)))
|
||||||
|
|
||||||
|
(s/def ::handlers (s/map-of any? fn?))
|
||||||
|
(s/def ::spec (s/keys :opt-un [::handlers]))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; helpers
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defn- super-classes [^Class k]
|
||||||
|
(loop [sk (.getSuperclass k), ks []]
|
||||||
|
(if-not (= sk Object)
|
||||||
|
(recur (.getSuperclass sk) (conj ks sk))
|
||||||
|
ks)))
|
||||||
|
|
||||||
|
(defn- call-error-handler [handlers error request]
|
||||||
|
(let [type (:type (ex-data error))
|
||||||
|
ex-class (class error)
|
||||||
|
error-handler (or (get handlers type)
|
||||||
|
(get handlers ex-class)
|
||||||
|
(some
|
||||||
|
(partial get handlers)
|
||||||
|
(descendants type))
|
||||||
|
(some
|
||||||
|
(partial get handlers)
|
||||||
|
(super-classes ex-class))
|
||||||
|
(get handlers ::default))]
|
||||||
|
(if-let [wrap (get handlers ::wrap)]
|
||||||
|
(wrap error-handler error request)
|
||||||
|
(error-handler error request))))
|
||||||
|
|
||||||
|
(defn print! [^PrintWriter writer & more]
|
||||||
|
(.write writer (str (str/join " " more) "\n")))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; handlers
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defn default-handler
|
||||||
|
"Default safe handler for any exception."
|
||||||
|
[^Exception e _]
|
||||||
|
{:status 500
|
||||||
|
:body {:type "exception"
|
||||||
|
:class (.getName (.getClass e))}})
|
||||||
|
|
||||||
|
(defn create-coercion-handler
|
||||||
|
"Creates a coercion exception handler."
|
||||||
|
[status]
|
||||||
|
(fn [e _]
|
||||||
|
{:status status
|
||||||
|
:body (coercion/encode-error (ex-data e))}))
|
||||||
|
|
||||||
|
(defn http-response-handler
|
||||||
|
"Reads response from Exception ex-data :response"
|
||||||
|
[e _]
|
||||||
|
(-> e ex-data :response))
|
||||||
|
|
||||||
|
(defn request-parsing-handler [e _]
|
||||||
|
{:status 400
|
||||||
|
:headers {"Content-Type" "text/plain"}
|
||||||
|
:body (str "Malformed " (-> e ex-data :format pr-str) " request.")})
|
||||||
|
|
||||||
|
(defn wrap-log-to-console [handler e {:keys [uri request-method] :as req}]
|
||||||
|
(print! *out* (Instant/now) request-method (pr-str uri) "=>" (.getMessage e))
|
||||||
|
(.printStackTrace e *out*)
|
||||||
|
(handler e req))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; public api
|
||||||
|
;;
|
||||||
|
|
||||||
|
(def default-handlers
|
||||||
|
{::default default-handler
|
||||||
|
::ring/response http-response-handler
|
||||||
|
:muuntaja/decode request-parsing-handler
|
||||||
|
::coercion/request-coercion (create-coercion-handler 400)
|
||||||
|
::coercion/response-coercion (create-coercion-handler 500)})
|
||||||
|
|
||||||
|
(defn exception-interceptor
|
||||||
|
"Creates an Interceptor that catches all exceptions. Takes a map
|
||||||
|
of `identifier => exception request => response` that is used to select
|
||||||
|
the exception handler for the thown/raised exception identifier. Exception
|
||||||
|
idenfier is either a `Keyword` or a Exception Class.
|
||||||
|
|
||||||
|
The following handlers special handlers are available:
|
||||||
|
|
||||||
|
| key | description
|
||||||
|
|------------------------|-------------
|
||||||
|
| `::exception/default` | a default exception handler if nothing else mathced (default [[default-handler]]).
|
||||||
|
| `::exception/wrap` | a 3-arity handler to wrap the actual handler `handler exception request => response`
|
||||||
|
|
||||||
|
The handler is selected from the options map by exception idenfiter
|
||||||
|
in the following lookup order:
|
||||||
|
|
||||||
|
1) `:type` of exception ex-data
|
||||||
|
2) Class of exception
|
||||||
|
3) `:type` ancestors of exception ex-data
|
||||||
|
4) Super Classes of exception
|
||||||
|
5) The ::default handler
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
(require '[reitit.ring.interceptors.exception :as exception])
|
||||||
|
|
||||||
|
;; type hierarchy
|
||||||
|
(derive ::error ::exception)
|
||||||
|
(derive ::failure ::exception)
|
||||||
|
(derive ::horror ::exception)
|
||||||
|
|
||||||
|
(defn handler [message exception request]
|
||||||
|
{:status 500
|
||||||
|
:body {:message message
|
||||||
|
:exception (str exception)
|
||||||
|
:uri (:uri request)}})
|
||||||
|
|
||||||
|
(exception/exception-interceptor
|
||||||
|
(merge
|
||||||
|
exception/default-handlers
|
||||||
|
{;; ex-data with :type ::error
|
||||||
|
::error (partial handler \"error\")
|
||||||
|
|
||||||
|
;; ex-data with ::exception or ::failure
|
||||||
|
::exception (partial handler \"exception\")
|
||||||
|
|
||||||
|
;; SQLException and all it's child classes
|
||||||
|
java.sql.SQLException (partial handler \"sql-exception\")
|
||||||
|
|
||||||
|
;; override the default handler
|
||||||
|
::exception/default (partial handler \"default\")
|
||||||
|
|
||||||
|
;; print stack-traces for all exceptions
|
||||||
|
::exception/wrap (fn [handler e request]
|
||||||
|
(.printStackTrace e)
|
||||||
|
(handler e request))}))"
|
||||||
|
([]
|
||||||
|
(exception-interceptor default-handlers))
|
||||||
|
([handlers]
|
||||||
|
{:name ::exception
|
||||||
|
:spec ::spec
|
||||||
|
:error (fn [ctx]
|
||||||
|
(let [error (:error ctx)
|
||||||
|
request (:request ctx)
|
||||||
|
response (call-error-handler handlers error request)]
|
||||||
|
(if (instance? Exception response)
|
||||||
|
(-> ctx (assoc :error response) (dissoc :response))
|
||||||
|
(-> ctx (assoc :response response) (dissoc :error)))))}))
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
(ns reitit.http.interceptors.multipart
|
||||||
|
(:require [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)))
|
||||||
|
|
||||||
|
(s/def ::filename string?)
|
||||||
|
(s/def ::content-type string?)
|
||||||
|
(s/def ::tempfile (partial instance? File))
|
||||||
|
(s/def ::bytes bytes?)
|
||||||
|
(s/def ::size int?)
|
||||||
|
|
||||||
|
(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"}))
|
||||||
|
|
||||||
|
(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"}))
|
||||||
|
|
||||||
|
(defn- coerced-request [request coercers]
|
||||||
|
(if-let [coerced (if coercers (coercion/coerce-request coercers request))]
|
||||||
|
(update request :parameters merge coerced)
|
||||||
|
request))
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; public api
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defn multipart-interceptor
|
||||||
|
"Creates a Interceptor to handle the multipart params, based on
|
||||||
|
ring.middleware.multipart-params, taking same options. Mounts only
|
||||||
|
if endpoint has `[:parameters :multipart]` defined. Publishes coerced
|
||||||
|
parameters into `[:parameters :multipart]` under request."
|
||||||
|
([]
|
||||||
|
(multipart-interceptor nil))
|
||||||
|
([options]
|
||||||
|
{:name ::multipart
|
||||||
|
:compile (fn [{:keys [parameters coercion]} opts]
|
||||||
|
(if-let [multipart (:multipart parameters)]
|
||||||
|
(let [parameter-coercion {:multipart (coercion/->ParameterCoercion
|
||||||
|
: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"}}}
|
||||||
|
:enter (fn [ctx]
|
||||||
|
(let [request (-> (:request ctx)
|
||||||
|
(multipart-params/multipart-params-request options)
|
||||||
|
(coerced-request coercers))]
|
||||||
|
(assoc ctx :request request)))})))}))
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
(ns reitit.http.interceptors.muuntaja
|
||||||
|
(:require [muuntaja.core :as m]
|
||||||
|
[muuntaja.interceptor]
|
||||||
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
|
(s/def ::muuntaja m/muuntaja?)
|
||||||
|
(s/def ::spec (s/keys :opt-un [::muuntaja]))
|
||||||
|
|
||||||
|
(defn- displace [x] (with-meta x {:displace true}))
|
||||||
|
(defn- stripped [x] (select-keys x [:enter :leave :error]))
|
||||||
|
|
||||||
|
(defn format-interceptor
|
||||||
|
"Interceptor for content-negotiation, request and response formatting.
|
||||||
|
|
||||||
|
Negotiates a request body based on `Content-Type` header and response body based on
|
||||||
|
`Accept`, `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request`
|
||||||
|
and `:muuntaja/response` keys into the request.
|
||||||
|
|
||||||
|
Decodes the request body into `:body-params` using the `:muuntaja/request` key in request
|
||||||
|
if the `:body-params` doesn't already exist.
|
||||||
|
|
||||||
|
Encodes the response body using the `:muuntaja/response` key in request if the response
|
||||||
|
doesn't have `Content-Type` header already set.
|
||||||
|
|
||||||
|
Optionally takes a default muuntaja instance as argument.
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| -------------|-------------|
|
||||||
|
| `:muuntaja` | `muuntaja.core/Muuntaja` instance, does not mount if not set."
|
||||||
|
([]
|
||||||
|
(format-interceptor nil))
|
||||||
|
([default-muuntaja]
|
||||||
|
{:name ::format
|
||||||
|
:spec ::spec
|
||||||
|
:compile (fn [{:keys [muuntaja]} _]
|
||||||
|
(if-let [muuntaja (or muuntaja default-muuntaja)]
|
||||||
|
(merge
|
||||||
|
(stripped (muuntaja.interceptor/format-interceptor muuntaja))
|
||||||
|
{:data {:swagger {:produces (displace (m/encodes muuntaja))
|
||||||
|
:consumes (displace (m/decodes muuntaja))}}})))}))
|
||||||
|
|
||||||
|
(defn format-negotiate-interceptor
|
||||||
|
"Interceptor for content-negotiation.
|
||||||
|
|
||||||
|
Negotiates a request body based on `Content-Type` header and response body based on
|
||||||
|
`Accept`, `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request`
|
||||||
|
and `:muuntaja/response` keys into the request.
|
||||||
|
|
||||||
|
Optionally takes a default muuntaja instance as argument.
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| -------------|-------------|
|
||||||
|
| `:muuntaja` | `muuntaja.core/Muuntaja` instance, does not mount if not set."
|
||||||
|
([]
|
||||||
|
(format-negotiate-interceptor nil))
|
||||||
|
([default-muuntaja]
|
||||||
|
{:name ::format-negotiate
|
||||||
|
:spec ::spec
|
||||||
|
:compile (fn [{:keys [muuntaja]} _]
|
||||||
|
(if-let [muuntaja (or muuntaja default-muuntaja)]
|
||||||
|
(stripped (muuntaja.interceptor/format-negotiate-interceptor muuntaja))))}))
|
||||||
|
|
||||||
|
(defn format-request-interceptor
|
||||||
|
"Interceptor for request formatting.
|
||||||
|
|
||||||
|
Decodes the request body into `:body-params` using the `:muuntaja/request` key in request
|
||||||
|
if the `:body-params` doesn't already exist.
|
||||||
|
|
||||||
|
Optionally takes a default muuntaja instance as argument.
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| -------------|-------------|
|
||||||
|
| `:muuntaja` | `muuntaja.core/Muuntaja` instance, does not mount if not set."
|
||||||
|
([]
|
||||||
|
(format-request-interceptor nil))
|
||||||
|
([default-muuntaja]
|
||||||
|
{:name ::format-request
|
||||||
|
:spec ::spec
|
||||||
|
:compile (fn [{:keys [muuntaja]} _]
|
||||||
|
(if-let [muuntaja (or muuntaja default-muuntaja)]
|
||||||
|
(merge
|
||||||
|
(stripped (muuntaja.interceptor/format-request-interceptor muuntaja))
|
||||||
|
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}})))}))
|
||||||
|
|
||||||
|
(defn format-response-interceptor
|
||||||
|
"Interceptor for response formatting.
|
||||||
|
|
||||||
|
Encodes the response body using the `:muuntaja/response` key in request if the response
|
||||||
|
doesn't have `Content-Type` header already set.
|
||||||
|
|
||||||
|
Optionally takes a default muuntaja instance as argument.
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| -------------|-------------|
|
||||||
|
| `:muuntaja` | `muuntaja.core/Muuntaja` instance, does not mount if not set."
|
||||||
|
([]
|
||||||
|
(format-response-interceptor nil))
|
||||||
|
([default-muuntaja]
|
||||||
|
{:name ::format-response
|
||||||
|
:spec ::spec
|
||||||
|
:compile (fn [{:keys [muuntaja]} _]
|
||||||
|
(if-let [muuntaja (or muuntaja default-muuntaja)]
|
||||||
|
(merge
|
||||||
|
(stripped (muuntaja.interceptor/format-response-interceptor muuntaja))
|
||||||
|
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}})))}))
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
(ns reitit.http.interceptors.parameters
|
||||||
|
(:require [ring.middleware.params :as params]))
|
||||||
|
|
||||||
|
(defn parameters-interceptor
|
||||||
|
"Interceptor to parse urlencoded parameters from the query string and form
|
||||||
|
body (if the request is a url-encoded form). Adds the following keys to
|
||||||
|
the request map:
|
||||||
|
|
||||||
|
:query-params - a map of parameters from the query string
|
||||||
|
:form-params - a map of parameters from the body
|
||||||
|
:params - a merged map of all types of parameter"
|
||||||
|
[]
|
||||||
|
{:name ::parameters
|
||||||
|
:enter (fn [ctx]
|
||||||
|
(let [request (:request ctx)]
|
||||||
|
(assoc ctx :request (params/params-request request))))})
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(ns ^:no-doc reitit.ring.middleware.multipart
|
(ns reitit.ring.middleware.multipart
|
||||||
(:refer-clojure :exclude [compile])
|
(:refer-clojure :exclude [compile])
|
||||||
(:require [reitit.coercion :as coercion]
|
(:require [reitit.coercion :as coercion]
|
||||||
[ring.middleware.multipart-params :as multipart-params]
|
[ring.middleware.multipart-params :as multipart-params]
|
||||||
|
|
@ -40,14 +40,10 @@
|
||||||
:wrap (fn [handler]
|
:wrap (fn [handler]
|
||||||
(fn
|
(fn
|
||||||
([request]
|
([request]
|
||||||
(try
|
|
||||||
(-> request
|
(-> request
|
||||||
(multipart-params/multipart-params-request options)
|
(multipart-params/multipart-params-request options)
|
||||||
(coerced-request coercers)
|
(coerced-request coercers)
|
||||||
(handler))
|
(handler)))
|
||||||
(catch Exception e
|
|
||||||
(.printStackTrace e)
|
|
||||||
(throw e))))
|
|
||||||
([request respond raise]
|
([request respond raise]
|
||||||
(-> request
|
(-> request
|
||||||
(multipart-params/multipart-params-request options)
|
(multipart-params/multipart-params-request options)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
(ns reitit.ring.middleware.parameters
|
||||||
|
(:require [ring.middleware.params :as params]))
|
||||||
|
|
||||||
|
(def parameters-middleware
|
||||||
|
"Middleware to parse urlencoded parameters from the query string and form
|
||||||
|
body (if the request is a url-encoded form). Adds the following keys to
|
||||||
|
the request map:
|
||||||
|
|
||||||
|
:query-params - a map of parameters from the query string
|
||||||
|
:form-params - a map of parameters from the body
|
||||||
|
:params - a merged map of all types of parameter"
|
||||||
|
{:name ::parameters
|
||||||
|
:enter (fn [ctx]
|
||||||
|
(let [request (:request ctx)]
|
||||||
|
(assoc ctx :request (params/params-request request))))})
|
||||||
|
|
@ -84,11 +84,14 @@
|
||||||
:x-id ids}))
|
:x-id ids}))
|
||||||
accept-route (fn [route]
|
accept-route (fn [route]
|
||||||
(-> route second :swagger :id (or ::default) ->set (set/intersection ids) seq))
|
(-> route second :swagger :id (or ::default) ->set (set/intersection ids) seq))
|
||||||
transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data middleware :middleware}]]
|
transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data
|
||||||
|
middleware :middleware
|
||||||
|
interceptors :interceptors}]]
|
||||||
(if (and data (not no-doc))
|
(if (and data (not no-doc))
|
||||||
[method
|
[method
|
||||||
(meta-merge
|
(meta-merge
|
||||||
(apply meta-merge (keep (comp :swagger :data) middleware))
|
(apply meta-merge (keep (comp :swagger :data) middleware))
|
||||||
|
(apply meta-merge (keep (comp :swagger :data) interceptors))
|
||||||
(if coercion
|
(if coercion
|
||||||
(coercion/get-apidocs coercion :swagger data))
|
(coercion/get-apidocs coercion :swagger data))
|
||||||
(select-keys data [:tags :summary :description])
|
(select-keys data [:tags :summary :description])
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
[metosin/reitit-ring]
|
[metosin/reitit-ring]
|
||||||
[metosin/reitit-middleware]
|
[metosin/reitit-middleware]
|
||||||
[metosin/reitit-http]
|
[metosin/reitit-http]
|
||||||
|
[metosin/reitit-interceptors]
|
||||||
[metosin/reitit-swagger]
|
[metosin/reitit-swagger]
|
||||||
[metosin/reitit-swagger-ui]
|
[metosin/reitit-swagger-ui]
|
||||||
[metosin/reitit-frontend]
|
[metosin/reitit-frontend]
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
[metosin/reitit-ring "0.2.1"]
|
[metosin/reitit-ring "0.2.1"]
|
||||||
[metosin/reitit-middleware "0.2.1"]
|
[metosin/reitit-middleware "0.2.1"]
|
||||||
[metosin/reitit-http "0.2.1"]
|
[metosin/reitit-http "0.2.1"]
|
||||||
|
[metosin/reitit-interceptors "0.2.1"]
|
||||||
[metosin/reitit-swagger "0.2.1"]
|
[metosin/reitit-swagger "0.2.1"]
|
||||||
[metosin/reitit-swagger-ui "0.2.1"]
|
[metosin/reitit-swagger-ui "0.2.1"]
|
||||||
[metosin/reitit-frontend "0.2.1"]
|
[metosin/reitit-frontend "0.2.1"]
|
||||||
|
|
@ -45,6 +46,7 @@
|
||||||
"modules/reitit-ring/src"
|
"modules/reitit-ring/src"
|
||||||
"modules/reitit-http/src"
|
"modules/reitit-http/src"
|
||||||
"modules/reitit-middleware/src"
|
"modules/reitit-middleware/src"
|
||||||
|
"modules/reitit-interceptors/src"
|
||||||
"modules/reitit-spec/src"
|
"modules/reitit-spec/src"
|
||||||
"modules/reitit-schema/src"
|
"modules/reitit-schema/src"
|
||||||
"modules/reitit-swagger/src"
|
"modules/reitit-swagger/src"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ for ext in \
|
||||||
reitit-ring \
|
reitit-ring \
|
||||||
reitit-middleware \
|
reitit-middleware \
|
||||||
reitit-http \
|
reitit-http \
|
||||||
|
reitit-interceptors \
|
||||||
reitit-swagger \
|
reitit-swagger \
|
||||||
reitit-swagger-ui \
|
reitit-swagger-ui \
|
||||||
reitit-frontend \
|
reitit-frontend \
|
||||||
|
|
|
||||||
119
test/clj/reitit/http/interceptors/exception_test.clj
Normal file
119
test/clj/reitit/http/interceptors/exception_test.clj
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
(ns reitit.http.interceptors.exception-test
|
||||||
|
(:require [clojure.test :refer [deftest testing is]]
|
||||||
|
[reitit.ring :as ring]
|
||||||
|
[reitit.http :as http]
|
||||||
|
[reitit.http.interceptors.exception :as exception]
|
||||||
|
[reitit.interceptor.sieppari :as sieppari]
|
||||||
|
[reitit.coercion.spec]
|
||||||
|
[reitit.http.coercion]
|
||||||
|
[muuntaja.core :as m])
|
||||||
|
(:import (java.sql SQLException SQLWarning)))
|
||||||
|
|
||||||
|
(derive ::kikka ::kukka)
|
||||||
|
|
||||||
|
(deftest exception-test
|
||||||
|
(letfn [(create
|
||||||
|
([f]
|
||||||
|
(create f nil))
|
||||||
|
([f wrap]
|
||||||
|
(http/ring-handler
|
||||||
|
(http/router
|
||||||
|
[["/defaults"
|
||||||
|
{:handler f}]
|
||||||
|
["/coercion"
|
||||||
|
{:interceptors [(reitit.http.coercion/coerce-request-interceptor)
|
||||||
|
(reitit.http.coercion/coerce-response-interceptor)]
|
||||||
|
:coercion reitit.coercion.spec/coercion
|
||||||
|
:parameters {:query {:x int?, :y int?}}
|
||||||
|
:responses {200 {:body {:total pos-int?}}}
|
||||||
|
:handler f}]]
|
||||||
|
{:data {:interceptors [(exception/exception-interceptor
|
||||||
|
(merge
|
||||||
|
exception/default-handlers
|
||||||
|
{::kikka (constantly {:status 400, :body "kikka"})
|
||||||
|
SQLException (constantly {:status 400, :body "sql"})
|
||||||
|
::exception/wrap wrap}))]}})
|
||||||
|
{:executor sieppari/executor})))]
|
||||||
|
|
||||||
|
(testing "normal calls work ok"
|
||||||
|
(let [response {:status 200, :body "ok"}
|
||||||
|
app (create (fn [_] response))]
|
||||||
|
(is (= response (app {:request-method :get, :uri "/defaults"})))))
|
||||||
|
|
||||||
|
(testing "unknown exception"
|
||||||
|
(let [app (create (fn [_] (throw (NullPointerException.))))]
|
||||||
|
(is (= {:status 500
|
||||||
|
:body {:type "exception"
|
||||||
|
:class "java.lang.NullPointerException"}}
|
||||||
|
(app {:request-method :get, :uri "/defaults"}))))
|
||||||
|
(let [app (create (fn [_] (throw (ex-info "fail" {:type ::invalid}))))]
|
||||||
|
(is (= {:status 500
|
||||||
|
:body {:type "exception"
|
||||||
|
:class "clojure.lang.ExceptionInfo"}}
|
||||||
|
(app {:request-method :get, :uri "/defaults"})))))
|
||||||
|
|
||||||
|
(testing "::ring/response"
|
||||||
|
(let [response {:status 200, :body "ok"}
|
||||||
|
app (create (fn [_] (throw (ex-info "fail" {:type ::ring/response, :response response}))))]
|
||||||
|
(is (= response (app {:request-method :get, :uri "/defaults"})))))
|
||||||
|
|
||||||
|
(testing ":muuntaja/decode"
|
||||||
|
(let [app (create (fn [_] (m/decode m/instance "application/json" "{:so \"invalid\"}")))]
|
||||||
|
(is (= {:body "Malformed \"application/json\" request."
|
||||||
|
:headers {"Content-Type" "text/plain"}
|
||||||
|
:status 400}
|
||||||
|
(app {:request-method :get, :uri "/defaults"}))))
|
||||||
|
|
||||||
|
(testing "::coercion/request-coercion"
|
||||||
|
(let [app (create (fn [{{{:keys [x y]} :query} :parameters}]
|
||||||
|
{:status 200, :body {:total (+ x y)}}))]
|
||||||
|
|
||||||
|
(let [{:keys [status body]} (app {:request-method :get
|
||||||
|
:uri "/coercion"
|
||||||
|
:query-params {"x" "1", "y" "2"}})]
|
||||||
|
(is (= 200 status))
|
||||||
|
(is (= {:total 3} body)))
|
||||||
|
|
||||||
|
(let [{:keys [status body]} (app {:request-method :get
|
||||||
|
:uri "/coercion"
|
||||||
|
:query-params {"x" "abba", "y" "2"}})]
|
||||||
|
(is (= 400 status))
|
||||||
|
(is (= :reitit.coercion/request-coercion (:type body))))
|
||||||
|
|
||||||
|
(let [{:keys [status body]} (app {:request-method :get
|
||||||
|
:uri "/coercion"
|
||||||
|
:query-params {"x" "-10", "y" "2"}})]
|
||||||
|
(is (= 500 status))
|
||||||
|
(is (= :reitit.coercion/response-coercion (:type body)))))))
|
||||||
|
|
||||||
|
(testing "exact :type"
|
||||||
|
(let [app (create (fn [_] (throw (ex-info "fail" {:type ::kikka}))))]
|
||||||
|
(is (= {:status 400, :body "kikka"}
|
||||||
|
(app {:request-method :get, :uri "/defaults"})))))
|
||||||
|
|
||||||
|
(testing "parent :type"
|
||||||
|
(let [app (create (fn [_] (throw (ex-info "fail" {:type ::kukka}))))]
|
||||||
|
(is (= {:status 400, :body "kikka"}
|
||||||
|
(app {:request-method :get, :uri "/defaults"})))))
|
||||||
|
|
||||||
|
(testing "exact Exception"
|
||||||
|
(let [app (create (fn [_] (throw (SQLException.))))]
|
||||||
|
(is (= {:status 400, :body "sql"}
|
||||||
|
(app {:request-method :get, :uri "/defaults"})))))
|
||||||
|
|
||||||
|
(testing "Exception SuperClass"
|
||||||
|
(let [app (create (fn [_] (throw (SQLWarning.))))]
|
||||||
|
(is (= {:status 400, :body "sql"}
|
||||||
|
(app {:request-method :get, :uri "/defaults"})))))
|
||||||
|
|
||||||
|
(testing "::exception/wrap"
|
||||||
|
(let [calls (atom 0)
|
||||||
|
app (create (fn [_] (throw (SQLWarning.)))
|
||||||
|
(fn [handler exception request]
|
||||||
|
(if (< (swap! calls inc) 2)
|
||||||
|
(handler exception request)
|
||||||
|
{:status 500, :body "too many tries"})))]
|
||||||
|
(is (= {:status 400, :body "sql"}
|
||||||
|
(app {:request-method :get, :uri "/defaults"})))
|
||||||
|
(is (= {:status 500, :body "too many tries"}
|
||||||
|
(app {:request-method :get, :uri "/defaults"})))))))
|
||||||
3
test/clj/reitit/http/interceptors/multipart_test.clj
Normal file
3
test/clj/reitit/http/interceptors/multipart_test.clj
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
(ns reitit.http.interceptors.multipart-test)
|
||||||
|
|
||||||
|
;; TODO
|
||||||
147
test/clj/reitit/http/interceptors/muuntaja_test.clj
Normal file
147
test/clj/reitit/http/interceptors/muuntaja_test.clj
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
(ns reitit.http.interceptors.muuntaja-test
|
||||||
|
(:require [clojure.test :refer [deftest testing is]]
|
||||||
|
[reitit.http :as http]
|
||||||
|
[reitit.http.interceptors.muuntaja :as muuntaja]
|
||||||
|
[reitit.swagger :as swagger]
|
||||||
|
[reitit.interceptor.sieppari :as sieppari]
|
||||||
|
[muuntaja.core :as m]))
|
||||||
|
|
||||||
|
(deftest muuntaja-test
|
||||||
|
(let [data {:kikka "kukka"}
|
||||||
|
app (http/ring-handler
|
||||||
|
(http/router
|
||||||
|
["/ping" {:get (constantly {:status 200, :body data})}]
|
||||||
|
{:data {:muuntaja m/instance
|
||||||
|
:interceptors [(muuntaja/format-interceptor)]}})
|
||||||
|
{:executor sieppari/executor})]
|
||||||
|
(is (= data (->> {:request-method :get, :uri "/ping"}
|
||||||
|
(app)
|
||||||
|
:body
|
||||||
|
(m/decode m/instance "application/json"))))))
|
||||||
|
|
||||||
|
(deftest muuntaja-swagger-test
|
||||||
|
(let [with-defaults m/instance
|
||||||
|
no-edn-decode (m/create (-> m/default-options (update-in [:formats "application/edn"] dissoc :decoder)))
|
||||||
|
just-edn (m/create (-> m/default-options (m/select-formats ["application/edn"])))
|
||||||
|
app (http/ring-handler
|
||||||
|
(http/router
|
||||||
|
[["/defaults"
|
||||||
|
{:get identity}]
|
||||||
|
["/explicit-defaults"
|
||||||
|
{:muuntaja with-defaults
|
||||||
|
:get identity}]
|
||||||
|
["/no-edn-decode"
|
||||||
|
{:muuntaja no-edn-decode
|
||||||
|
:get identity}]
|
||||||
|
["/just-edn"
|
||||||
|
{:muuntaja just-edn
|
||||||
|
:get identity}]
|
||||||
|
["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:handler (swagger/create-swagger-handler)}}]]
|
||||||
|
{:data {:muuntaja m/instance
|
||||||
|
:interceptors [(muuntaja/format-interceptor)]}})
|
||||||
|
{:executor sieppari/executor})
|
||||||
|
spec (fn [path]
|
||||||
|
(let [path (keyword path)]
|
||||||
|
(-> {:request-method :get :uri "/swagger.json"}
|
||||||
|
(app) :body
|
||||||
|
(->> (m/decode m/instance "application/json"))
|
||||||
|
:paths path :get)))
|
||||||
|
produces (comp set :produces spec)
|
||||||
|
consumes (comp set :consumes spec)]
|
||||||
|
|
||||||
|
(testing "with defaults"
|
||||||
|
(let [path "/defaults"]
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"
|
||||||
|
"application/edn"}
|
||||||
|
(produces path)
|
||||||
|
(consumes path)))))
|
||||||
|
|
||||||
|
(testing "with explicit muuntaja defaults"
|
||||||
|
(let [path "/explicit-defaults"]
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"
|
||||||
|
"application/edn"}
|
||||||
|
(produces path)
|
||||||
|
(consumes path)))))
|
||||||
|
|
||||||
|
(testing "without edn decode"
|
||||||
|
(let [path "/no-edn-decode"]
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"
|
||||||
|
"application/edn"}
|
||||||
|
(produces path)))
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"}
|
||||||
|
(consumes path)))))
|
||||||
|
|
||||||
|
(testing "just edn"
|
||||||
|
(let [path "/just-edn"]
|
||||||
|
(is (= #{"application/edn"}
|
||||||
|
(produces path)
|
||||||
|
(consumes path)))))))
|
||||||
|
|
||||||
|
(deftest muuntaja-swagger-parts-test
|
||||||
|
(let [app (http/ring-handler
|
||||||
|
(http/router
|
||||||
|
[["/request"
|
||||||
|
{:interceptors [(muuntaja/format-negotiate-interceptor)
|
||||||
|
(muuntaja/format-request-interceptor)]
|
||||||
|
:get identity}]
|
||||||
|
["/response"
|
||||||
|
{:interceptors [(muuntaja/format-negotiate-interceptor)
|
||||||
|
(muuntaja/format-response-interceptor)]
|
||||||
|
:get identity}]
|
||||||
|
["/both"
|
||||||
|
{:interceptors [(muuntaja/format-negotiate-interceptor)
|
||||||
|
(muuntaja/format-response-interceptor)
|
||||||
|
(muuntaja/format-request-interceptor)]
|
||||||
|
:get identity}]
|
||||||
|
|
||||||
|
["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:handler (swagger/create-swagger-handler)}}]]
|
||||||
|
{:data {:muuntaja m/instance}})
|
||||||
|
{:executor sieppari/executor})
|
||||||
|
spec (fn [path]
|
||||||
|
(-> {:request-method :get :uri "/swagger.json"}
|
||||||
|
(app) :body :paths (get path) :get))
|
||||||
|
produces (comp :produces spec)
|
||||||
|
consumes (comp :consumes spec)]
|
||||||
|
|
||||||
|
(testing "just request formatting"
|
||||||
|
(let [path "/request"]
|
||||||
|
(is (nil? (produces path)))
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"
|
||||||
|
"application/edn"}
|
||||||
|
(consumes path)))))
|
||||||
|
|
||||||
|
(testing "just response formatting"
|
||||||
|
(let [path "/response"]
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"
|
||||||
|
"application/edn"}
|
||||||
|
(produces path)))
|
||||||
|
(is (nil? (consumes path)))))
|
||||||
|
|
||||||
|
(testing "just response formatting"
|
||||||
|
(let [path "/both"]
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"
|
||||||
|
"application/edn"}
|
||||||
|
(produces path)))
|
||||||
|
(is (= #{"application/json"
|
||||||
|
"application/transit+msgpack"
|
||||||
|
"application/transit+json"
|
||||||
|
"application/edn"}
|
||||||
|
(consumes path)))))))
|
||||||
3
test/clj/reitit/http/interceptors/parameters_test.clj
Normal file
3
test/clj/reitit/http/interceptors/parameters_test.clj
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
(ns reitit.http.interceptors.parameters-test)
|
||||||
|
|
||||||
|
;; TODO
|
||||||
3
test/clj/reitit/ring/middleware/parameters_test.clj
Normal file
3
test/clj/reitit/ring/middleware/parameters_test.clj
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
(ns reitit.ring.middleware.parameters-test)
|
||||||
|
|
||||||
|
;; TODO
|
||||||
|
|
@ -111,21 +111,21 @@
|
||||||
(let [app (create [[i1 :value]])]
|
(let [app (create [[i1 :value]])]
|
||||||
(dotimes [_ 10]
|
(dotimes [_ 10]
|
||||||
(is (= [:data :value :ok] (app ctx)))
|
(is (= [:data :value :ok] (app ctx)))
|
||||||
(is (= 2 @calls)))))
|
(is (= 1 @calls)))))
|
||||||
|
|
||||||
(testing "as interceptor"
|
(testing "as interceptor"
|
||||||
(reset! calls 0)
|
(reset! calls 0)
|
||||||
(let [app (create [(i1 :value)])]
|
(let [app (create [(i1 :value)])]
|
||||||
(dotimes [_ 10]
|
(dotimes [_ 10]
|
||||||
(is (= [:data :value :ok] (app ctx)))
|
(is (= [:data :value :ok] (app ctx)))
|
||||||
(is (= 2 @calls)))))
|
(is (= 1 @calls)))))
|
||||||
|
|
||||||
(testing "deeply compiled interceptor"
|
(testing "deeply compiled interceptor"
|
||||||
(reset! calls 0)
|
(reset! calls 0)
|
||||||
(let [app (create [[i3 :value]])]
|
(let [app (create [[i3 :value]])]
|
||||||
(dotimes [_ 10]
|
(dotimes [_ 10]
|
||||||
(is (= [:data :value :ok] (app ctx)))
|
(is (= [:data :value :ok] (app ctx)))
|
||||||
(is (= 4 @calls)))))
|
(is (= 3 @calls)))))
|
||||||
|
|
||||||
(testing "too deeply compiled interceptor fails"
|
(testing "too deeply compiled interceptor fails"
|
||||||
(binding [interceptor/*max-compile-depth* 2]
|
(binding [interceptor/*max-compile-depth* 2]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue