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}))
|
: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])
|
||||||
|
|
|
||||||
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