Codebase list comidi-clojure / c58fdb8
Merge pull request #17 from scottyw/better-pattern-support (maint) Better support for Bidi patterns Ruth Linehan 8 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)))))