Parameter & Response Coercion with specs

This commit is contained in:
Tommi Reiman 2017-12-31 11:34:37 +02:00
parent 07bcd2ea59
commit fb99b4f9fd
4 changed files with 113 additions and 1 deletions

View file

@ -70,9 +70,42 @@
:ret ::router)
;;
;; Route data validator
;; coercion
;;
(s/def :reitit.core.coercion/kw-map (s/map-of keyword? any?))
(s/def :reitit.core.coercion/query :reitit.core.coercion/kw-map)
(s/def :reitit.core.coercion/body :reitit.core.coercion/kw-map)
(s/def :reitit.core.coercion/form :reitit.core.coercion/kw-map)
(s/def :reitit.core.coercion/header :reitit.core.coercion/kw-map)
(s/def :reitit.core.coercion/path :reitit.core.coercion/kw-map)
(s/def :reitit.core.coercion/parameters
(s/keys :opt-un [:reitit.core.coercion/query
:reitit.core.coercion/body
:reitit.core.coercion/form
:reitit.core.coercion/header
:reitit.core.coercion/path]))
(s/def ::parameters
(s/keys :opt-un [:reitit.core.coercion/parameters]))
(s/def :reitit.core.coercion/status
(s/or :number number? :default #{:default}))
(s/def :reitit.core.coercion/schema any?)
(s/def :reitit.core.coercion/description string?)
(s/def :reitit.core.coercion/response
(s/keys :opt-un [:reitit.core.coercion/schema
:reitit.core.coercion/description]))
(s/def :reitit.core.coercion/responses
(s/map-of :reitit.core.coercion/status :reitit.core.coercion/response))
(s/def ::responses
(s/keys :opt-un [:reitit.core.coercion/responses]))
;;
;; Route data validator
;;
(defrecord Problem [path scope data spec problems])

View file

@ -1,5 +1,6 @@
(ns reitit.ring.coercion
(:require [reitit.coercion :as coercion]
[reitit.spec :as rs]
[reitit.impl :as impl]))
(defn handle-coercion-exception [e respond raise]
@ -22,6 +23,7 @@
Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters from route data, otherwise does not mount."
{:name ::coerce-request
:spec ::rs/parameters
:compile (fn [{:keys [coercion parameters]} opts]
(if (and coercion parameters)
(let [coercers (coercion/request-coercers coercion parameters opts)]
@ -39,6 +41,7 @@
Expects a :coercion of type `reitit.coercion/Coercion`
and :responses from route data, otherwise does not mount."
{:name ::coerce-response
:spec ::rs/responses
:compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses)
(let [coercers (coercion/response-coercers coercion responses opts)]

View file

@ -2,6 +2,8 @@
(:require [clojure.test :refer [deftest testing is]]
[reitit.ring :as ring]
[reitit.ring.spec :as rrs]
[reitit.ring.coercion :as rrc]
[reitit.coercion.spec]
[clojure.spec.alpha :as s]
[reitit.core :as r])
#?(:clj
@ -76,3 +78,49 @@
:wrap (fn [handler]
(fn [request]
(handler request)))}]}})))))
(deftest coercion-spec-test
(is (r/router?
(ring/router
["/api"
["/plus/:e"
{:get {:parameters {:query {:a string?}
:body {:b string?}
:form {:c string?}
:header {:d string?}
:path {:e string?}}
:responses {200 {:schema {:total pos-int?}}}
:handler identity}}]]
{:data {:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion reitit.coercion.spec/coercion}
:validate rrs/validate-spec!})))
(is (thrown-with-msg?
ExceptionInfo
#"Invalid route data"
(ring/router
["/api"
["/plus/:e"
{:get {:parameters {:query {"a" string?}}
:handler identity}}]]
{:data {:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion reitit.coercion.spec/coercion}
:validate rrs/validate-spec!})))
(is (thrown-with-msg?
ExceptionInfo
#"Invalid route data"
(ring/router
["/api"
["/plus/:e"
{:get {:responses {"200" {}}
:handler identity}}]]
{:data {:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion reitit.coercion.spec/coercion}
:validate rrs/validate-spec!}))))

View file

@ -100,3 +100,31 @@
["/api" {:handler "identity"}]
{:spec any?
:validate rs/validate-spec!})))))
(deftest parameters-test
(is (s/valid?
::rs/parameters
{:parameters {:query {:a string?}
:body {:b string?}
:form {:c string?}
:header {:d string?}
:path {:e string?}}}))
(is (not (s/valid?
::rs/parameters
{:parameters {:header {"d" string?}}})))
(is (s/valid?
::rs/responses
{:responses {200 {:description "ok", :schema string?}
400 {:description "fail"}
500 {:schema string?}
:default {}}}))
(is (not (s/valid?
::rs/responses
{:responses {"200" {:description "ok", :schema string?}}})))
(is (not (s/valid?
::rs/responses
{:responses {200 {:description :ok, :schema string?}}}))))