Merge pull request #141 from metosin/interceptors

Interceptors
This commit is contained in:
Tommi Reiman 2018-09-08 10:52:58 +03:00 committed by GitHub
commit 3890e68100
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1164 additions and 63 deletions

View file

@ -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)
## `reitit-schema`

View file

@ -32,8 +32,8 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
* `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-http` http-routing with Pedestal-style Interceptors (WIP)
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors (WIP)
* `reitit-http` http-routing with Pedestal-style Interceptors
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors
## Latest version
@ -63,7 +63,7 @@ Optionally, the parts can be required separately:
;; frontend helpers
[metosin/reitit-frontend "0.2.1"]
;; http with interceptors (WIP)
;; http with interceptors
[metosin/reitit-http "0.2.1"]
[metosin/reitit-sieppari "0.2.1"]
```
@ -147,10 +147,6 @@ Invalid request:
; :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
* [`reitit-ring` with coercion, swagger and default middleware](https://github.com/metosin/reitit/blob/master/examples/ring-swagger/src/example/server.clj)

View file

@ -23,8 +23,8 @@ Modules:
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
* `reitit-frontend` Tools for [frontend routing](frontend/basics.md)
* `reitit-http` http-routing with Pedestal-style Interceptors (WIP)
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors (WIP)
* `reitit-http` http-routing with Pedestal-style Interceptors
* `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors
## Latest version
@ -54,7 +54,7 @@ Optionally, the parts can be required separately:
;; frontend helpers
[metosin/reitit-frontend "0.2.1"]
;; http with interceptors (WIP)
;; http with interceptors
[metosin/reitit-http "0.2.1"]
[metosin/reitit-sieppari "0.2.1"]
```

View file

@ -37,15 +37,18 @@
* [Compiling Middleware](ring/compiling_middleware.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
* [Basics](frontend/basics.md)
* [Browser integration](frontend/browser.md)
* [Controllers (WIP)](frontend/controllers.md)
## HTTP
* [Interceptors](http/interceptors.md)
* [Controllers](frontend/controllers.md)
## Advanced

View file

@ -35,12 +35,15 @@
["Route Data Validation" {:file "doc/ring/route_data_validation.md"}]
["Compiling Middleware" {:file "doc/ring/compiling_middleware.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" {}
["Basics" {:file "doc/frontend/basics.md"}]
["Browser integration" {:file "doc/frontend/browser.md"}]
["Controllers (WIP)" {:file "doc/frontend/controllers.md"}]]
["HTTP" {}
["Interceptors" {:file "doc/http/interceptors.md"}]]
["Controllers" {:file "doc/frontend/controllers.md"}]]
["Advanced" {}
["Configuring Routers" {:file "doc/advanced/configuring_routers.md"}]
["Composing Routers" {:file "doc/advanced/composing_routers.md"}]

View file

@ -1,4 +1,4 @@
# Controllers (WIP)
# Controllers
* https://github.com/metosin/reitit/tree/master/examples/frontend-controllers

View 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.

View file

@ -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.
## Current Status
Work-in-progress and considered alpha quality.
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.
## Reitit-http
@ -12,18 +8,15 @@ Work-in-progress and considered alpha quality.
[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`
* 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.
* optional entry poitn `reitit.http/routing-interceptor` to provide a routing interceptor, to be used with Pedestal.
* 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.
* 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
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
* https://quanttype.net/posts/2018-08-03-why-interceptors.html
* https://www.reddit.com/r/Clojure/comments/9csmty/why_interceptors/

25
doc/http/pedestal.md Normal file
View 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
View 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

View file

