mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 16:31:11 +00:00
Expose spec problems to error handlers
This commit is contained in:
parent
c3af856893
commit
37cb99a13e
3 changed files with 95 additions and 4 deletions
48
CHANGELOG.md
48
CHANGELOG.md
|
|
@ -1,3 +1,51 @@
|
||||||
|
## UNRELEASED
|
||||||
|
|
||||||
|
## `reitit-spec`
|
||||||
|
|
||||||
|
* Spec problems are exposed as-is into request & response coercion errors, enabling pretty-printers like [expound](https://github.com/bhb/expound) to be used:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[reitit.ring :as ring])
|
||||||
|
(require '[reitit.ring.middleware.exception :as exception])
|
||||||
|
(require '[reitit.ring.coercion :as coercion])
|
||||||
|
(require '[expound.alpha :as expound])
|
||||||
|
|
||||||
|
(defn coercion-error-handler [status]
|
||||||
|
(let [printer (expound/custom-printer {:theme :figwheel-theme, :print-specs? false})
|
||||||
|
handler (exception/create-coercion-handler status)]
|
||||||
|
(fn [exception request]
|
||||||
|
(printer (-> exception ex-data :problems))
|
||||||
|
(handler exception request))))
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/plus"
|
||||||
|
{:get
|
||||||
|
{:parameters {:query {:x int?, :y int?}}
|
||||||
|
:responses {200 {:body {:total pos-int?}}}
|
||||||
|
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||||
|
{:status 200, :body {:total (+ x y)}})}}]
|
||||||
|
{:data {:coercion reitit.coercion.spec/coercion
|
||||||
|
:middleware [(exception/create-exception-middleware
|
||||||
|
(merge
|
||||||
|
exception/default-handlers
|
||||||
|
{:reitit.coercion/request-coercion (coercion-error-handler 400)
|
||||||
|
:reitit.coercion/response-coercion (coercion-error-handler 500)}))
|
||||||
|
coercion/coerce-request-middleware
|
||||||
|
coercion/coerce-response-middleware]}})))
|
||||||
|
|
||||||
|
(app
|
||||||
|
{:uri "/plus"
|
||||||
|
:request-method :get
|
||||||
|
:query-params {"x" "1", "y" "fail"}})
|
||||||
|
|
||||||
|
(app
|
||||||
|
{:uri "/plus"
|
||||||
|
:request-method :get
|
||||||
|
:query-params {"x" "1", "y" "-2"}})
|
||||||
|
```
|
||||||
|
|
||||||
## 0.2.7 (2018-11-11)
|
## 0.2.7 (2018-11-11)
|
||||||
|
|
||||||
## `reitit-spec`
|
## `reitit-spec`
|
||||||
|
|
|
||||||
|
|
@ -114,9 +114,10 @@
|
||||||
(into-spec model name))
|
(into-spec model name))
|
||||||
(-open-model [_ spec] spec)
|
(-open-model [_ spec] spec)
|
||||||
(-encode-error [_ error]
|
(-encode-error [_ error]
|
||||||
|
(let [problems (::s/problems error)]
|
||||||
(-> error
|
(-> error
|
||||||
(update :spec (comp str s/form))
|
(update :spec (comp str s/form))
|
||||||
(update :problems (partial mapv #(update % :pred stringify-pred)))))
|
(assoc :problems (mapv #(update % :pred stringify-pred) problems)))))
|
||||||
(-request-coercer [this type spec]
|
(-request-coercer [this type spec]
|
||||||
(let [spec (coercion/-compile-model this spec nil)
|
(let [spec (coercion/-compile-model this spec nil)
|
||||||
{:keys [formats default]} (transformers type)]
|
{:keys [formats default]} (transformers type)]
|
||||||
|
|
@ -130,7 +131,7 @@
|
||||||
(let [problems (st/explain-data spec value transformer)]
|
(let [problems (st/explain-data spec value transformer)]
|
||||||
(coercion/map->CoercionError
|
(coercion/map->CoercionError
|
||||||
{:spec spec
|
{:spec spec
|
||||||
:problems (::s/problems problems)}))
|
:problems problems}))
|
||||||
(s/unform spec transformed)))))
|
(s/unform spec transformed)))))
|
||||||
value))))
|
value))))
|
||||||
(-response-coercer [this spec]
|
(-response-coercer [this spec]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
(:require [clojure.test :refer [deftest testing is]]
|
(:require [clojure.test :refer [deftest testing is]]
|
||||||
[reitit.ring :as ring]
|
[reitit.ring :as ring]
|
||||||
[reitit.ring.middleware.exception :as exception]
|
[reitit.ring.middleware.exception :as exception]
|
||||||
|
[reitit.coercion :as coercion]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
[reitit.coercion.spec]
|
[reitit.coercion.spec]
|
||||||
[reitit.ring.coercion]
|
[reitit.ring.coercion]
|
||||||
[muuntaja.core :as m])
|
[muuntaja.core :as m])
|
||||||
|
|
@ -114,3 +116,43 @@
|
||||||
(app {:request-method :get, :uri "/defaults"})))
|
(app {:request-method :get, :uri "/defaults"})))
|
||||||
(is (= {:status 500, :body "too many tries"}
|
(is (= {:status 500, :body "too many tries"}
|
||||||
(app {:request-method :get, :uri "/defaults"})))))))
|
(app {:request-method :get, :uri "/defaults"})))))))
|
||||||
|
|
||||||
|
(deftest spec-coercion-exception-test
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/plus"
|
||||||
|
{:get
|
||||||
|
{:parameters {:query {:x int?, :y int?}}
|
||||||
|
:responses {200 {:body {:total pos-int?}}}
|
||||||
|
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||||
|
{:status 200, :body {:total (+ x y)}})}}]
|
||||||
|
{:data {:coercion reitit.coercion.spec/coercion
|
||||||
|
:middleware [(exception/create-exception-middleware
|
||||||
|
(merge
|
||||||
|
exception/default-handlers
|
||||||
|
{::coercion/request-coercion (fn [e _] {:status 400, :body (ex-data e)})
|
||||||
|
::coercion/response-coercion (fn [e _] {:status 500, :body (ex-data e)})}))
|
||||||
|
reitit.ring.coercion/coerce-request-middleware
|
||||||
|
reitit.ring.coercion/coerce-response-middleware]}}))]
|
||||||
|
(testing "success"
|
||||||
|
(let [{:keys [status body]} (app {:uri "/plus", :request-method :get, :query-params {"x" "1", "y" "2"}})]
|
||||||
|
(is (= 200 status))
|
||||||
|
(is (= body {:total 3}))))
|
||||||
|
|
||||||
|
(testing "request error"
|
||||||
|
(let [{:keys [status body]} (app {:uri "/plus", :request-method :get, :query-params {"x" "1", "y" "fail"}})]
|
||||||
|
(is (= 400 status))
|
||||||
|
(testing "spec error is exposed as is"
|
||||||
|
(let [problems (:problems body)]
|
||||||
|
(is (contains? problems ::s/spec))
|
||||||
|
(is (contains? problems ::s/value))
|
||||||
|
(is (contains? problems ::s/problems))))))
|
||||||
|
|
||||||
|
(testing "response error"
|
||||||
|
(let [{:keys [status body]} (app {:uri "/plus", :request-method :get, :query-params {"x" "1", "y" "-2"}})]
|
||||||
|
(is (= 500 status))
|
||||||
|
(testing "spec error is exposed as is"
|
||||||
|
(let [problems (:problems body)]
|
||||||
|
(is (contains? problems ::s/spec))
|
||||||
|
(is (contains? problems ::s/value))
|
||||||
|
(is (contains? problems ::s/problems))))))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue