Skip to content

Commit 113263b

Browse files
committed
resupport fn? coercions
1 parent 23b96cb commit 113263b

File tree

6 files changed

+136
-11
lines changed

6 files changed

+136
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md)
22

33
## Next
4-
* Lazily load spec and schema coercion
54
* bump spec-tools to 0.10.6
65
* notable changes: swagger `:name` defaults to `"body"` instead of `""` ([diff](https://github.com/metosin/spec-tools/compare/0.10.2...0.10.3))
6+
* Add compatibility for 1.x coercions
7+
* implies Schema backend
8+
* fn from request->field->schema->coercer
9+
* mostly often just `(constantly nil)` or (constantly {:body matcher})
710

811
## 2.0.0-alpha34-SNAPSHOT
912
* **BREAKING CHANGE**: `:formatter :muuntaja` sometimes required for `api{-middleware}` options

src/compojure/api/api.clj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
[compojure.api.request :as request]
66
[compojure.api.routes :as routes]
77
[compojure.api.common :as common]
8-
[compojure.api.request :as request]
98
[ring.swagger.common :as rsc]
109
[ring.swagger.middleware :as rsm]))
1110

src/compojure/api/coercion.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[compojure.api.request :as request]
55
[compojure.api.coercion.core :as cc]
66
;; side effects
7-
compojure.api.coercion.schema
7+
[compojure.api.coercion.schema :as cschema]
88
compojure.api.coercion.spec)
99
(:import (compojure.api.coercion.core CoercionError)))
1010

@@ -23,6 +23,7 @@
2323
(nil? coercion) nil
2424
(keyword? coercion) (cc/named-coercion coercion)
2525
(satisfies? cc/Coercion coercion) coercion
26+
(fn? coercion) (cschema/create-coercion coercion)
2627
:else (throw (ex-info (str "invalid coercion " coercion) {:coercion coercion}))))
2728

2829
(defn get-apidocs [maybe-coercion spec info]

src/compojure/api/coercion/schema.clj

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,15 @@
5757
(update :errors stringify)))
5858

5959
(coerce-request [_ schema value type format request]
60-
(let [type-options (options type)]
61-
(if-let [matcher (or (get (get type-options :formats) format)
62-
(get type-options :default))]
60+
(let [legacy? (fn? options)
61+
type-options (if legacy?
62+
(when-let [provider (options request)]
63+
(provider type))
64+
(options type))]
65+
(if-let [matcher (if legacy?
66+
type-options
67+
(or (get (get type-options :formats) format)
68+
(get type-options :default)))]
6369
(let [coerce (memoized-coercer schema matcher)
6470
coerced (coerce value)]
6571
(if (su/error? coerced)
@@ -85,6 +91,8 @@
8591
:response {:default (constantly nil)}})
8692

8793
(defn create-coercion [options]
94+
{:pre [(or (map? options)
95+
(fn? options))]}
8896
(->SchemaCoercion :schema options))
8997

9098
(def default-coercion (create-coercion default-options))

src/compojure/api/middleware.clj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@
121121
:string coerce/query-schema-coercion-matcher
122122
:response coerce/json-schema-coercion-matcher})
123123

124-
;; 1.1.x
124+
(def no-response-coercion
125+
(constantly (dissoc default-coercion-matchers :response)))
126+
125127
(defn coercion-matchers [request]
126128
(let [options (get-options request)]
127129
(if (contains? options :coercion)

test/compojure/api/coercion_test.clj

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@
66
[ring.util.http-response :refer :all]
77
[clojure.core.async :as a]
88
[schema.core :as s]
9+
[compojure.api.middleware :as mw]
910
[compojure.api.coercion.schema :as cs]))
1011

1112
(defn is-has-body [expected value]
1213
(is (= (second value) expected)))
1314

1415
(defn is-fails-with [expected-status [status body]]
15-
(is (= status expected-status))
16-
(is (every? (partial contains? body) [:type :coercion :in :value :schema :errors])))
16+
(is (= status expected-status)
17+
(pr-str body))
18+
(is (every? (partial contains? body) [:type :coercion :in :value :schema :errors])
19+
(pr-str body)))
1720

1821
(deftest schema-coercion-test
1922
(testing "response schemas"
@@ -74,7 +77,15 @@
7477
ping-route)]
7578
(let [[status body] (get* app "/ping")]
7679
(is (= 200 status))
77-
(is (= {:pong 123} body)))))
80+
(is (= {:pong 123} body))))
81+
(testing "legacy"
82+
(let [app (api
83+
{:formatter :muuntaja
84+
:coercion mw/no-response-coercion}
85+
ping-route)]
86+
(let [[status body] (get* app "/ping")]
87+
(is (= 200 status))
88+
(is (= {:pong 123} body))))))
7889
(testing "all coercion"
7990
(let [app (api
8091
{:formatter :muuntaja
@@ -101,7 +112,7 @@
101112
(a/go (ok "foo"))))]
102113
(is-fails-with 500 (get* app "/async"))))))))
103114

