Package list comidi-clojure / bbc86cc
(maint) Better support for Bidi patterns Bidi permits true/false as Patterns on the left hand side of the route and this commit adds metadata support for those routes and clarifies the corresponding schema. It also adds some new tests. Scott Walker 6 years ago
2 changed file(s) with 146 addition(s) and 46 deletion(s). Raw diff Collapse all Expand all
2121 (def Zipper
2222 (schema/pred ks/zipper?))
2323
24 (def http-methods
25 #{:any :get :post :put :delete :head :options})
26
2724 (def RequestMethod
2825 (schema/enum :any :get :post :put :delete :head :options))
2926
27 ; Derived from bidi-schema PatternSegment
3028 (def RegexPatternSegment
31 [(schema/one Pattern "regex") (schema/one schema/Keyword "variable")])
29 (schema/pair schema/Regex "qual" schema/Keyword "id"))
3230
3331 (def RouteInfo
3432 {:path [bidi-schema/PatternSegment]
6361 (str/replace #"-$" "")))
6462
6563 (defn special-chars->underscores
66 "Convert all non-alpha chars except * and - to underscores"
67 [s]
68 (str/replace s #"[^\w\*\-]" "_"))
64 "Convert all non-alpha chars except ! * and - to underscores"
65 [s]
66 (str/replace s #"[^\w\!\*\-]" "_"))
6967
7068 (defn collapse-consecutive-underscores
7169 [s]
111109 suitable for use in building a route id string. This function is mostly
112110 responsible for determining the type of the path element and dispatching to
113111 the appropriate function."
114 [path-element :- bidi-schema/PatternSegment]
112 [path-element]
115113 (cond
116114 (string? path-element)
117115 (path-element->route-id-element path-element)
129127 route-path->route-id :- schema/Str
130128 "Given a route path (from comidi route-metadata), build a route-id string for
131129 the route. This route-id can be used as a unique identifier for a route."
132 [route-path :- bidi-schema/Pattern]
130 [route-path :- [bidi-schema/PatternSegment]]
133131 (->> route-path
134132 (map route-path-element->route-id-element)
135133 (filter #(not (empty? %)))
143141
144142 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
145143 ;;; Private - route metadata computation
144
145 (def http-methods
146 #{:any :get :post :put :delete :head :options})
146147
147148 (schema/defn ^:always-validate
148149 update-route-info* :- RouteInfo
157158
158159 (nil? (schema/check RegexPatternSegment pattern))
159160 (update-in route-info [:path] concat [pattern])
161
162 (= true pattern)
163 (update-in route-info [:path] conj "*")
164
165 (= false pattern)
166 (update-in route-info [:path] conj "!")
160167
161168 (sequential? pattern)
162169 (if-let [next (first pattern)]
1717 (zip/edit loc #(str "REGEX: " (.pattern %)))
1818 (zip/next loc)))))))
1919
20 (deftest handler-schema-test
21 (testing "handler schema"
22 (is (nil? (schema/check Handler :foo)))
23 (is (nil? (schema/check Handler (fn [] :foo))))
24 (is (nil? (schema/check Handler {:get (fn [] :foo)})))
25 (is (nil? (schema/check Handler {:post :foo})))))
26
2720 (deftest update-route-info-test
2821 (let [orig-route-info {:path []
2922 :request-method :any}]
3225 (is (= {:path []
3326 :request-method verb}
3427 (update-route-info* orig-route-info verb)))))
28 (testing "string path gets added to the path"
29 (is (= {:path ["/foo"]
30 :request-method :any}
31 (update-route-info* orig-route-info "/foo"))))
3532 (testing "string path elements get added to the path"
3633 (is (= {:path ["/foo"]
3734 :request-method :any}
38 (update-route-info* orig-route-info "/foo"))))
35 (update-route-info* orig-route-info ["/foo"]))))
3936 (testing "keyword path elements get added to the path"
4037 (is (= {:path [:foo]
4138 :request-method :any}
4441 (is (= {:path ["/foo/" :foo]
4542 :request-method :any}
4643 (update-route-info* orig-route-info ["/foo/" :foo]))))
44 (testing "boolean true is handled specially"
45 (is (= {:path ["*"]
46 :request-method :any}
47 (update-route-info* orig-route-info true))))
48 (testing "boolean false is handled specially"
49 (is (= {:path ["!"]
50 :request-method :any}
51 (update-route-info* orig-route-info false))))
4752 (testing "regex path element gets added to the path"
4853 (is (= {:path ["/foo/" ["REGEX: .*" :foo]]
4954 :request-method :any}
5257
5358 (deftest route-metadata-test
5459 (testing "route metadata includes ordered list of routes and lookup by handler"
55 (let [routes ["" [[["/foo/" :foo]
56 :foo-handler]
60 (let [routes ["" [[["/foo/" :foo] :foo-handler]
5761 [["/bar/" :bar]
5862 [["/baz" {:get :baz-handler}]
5963 ["/bam" {:put :bam-handler}]
64 ["/boop" :boop-handler]
6065 ["/bap" {:options :bap-handler}]]]
61 ["/buzz" {:post :buzz-handler}]]]
66 ["/buzz" {:post :buzz-handler}]
67 [true {:get :true-handler}]]]
6268 expected-foo-meta {:path ["" "/foo/" :foo]
6369 :route-id "foo-:foo"
6470 :request-method :any}
6874 expected-bam-meta {:path ["" "/bar/" :bar "/bam"]
6975 :route-id "bar-:bar-bam"
7076 :request-method :put}
77 expected-boop-meta {:path ["" "/bar/" :bar "/boop"]
78 :route-id "bar-:bar-boop"
79 :request-method :any}
7180 expected-bap-meta {:path ["" "/bar/" :bar "/bap"]
7281 :route-id "bar-:bar-bap"
7382 :request-method :options}
7483 expected-buzz-meta {:path ["" "/buzz"]
7584 :route-id "buzz"
76 :request-method :post}]
77 (is (= (comidi/route-metadata routes)
78 {:routes [expected-foo-meta
79 expected-baz-meta
80 expected-bam-meta
81 expected-bap-meta
82 expected-buzz-meta]
83 :handlers {:foo-handler expected-foo-meta
84 :baz-handler expected-baz-meta
85 :bam-handler expected-bam-meta
86 :bap-handler expected-bap-meta
87 :buzz-handler expected-buzz-meta}})))))
85 :request-method :post}
86 expected-true-meta {:path ["" "*"]
87 :route-id "*"
88 :request-method :get}]
89 (is (= (comidi/route-metadata* routes)
90 {:routes [expected-foo-meta
91 expected-baz-meta
92 expected-bam-meta
93 expected-boop-meta
94 expected-bap-meta
95 expected-buzz-meta
96 expected-true-meta]
97 :handlers {:foo-handler expected-foo-meta
98 :baz-handler expected-baz-meta
99 :bam-handler expected-bam-meta
100 :boop-handler expected-boop-meta
101 :bap-handler expected-bap-meta
102 :buzz-handler expected-buzz-meta
103 :true-handler expected-true-meta}})))))
88104
89105 (deftest routes-test
90106 (is (= ["" [["/foo" :foo-handler]
114130 (fn [req] :bar)]
115131 [["/baz/" :baz]
116132 (fn [req]
117 {:endpoint :baz
118 :route-params (:route-params req)})]]]]])]
133 {:endpoint :baz
134 :route-params (:route-params req)})]
135 [true
136 (fn [req] :true)]]]]])]
137 (is (= :true (handler {:uri "/foo/something/else"})))
119138 (is (= :bar (handler {:uri "/foo/bar"})))
120139 (is (= {:endpoint :baz
121140 :route-params {:baz "howdy"}}
221240
222241 (deftest route-names-test
223242 (let [test-routes (routes
224 (GET ["/foo/something/"] request
225 "foo!")
226 (POST ["/bar"] request
227 "bar!")
228 (PUT ["/baz" [#".*" :rest]] request
229 "baz!")
230 (ANY ["/bam/" [#"(?:bip|bap)" :rest]] request
231 "bam!")
232 (HEAD ["/bang/" [#".*" :rest] "/pow/" :pow] request
233 "bang!"))
243 ; Bidi Pattern: Path
244 (GET "/foo" request
245 "foo!")
246 (GET ["/foo/something/"] request
247 "foo something!")
248 ; Bidi Pattern: [ PatternSegment+ ]
249 (POST ["/bar"] request
250 "bar!")
251 (POST ["/bar" "bie"] request
252 "barbie!")
253 (PUT ["/baz" [#".*" :rest]] request
254 "baz!")
255 (ANY ["/bam/" [#"(?:bip|bap)" :rest]] request
256 "bam!")
257 (HEAD ["/bang/" [#".*" :rest] "/pow/" :pow] request
258 "bang!")
259 ; Bidi Pattern: false
260 (GET false request
261 "catch none!")
262 (GET "/false" request
263 "omg why would you do this?")
264 (GET ["/is" :false] request
265 "it hurts so bad")
266 ; Bidi Pattern: true
267 (GET true request
268 "catch all!")
269 (GET "/true" request
270 "omg why would you do this?")
271 (GET ["/is" :true] request
272 "it hurts so bad"))
234273 route-meta (route-metadata test-routes)
235274 route-ids (map :route-id (:routes route-meta))]
236 (is (= #{"foo-something" "bar" "baz-/*/" "bam-/bip_bap/" "bang-/*/-pow-:pow"}
275 (is (= #{"foo"
276 "foo-something"
277 "bar"
278 "bar-bie"
279 "baz-/*/"
280 "bam-/bip_bap/"
281 "bang-/*/-pow-:pow"
282 "*"
283 "true"
284 "is-:true"
285 "!"
286 "false"
287 "is-:false"}
237288 (set route-ids)))))
238289
239290 (deftest wrap-with-route-metadata-test
241292 (ANY ["/foo/" :foo] request
242293 "foo!")
243294 (GET ["/bar/" :bar] request
244 "bar!"))
295 "bar!")
296 (GET "/false" request "falsefalse!")
297 (GET false request "truefalse!")
298 (GET "/true" request "falsetrue!")
299 (GET true request "truetrue!"))
245300 route-meta (route-metadata test-routes)
246301 test-atom (atom {})
247302 test-middleware (fn [f]
257312
258313 (handler {:uri "/bar/something" :request-method :get})
259314 (is (= (-> test-atom deref :route-info :route-id) "bar-:bar"))
315 (is (= (-> test-atom deref :route-metadata) route-meta))
316
317 (handler {:uri "/false" :request-method :get})
318 (is (= (-> test-atom deref :route-info :route-id) "false"))
319 (is (= (-> test-atom deref :route-metadata) route-meta))
320
321 (handler {:uri "/true" :request-method :get})
322 (is (= (-> test-atom deref :route-info :route-id) "true"))
323 (is (= (-> test-atom deref :route-metadata) route-meta))
324
325 (handler {:uri "/wat" :request-method :get})
326 (is (= (-> test-atom deref :route-info :route-id) "*"))
260327 (is (= (-> test-atom deref :route-metadata) route-meta))))
261328
262329 (deftest route-handler-uses-existing-match-context-test
316383 (is (= (:body (handler {:uri "/middle/dd" :request-method :delete})) "dd!"))
317384 (is (= (:body (handler {:uri "/right/ee" :request-method :get})) "outer-ee!"))
318385 (is (= (:body (handler {:uri "/right/ff" :request-method :delete})) "outer-ff!"))))))
386
387 (deftest destructuring-test
388 (testing "Compojure-style destructuring works as expected"
389 (let [test-request {:uri "/foo"
390 :params {:aa "aa"
391 :bb "bb"}}]
392 (testing "A single binding outside a vector is the whole request"
393 ((routes->handler (ANY "/foo" request
394 (is (= test-request (select-keys request [:uri :params])))
395 "foo!")) test-request))
396 (testing "A vector binding called 'request' tries to bind to non-existent query param of the same name"
397 ((routes->handler (ANY "/foo" [request]
398 (is (nil? request))
399 "foo!")) test-request))
400 (testing "A vector binding with two params binds as expected"
401 ((routes->handler (ANY "/foo" [aa bb]
402 (is (= aa "aa"))
403 (is (= bb "bb"))
404 "foo!")) test-request))
405 (testing "A vector binding with two valid params, one invalid param and an ':as request' segment binds as expected"
406 ((routes->handler (ANY "/foo" [aa bb cc :as request]
407 (is (= aa "aa"))
408 (is (= bb "bb"))
409 (is (nil? cc))
410 (is (= test-request (select-keys request [:uri :params])))
411 "foo!")) test-request)))))