diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a51a5ed..b7003d83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,51 @@ * backed by a new `:trie-router`, replacing `:segment-router` * [over 40% faster](https://metosin.github.io/reitit/performance.html) on the JVM -## `reitit-frontend` +* **BREAKING**: `reitit.spec/validate-spec!` has been renamed to `validate` -* **BREAKING** New frontend controllers: +### `reitit-dev` + +* new module for friendly router creation time exception handling + * new option `:exception` in `r/router`, of type `Exception => Exception` (default `reitit.exception/exception`) + * new exception pretty-printer `reitit.dev.pretty/exception`, based on [fipp](https://github.com/brandonbloom/fipp) and [expund](https://github.com/bhb/expound) for human readable, newbie-friendly errors. + +#### Conflicting paths + +```clj +(require '[reitit.core :as r]) +(require '[reitit.dev.pretty :as pretty]) + +(r/router + [["/ping"] + ["/:user-id/orders"] + ["/bulk/:bulk-id"] + ["/public/*path"] + ["/:version/status"]] + {:exception pretty/exception}) +``` + + + +#### Route data error + +```clj +(require '[reitit.spec :as spec]) +(require '[clojure.spec.alpha :as s]) + +(s/def ::role #{:admin :user}) +(s/def ::roles (s/coll-of ::role :into #{})) + +(r/router + ["/api/admin" {::roles #{:adminz}}] + {:validate spec/validate + :exception pretty/exception}) +``` + + + +### `reitit-frontend` + +* **BREAKING**: Frontend controllers redesigned * Controller `:params` function has been deprecated * Controller `:identity` function works the same as `:params` * New `:parameters` option can be used to declare which parameters @@ -24,13 +66,24 @@ use cases: `{:start start-fn, :parameters {:path [:foo-id]}}` * Ensure HTML5 History routing works with IE11 -## `reitit-ring` +### `reitit-ring` * Allow Middleware to compile to `nil` with Middleware Registries, fixes to [#216](https://github.com/metosin/reitit/issues/216). +* **BREAKING**: `reitit.ring.spec/validate-spec!` has been renamed to `validate` -## `reitit-http` +### `reitit-http` * Allow Interceptors to compile to `nil` with Interceptor Registries, related to [#216](https://github.com/metosin/reitit/issues/216). +* **BREAKING**: `reitit.http.spec/validate-spec!` has been renamed to `validate` + +## Dependencies + +* updated: + +```clj +[metosin/spec-tools "0.9.0"] is available but we use "0.8.3" +[metosin/schema-tools "0.11.0"] is available but we use "0.10.5" +``` ## 0.2.13 (2019-01-26) diff --git a/README.md b/README.md index e0f2a409..f5f31ede 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # reitit [![Build Status](https://img.shields.io/circleci/project/github/metosin/reitit.svg)](https://circleci.com/gh/metosin/reitit) [![cljdoc badge](https://cljdoc.xyz/badge/metosin/reitit)](https://cljdoc.xyz/jump/release/metosin/reitit) - A fast data-driven router for Clojure(Script). * Simple data-driven [route syntax](https://metosin.github.io/reitit/basics/route_syntax.html) @@ -36,6 +35,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians * `reitit-http` http-routing with Interceptors * `reitit-interceptors` - [common interceptors](https://metosin.github.io/reitit/http/default_interceptors.html) * `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) +* `reitit-dev` - development utilities ## Extra modules @@ -49,36 +49,7 @@ All main modules bundled: [metosin/reitit "0.2.13"] ``` -Optionally, the parts can be required separately: - -```clj -[metosin/reitit-core "0.2.13"] - -;; coercion -[metosin/reitit-spec "0.2.13"] -[metosin/reitit-schema "0.2.13"] - -;; ring helpers -[metosin/reitit-ring "0.2.13"] -[metosin/reitit-middleware "0.2.13"] - -;; swagger-support for ring & http -[metosin/reitit-swagger "0.2.13"] -[metosin/reitit-swagger-ui "0.2.13"] - -;; frontend helpers -[metosin/reitit-frontend "0.2.13"] - -;; http with interceptors -[metosin/reitit-http "0.2.13"] -[metosin/reitit-interceptors "0.2.13"] -[metosin/reitit-sieppari "0.2.13"] -``` - -```clj -;; pedestal -[metosin/reitit-pedestal "0.2.13"] -``` +Optionally, the parts can be required separately. ## Quick start diff --git a/doc/README.md b/doc/README.md index 607ac66b..8377da5f 100644 --- a/doc/README.md +++ b/doc/README.md @@ -12,6 +12,8 @@ * Modular * [Fast](performance.md) +There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians Slack](http://clojurians.net/) for discussion & help. + ## Main Modules * `reitit` - all bundled @@ -26,6 +28,7 @@ * `reitit-http` http-routing with Pedestal-style Interceptors * `reitit-interceptors` - [common interceptors](./http/default_interceptors.md) for `reitit-http` * `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) Interceptors +* `reitit-dev` - development utilities ## Extra modules @@ -39,38 +42,7 @@ All bundled: [metosin/reitit "0.2.13"] ``` -Optionally, the parts can be required separately: - -```clj -[metosin/reitit-core "0.2.13"] - -;; coercion -[metosin/reitit-spec "0.2.13"] -[metosin/reitit-schema "0.2.13"] - -;; ring helpers -[metosin/reitit-ring "0.2.13"] -[metosin/reitit-middleware "0.2.13"] - -;; swagger-support for ring & http -[metosin/reitit-swagger "0.2.13"] -[metosin/reitit-swagger-ui "0.2.13"] - -;; frontend helpers -[metosin/reitit-frontend "0.2.13"] - -;; http with interceptors -[metosin/reitit-http "0.2.13"] -[metosin/reitit-interceptors "0.2.13"] -[metosin/reitit-sieppari "0.2.13"] -``` - -```clj -;; pedestal -[metosin/reitit-pedestal "0.2.13"] -``` - -There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians Slack](http://clojurians.net/) for discussion & help. +Optionally, the parts can be required separately. # Examples diff --git a/doc/advanced/configuring_routers.md b/doc/advanced/configuring_routers.md index 9d2fbb6c..b624355d 100644 --- a/doc/advanced/configuring_routers.md +++ b/doc/advanced/configuring_routers.md @@ -4,13 +4,14 @@ Routers can be configured via options. The following options are available for t | key | description | |--------------|-------------| -| `:path` | Base-path for routes | -| `:routes` | Initial resolved routes (default `[]`) | -| `:data` | Initial route data (default `{}`) | -| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this | -| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`) | -| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` | -| `:compile` | Function of `route opts => result` to compile a route handler | -| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects | -| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`) | -| `:router` | Function of `routes opts => router` to override the actual router implementation | +| `:path` | Base-path for routes +| `:routes` | Initial resolved routes (default `[]`) +| `:data` | Initial route data (default `{}`) +| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this +| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`) +| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` +| `:compile` | Function of `route opts => result` to compile a route handler +| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects +| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes +| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) +| `:router` | Function of `routes opts => router` to override the actual router implementation diff --git a/doc/basics/route_data_validation.md b/doc/basics/route_data_validation.md index a66c210e..bc98ea1a 100644 --- a/doc/basics/route_data_validation.md +++ b/doc/basics/route_data_validation.md @@ -8,7 +8,7 @@ But there is a better way. Router has a `:validation` hook to validate the whole ## clojure.spec -Namespace `reitit.spec` contains specs for main parts of `reitit.core` and a helper function `validate-spec!` that runs spec validation for all route data and throws an exception if any errors are found. +Namespace `reitit.spec` contains specs for main parts of `reitit.core` and a helper function `validate` that runs spec validation for all route data and throws an exception if any errors are found. A Router with invalid route data: @@ -27,7 +27,7 @@ Fails fast with `clojure.spec` validation turned on: (r/router ["/api" {:handler "identity"}] - {:validate rs/validate-spec!}) + {:validate rs/validate}) ; CompilerException clojure.lang.ExceptionInfo: Invalid route data: ; ; -- On route ----------------------- @@ -42,7 +42,7 @@ Fails fast with `clojure.spec` validation turned on: ### Customizing spec validation -`rs/validate-spec!` reads the following router options: +`rs/validate` reads the following router options: | key | description | | ---------------|-------------| @@ -64,7 +64,7 @@ Below is an example of using [expound](https://github.com/bhb/expound) to pretty ["/api" {:handler identity ::roles #{:adminz}}] {::rs/explain e/expound-str - :validate rs/validate-spec!}) + :validate rs/validate}) ; CompilerException clojure.lang.ExceptionInfo: Invalid route data: ; ; -- On route ----------------------- @@ -102,7 +102,7 @@ Explicitly requiring a `::roles` key in a route data: ["/api" {:handler identity}] {:spec (s/merge (s/keys :req [::roles]) ::rs/default-data) ::rs/explain e/expound-str - :validate rs/validate-spec!}) + :validate rs/validate}) ; CompilerException clojure.lang.ExceptionInfo: Invalid route data: ; ; -- On route ----------------------- diff --git a/doc/ring/route_data_validation.md b/doc/ring/route_data_validation.md index b50d2837..402a1b30 100644 --- a/doc/ring/route_data_validation.md +++ b/doc/ring/route_data_validation.md @@ -2,7 +2,7 @@ Ring route validation works [just like with core router](../basics/route_data_validation.md), with few differences: -* `reitit.ring.spec/validate-spec!` should be used instead of `reitit.spec/validate-spec!` - to support validating all endpoints (`:get`, `:post` etc.) +* `reitit.ring.spec/validate` should be used instead of `reitit.spec/validate` - to support validating all endpoints (`:get`, `:post` etc.) * With `clojure.spec` validation, Middleware can contribute to route spec via `:specs` key. The effective route data spec is router spec merged with middleware specs. ## Example @@ -28,7 +28,7 @@ A simple app with spec-validation turned on: ["/internal" ["/users" {:get {:handler handler} :delete {:handler handler}}]]] - {:validate rrs/validate-spec! + {:validate rrs/validate ::rs/explain e/expound-str}))) ``` @@ -69,7 +69,7 @@ Missing route data fails fast at router creation: ["/internal" ["/users" {:get {:handler handler} :delete {:handler handler}}]]] - {:validate rrs/validate-spec! + {:validate rrs/validate ::rs/explain e/expound-str}))) ; CompilerException clojure.lang.ExceptionInfo: Invalid route data: ; @@ -133,7 +133,7 @@ Adding the `:zone` to route data fixes the problem: ["/internal" {:zone :internal} ;; <--- added ["/users" {:get {:handler handler} :delete {:handler handler}}]]] - {:validate rrs/validate-spec! + {:validate rrs/validate ::rs/explain e/expound-str}))) (app {:request-method :get @@ -175,7 +175,7 @@ Let's reuse the `wrap-enforce-roles` from [Dynamic extensions](dynamic_extension ["/internal" {:zone :internal} ["/users" {:get {:handler handler} :delete {:handler handler}}]]] - {:validate rrs/validate-spec! + {:validate rrs/validate ::rs/explain e/expound-str}))) (app {:request-method :get @@ -199,7 +199,7 @@ But fails if they are present and invalid: ::roles #{:manager} ;; <--- added :delete {:handler handler ::roles #{:adminz}}}]]] ;; <--- added - {:validate rrs/validate-spec! + {:validate rrs/validate ::rs/explain e/expound-str}))) ; CompilerException clojure.lang.ExceptionInfo: Invalid route data: ; @@ -240,7 +240,7 @@ Ability to define (and reuse) route-data in mid-paths is a powerful feature, but ::roles #{:manager}} :delete {:handler handler ::roles #{:admin}}}]]] - {:validate rrs/validate-spec! + {:validate rrs/validate ::rs/explain e/expound-str}))) ``` @@ -261,7 +261,7 @@ Or even flatten the routes: ::roles #{:manager}} :delete {:handler handler ::roles #{:admin}}}]] - {:validate rrs/validate-spec! + {:validate rrs/validate ::rs/explain e/expound-str}))) ``` @@ -279,6 +279,6 @@ The common Middleware can also be pushed to the router, here cleanly separing be :delete {:handler handler ::roles #{:admin}}}]] {:data {:middleware [zone-middleware wrap-enforce-roles]} - :validate rrs/validate-spec! + :validate rrs/validate ::rs/explain e/expound-str}))) ``` diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index ca9720d6..8ce2d3dc 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -1,6 +1,5 @@ (ns reitit.core - (:require [clojure.string :as str] - [reitit.impl :as impl] + (:require [reitit.impl :as impl] [reitit.exception :as exception] [reitit.trie :as trie])) @@ -32,27 +31,6 @@ nil (expand [_ _])) -;; -;; Conflicts -;; - -(defn path-conflicts-str [conflicts] - (apply str "Router contains conflicting route paths:\n\n" - (mapv - (fn [[[path] vals]] - (str " " path "\n-> " (str/join "\n-> " (mapv first vals)) "\n\n")) - conflicts))) - -(defn name-conflicts-str [conflicts] - (apply str "Router contains conflicting route names:\n\n" - (mapv - (fn [[name vals]] - (str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n\n")) - conflicts))) - -(defn throw-on-conflicts! [f conflicts] - (exception/fail! (f conflicts) {:conflicts conflicts})) - ;; ;; Router ;; @@ -359,7 +337,8 @@ :expand expand :coerce (fn coerce [route _] route) :compile (fn compile [[_ {:keys [handler]}] _] handler) - :conflicts (fn throw! [conflicts] (throw-on-conflicts! path-conflicts-str conflicts))}) + :exception exception/exception + :conflicts (fn throw! [conflicts] (exception/fail! :path-conflicts conflicts))}) (defn router "Create a [[Router]] from raw route data and optionally an options map. @@ -376,33 +355,39 @@ | `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` | `:compile` | Function of `route opts => result` to compile a route handler | `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects - | `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`) + | `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes + | `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) | `:router` | Function of `routes opts => router` to override the actual router implementation" ([raw-routes] (router raw-routes {})) ([raw-routes opts] - (let [{:keys [router] :as opts} (merge (default-router-options) opts) - routes (impl/resolve-routes raw-routes opts) - path-conflicting (impl/path-conflicting-routes routes) - name-conflicting (impl/name-conflicting-routes routes) - compiled-routes (impl/compile-routes routes opts) - wilds? (boolean (some impl/wild-route? compiled-routes)) - all-wilds? (every? impl/wild-route? compiled-routes) - router (cond - router router - (and (= 1 (count compiled-routes)) (not wilds?)) single-static-path-router - path-conflicting quarantine-router - (not wilds?) lookup-router - all-wilds? trie-router - :else mixed-router)] + (let [{:keys [router] :as opts} (merge (default-router-options) opts)] + (try + (let [routes (impl/resolve-routes raw-routes opts) + path-conflicting (impl/path-conflicting-routes routes) + name-conflicting (impl/name-conflicting-routes routes) + compiled-routes (impl/compile-routes routes opts) + wilds? (boolean (some impl/wild-route? compiled-routes)) + all-wilds? (every? impl/wild-route? compiled-routes) + router (cond + router router + (and (= 1 (count compiled-routes)) (not wilds?)) single-static-path-router + path-conflicting quarantine-router + (not wilds?) lookup-router + all-wilds? trie-router + :else mixed-router)] - (when-let [conflicts (:conflicts opts)] - (when path-conflicting (conflicts path-conflicting))) + (when-let [conflicts (:conflicts opts)] + (when path-conflicting (conflicts path-conflicting))) - (when name-conflicting - (throw-on-conflicts! name-conflicts-str name-conflicting)) + (when name-conflicting + (exception/fail! :name-conflicts name-conflicting)) - (when-let [validate (:validate opts)] - (validate compiled-routes opts)) + (when-let [validate (:validate opts)] + (validate compiled-routes opts)) - (router compiled-routes opts)))) + (router compiled-routes opts)) + + (catch #?(:clj Exception, :cljs js/Error) e + (let [exception (:exception opts)] + (throw (if exception (exception e) e)))))))) diff --git a/modules/reitit-core/src/reitit/exception.cljc b/modules/reitit-core/src/reitit/exception.cljc index 8b67accc..b7c1c3e6 100644 --- a/modules/reitit-core/src/reitit/exception.cljc +++ b/modules/reitit-core/src/reitit/exception.cljc @@ -1,7 +1,36 @@ -(ns reitit.exception) +(ns reitit.exception + (:require [clojure.string :as str])) (defn fail! - ([message] - (throw (ex-info message {::type :exeption}))) - ([message data] - (throw (ex-info message (merge {::type ::exeption} data))))) + ([type] + (fail! type nil)) + ([type data] + (throw (ex-info (str type) {:type type, :data data})))) + +(defmulti format-type (fn [type _ _] type)) + +(defn exception [e] + (let [data (ex-data e) + message (format-type (:type data) #?(:clj (.getMessage ^Exception e) :cljs (ex-message e)) (:data data))] + (ex-info message (or data {})))) + +;; +;; Formatters +;; + +(defmethod format-type :default [_ message data] + (str message (if data (str "\n\n" (pr-str data))))) + +(defmethod format-type :path-conflicts [_ _ conflicts] + (apply str "Router contains conflicting route paths:\n\n" + (mapv + (fn [[[path] vals]] + (str " " path "\n-> " (str/join "\n-> " (mapv first vals)) "\n\n")) + conflicts))) + +(defmethod format-type :name-conflicts [_ _ conflicts] + (apply str "Router contains conflicting route names:\n\n" + (mapv + (fn [[name vals]] + (str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n")) + conflicts))) diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index 0c3e2e36..5d0ca66d 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -1,8 +1,8 @@ (ns reitit.spec (:require [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as gen] - [reitit.core :as reitit] - [reitit.exception :as exception])) + [reitit.exception :as exception] + [reitit.core :as r])) ;; ;; routes @@ -45,7 +45,7 @@ ;; router ;; -(s/def ::router reitit/router?) +(s/def ::router r/router?) (s/def :reitit.router/path ::path) (s/def :reitit.router/routes ::routes) (s/def :reitit.router/data ::data) @@ -66,7 +66,7 @@ :reitit.router/conflicts :reitit.router/router]))) -(s/fdef reitit/router +(s/fdef r/router :args (s/or :1arity (s/cat :data (s/spec ::raw-routes)) :2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts)) :ret ::router) @@ -111,28 +111,24 @@ (defrecord Problem [path scope data spec problems]) -(defn problems-str [problems explain] +(defn validate-route-data [routes spec] + (some->> (for [[p d _] routes] + (when-let [problems (and spec (s/explain-data spec d))] + (->Problem p nil d spec problems))) + (keep identity) (seq) (vec))) + +(defn validate [routes {:keys [spec] :or {spec ::default-data}}] + (when-let [problems (validate-route-data routes spec)] + (exception/fail! + ::invalid-route-data + {:problems problems}))) + +(defmethod exception/format-type :reitit.spec/invalid-route-data [_ _ {:keys [problems]}] (apply str "Invalid route data:\n\n" (mapv (fn [{:keys [path scope data spec]}] (str "-- On route -----------------------\n\n" - (pr-str path) (if scope (str " " (pr-str scope))) "\n\n" (explain spec data) "\n")) + (pr-str path) (if scope (str " " (pr-str scope))) "\n\n" + (pr-str data) "\n\n" + (s/explain-str spec data) "\n")) problems))) - -(defn throw-on-problems! [problems explain] - (exception/fail! - (problems-str problems explain) - {:problems problems})) - -(defn validate-route-data [routes spec] - (->> (for [[p d _] routes] - (when-let [problems (and spec (s/explain-data spec d))] - (->Problem p nil d spec problems))) - (keep identity) (seq))) - -(defn validate-spec! - [routes {:keys [spec ::explain] - :or {explain s/explain-str - spec ::default-data}}] - (when-let [problems (validate-route-data routes spec)] - (throw-on-problems! problems explain))) diff --git a/modules/reitit-core/src/reitit/trie.cljc b/modules/reitit-core/src/reitit/trie.cljc index 9a440b35..edc056f1 100644 --- a/modules/reitit-core/src/reitit/trie.cljc +++ b/modules/reitit-core/src/reitit/trie.cljc @@ -59,7 +59,7 @@ (if (= to (count s)) (concat ss (-static from to)) (case (get s to) - \{ (let [to' (or (str/index-of s "}" to) (ex/fail! (str "Unclosed brackets: " (pr-str s))))] + \{ (let [to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))] (if (= \* (get s (inc to))) (recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to'))) (recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to'))))) @@ -134,7 +134,7 @@ (defn- -node [m] (map->Node (merge {:children {}, :wilds {}, :catch-all {}, :params {}} m))) -(defn- -insert [node [path & ps] params data] +(defn- -insert [node [path & ps] fp params data] (let [node' (cond (nil? path) @@ -143,14 +143,14 @@ (instance? Wild path) (let [next (first ps)] (if (or (instance? Wild next) (instance? CatchAll next)) - (ex/fail! (str "Two following wilds: " path ", " next)) - (update-in node [:wilds path] (fn [n] (-insert (or n (-node {})) ps params data))))) + (ex/fail! ::following-parameters {:path fp, :parameters (map :value [path next])}) + (update-in node [:wilds path] (fn [n] (-insert (or n (-node {})) ps fp params data))))) (instance? CatchAll path) (assoc-in node [:catch-all path] (-node {:params params, :data data})) (str/blank? path) - (-insert node ps params data) + (-insert node ps fp params data) :else (or @@ -159,20 +159,20 @@ (if-let [cp (common-prefix p path)] (if (= cp p) ;; insert into child node - (let [n' (-insert n (conj ps (subs path (count p))) params data)] + (let [n' (-insert n (conj ps (subs path (count p))) fp params data)] (reduced (assoc-in node [:children p] n'))) ;; split child node (let [rp (subs p (count cp)) rp' (subs path (count cp)) - n' (-insert (-node {}) ps params data) - n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil)] + n' (-insert (-node {}) ps fp params data) + n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)] (reduced (update node :children (fn [children] (-> children (dissoc p) (assoc cp n''))))))))) nil (:children node)) ;; new child node - (assoc-in node [:children path] (-insert (-node {}) ps params data))))] + (assoc-in node [:children path] (-insert (-node {}) ps fp params data))))] (if-let [child (get-in node' [:children ""])] ;; optimize by removing empty paths (-> (merge-with merge (dissoc node' :data) child) @@ -300,7 +300,7 @@ ([node path data] (let [parts (split-path path) params (zipmap (->> parts (remove string?) (map :value)) (repeat nil))] - (-insert (or node (-node {})) (split-path path) params data)))) + (-insert (or node (-node {})) (split-path path) path params data)))) (defn compiler "Returns a default [[TrieCompiler]]." @@ -312,18 +312,20 @@ "Returns a compiled trie, to be used with [[pretty]] or [[path-matcher]]." ([options] (compile options (compiler))) - ([{:keys [data params children wilds catch-all] :or {params {}}} compiler] + ([options compiler] + (compile options compiler [])) + ([{:keys [data params children wilds catch-all] :or {params {}}} compiler cp] (let [ends (fn [{:keys [children]}] (or (keys children) ["/"])) matchers (-> [] (cond-> data (conj (data-matcher compiler params data))) - (into (for [[p c] children] (static-matcher compiler p (compile c compiler)))) + (into (for [[p c] children] (static-matcher compiler p (compile c compiler (conj cp p))))) (into (for [[p c] wilds] - (let [p (:value p) + (let [pv (:value p) ends (ends c)] (if (next ends) - (ex/fail! (str "Trie compliation error: wild " p " has two terminators: " ends)) - (wild-matcher compiler p (ffirst ends) (compile c compiler)))))) + (ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))}) + (wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv))))))) (into (for [[p c] catch-all] (catch-all-matcher compiler (:value p) params (:data c)))))] (cond (> (count matchers) 1) (linear-matcher compiler matchers) diff --git a/modules/reitit-dev/project.clj b/modules/reitit-dev/project.clj new file mode 100644 index 00000000..9b2164e6 --- /dev/null +++ b/modules/reitit-dev/project.clj @@ -0,0 +1,13 @@ +(defproject metosin/reitit-dev "0.2.13" + :description "Snappy data-driven router for Clojure(Script)" + :url "https://github.com/metosin/reitit" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :scm {:name "git" + :url "https://github.com/metosin/reitit"} + :plugins [[lein-parent "0.3.2"]] + :parent-project {:path "../../project.clj" + :inherit [:deploy-repositories :managed-dependencies]} + :dependencies [[metosin/reitit-core] + [expound] + [fipp]]) diff --git a/modules/reitit-dev/src/reitit/dev/pretty.cljc b/modules/reitit-dev/src/reitit/dev/pretty.cljc new file mode 100644 index 00000000..62b358bf --- /dev/null +++ b/modules/reitit-dev/src/reitit/dev/pretty.cljc @@ -0,0 +1,333 @@ +(ns reitit.dev.pretty + (:require [clojure.string :as str] + [clojure.spec.alpha :as s] + [arrangement.core] + ;; expound + [expound.ansi] + [expound.alpha] + ;; fipp + [fipp.visit] + [fipp.edn] + [fipp.ednize] + [fipp.engine])) + +;; +;; colors +;; + +(def colors + {:white 255 + :text 253 + :grey 245 + :title-dark 32 + :title 45 + :red 217 + + :string 180 + :comment 243 + :doc 223 + :core-form 39 + :function-name 178 + :variable-name 85 + :constant 149 + :type 123 + :foreign 220 + :builtin 167 + :half-contrast 243 + :half-contrast-inverse 243 + :eldoc-varname 178 + :eldoc-separator 243 + :arglists 243 + :anchor 39 + :light-anchor 39 + :apropos-highlight 45 + :apropos-namespace 243 + :error 196}) + +(defn -color [color & text] + (str "\033[38;5;" (colors color color) "m" (apply str text) "\u001B[0m")) + +(comment + (doseq [c (range 0 255)] + (println (-color c "kikka") "->" c)) + + (doseq [[n c] colors] + (println (-color c "kikka") "->" c n)) + + (doseq [[k v] expound.ansi/sgr-code] + (println (expound.ansi/sgr "kikka" k) "->" k)) + ) + +(defn -start [x] (str "\033[38;5;" x "m")) +(defn -end [] "\u001B[0m") + +(defn color [color & text] + [:span + [:pass (-start (colors color))] + (apply str text) + [:pass (-end)]]) + +;; +;; EDN +;; + +(defrecord EdnPrinter [symbols print-meta print-length print-level] + + fipp.visit/IVisitor + + (visit-unknown [this x] + (fipp.visit/visit this (fipp.ednize/edn x))) + + (visit-nil [this] + (color :text "nil")) + + (visit-boolean [this x] + (color :text (str x))) + + (visit-string [this x] + (color :string (pr-str x))) + + (visit-character [this x] + (color :text (pr-str x))) + + (visit-symbol [this x] + (color :text (str x))) + + (visit-keyword [this x] + (color :constant (pr-str x))) + + (visit-number [this x] + (color :text (pr-str x))) + + (visit-seq [this x] + (if-let [pretty (symbols (first x))] + (pretty this x) + (fipp.edn/pretty-coll this (color :text "(") x :line (color :text ")") fipp.visit/visit))) + + (visit-vector [this x] + (fipp.edn/pretty-coll this (color :text "[") x :line (color :text "]") fipp.visit/visit)) + + (visit-map [this x] + (let [xs (sort-by identity (fn [a b] (arrangement.core/rank (first a) (first b))) x)] + (fipp.edn/pretty-coll this (color :text "{") xs [:span (color :text ",") :line] (color :text "}") + (fn [printer [k v]] + [:span (fipp.visit/visit printer k) " " (fipp.visit/visit printer v)])))) + + (visit-set [this x] + (let [xs (sort-by identity (fn [a b] (arrangement.core/rank a b)) x)] + (fipp.edn/pretty-coll this "#{" xs :line "}" fipp.visit/visit))) + + (visit-tagged [this {:keys [tag form]}] + (let [object? (= 'object tag) + tag-f (if (map? form) (partial color :type) identity)] + [:group "#" (tag-f (pr-str tag)) + (when (or (and print-meta (meta form)) (not (coll? form))) + " ") + (if object? + [:group "[" + [:align + (color :type (first form)) :line + (color :text (second form)) :line + (fipp.visit/visit this (last form))] "]"] + (fipp.visit/visit this form))])) + + (visit-meta [this m x] + (if print-meta + [:align [:span "^" (fipp.visit/visit this m)] :line (fipp.visit/visit* this x)] + (fipp.visit/visit* this x))) + + (visit-var [this x] + [:text (str x)]) + + (visit-pattern [this x] + [:text (pr-str x)]) + + (visit-record [this x] + (fipp.visit/visit this (fipp.ednize/record->tagged x)))) + +(defn ->printer + ([] + (->printer nil)) + ([options] + (map->EdnPrinter + (merge + {:width 80 + :symbols {} + :print-length *print-length* + :print-level *print-level* + :print-meta *print-meta*} + options)))) + +(defn pprint + ([x] (pprint x {})) + ([x options] + (let [printer (->printer (dissoc options :margin)) + margin (apply str (take (:margin options 0) (repeat " ")))] + (binding [*print-meta* false] + (fipp.engine/pprint-document [:group margin [:group (fipp.visit/visit printer x)]] options))))) + +(defn print-doc [doc printer] + (fipp.engine/pprint-document doc {:width (:width printer)})) + +(defn repeat-str [s n] + (apply str (take n (repeat s)))) + +;; TODO: this is hack, but seems to work and is safe. +(defn source-str [[target _ file line]] + (try + (if (and (not= 1 line)) + (let [file-name (str/replace file #"(.*?)\.\S[^\.]+" "$1") + target-name (name target) + ns (str (subs target-name 0 (str/index-of target-name (str "user" "$"))) file-name)] + (str ns ":" line)) + "repl") + (catch #?(:clj Exception, :cljs js/Error) _ + "unknown"))) + +(defn title [message source {:keys [width]}] + (let [between (- width (count message) 8 (count source))] + [:group + (color :title-dark "-- ") + (color :title message " ") + (color :title-dark (repeat-str "-" between) " ") + (color :title source) " " + (color :title-dark (str "--"))])) + +(defn footer [{:keys [width]}] + (color :title-dark (repeat-str "-" width))) + +(defn text [& text] + (apply color :text text)) + +(defn edn + ([x] (edn x {})) + ([x options] + (with-out-str (pprint x options)))) + +(defn exception-str [message source printer] + (with-out-str + (print-doc + [:group + (title "Router creation failed" source printer) + [:break] [:break] + message + [:break] + (footer printer)] + printer))) + +(defmulti format-type (fn [type _ _] type)) + +(defn exception [e] + (let [data (-> e ex-data :data) + message (format-type (-> e ex-data :type) #?(:clj (.getMessage ^Exception e) :cljs (ex-message e)) data) + source (->> e Throwable->map :trace + (drop-while #(not= (name (first %)) "reitit.core$router")) + (drop-while #(= (name (first %)) "reitit.core$router")) + next first source-str)] + (ex-info (exception-str message source (->printer)) (or data {})))) + +(defn de-expound-colors [^String s mappings] + (let [s' (reduce + (fn [s [from to]] + (.replace ^String s + ^String (expound.ansi/esc [from]) + ^String (-start (colors to)))) + s mappings)] + (.replace ^String s' + ^String (expound.ansi/esc [:none]) + (str (expound.ansi/esc [:none]) (-start (colors :text)))))) + +(defn fippify [s] + [:align + (-> s + (de-expound-colors {:cyan :grey + :red :red + :magenta :grey + :green :constant}) + (str/split #"\n") (interleave (repeat [:break])))]) + +(defn indent [x n] + [:group (repeat-str " " n) [:align x]]) + +(def expound-printer + (expound.alpha/custom-printer + {:theme :figwheel-theme + :show-valid-values? false + :print-specs? false})) + +;; +;; Formatters +;; + +(defmethod format-type :default [_ message data] + (into [:group (text message)] (if data [[:break] [:break] (edn data)]))) + +(defmethod format-type :path-conflicts [_ _ conflicts] + [:group + (text "Router contains conflicting route paths:") + [:break] [:break] + (into + [:group] + (mapv + (fn [[[path] vals]] + [:group + [:span " " (text path)] + [:break] + (into + [:group] + (map + (fn [p] [:span (color :grey "-> " p) [:break]]) + (mapv first vals))) + [:break]]) + conflicts)) + [:span (text "Either fix the conflicting paths or disable the conflict resolution") + [:break] (text "by setting a router option: ") [:break] [:break] + (edn {:conflicts nil} {:margin 3})] + [:break] + (color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts") + [:break]]) + +(defmethod format-type :name-conflicts [_ _ conflicts] + [:group + (text "Router contains conflicting route names:") + [:break] [:break] + (into + [:group] + (mapv + (fn [[name vals]] + [:group + [:span (text name)] + [:break] + (into + [:group] + (map + (fn [p] [:span (color :grey "-> " p) [:break]]) + (mapv first vals))) + [:break]]) + conflicts)) + (color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts") + [:break]]) + +(defmethod format-type :reitit.spec/invalid-route-data [_ _ {:keys [problems]}] + [:group + (text "Route data validation failed:") + [:break] [:break] + (into + [:group] + (map + (fn [{:keys [data path spec]}] + [:group + [:span (color :grey "-- On route -----------------------")] + [:break] + [:break] + (text path) + [:break] + [:break] + (-> (s/explain-data spec data) + (expound-printer) + (with-out-str) + (fippify)) + [:break]]) + problems)) + (color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-data-validation") + [:break]]) diff --git a/modules/reitit-http/src/reitit/http/coercion.cljc b/modules/reitit-http/src/reitit/http/coercion.cljc index a4d6cbda..45e62fd1 100644 --- a/modules/reitit-http/src/reitit/http/coercion.cljc +++ b/modules/reitit-http/src/reitit/http/coercion.cljc @@ -35,22 +35,22 @@ (let [response (coercion/coerce-response coercers request response)] (assoc ctx :response response))))})))}) - (defn coerce-exceptions-interceptor - "Interceptor for handling coercion exceptions. - Expects a :coercion of type `reitit.coercion/Coercion` - and :parameters or :responses from route data, otherwise does not mount." - [] - {:name ::coerce-exceptions - :compile (fn [{:keys [coercion parameters responses]} _] - (if (and coercion (or parameters responses)) - {:error (fn [ctx] - (let [data (ex-data (:error ctx))] - (if-let [status (case (:type data) - ::coercion/request-coercion 400 - ::coercion/response-coercion 500 - nil)] - (let [response {:status status, :body (coercion/encode-error data)}] - (-> ctx - (assoc :response response) - (assoc :error nil))) - ctx)))}))}) +(defn coerce-exceptions-interceptor + "Interceptor for handling coercion exceptions. + Expects a :coercion of type `reitit.coercion/Coercion` + and :parameters or :responses from route data, otherwise does not mount." + [] + {:name ::coerce-exceptions + :compile (fn [{:keys [coercion parameters responses]} _] + (if (and coercion (or parameters responses)) + {:error (fn [ctx] + (let [data (ex-data (:error ctx))] + (if-let [status (case (:type data) + ::coercion/request-coercion 400 + ::coercion/response-coercion 500 + nil)] + (let [response {:status status, :body (coercion/encode-error data)}] + (-> ctx + (assoc :response response) + (assoc :error nil))) + ctx)))}))}) diff --git a/modules/reitit-http/src/reitit/http/spec.cljc b/modules/reitit-http/src/reitit/http/spec.cljc index 2834a705..fad6ee01 100644 --- a/modules/reitit-http/src/reitit/http/spec.cljc +++ b/modules/reitit-http/src/reitit/http/spec.cljc @@ -2,6 +2,7 @@ (:require [clojure.spec.alpha :as s] [reitit.ring.spec :as rrs] [reitit.interceptor :as interceptor] + [reitit.exception :as exception] [reitit.spec :as rs])) ;; @@ -17,7 +18,9 @@ ;; Validator ;; -(defn validate-spec! - [routes {:keys [spec ::rs/explain] :or {explain s/explain-str, spec ::data}}] +(defn validate + [routes {:keys [spec] :or {spec ::data}}] (when-let [problems (rrs/validate-route-data routes :interceptors spec)] - (rs/throw-on-problems! problems explain))) + (exception/fail! + ::invalid-route-data + {:problems problems}))) diff --git a/modules/reitit-ring/src/reitit/ring/spec.cljc b/modules/reitit-ring/src/reitit/ring/spec.cljc index 7bc4d459..60fb6f4f 100644 --- a/modules/reitit-ring/src/reitit/ring/spec.cljc +++ b/modules/reitit-ring/src/reitit/ring/spec.cljc @@ -21,9 +21,9 @@ (defn merge-specs [specs] (when-let [non-specs (seq (remove #(or (s/spec? %) (s/get-spec %)) specs))] (exception/fail! - (str "Not all specs satisfy the Spec protocol: " non-specs) + ::invalid-specs {:specs specs - :non-specs non-specs})) + :invalid non-specs})) (s/merge-spec-impl (vec specs) (vec specs) nil)) (defn validate-route-data [routes key spec] @@ -31,14 +31,16 @@ [method {:keys [data] :as endpoint}] c :when endpoint :let [target (key endpoint) - mw-specs (seq (keep :spec target)) - specs (keep identity (into [spec] mw-specs)) + component-specs (seq (keep :spec target)) + specs (keep identity (into [spec] component-specs)) spec (merge-specs specs)]] (when-let [problems (and spec (s/explain-data spec data))] (rs/->Problem p method data spec problems))) (keep identity) (seq))) -(defn validate-spec! - [routes {:keys [spec ::rs/explain] :or {explain s/explain-str, spec ::data}}] +(defn validate + [routes {:keys [spec] :or {spec ::data}}] (when-let [problems (validate-route-data routes :middleware spec)] - (rs/throw-on-problems! problems explain))) + (exception/fail! + ::invalid-route-data + {:problems problems}))) diff --git a/modules/reitit/project.clj b/modules/reitit/project.clj index e462854f..64092b4e 100644 --- a/modules/reitit/project.clj +++ b/modules/reitit/project.clj @@ -10,6 +10,7 @@ :parent-project {:path "../../project.clj" :inherit [:deploy-repositories :managed-dependencies]} :dependencies [[metosin/reitit-core] + [metosin/reitit-dev] [metosin/reitit-spec] [metosin/reitit-schema] [metosin/reitit-ring] diff --git a/project.clj b/project.clj index 348c6ccb..b633ea5c 100644 --- a/project.clj +++ b/project.clj @@ -13,6 +13,7 @@ :javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"] :managed-dependencies [[metosin/reitit "0.2.13"] [metosin/reitit-core "0.2.13"] + [metosin/reitit-dev "0.2.13"] [metosin/reitit-spec "0.2.13"] [metosin/reitit-schema "0.2.13"] [metosin/reitit-ring "0.2.13"] @@ -25,24 +26,26 @@ [metosin/reitit-sieppari "0.2.13"] [metosin/reitit-pedestal "0.2.13"] [metosin/ring-swagger-ui "2.2.10"] - [metosin/spec-tools "0.8.3"] - [metosin/schema-tools "0.10.5"] + [metosin/spec-tools "0.9.0"] + [metosin/schema-tools "0.11.0"] [metosin/muuntaja "0.6.3"] [metosin/jsonista "0.2.2"] [metosin/sieppari "0.0.0-alpha7"] [meta-merge "1.0.0"] + [fipp "0.6.17" :exclusions [org.clojure/core.rrb-vector]] + [expound "0.7.2"] [lambdaisland/deep-diff "0.0-25"] [ring/ring-core "1.7.1"] [io.pedestal/pedestal.service "0.5.5"]] - :plugins [[jonase/eastwood "0.3.4"] + :plugins [[jonase/eastwood "0.3.5"] ;[lein-virgil "0.1.7"] [lein-doo "0.1.11"] [lein-cljsbuild "1.1.7"] [lein-cloverage "1.0.13"] - [lein-codox "0.10.5"] + [lein-codox "0.10.6"] [metosin/bat-test "0.4.2"]] :profiles {:dev {:jvm-opts ^:replace ["-server"] @@ -50,6 +53,7 @@ ;; all module sources for development :source-paths ["modules/reitit/src" "modules/reitit-core/src" + "modules/reitit-dev/src" "modules/reitit-ring/src" "modules/reitit-http/src" "modules/reitit-middleware/src" @@ -75,9 +79,10 @@ [metosin/jsonista] [lambdaisland/deep-diff] [meta-merge] + [expound] + [fipp] - [expound "0.7.2"] - [orchestra "2018.12.06-2"] + [orchestra "2019.02.06-1"] [ring "1.7.1"] [ikitommi/immutant-web "3.0.0-alpha1"] @@ -95,12 +100,11 @@ [org.clojure/core.async "0.4.490"] [manifold "0.1.8"] - [funcool/promesa "1.9.0"] + [funcool/promesa "2.0.0"] [com.clojure-goes-fast/clj-async-profiler "0.3.0"] - ;; https://github.com/bensu/doo/issues/180 - [fipp "0.6.14" :exclusions [org.clojure/core.rrb-vector]]]} + [com.bhauman/rebel-readline "0.1.4"]]} :1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]} :perf {:jvm-opts ^:replace ["-server" "-Xmx4096m" @@ -114,7 +118,7 @@ [calfpath "0.7.2"] [org.clojure/core.async "0.4.490"] [manifold "0.1.8"] - [funcool/promesa "1.9.0"] + [funcool/promesa "2.0.0"] [metosin/sieppari] [yada "1.2.16"] [aleph "0.4.6"] diff --git a/scripts/lein-modules b/scripts/lein-modules index e78d3d08..ff25d3cb 100755 --- a/scripts/lein-modules +++ b/scripts/lein-modules @@ -5,6 +5,7 @@ set -e # Modules for ext in \ reitit-core \ + reitit-dev \ reitit-spec \ reitit-schema \ reitit-ring \ diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index e2920342..5d2e372d 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -129,12 +129,12 @@ (testing "unclosed brackets" (is (thrown-with-msg? ExceptionInfo - #"^Unclosed brackets" + #":reitit.trie/unclosed-brackets" (r/router ["/kikka/{kukka"])))) (testing "multiple terminators" (is (thrown-with-msg? ExceptionInfo - #"^Trie compliation error: wild :kukka has two terminators" + #":reitit.trie/multiple-terminators" (r/router [["/{kukka}.json"] ["/{kukka}-json"]])))))) diff --git a/test/cljc/reitit/errors_test.cljc b/test/cljc/reitit/errors_test.cljc new file mode 100644 index 00000000..11839edb --- /dev/null +++ b/test/cljc/reitit/errors_test.cljc @@ -0,0 +1,52 @@ +(ns reitit.errors-test + (:require [reitit.spec :as rs] + [reitit.core :as r] + [reitit.dev.pretty :as pretty] + [clojure.spec.alpha :as s])) + +(s/def ::role #{:admin :manager}) +(s/def ::roles (s/coll-of ::role :into #{})) +(s/def ::data (s/keys :req [::role ::roles])) + +(comment + + ;; route conflicts + (r/router + [["/:a/1"] + ["/1/:a"]] + {:exception pretty/exception}) + + ;; path conflicts + (r/router + [["/kikka" ::kikka] + ["/kukka" ::kikka]] + {:exception pretty/exception}) + + ;; + ;; trie + ;; + + ;; two terminators + (r/router + [["/{a}.pdf"] + ["/{a}-pdf"]] + {:exception pretty/exception}) + + ;; two following wilds + (r/router + ["/{a}{b}"] + {:exception pretty/exception}) + + ;; unclosed brackers + (r/router + ["/api/{ipa"] + {:exception pretty/exception}) + + ;; + ;; spec + ;; + + (r/router + ["/api/ipa" {::roles #{:adminz}}] + {:validate rs/validate + :exception pretty/exception})) diff --git a/test/cljc/reitit/ring_spec_test.cljc b/test/cljc/reitit/ring_spec_test.cljc index 3560b73d..ae99764d 100644 --- a/test/cljc/reitit/ring_spec_test.cljc +++ b/test/cljc/reitit/ring_spec_test.cljc @@ -21,59 +21,59 @@ (testing "with default spec validates :name, :handler and :middleware" (is (thrown-with-msg? ExceptionInfo - #"Invalid route data" + #":reitit.ring.spec/invalid-route-data" (ring/router ["/api" {:handler "identity"}] - {:validate rrs/validate-spec!}))) + {:validate rrs/validate}))) (is (thrown-with-msg? ExceptionInfo - #"Invalid route data" + #":reitit.ring.spec/invalid-route-data" (ring/router ["/api" {:handler identity :name "kikka"}] - {:validate rrs/validate-spec!})))) + {:validate rrs/validate})))) (testing "all endpoints are validated" (is (thrown-with-msg? ExceptionInfo - #"Invalid route data" + #":reitit.ring.spec/invalid-route-data" (ring/router ["/api" {:patch {:handler "identity"}}] - {:validate rrs/validate-spec!})))) + {:validate rrs/validate})))) (testing "spec can be overridden" (is (r/router? (ring/router ["/api" {:handler "identity"}] {:spec (s/spec any?) - :validate rrs/validate-spec!}))) + :validate rrs/validate}))) (testing "predicates are not allowed" (is (thrown-with-msg? ExceptionInfo - #"Not all specs satisfy the Spec protocol" + #":reitit.ring.spec/invalid-specs" (ring/router ["/api" {:handler "identity"}] {:spec any? - :validate rrs/validate-spec!}))))) + :validate rrs/validate}))))) (testing "middleware can contribute to specs" (is (r/router? (ring/router ["/api" {:get {:handler identity :roles #{:admin}}}] - {:validate rrs/validate-spec! + {:validate rrs/validate :data {:middleware [{:spec (s/keys :opt-un [::roles]) :wrap (fn [handler] (fn [request] (handler request)))}]}}))) (is (thrown-with-msg? ExceptionInfo - #"Invalid route data" + #":reitit.ring.spec/invalid-route-data" (ring/router ["/api" {:get {:handler identity :roles #{:adminz}}}] - {:validate rrs/validate-spec! + {:validate rrs/validate :data {:middleware [{:spec (s/keys :opt-un [::roles]) :wrap (fn [handler] (fn [request] @@ -97,7 +97,7 @@ rrc/coerce-request-middleware rrc/coerce-response-middleware] :coercion reitit.coercion.spec/coercion} - :validate rrs/validate-spec!}))) + :validate rrs/validate}))) (is (r/router? (ring/router @@ -109,11 +109,11 @@ rrc/coerce-request-middleware rrc/coerce-response-middleware] :coercion reitit.coercion.spec/coercion} - :validate rrs/validate-spec!}))) + :validate rrs/validate}))) (is (thrown-with-msg? ExceptionInfo - #"Invalid route data" + #":reitit.ring.spec/invalid-route-data" (ring/router ["/api" ["/plus/:e" @@ -123,4 +123,4 @@ rrc/coerce-request-middleware rrc/coerce-response-middleware] :coercion reitit.coercion.spec/coercion} - :validate rrs/validate-spec!})))) + :validate rrs/validate})))) diff --git a/test/cljc/reitit/spec_test.cljc b/test/cljc/reitit/spec_test.cljc index 402b647a..f8506b6a 100644 --- a/test/cljc/reitit/spec_test.cljc +++ b/test/cljc/reitit/spec_test.cljc @@ -93,19 +93,19 @@ #"Invalid route data" (r/router ["/api" {:handler "identity"}] - {:validate rs/validate-spec!}))) + {:validate rs/validate}))) (is (thrown-with-msg? ExceptionInfo #"Invalid route data" (r/router ["/api" {:name "kikka"}] - {:validate rs/validate-spec!})))) + {:validate rs/validate})))) (testing "spec can be overridden" (is (r/router? (r/router ["/api" {:handler "identity"}] {:spec any? - :validate rs/validate-spec!}))))) + :validate rs/validate}))))) (deftest parameters-test (is (s/valid?