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 [](https://circleci.com/gh/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?