Middleware can also contribute to router specs

This commit is contained in:
Tommi Reiman 2017-12-28 22:41:02 +02:00
parent 70209aabce
commit 9273f99806
3 changed files with 55 additions and 18 deletions

View file

@ -6,7 +6,7 @@
(defprotocol IntoMiddleware (defprotocol IntoMiddleware
(into-middleware [this data opts])) (into-middleware [this data opts]))
(defrecord Middleware [name wrap]) (defrecord Middleware [name wrap spec])
(defrecord Endpoint [data handler middleware]) (defrecord Endpoint [data handler middleware])
(def ^:dynamic *max-compile-depth* 10) (def ^:dynamic *max-compile-depth* 10)

View file

@ -1,15 +1,13 @@
(ns reitit.ring.spec (ns reitit.ring.spec
(:require [clojure.spec.alpha :as s] (:require [clojure.spec.alpha :as s]
[reitit.middleware #?@(:cljs [:refer [Middleware]])] [reitit.middleware :as middleware]
[reitit.spec :as rs]) [reitit.spec :as rs]))
#?(:clj
(:import (reitit.middleware Middleware))))
;; ;;
;; Specs ;; Specs
;; ;;
(s/def ::middleware (s/coll-of (partial instance? Middleware))) (s/def ::middleware (s/coll-of (partial satisfies? middleware/IntoMiddleware)))
(s/def ::data (s/def ::data
(s/keys :req-un [::rs/handler] (s/keys :req-un [::rs/handler]
@ -19,10 +17,22 @@
;; Validator ;; Validator
;; ;;
(defn merge-specs [specs]
(when-let [non-specs (seq (remove #(or (s/spec? %) (s/get-spec %)) specs))]
(throw
(ex-info
(str "Not all specs satisfy the Spec protocol: " non-specs)
{:specs specs
:non-specs non-specs})))
(s/merge-spec-impl (vec specs) (vec specs) nil))
(defn- validate-ring-route-data [routes spec] (defn- validate-ring-route-data [routes spec]
(->> (for [[p _ c] routes (->> (for [[p _ c] routes
[method {:keys [data] :as endpoint}] c [method {:keys [data middleware] :as endpoint}] c
:when endpoint] :when endpoint
:let [mw-specs (seq (keep :spec middleware))
specs (keep identity (into [spec] mw-specs))
spec (merge-specs specs)]]
(when-let [problems (and spec (s/explain-data spec data))] (when-let [problems (and spec (s/explain-data spec data))]
(rs/->Problem p method data spec problems))) (rs/->Problem p method data spec problems)))
(keep identity) (seq))) (keep identity) (seq)))

View file

@ -2,11 +2,14 @@
(:require [clojure.test :refer [deftest testing is]] (:require [clojure.test :refer [deftest testing is]]
[reitit.ring :as ring] [reitit.ring :as ring]
[reitit.ring.spec :as rrs] [reitit.ring.spec :as rrs]
[clojure.spec.alpha :as s]
[reitit.core :as r] [reitit.core :as r]
[reitit.spec :as rs]) [expound.alpha :as e])
#?(:clj #?(:clj
(:import (clojure.lang ExceptionInfo)))) (:import (clojure.lang ExceptionInfo))))
(s/def ::role #{:admin :user})
(s/def ::roles (s/and (s/coll-of ::role :into #{}) set?))
(deftest route-data-validation-test (deftest route-data-validation-test
(testing "validation is turned off by default" (testing "validation is turned off by default"
@ -27,13 +30,6 @@
(ring/router (ring/router
["/api" {:handler identity ["/api" {:handler identity
:name "kikka"}] :name "kikka"}]
{:validate rrs/validate-spec!})))
(is (thrown-with-msg?
ExceptionInfo
#"Invalid route data"
(ring/router
["/api" {:handler identity
:middleware [{}]}]
{:validate rrs/validate-spec!})))) {:validate rrs/validate-spec!}))))
(testing "all endpoints are validated" (testing "all endpoints are validated"
@ -46,7 +42,38 @@
(testing "spec can be overridden" (testing "spec can be overridden"
(is (true? (r/router? (is (true? (r/router?
(ring/router
["/api" {:handler "identity"}]
{:spec (s/spec any?)
:validate rrs/validate-spec!}))))
(testing "predicates are not allowed"
(is (thrown-with-msg?
ExceptionInfo
#"Not all specs satisfy the Spec protocol"
(ring/router (ring/router
["/api" {:handler "identity"}] ["/api" {:handler "identity"}]
{:spec any? {:spec any?
:validate rrs/validate-spec!})))))) :validate rrs/validate-spec!})))))
(testing "middleware can contribute to specs"
(is (true? (r/router?
(ring/router
["/api" {:get {:handler identity
:roles #{:admin}}}]
{:validate rrs/validate-spec!
:data {:middleware [{:spec (s/keys :opt-un [::roles])
:wrap (fn [handler]
(fn [request]
(handler request)))}]}}))))
(is (thrown-with-msg?
ExceptionInfo
#"Invalid route data"
(ring/router
["/api" {:get {:handler identity
:roles #{:adminz}}}]
{:validate rrs/validate-spec!
:data {:middleware [{:spec (s/keys :opt-un [::roles])
:wrap (fn [handler]
(fn [request]
(handler request)))}]}})))))