104-
(testing "body coersion"
115+
(testing "body coercion"
105116
(let [beer-route (POST "/beer" []
106117
:body [body {:beers #{(s/enum "ipa" "apa")}}]
107118
(ok body))]
@@ -131,12 +142,36 @@
131142
(is (= 200 status))
132143
(is (= {:beers ["ipa" "apa" "ipa"]} body)))))
133144

145+
(testing "legacy body-coercion can be disabled"
146+
(let [no-body-coercion (constantly (dissoc mw/default-coercion-matchers :body))
147+
app (api
148+
{:formatter :muuntaja
149+
:coercion no-body-coercion}
150+
beer-route)]
151+
(let [[status body] (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]}))]
152+
(is (= 200 status))
153+
(is (= {:beers ["ipa" "apa" "ipa"]} body))))
154+
(let [app (api
155+
{:formatter :muuntaja
156+
:coercion nil}
157+
beer-route)]
158+
(let [[status body] (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]}))]
159+
(is (= 200 status))
160+
(is (= {:beers ["ipa" "apa" "ipa"]} body)))))
161+
134162
(testing "body-coercion can be changed"
135163
(let [nop-body-coercion (cs/create-coercion (assoc cs/default-options :body {:default (constantly nil)}))
136164
app (api
137165
{:formatter :muuntaja
138166
:coercion nop-body-coercion}
139167
beer-route)]
168+
(is-fails-with 400 (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]})))))
169+
(testing "legacy body-coercion can be changed"
170+
(let [nop-body-coercion (constantly (assoc mw/default-coercion-matchers :body (constantly nil)))
171+
app (api
172+
{:formatter :muuntaja
173+
:coercion nop-body-coercion}
174+
beer-route)]
140175
(is-fails-with 400 (post* app "/beer" (json-string {:beers ["ipa" "apa" "ipa"]})))))))
141176

142177
(testing "query coercion"
@@ -162,12 +197,30 @@
162197
(is (= 200 status))
163198
(is (= {:i "10"} body)))))
164199

200+
(testing "legacy query-coercion can be disabled"
201+
(let [no-query-coercion (constantly (dissoc mw/default-coercion-matchers :string))
202+
app (api
203+
{:formatter :muuntaja
204+
:coercion no-query-coercion}
205+
query-route)]
206+
(let [[status body] (get* app "/query" {:i 10})]
207+
(is (= 200 status))
208+
(is (= {:i "10"} body)))))
209+
165210
(testing "query-coercion can be changed"
166211
(let [nop-query-coercion (cs/create-coercion (assoc cs/default-options :string {:default (constantly nil)}))
167212
app (api
168213
{:formatter :muuntaja
169214
:coercion nop-query-coercion}
170215
query-route)]
216+
(is-fails-with 400 (get* app "/query" {:i 10}))))
217+
218+
(testing "legacy query-coercion can be changed"
219+
(let [nop-query-coercion (constantly (assoc mw/default-coercion-matchers :string (constantly nil)))
220+
app (api
221+
{:formatter :muuntaja
222+
:coercion nop-query-coercion}
223+
query-route)]
171224
(is-fails-with 400 (get* app "/query" {:i 10}))))))
172225

173226
(testing "route-specific coercion"
@@ -207,6 +260,52 @@
207260

208261
(testing "no coercion"
209262
(let [[status body] (get* app "/no-coercion" {:i 10})]
263+
(is (= 200 status))
264+
(is (= {:i "10"} body))))))
265+
(testing "legacy route-specific coercion"
266+
(let [app (api
267+
{:formatter :muuntaja}
268+
(GET "/default" []
269+
:query-params [i :- s/Int]
270+
(ok {:i i}))
271+
(GET "/disabled-coercion" []
272+
:coercion (constantly (assoc mw/default-coercion-matchers :string (constantly nil)))
273+
:query-params [i :- s/Int]
274+
(ok {:i i}))
275+
(GET "/no-coercion" []
276+
:coercion (constantly nil)
277+
:query-params [i :- s/Int]
278+
(ok {:i i}))
279+
(GET "/nil-coercion" []
280+
:coercion nil
281+
:query-params [i :- s/Int]
282+
(ok {:i i})))]
283+
284+
(testing "default coercion"
285+
(let [[status body] (get* app "/default" {:i 10})]
286+
(is (= 200 status))
287+
(is (= {:i 10} body))))
288+
289+
(testing "disabled coercion"
290+
(is-fails-with 400 (get* app "/disabled-coercion" {:i 10})))
291+
292+
(testing "exception data"
293+
(let [ex (get* app "/disabled-coercion" {:i 10})]
294+
(is (= 400 (first ex)))
295+
(is (= {:type "compojure.api.exception/request-validation"
296+
:coercion "schema",
297+
:in ["request" "query-params"],
298+
:value {:i "10"}
299+
:schema "{Keyword Any, :i Int}",
300+
:errors {:i "(not (integer? \"10\"))"}}
301+
(select-keys (second ex)
302+
[:type :coercion :in :value :schema :errors])))))
303+
304+
(testing "no coercion"
305+
(let [[status body] (get* app "/no-coercion" {:i 10})]
306+
(is (= 200 status))
307+
(is (= {:i "10"} body)))
308+
(let [[status body] (get* app "/nil-coercion" {:i 10})]
210309
(is (= 200 status))
211310
(is (= {:i "10"} body)))))))
212311

@@ -237,6 +336,19 @@
237336
(is (= ["1" 2] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y 2}}))))
238337
(is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y "abba"}})))))
239338

339+
(testing "legacy coercion can be overridden"
340+
(let [app (context "/api" []
341+
:query-params [{y :- Long 0}]
342+
(GET "/ping" []
343+
:coercion (constantly nil)
344+
:query-params [x :- Long]
345+
(ok [x y])))]
346+
(is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {}})))
347+
(is (= ["abba" 0] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "abba"}}))))
348+
(is (= ["1" 0] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "1"}}))))
349+
(is (= ["1" 2] (:body (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y 2}}))))
350+
(is (thrown? Exception (app {:request-method :get :uri "/api/ping" :query-params {:x "1", :y "abba"}})))))
351+
240352
(testing "context coercion is used for subroutes"
241353
(let [app (context "/api" []
242354
:coercion nil

0 commit comments

Comments
 (0)