@ -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.
* [Parameter handling](#parameters-handling)
* [Exception handling](#exception-handling)
* [Content negotiation](#content-negotiation)
* [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
A polished version of [compojure-api](https://github.com/metosin/compojure-api) exception handling. Catches all exceptions and invokes configured exception handler.

View file

@ -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.exception :as exception]
[reitit.ring.middleware.multipart :as multipart]
[reitit.ring.middleware.parameters :as parameters]
[ring.middleware.params :as params]
[ring.adapter.jetty :as jetty]
[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
:muuntaja m/instance
:middleware [;; query-params & form-params
params/wrap-params
parameters/parameters-middleware
;; content-negotiation
muuntaja/format-negotiate-middleware
;; encoding response body

11
examples/http-swagger/.gitignore vendored Normal file
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 @@
# 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

View 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})

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

View 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))

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

View file

@ -4,6 +4,6 @@
[org.clojure/core.async "0.4.474"]
[funcool/promesa "1.9.0"]
[manifold "0.1.8"]
[ring "1.6.3"]
[ring/ring-jetty-adapter "1.7.0-RC2"]
[metosin/reitit "0.2.1"]]
:repl-options {:init-ns example.server})

View file

@ -1,6 +1,6 @@
(defproject just-coercion-with-ring "0.1.0-SNAPSHOT"
:description "Reitit coercion with vanilla ring"
: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/reitit "0.2.1"]])

11
examples/pedestal-swagger/.gitignore vendored Normal file
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 @@
# 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

View 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})

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

View 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))

View file

@ -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)))

View file

@ -1,6 +1,6 @@
(defproject ring-example "0.1.0-SNAPSHOT"
:description "Reitit Ring App"
:dependencies [[org.clojure/clojure "1.9.0"]
[ring "1.6.3"]
[ring/ring-jetty-adapter "1.7.0-RC2"]
[metosin/reitit "0.2.1"]]
:repl-options {:init-ns example.server})

View file

@ -1,6 +1,6 @@
(defproject ring-example "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.9.0"]
[ring "1.6.3"]
[ring/ring-jetty-adapter "1.7.0-RC2"]
[metosin/reitit "0.2.1"]]
:repl-options {:init-ns example.server})

View file

@ -7,7 +7,7 @@
[reitit.ring.middleware.muuntaja :as muuntaja]
[reitit.ring.middleware.exception :as exception]
[reitit.ring.middleware.multipart :as multipart]
[ring.middleware.params :as params]
[reitit.ring.middleware.parameters :as parameters]
[ring.adapter.jetty :as jetty]
[muuntaja.core :as m]
[clojure.spec.alpha :as s]
@ -76,7 +76,7 @@
{:data {:coercion reitit.coercion.spec/coercion
:muuntaja m/instance
:middleware [;; query-params & form-params
params/wrap-params
parameters/parameters-middleware
;; content-negotiation
muuntaja/format-negotiate-middleware
;; encoding response body

View file

@ -1,6 +1,6 @@
(defproject ring-example "0.1.0-SNAPSHOT"
:description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.9.0"]
[ring "1.6.3"]
[ring/ring-jetty-adapter "1.7.0-RC2"]
[metosin/reitit "0.2.1"]]
:repl-options {:init-ns example.server})

View file

@ -7,7 +7,7 @@
[reitit.ring.middleware.muuntaja :as muuntaja]
[reitit.ring.middleware.exception :as exception]
[reitit.ring.middleware.multipart :as multipart]
[ring.middleware.params :as params]
[reitit.ring.middleware.parameters :as parameters]
[ring.adapter.jetty :as jetty]
[muuntaja.core :as m]
[clojure.java.io :as io]))
@ -17,7 +17,8 @@
(ring/router
[["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "my-api"}}
:swagger {:info {:title "my-api"
:description "with reitit-ring"}}
:handler (swagger/create-swagger-handler)}}]
["/files"
@ -38,8 +39,9 @@
:handler (fn [_]
{:status 200
:headers {"Content-Type" "image/png"}
:body (io/input-stream
(io/resource "reitit.png"))})}}]]
:body (-> "reitit.png"
(io/resource)
(io/input-stream))})}}]]
["/math"
{:swagger {:tags ["math"]}}
@ -61,7 +63,7 @@
{:data {:coercion reitit.coercion.spec/coercion
:muuntaja m/instance
:middleware [;; query-params & form-params
params/wrap-params
parameters/parameters-middleware
;; content-negotiation
muuntaja/format-negotiate-middleware
;; encoding response body

View file

@ -92,7 +92,7 @@
(if-let [interceptor (into-interceptor (compile data opts) data opts)]
(map->Interceptor
(merge
(dissoc this :create)
(dissoc this :compile)
(impl/strip-nils interceptor)))))))
nil

View 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]])

