This commit is contained in:
Tommi Reiman 2018-09-07 19:50:44 +03:00
parent 3d611d6cdb
commit de3fc480b4
3 changed files with 256 additions and 1 deletions

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-request-interceptor muuntaja))
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}})))}))

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

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