From fb99b4f9fdc9e10ea6810b5cd50961492ae6c6c4 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sun, 31 Dec 2017 11:34:37 +0200 Subject: [PATCH] Parameter & Response Coercion with specs --- modules/reitit-core/src/reitit/spec.cljc | 35 +++++++++++++- .../reitit-ring/src/reitit/ring/coercion.cljc | 3 ++ test/cljc/reitit/ring_spec_test.cljc | 48 +++++++++++++++++++ test/cljc/reitit/spec_test.cljc | 28 +++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index 9568593a..88fe97af 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -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]) diff --git a/modules/reitit-ring/src/reitit/ring/coercion.cljc b/modules/reitit-ring/src/reitit/ring/coercion.cljc index e01a7f50..e5c91da2 100644 --- a/modules/reitit-ring/src/reitit/ring/coercion.cljc +++ b/modules/reitit-ring/src/reitit/ring/coercion.cljc @@ -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)] diff --git a/test/cljc/reitit/ring_spec_test.cljc b/test/cljc/reitit/ring_spec_test.cljc index 69fd947b..b965420b 100644 --- a/test/cljc/reitit/ring_spec_test.cljc +++ b/test/cljc/reitit/ring_spec_test.cljc @@ -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!})))) diff --git a/test/cljc/reitit/spec_test.cljc b/test/cljc/reitit/spec_test.cljc index 6ef967df..3fb68d6a 100644 --- a/test/cljc/reitit/spec_test.cljc +++ b/test/cljc/reitit/spec_test.cljc @@ -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?}}}))))