View file

@ -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)))))}))

View file

@ -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)))})))}))

View file

@ -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))}}})))}))

View file

@ -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))))})

View file

@ -1,4 +1,4 @@
(ns ^:no-doc reitit.ring.middleware.multipart
(ns reitit.ring.middleware.multipart
(:refer-clojure :exclude [compile])
(:require [reitit.coercion :as coercion]
[ring.middleware.multipart-params :as multipart-params]
@ -40,14 +40,10 @@
:wrap (fn [handler]
(fn
([request]
(try
(-> request
(multipart-params/multipart-params-request options)
(coerced-request coercers)
(handler))
(catch Exception e
(.printStackTrace e)
(throw e))))
(-> request
(multipart-params/multipart-params-request options)
(coerced-request coercers)
(handler)))
([request respond raise]
(-> request
(multipart-params/multipart-params-request options)

View file

@ -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))))})

View file

@ -84,11 +84,14 @@
:x-id ids}))
accept-route (fn [route]
(-> 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))
[method
(meta-merge
(apply meta-merge (keep (comp :swagger :data) middleware))
(apply meta-merge (keep (comp :swagger :data) interceptors))
(if coercion
(coercion/get-apidocs coercion :swagger data))
(select-keys data [:tags :summary :description])

View file

@ -14,6 +14,7 @@
[metosin/reitit-ring]
[metosin/reitit-middleware]
[metosin/reitit-http]
[metosin/reitit-interceptors]
[metosin/reitit-swagger]
[metosin/reitit-swagger-ui]
[metosin/reitit-frontend]

View file

@ -17,6 +17,7 @@
[metosin/reitit-ring "0.2.1"]
[metosin/reitit-middleware "0.2.1"]
[metosin/reitit-http "0.2.1"]
[metosin/reitit-interceptors "0.2.1"]
[metosin/reitit-swagger "0.2.1"]
[metosin/reitit-swagger-ui "0.2.1"]
[metosin/reitit-frontend "0.2.1"]
@ -45,6 +46,7 @@
"modules/reitit-ring/src"
"modules/reitit-http/src"
"modules/reitit-middleware/src"
"modules/reitit-interceptors/src"
"modules/reitit-spec/src"
"modules/reitit-schema/src"
"modules/reitit-swagger/src"

View file

@ -10,6 +10,7 @@ for ext in \
reitit-ring \
reitit-middleware \
reitit-http \
reitit-interceptors \
reitit-swagger \
reitit-swagger-ui \
reitit-frontend \

View 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"})))))))

View file

@ -0,0 +1,3 @@
(ns reitit.http.interceptors.multipart-test)
;; TODO

View 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)))))))

View file

@ -0,0 +1,3 @@
(ns reitit.http.interceptors.parameters-test)
;; TODO

View file

@ -0,0 +1,3 @@
(ns reitit.ring.middleware.parameters-test)
;; TODO

View file

@ -111,21 +111,21 @@
(let [app (create [[i1 :value]])]
(dotimes [_ 10]
(is (= [:data :value :ok] (app ctx)))
(is (= 2 @calls)))))
(is (= 1 @calls)))))
(testing "as interceptor"
(reset! calls 0)
(let [app (create [(i1 :value)])]
(dotimes [_ 10]
(is (= [:data :value :ok] (app ctx)))
(is (= 2 @calls)))))
(is (= 1 @calls)))))
(testing "deeply compiled interceptor"
(reset! calls 0)
(let [app (create [[i3 :value]])]
(dotimes [_ 10]
(is (= [:data :value :ok] (app ctx)))
(is (= 4 @calls)))))
(is (= 3 @calls)))))
(testing "too deeply compiled interceptor fails"
(binding [interceptor/*max-compile-depth* 2]