mirror of
https://github.com/metosin/reitit.git
synced 2025-12-21 10:01:11 +00:00
muuntaja
This commit is contained in:
parent
3d611d6cdb
commit
de3fc480b4
3 changed files with 256 additions and 1 deletions
|
|
@ -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))}}})))}))
|
||||
|
|
@ -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])
|
||||
|
|
|
|||
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)))))))
|
||||
Loading…
Reference in a new issue