Codebase list comidi-clojure / a32e6ff
(PE-9181) Rename to `comidi` Chris Price 9 years ago
6 changed file(s) with 576 addition(s) and 586 deletion(s). Raw diff Collapse all Expand all
0 # pl-bidi
0 # comidi
11
2 [![Build Status](https://magnum.travis-ci.com/puppetlabs/pl-bidi.svg?token=ApBsaKK1zdeqHwzhXLzw&branch=master)](https://magnum.travis-ci.com/puppetlabs/pl-bidi)
2 [![Build Status](https://magnum.travis-ci.com/puppetlabs/comidi.svg?token=ApBsaKK1zdeqHwzhXLzw&branch=master)](https://magnum.travis-ci.com/puppetlabs/comidi)
33
4 A Clojure library designed to ... well, that part is up to you.
5
6 ## Usage
7
8 FIXME
9
10 ## License
11
12 Copyright © 2015 FIXME
13
14 Distributed under the Eclipse Public License either version 1.0 or (at
15 your option) any later version.
4 Puppet Labs utility functions and compojure-like syntax-sugar wrappers around
5 the [bidi](https://github.com/juxt/bidi) web routing library.
0 (defproject puppetlabs/pl-bidi "0.1.0-SNAPSHOT"
1 :description "Puppet Labs utility functions for use with the bidi web routing library"
2 :url "https://github.com/puppetlabs/pl-bidi"
0 (defproject puppetlabs/comidi "0.1.0-SNAPSHOT"
1 :description "Puppet Labs utility functions and compojure-like wrappers for use with the bidi web routing library"
2 :url "https://github.com/puppetlabs/comidi"
33 :dependencies [[org.clojure/clojure "1.6.0"]
44 [bidi "1.18.9"]
55 [compojure "1.3.2"]
6 [prismatic/schema "0.2.2"]
6 [prismatic/schema "0.4.0"]
77 [puppetlabs/kitchensink "1.1.0"]])
+0
-284
src/puppetlabs/bidi.clj less more
0 (ns puppetlabs.bidi
1 (:require [bidi.ring :as bidi-ring]
2 [bidi.bidi :as bidi]
3 [clojure.zip :as zip]
4 [compojure.core :as compojure]
5 [compojure.response :as compojure-response]
6 [ring.util.response :as ring-response]
7 [schema.core :as schema]
8 [puppetlabs.kitchensink.core :as ks])
9 (:import (java.util.regex Pattern)))
10
11 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
12 ;;; Schemas
13
14 (defn pattern?
15 [x]
16 (instance? Pattern x))
17
18 (def Zipper
19 (schema/pred ks/zipper?))
20
21 (def http-methods
22 #{:any :get :post :put :delete :head})
23
24 (def RequestMethod
25 (schema/enum :any :get :post :put :delete :head))
26
27 (def RegexPathElement
28 [(schema/one Pattern "regex") (schema/one schema/Keyword "variable")])
29
30 (def PathElement
31 (schema/conditional
32 string? schema/Str
33 keyword? schema/Keyword
34 vector? RegexPathElement))
35
36 (def RouteInfo
37 {:path [PathElement]
38 :request-method RequestMethod})
39
40 (def Handler
41 (schema/conditional
42 keyword? schema/Keyword
43 fn? (schema/pred fn?)
44 map? {RequestMethod (schema/recursive #'Handler)}))
45
46 (def RouteMetadata
47 {:routes [RouteInfo]
48 :handlers {Handler RouteInfo}})
49
50 (def BidiPattern
51 (schema/conditional
52 keyword? schema/Keyword
53 string? schema/Str
54 sequential? [PathElement]))
55
56 (def BidiRouteDestination
57 (schema/conditional
58 #(nil? (schema/check Handler %)) Handler
59 :else [[(schema/one BidiPattern "pattern")
60 (schema/one (schema/recursive #'BidiRouteDestination) "destination")]]))
61
62 (def BidiRoute
63 [(schema/one BidiPattern "pattern")
64 (schema/one BidiRouteDestination "destination")])
65
66
67 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
68 ;;; Private
69
70 (defmacro handler-fn*
71 "Helper macro, used by the compojure-like macros (GET/POST/etc.) to generate
72 a function that provides compojure's destructuring and rendering support."
73 [bindings body]
74 `(fn [request#]
75 (compojure-response/render
76 (compojure/let-request [~bindings request#] ~@body)
77 request#)))
78
79 (defn route-with-method*
80 "Helper function, used by the compojure-like macros (GET/POST/etc.) to generate
81 a bidi route that includes a wrapped handler function."
82 [method pattern bindings body]
83 `[~pattern {~method (handler-fn* ~bindings ~body)}])
84
85 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
86 ;;; Private - route metadata computation
87
88 (schema/defn ^:always-validate
89 update-route-info* :- RouteInfo
90 "Helper function, used to maintain a RouteInfo data structure that represents
91 the current path elements of a route as we traverse the Bidi route tree via
92 zipper."
93 [route-info :- RouteInfo
94 pattern :- BidiPattern]
95 (cond
96 (contains? http-methods pattern)
97 (assoc-in route-info [:request-method] pattern)
98
99 (nil? (schema/check RegexPathElement pattern))
100 (update-in route-info [:path] concat [pattern])
101
102 (sequential? pattern)
103 (if-let [next (first pattern)]
104 (update-route-info*
105 (update-in route-info [:path] conj next)
106 (rest pattern))
107 route-info)
108
109 :else
110 (update-in route-info [:path] conj pattern)))
111
112 (declare breadth-route-metadata*)
113
114 (schema/defn ^:always-validate
115 depth-route-metadata* :- RouteMetadata
116 "Helper function used to traverse branches of the Bidi route tree, depth-first."
117 [route-meta :- RouteMetadata
118 route-info :- RouteInfo
119 loc :- Zipper]
120 (let [[pattern matched] (zip/node loc)]
121 (cond
122 (map? matched)
123 (depth-route-metadata*
124 route-meta
125 route-info
126 (-> loc zip/down zip/right (zip/edit #(into [] %)) zip/up))
127
128 (vector? matched)
129 (breadth-route-metadata*
130 route-meta
131 (update-route-info* route-info pattern)
132 (-> loc zip/down zip/right zip/down))
133
134 :else
135 (let [route-info (update-route-info* route-info pattern)]
136 (-> route-meta
137 (update-in [:routes] conj route-info)
138 (assoc-in [:handlers matched] route-info))))))
139
140 (schema/defn ^:always-validate
141 breadth-route-metadata* :- RouteMetadata
142 "Helper function used to traverse branches of the Bidi route tree, breadth-first."
143 [route-meta :- RouteMetadata
144 route-info :- RouteInfo
145 loc :- Zipper]
146 (loop [route-meta route-meta
147 loc loc]
148 (let [routes (depth-route-metadata* route-meta route-info loc)]
149 (if-let [next (zip/right loc)]
150 (recur routes next)
151 routes))))
152
153 (schema/defn ^:always-validate
154 route-metadata :- RouteMetadata
155 "Traverses a Bidi route tree and returns route metadata, which includes a list
156 of RouteInfo objects (one per route), plus a mechanism to look up the
157 RouteInfo for a given handler."
158 [routes :- BidiRoute]
159 (let [route-info {:path []
160 :request-method :any}
161 loc (-> [routes] zip/vector-zip zip/down)]
162 (breadth-route-metadata* {:routes []
163 :handlers {}} route-info loc)))
164
165 (schema/defn ^:always-validate
166 make-handler :- (schema/pred fn?)
167 "Create a Ring handler from the route definition data structure. (This code
168 is largely borrowed from bidi core.) Arguments:
169
170 - route-meta: metadata about the routes; allows us to look up the route info
171 by handler. You can get this by calling `route-metadata`.
172 - routes: the Bidi route tree
173 - handler-fn: this fn will be called on all of the handlers found in the bidi
174 route tree; it is expected to return a ring handler fn for that
175 route. If you are using the compojure-like macros in this
176 namespace or have nested your ring handler functions in the bidi
177 tree by other means, you can just pass `identity` here, or pass
178 in some middleware fn to wrap around the nested ring handlers.
179 The handlers will have access to the `RouteInfo` of the matching
180 bidi route via the `:route-info` key in the request map."
181 [route-meta :- RouteMetadata
182 routes :- BidiRoute
183 handler-fn :- (schema/pred fn?)]
184 (let [compiled-routes (bidi/compile-route routes)]
185 (fn [{:keys [uri path-info] :as req}]
186 (let [path (or path-info uri)
187 {:keys [handler route-params] :as match-context}
188 (apply bidi/match-route compiled-routes path (apply concat (seq req)))]
189 (when handler
190 (let [req (-> req
191 (update-in [:params] merge route-params)
192 (update-in [:route-params] merge route-params)
193 (assoc-in [:route-info] (get-in route-meta
194 [:handlers handler])))]
195 (bidi-ring/request
196 (handler-fn handler)
197 req
198 (apply dissoc match-context :handler (keys req)))))))))
199
200
201 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
202 ;;;; Public - core functions
203
204 (schema/defn ^:always-validate
205 routes :- BidiRoute
206 "Combines multiple bidi routes into a single data structure; this is largely
207 just a convenience function for grouping several routes together as a single
208 object that can be passed around."
209 [& routes :- [BidiRoute]]
210 ["" (vec routes)])
211
212 (schema/defn ^:always-validate
213 context :- BidiRoute
214 "Combines multiple bidi routes together into a single data structure, but nests
215 them all under the given url-prefix. This is similar to compojure's `context`
216 macro, but does not accept a binding form. You can still destructure variables
217 by passing a bidi pattern for `url-prefix`, and the variables will be available
218 to all nested routes."
219 [url-prefix :- BidiPattern
220 & routes :- [BidiRoute]]
221 [url-prefix (vec routes)])
222
223 (schema/defn ^:always-validate
224 routes->handler :- (schema/pred fn?)
225 "Given a bidi route tree, converts into a ring request handler function. You
226 may pass an optional middleware function that will be wrapped around the
227 request handling; the middleware fn will have access to the `RouteInfo` of the
228 matching bidi route via the `:route-info` key in the request map."
229 ([routes :- BidiRoute
230 route-middleware-fn :- (schema/maybe (schema/pred fn?))]
231 (let [route-meta (route-metadata routes)]
232 (with-meta
233 (make-handler route-meta
234 routes
235 route-middleware-fn)
236 {:route-metadata route-meta})))
237 ([routes]
238 (routes->handler routes identity)))
239
240 (schema/defn ^:always-validate
241 context-handler :- (schema/pred fn?)
242 "Convenience function that effectively composes `context` and `routes->handler`."
243 [url-prefix :- BidiPattern
244 & routes :- [BidiRoute]]
245 (routes->handler
246 (apply context url-prefix routes)))
247
248
249 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
250 ;;; Public - compojure-like convenience macros
251
252 (defmacro ANY
253 [pattern bindings & body]
254 `[~pattern (handler-fn* ~bindings ~body)])
255
256 (defmacro GET
257 [pattern bindings & body]
258 (route-with-method* :get pattern bindings body))
259
260 (defmacro HEAD
261 [pattern bindings & body]
262 (route-with-method* :head pattern bindings body))
263
264 (defmacro PUT
265 [pattern bindings & body]
266 (route-with-method* :put pattern bindings body))
267
268 (defmacro POST
269 [pattern bindings & body]
270 (route-with-method* :post pattern bindings body))
271
272 (defmacro DELETE
273 [pattern bindings & body]
274 (route-with-method* :delete pattern bindings body))
275
276 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
277 ;;; Public - pre-built routes
278
279 (defn not-found
280 [body]
281 [[[#".*" :rest]] (fn [request]
282 (-> (compojure-response/render body request)
283 (ring-response/status 404)))])
0 (ns puppetlabs.comidi
1 (:require [bidi.ring :as bidi-ring]
2 [bidi.bidi :as bidi]
3 [clojure.zip :as zip]
4 [compojure.core :as compojure]
5 [compojure.response :as compojure-response]
6 [ring.util.response :as ring-response]
7 [schema.core :as schema]
8 [puppetlabs.kitchensink.core :as ks])
9 (:import (java.util.regex Pattern)))
10
11 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
12 ;;; Schemas
13
14 (defn pattern?
15 [x]
16 (instance? Pattern x))
17
18 (def Zipper
19 (schema/pred ks/zipper?))
20
21 (def http-methods
22 #{:any :get :post :put :delete :head})
23
24 (def RequestMethod
25 (schema/enum :any :get :post :put :delete :head))
26
27 (def RegexPathElement
28 [(schema/one Pattern "regex") (schema/one schema/Keyword "variable")])
29
30 (def PathElement
31 (schema/conditional
32 string? schema/Str
33 keyword? schema/Keyword
34 vector? RegexPathElement))
35
36 (def RouteInfo
37 {:path [PathElement]
38 :request-method RequestMethod})
39
40 (def Handler
41 (schema/conditional
42 keyword? schema/Keyword
43 fn? (schema/pred fn?)
44 map? {RequestMethod (schema/recursive #'Handler)}))
45
46 (def RouteMetadata
47 {:routes [RouteInfo]
48 :handlers {Handler RouteInfo}})
49
50 (def BidiPattern
51 (schema/conditional
52 keyword? schema/Keyword
53 string? schema/Str
54 sequential? [PathElement]))
55
56 (def BidiRouteDestination
57 (schema/conditional
58 #(nil? (schema/check Handler %)) Handler
59 :else [[(schema/one BidiPattern "pattern")
60 (schema/one (schema/recursive #'BidiRouteDestination) "destination")]]))
61
62 (def BidiRoute
63 [(schema/one BidiPattern "pattern")
64 (schema/one BidiRouteDestination "destination")])
65
66
67 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
68 ;;; Private
69
70 (defmacro handler-fn*
71 "Helper macro, used by the compojure-like macros (GET/POST/etc.) to generate
72 a function that provides compojure's destructuring and rendering support."
73 [bindings body]
74 `(fn [request#]
75 (compojure-response/render
76 (compojure/let-request [~bindings request#] ~@body)
77 request#)))
78
79 (defn route-with-method*
80 "Helper function, used by the compojure-like macros (GET/POST/etc.) to generate
81 a bidi route that includes a wrapped handler function."
82 [method pattern bindings body]
83 `[~pattern {~method (handler-fn* ~bindings ~body)}])
84
85 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
86 ;;; Private - route metadata computation
87
88 (schema/defn ^:always-validate
89 update-route-info* :- RouteInfo
90 "Helper function, used to maintain a RouteInfo data structure that represents
91 the current path elements of a route as we traverse the Bidi route tree via
92 zipper."
93 [route-info :- RouteInfo
94 pattern :- BidiPattern]
95 (cond
96 (contains? http-methods pattern)
97 (assoc-in route-info [:request-method] pattern)
98
99 (nil? (schema/check RegexPathElement pattern))
100 (update-in route-info [:path] concat [pattern])
101
102 (sequential? pattern)
103 (if-let [next (first pattern)]
104 (update-route-info*
105 (update-in route-info [:path] conj next)
106 (rest pattern))
107 route-info)
108
109 :else
110 (update-in route-info [:path] conj pattern)))
111
112 (declare breadth-route-metadata*)
113
114 (schema/defn ^:always-validate
115 depth-route-metadata* :- RouteMetadata
116 "Helper function used to traverse branches of the Bidi route tree, depth-first."
117 [route-meta :- RouteMetadata
118 route-info :- RouteInfo
119 loc :- Zipper]
120 (let [[pattern matched] (zip/node loc)]
121 (cond
122 (map? matched)
123 (depth-route-metadata*
124 route-meta
125 route-info
126 (-> loc zip/down zip/right (zip/edit #(into [] %)) zip/up))
127
128 (vector? matched)
129 (breadth-route-metadata*
130 route-meta
131 (update-route-info* route-info pattern)
132 (-> loc zip/down zip/right zip/down))
133
134 :else
135 (let [route-info (update-route-info* route-info pattern)]
136 (-> route-meta
137 (update-in [:routes] conj route-info)
138 (assoc-in [:handlers matched] route-info))))))
139
140 (schema/defn ^:always-validate
141 breadth-route-metadata* :- RouteMetadata
142 "Helper function used to traverse branches of the Bidi route tree, breadth-first."
143 [route-meta :- RouteMetadata
144 route-info :- RouteInfo
145 loc :- Zipper]
146 (loop [route-meta route-meta
147 loc loc]
148 (let [routes (depth-route-metadata* route-meta route-info loc)]
149 (if-let [next (zip/right loc)]
150 (recur routes next)
151 routes))))
152
153 (schema/defn ^:always-validate
154 route-metadata :- RouteMetadata
155 "Traverses a Bidi route tree and returns route metadata, which includes a list
156 of RouteInfo objects (one per route), plus a mechanism to look up the
157 RouteInfo for a given handler."
158 [routes :- BidiRoute]
159 (let [route-info {:path []
160 :request-method :any}
161 loc (-> [routes] zip/vector-zip zip/down)]
162 (breadth-route-metadata* {:routes []
163 :handlers {}} route-info loc)))
164
165 (schema/defn ^:always-validate
166 make-handler :- (schema/pred fn?)
167 "Create a Ring handler from the route definition data structure. (This code
168 is largely borrowed from bidi core.) Arguments:
169
170 - route-meta: metadata about the routes; allows us to look up the route info
171 by handler. You can get this by calling `route-metadata`.
172 - routes: the Bidi route tree
173 - handler-fn: this fn will be called on all of the handlers found in the bidi
174 route tree; it is expected to return a ring handler fn for that
175 route. If you are using the compojure-like macros in this
176 namespace or have nested your ring handler functions in the bidi
177 tree by other means, you can just pass `identity` here, or pass
178 in some middleware fn to wrap around the nested ring handlers.
179 The handlers will have access to the `RouteInfo` of the matching
180 bidi route via the `:route-info` key in the request map."
181 [route-meta :- RouteMetadata
182 routes :- BidiRoute
183 handler-fn :- (schema/pred fn?)]
184 (let [compiled-routes (bidi/compile-route routes)]
185 (fn [{:keys [uri path-info] :as req}]
186 (let [path (or path-info uri)
187 {:keys [handler route-params] :as match-context}
188 (apply bidi/match-route compiled-routes path (apply concat (seq req)))]
189 (when handler
190 (let [req (-> req
191 (update-in [:params] merge route-params)
192 (update-in [:route-params] merge route-params)
193 (assoc-in [:route-info] (get-in route-meta
194 [:handlers handler])))]
195 (bidi-ring/request
196 (handler-fn handler)
197 req
198 (apply dissoc match-context :handler (keys req)))))))))
199
200
201 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
202 ;;;; Public - core functions
203
204 (schema/defn ^:always-validate
205 routes :- BidiRoute
206 "Combines multiple bidi routes into a single data structure; this is largely
207 just a convenience function for grouping several routes together as a single
208 object that can be passed around."
209 [& routes :- [BidiRoute]]
210 ["" (vec routes)])
211
212 (schema/defn ^:always-validate
213 context :- BidiRoute
214 "Combines multiple bidi routes together into a single data structure, but nests
215 them all under the given url-prefix. This is similar to compojure's `context`
216 macro, but does not accept a binding form. You can still destructure variables
217 by passing a bidi pattern for `url-prefix`, and the variables will be available
218 to all nested routes."
219 [url-prefix :- BidiPattern
220 & routes :- [BidiRoute]]
221 [url-prefix (vec routes)])
222
223 (schema/defn ^:always-validate
224 routes->handler :- (schema/pred fn?)
225 "Given a bidi route tree, converts into a ring request handler function. You
226 may pass an optional middleware function that will be wrapped around the
227 request handling; the middleware fn will have access to the `RouteInfo` of the
228 matching bidi route via the `:route-info` key in the request map."
229 ([routes :- BidiRoute
230 route-middleware-fn :- (schema/maybe (schema/pred fn?))]
231 (let [route-meta (route-metadata routes)]
232 (with-meta
233 (make-handler route-meta
234 routes
235 route-middleware-fn)
236 {:route-metadata route-meta})))
237 ([routes]
238 (routes->handler routes identity)))
239
240 (schema/defn ^:always-validate
241 context-handler :- (schema/pred fn?)
242 "Convenience function that effectively composes `context` and `routes->handler`."
243 [url-prefix :- BidiPattern
244 & routes :- [BidiRoute]]
245 (routes->handler
246 (apply context url-prefix routes)))
247
248
249 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
250 ;;; Public - compojure-like convenience macros
251
252 (defmacro ANY
253 [pattern bindings & body]
254 `[~pattern (handler-fn* ~bindings ~body)])
255
256 (defmacro GET
257 [pattern bindings & body]
258 (route-with-method* :get pattern bindings body))
259
260 (defmacro HEAD
261 [pattern bindings & body]
262 (route-with-method* :head pattern bindings body))
263
264 (defmacro PUT
265 [pattern bindings & body]
266 (route-with-method* :put pattern bindings body))
267
268 (defmacro POST
269 [pattern bindings & body]
270 (route-with-method* :post pattern bindings body))
271
272 (defmacro DELETE
273 [pattern bindings & body]
274 (route-with-method* :delete pattern bindings body))
275
276 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
277 ;;; Public - pre-built routes
278
279 (defn not-found
280 [body]
281 [[[#".*" :rest]] (fn [request]
282 (-> (compojure-response/render body request)
283 (ring-response/status 404)))])
+0
-284
test/puppetlabs/bidi_test.clj less more
0 (ns puppetlabs.bidi-test
1 (require [clojure.test :refer :all]
2 [puppetlabs.bidi :as pl-bidi]
3 [schema.test :as schema-test]
4 [puppetlabs.bidi :refer :all]
5 [schema.core :as schema]
6 [clojure.zip :as zip]))
7
8 (use-fixtures :once schema-test/validate-schemas)
9
10 (defn replace-regexes-for-equality-check
11 [xs]
12 (loop [loc (zip/vector-zip xs)]
13 (if (zip/end? loc)
14 (zip/root loc)
15 (recur
16 (let [node (zip/node loc)]
17 (if (pattern? node)
18 (zip/edit loc #(str "REGEX: " (.pattern %)))
19 (zip/next loc)))))))
20
21 (deftest handler-schema-test
22 (testing "handler schema"
23 (is (nil? (schema/check Handler :foo)))
24 (is (nil? (schema/check Handler (fn [] :foo))))
25 (is (nil? (schema/check Handler {:get (fn [] :foo)})))
26 (is (nil? (schema/check Handler {:post :foo})))))
27
28 (deftest pattern-schema-test
29 (testing "pattern schema"
30 (is (nil? (schema/check BidiPattern "/foo")))
31 (is (nil? (schema/check BidiPattern :foo)))
32 (is (nil? (schema/check BidiPattern ["/foo/" :foo "/foo"])))
33 (is (nil? (schema/check BidiPattern ["/foo/" [#".*" :rest]])))))
34
35 (deftest destination-schema-test
36 (testing "route destination schema"
37 (is (nil? (schema/check BidiRouteDestination :foo)))
38 (is (nil? (schema/check BidiRouteDestination (fn [] nil))))
39 (is (nil? (schema/check BidiRouteDestination {:get (fn [] nil)})))
40 (is (nil? (schema/check BidiRouteDestination {:get :my-handler})))
41 (is (nil? (schema/check BidiRouteDestination [[["/foo/" :foo "/foo"] :foo]])))
42 (is (not (nil? (schema/check BidiRouteDestination [["/foo/" :foo "/foo"] :foo]))))
43 (is (nil? (schema/check BidiRouteDestination [[["/foo/" :foo]
44 :foo-handler]
45 [["/bar/" :bar]
46 {:get :bar-handler}]])))))
47
48 (deftest route-schema-test
49 (testing "route schema"
50 (is (nil? (schema/check BidiRoute [:foo :foo])))
51 (is (nil? (schema/check BidiRoute ["/foo" [[:foo :foo]]])))
52 (is (not (nil? (schema/check BidiRoute ["/foo" [:foo :foo]]))))
53 (is (nil? (schema/check BidiRoute ["" [[["/foo/" :foo]
54 :foo-handler]
55 [["/bar/" :bar]
56 {:get :bar-handler}]]])))))
57
58 (deftest update-route-info-test
59 (let [orig-route-info {:path []
60 :request-method :any}]
61 (testing "HTTP verb keyword causes request-method to be updated"
62 (doseq [verb [:get :post :put :delete :head]]
63 (is (= {:path []
64 :request-method verb}
65 (update-route-info* orig-route-info verb)))))
66 (testing "string path elements get added to the path"
67 (is (= {:path ["/foo"]
68 :request-method :any}
69 (update-route-info* orig-route-info "/foo"))))
70 (testing "keyword path elements get added to the path"
71 (is (= {:path [:foo]
72 :request-method :any}
73 (update-route-info* orig-route-info :foo))))
74 (testing "vector path elements get flattened and added to the path"
75 (is (= {:path ["/foo/" :foo]
76 :request-method :any}
77 (update-route-info* orig-route-info ["/foo/" :foo]))))
78 (testing "regex path element gets added to the path"
79 (is (= {:path ["/foo/" ["REGEX: .*" :foo]]
80 :request-method :any}
81 (-> (update-route-info* orig-route-info ["/foo/" [#".*" :foo]])
82 (update-in [:path] replace-regexes-for-equality-check)))))))
83
84 (deftest route-metadata-test
85 (testing "route metadata includes ordered list of routes and lookup by handler"
86 (let [routes ["" [[["/foo/" :foo]
87 :foo-handler]
88 [["/bar/" :bar]
89 [["/baz" {:get :baz-handler}]
90 ["/bam" {:put :bam-handler}]
91 ["/bap" {:any :bap-handler}]]]
92 ["/buzz" {:post :buzz-handler}]]]
93 expected-foo-meta {:path '("" "/foo/" :foo)
94 :request-method :any}
95 expected-baz-meta {:path '("" "/bar/" :bar "/baz")
96 :request-method :get}
97 expected-bam-meta {:path '("" "/bar/" :bar "/bam")
98 :request-method :put}
99 expected-bap-meta {:path '("" "/bar/" :bar "/bap")
100 :request-method :any}
101 expected-buzz-meta {:path '("" "/buzz")
102 :request-method :post}]
103 (is (= (pl-bidi/route-metadata routes)
104 {:routes [expected-foo-meta
105 expected-baz-meta
106 expected-bam-meta
107 expected-bap-meta
108 expected-buzz-meta]
109 :handlers {:foo-handler expected-foo-meta
110 :baz-handler expected-baz-meta
111 :bam-handler expected-bam-meta
112 :bap-handler expected-bap-meta
113 :buzz-handler expected-buzz-meta}})))))
114
115 (deftest routes-test
116 (is (= ["" [["/foo" :foo-handler]
117 [["/bar/" :bar] :bar-handler]]]
118 (routes ["/foo" :foo-handler]
119 [["/bar/" :bar] :bar-handler]))))
120
121 (deftest context-test
122 (testing "simple context"
123 (is (= ["/foo" [["/bar" :bar-handler]
124 [["/baz" :baz] :baz-handler]]]
125 (context "/foo"
126 ["/bar" :bar-handler]
127 [["/baz" :baz] :baz-handler]))))
128 (testing "context with variable"
129 (is (= [["/foo" :foo] [["/bar" :bar-handler]
130 [["/baz" :baz] :baz-handler]]]
131 (context ["/foo" :foo]
132 ["/bar" :bar-handler]
133 [["/baz" :baz] :baz-handler])))))
134
135 (deftest routes->handler-test
136 (testing "routes are matched against a request properly, with route params"
137 (let [handler (routes->handler ["/foo"
138 [[""
139 [["/bar"
140 (fn [req] :bar)]
141 [["/baz/" :baz]
142 (fn [req]
143 {:endpoint :baz
144 :route-params (:route-params req)})]]]]])]
145 (is (= :bar (handler {:uri "/foo/bar"})))
146 (is (= {:endpoint :baz
147 :route-params {:baz "howdy"}}
148 (handler {:uri "/foo/baz/howdy"})))))
149 (testing "request-methods are honored"
150 (let [handler (routes->handler ["/foo" {:get (fn [req] :foo)}])]
151 (is (nil? (handler {:uri "/foo"})))
152 (is (= :foo (handler {:uri "/foo" :request-method :get})))))
153 (testing "contexts can bind route variables"
154 (let [handler (routes->handler
155 (context ["/foo/" :foo]
156 [["/bar/" :bar]
157 (fn [req] (:route-params req))]))]
158 (is (= {:foo "hi"
159 :bar "there"}
160 (handler {:uri "/foo/hi/bar/there"})))))
161 (testing "route metadata is added to fn metadata"
162 (let [foo-handler (fn [req] :foo)
163 handler (routes->handler ["/foo" {:get foo-handler}])]
164 (let [route-meta (:route-metadata (meta handler))]
165 (is (= {:routes [{:path ["/foo"]
166 :request-method :get}]
167 :handlers {foo-handler {:path ["/foo"]
168 :request-method :get}}}
169 route-meta))))))
170
171 (deftest routes->handler-middleware-test
172 (let [handler (routes->handler
173 (context ["/foo/" :foo]
174 [["/bar/" :bar]
175 (fn [req] (:route-params req))])
176 (fn [f]
177 (fn [req]
178 {:result (f req)
179 :route-info (:route-info req)})))]
180 (is (= {:result {:foo "hi"
181 :bar "there"}
182 :route-info {:path ["/foo/" :foo "/bar/" :bar]
183 :request-method :any}}
184 (handler {:uri "/foo/hi/bar/there"})))))
185
186 (deftest context-handler-test
187 (let [handler (context-handler ["/foo/" :foo]
188 [["/bar/" :bar]
189 (fn [req] (:route-params req))])]
190 (is (= {:foo "hi"
191 :bar "there"}
192 (handler {:uri "/foo/hi/bar/there"})))))
193
194
195 (deftest compojure-macros-test
196 (let [routes (context ["/foo/" :foo]
197 (ANY ["/any/" :any] [foo any]
198 (str "foo: " foo " any: " any))
199 (GET ["/get/" :get] [foo get]
200 (fn [req] {:foo foo
201 :get get}))
202 (HEAD ["/head/" :head] [foo head]
203 {:foo foo
204 :head head})
205 (PUT "/put" [foo]
206 {:status 500
207 :body foo})
208 (POST ["/post/" :post] [post]
209 post)
210 (DELETE ["/delete/" :delete] [foo delete]
211 (atom {:foo foo
212 :delete delete})))
213 handler (routes->handler routes)]
214 (is (nil? (handler {:uri "/foo/hi/get/there" :request-method :post})))
215 (is (nil? (handler {:uri "/foo/hi/head/there" :request-method :get})))
216 (is (nil? (handler {:uri "/foo/hi/put" :request-method :get})))
217 (is (nil? (handler {:uri "/foo/hi/post/there" :request-method :get})))
218 (is (nil? (handler {:uri "/foo/hi/delete/there" :request-method :get})))
219
220 (is (= "foo: hi any: there" (:body (handler {:uri "/foo/hi/any/there"}))))
221 (is (= {:foo "hi"
222 :get "there"}
223 (select-keys
224 (handler {:uri "/foo/hi/get/there" :request-method :get})
225 [:foo :get])))
226 (is (= {:foo "hi"
227 :head "there"}
228 (select-keys
229 (handler {:uri "/foo/hi/head/there" :request-method :head})
230 [:foo :head])))
231 (is (= {:status 500
232 :body "hi"}
233 (select-keys
234 (handler {:uri "/foo/hi/put" :request-method :put})
235 [:status :body])))
236 (is (= {:status 200
237 :body "there"}
238 (select-keys
239 (handler {:uri "/foo/hi/post/there" :request-method :post})
240 [:status :body])))
241 (is (= {:status 200
242 :foo "hi"
243 :delete "there"}
244 (select-keys
245 (handler {:uri "/foo/hi/delete/there" :request-method :delete})
246 [:status :foo :delete])))))
247
248 (deftest not-found-test
249 (testing "root not-found handler"
250 (let [handler (routes->handler (not-found "nobody's home, yo"))]
251 (is (= {:status 404
252 :body "nobody's home, yo"}
253 (select-keys
254 (handler {:uri "/hi/there"})
255 [:body :status])))))
256 (testing "nested not-found handler"
257 (let [handler (routes->handler
258 (routes
259 ["/bar" [["" (fn [req] :bar)]
260 (not-found "nothing else under bar!")]]
261 (not-found "nothing else under root!")))]
262 (is (= :bar (handler {:uri "/bar"})))
263 (is (= {:status 404
264 :body "nothing else under bar!"}
265 (select-keys
266 (handler {:uri "/bar/baz"})
267 [:status :body])))
268 (is (= {:status 404
269 :body "nothing else under root!"}
270 (select-keys
271 (handler {:uri "/yo/mang"})
272 [:status :body]))))))
273
274 (deftest regex-test
275 (let [handler (routes->handler
276 ["/foo" [[["/boo/" [#".*" :rest]]
277 (fn [req] (:rest (:route-params req)))]]])]
278 (is (= "hi/there"
279 (handler {:uri "/foo/boo/hi/there"})))))
280
281
282
283
0 (ns puppetlabs.comidi-test
1 (require [clojure.test :refer :all]
2 [puppetlabs.comidi :as comidi]
3 [schema.test :as schema-test]
4 [puppetlabs.comidi :refer :all]
5 [schema.core :as schema]
6 [clojure.zip :as zip]))
7
8 (use-fixtures :once schema-test/validate-schemas)
9
10 (defn replace-regexes-for-equality-check
11 [xs]
12 (loop [loc (zip/vector-zip xs)]
13 (if (zip/end? loc)
14 (zip/root loc)
15 (recur
16 (let [node (zip/node loc)]
17 (if (pattern? node)
18 (zip/edit loc #(str "REGEX: " (.pattern %)))
19 (zip/next loc)))))))
20
21 (deftest handler-schema-test
22 (testing "handler schema"
23 (is (nil? (schema/check Handler :foo)))
24 (is (nil? (schema/check Handler (fn [] :foo))))
25 (is (nil? (schema/check Handler {:get (fn [] :foo)})))
26 (is (nil? (schema/check Handler {:post :foo})))))
27
28 (deftest pattern-schema-test
29 (testing "pattern schema"
30 (is (nil? (schema/check BidiPattern "/foo")))
31 (is (nil? (schema/check BidiPattern :foo)))
32 (is (nil? (schema/check BidiPattern ["/foo/" :foo "/foo"])))
33 (is (nil? (schema/check BidiPattern ["/foo/" [#".*" :rest]])))))
34
35 (deftest destination-schema-test
36 (testing "route destination schema"
37 (is (nil? (schema/check BidiRouteDestination :foo)))
38 (is (nil? (schema/check BidiRouteDestination (fn [] nil))))
39 (is (nil? (schema/check BidiRouteDestination {:get (fn [] nil)})))
40 (is (nil? (schema/check BidiRouteDestination {:get :my-handler})))
41 (is (nil? (schema/check BidiRouteDestination [[["/foo/" :foo "/foo"] :foo]])))
42 (is (not (nil? (schema/check BidiRouteDestination [["/foo/" :foo "/foo"] :foo]))))
43 (is (nil? (schema/check BidiRouteDestination [[["/foo/" :foo]
44 :foo-handler]
45 [["/bar/" :bar]
46 {:get :bar-handler}]])))))
47
48 (deftest route-schema-test
49 (testing "route schema"
50 (is (nil? (schema/check BidiRoute [:foo :foo])))
51 (is (nil? (schema/check BidiRoute ["/foo" [[:foo :foo]]])))
52 (is (not (nil? (schema/check BidiRoute ["/foo" [:foo :foo]]))))
53 (is (nil? (schema/check BidiRoute ["" [[["/foo/" :foo]
54 :foo-handler]
55 [["/bar/" :bar]
56 {:get :bar-handler}]]])))))
57
58 (deftest update-route-info-test
59 (let [orig-route-info {:path []
60 :request-method :any}]
61 (testing "HTTP verb keyword causes request-method to be updated"
62 (doseq [verb [:get :post :put :delete :head]]
63 (is (= {:path []
64 :request-method verb}
65 (update-route-info* orig-route-info verb)))))
66 (testing "string path elements get added to the path"
67 (is (= {:path ["/foo"]
68 :request-method :any}
69 (update-route-info* orig-route-info "/foo"))))
70 (testing "keyword path elements get added to the path"
71 (is (= {:path [:foo]
72 :request-method :any}
73 (update-route-info* orig-route-info :foo))))
74 (testing "vector path elements get flattened and added to the path"
75 (is (= {:path ["/foo/" :foo]
76 :request-method :any}
77 (update-route-info* orig-route-info ["/foo/" :foo]))))
78 (testing "regex path element gets added to the path"
79 (is (= {:path ["/foo/" ["REGEX: .*" :foo]]
80 :request-method :any}
81 (-> (update-route-info* orig-route-info ["/foo/" [#".*" :foo]])
82 (update-in [:path] replace-regexes-for-equality-check)))))))
83
84 (deftest route-metadata-test
85 (testing "route metadata includes ordered list of routes and lookup by handler"
86 (let [routes ["" [[["/foo/" :foo]
87 :foo-handler]
88 [["/bar/" :bar]
89 [["/baz" {:get :baz-handler}]
90 ["/bam" {:put :bam-handler}]
91 ["/bap" {:any :bap-handler}]]]
92 ["/buzz" {:post :buzz-handler}]]]
93 expected-foo-meta {:path '("" "/foo/" :foo)
94 :request-method :any}
95 expected-baz-meta {:path '("" "/bar/" :bar "/baz")
96 :request-method :get}
97 expected-bam-meta {:path '("" "/bar/" :bar "/bam")
98 :request-method :put}
99 expected-bap-meta {:path '("" "/bar/" :bar "/bap")
100 :request-method :any}
101 expected-buzz-meta {:path '("" "/buzz")
102 :request-method :post}]
103 (is (= (comidi/route-metadata routes)
104 {:routes [expected-foo-meta
105 expected-baz-meta
106 expected-bam-meta
107 expected-bap-meta
108 expected-buzz-meta]
109 :handlers {:foo-handler expected-foo-meta
110 :baz-handler expected-baz-meta
111 :bam-handler expected-bam-meta
112 :bap-handler expected-bap-meta
113 :buzz-handler expected-buzz-meta}})))))
114
115 (deftest routes-test
116 (is (= ["" [["/foo" :foo-handler]
117 [["/bar/" :bar] :bar-handler]]]
118 (routes ["/foo" :foo-handler]
119 [["/bar/" :bar] :bar-handler]))))
120
121 (deftest context-test
122 (testing "simple context"
123 (is (= ["/foo" [["/bar" :bar-handler]
124 [["/baz" :baz] :baz-handler]]]
125 (context "/foo"
126 ["/bar" :bar-handler]
127 [["/baz" :baz] :baz-handler]))))
128 (testing "context with variable"
129 (is (= [["/foo" :foo] [["/bar" :bar-handler]
130 [["/baz" :baz] :baz-handler]]]
131 (context ["/foo" :foo]
132 ["/bar" :bar-handler]
133 [["/baz" :baz] :baz-handler])))))
134
135 (deftest routes->handler-test
136 (testing "routes are matched against a request properly, with route params"
137 (let [handler (routes->handler ["/foo"
138 [[""
139 [["/bar"
140 (fn [req] :bar)]
141 [["/baz/" :baz]
142 (fn [req]
143 {:endpoint :baz
144 :route-params (:route-params req)})]]]]])]
145 (is (= :bar (handler {:uri "/foo/bar"})))
146 (is (= {:endpoint :baz
147 :route-params {:baz "howdy"}}
148 (handler {:uri "/foo/baz/howdy"})))))
149 (testing "request-methods are honored"
150 (let [handler (routes->handler ["/foo" {:get (fn [req] :foo)}])]
151 (is (nil? (handler {:uri "/foo"})))
152 (is (= :foo (handler {:uri "/foo" :request-method :get})))))
153 (testing "contexts can bind route variables"
154 (let [handler (routes->handler
155 (context ["/foo/" :foo]
156 [["/bar/" :bar]
157 (fn [req] (:route-params req))]))]
158 (is (= {:foo "hi"
159 :bar "there"}
160 (handler {:uri "/foo/hi/bar/there"})))))
161 (testing "route metadata is added to fn metadata"
162 (let [foo-handler (fn [req] :foo)
163 handler (routes->handler ["/foo" {:get foo-handler}])]
164 (let [route-meta (:route-metadata (meta handler))]
165 (is (= {:routes [{:path ["/foo"]
166 :request-method :get}]
167 :handlers {foo-handler {:path ["/foo"]
168 :request-method :get}}}
169 route-meta))))))
170
171 (deftest routes->handler-middleware-test
172 (let [handler (routes->handler
173 (context ["/foo/" :foo]
174 [["/bar/" :bar]
175 (fn [req] (:route-params req))])
176 (fn [f]
177 (fn [req]
178 {:result (f req)
179 :route-info (:route-info req)})))]
180 (is (= {:result {:foo "hi"
181 :bar "there"}
182 :route-info {:path ["/foo/" :foo "/bar/" :bar]
183 :request-method :any}}
184 (handler {:uri "/foo/hi/bar/there"})))))
185
186 (deftest context-handler-test
187 (let [handler (context-handler ["/foo/" :foo]
188 [["/bar/" :bar]
189 (fn [req] (:route-params req))])]
190 (is (= {:foo "hi"
191 :bar "there"}
192 (handler {:uri "/foo/hi/bar/there"})))))
193
194
195 (deftest compojure-macros-test
196 (let [routes (context ["/foo/" :foo]
197 (ANY ["/any/" :any] [foo any]
198 (str "foo: " foo " any: " any))
199 (GET ["/get/" :get] [foo get]
200 (fn [req] {:foo foo
201 :get get}))
202 (HEAD ["/head/" :head] [foo head]
203 {:foo foo
204 :head head})
205 (PUT "/put" [foo]
206 {:status 500
207 :body foo})
208 (POST ["/post/" :post] [post]
209 post)
210 (DELETE ["/delete/" :delete] [foo delete]
211 (atom {:foo foo
212 :delete delete})))
213 handler (routes->handler routes)]
214 (is (nil? (handler {:uri "/foo/hi/get/there" :request-method :post})))
215 (is (nil? (handler {:uri "/foo/hi/head/there" :request-method :get})))
216 (is (nil? (handler {:uri "/foo/hi/put" :request-method :get})))
217 (is (nil? (handler {:uri "/foo/hi/post/there" :request-method :get})))
218 (is (nil? (handler {:uri "/foo/hi/delete/there" :request-method :get})))
219
220 (is (= "foo: hi any: there" (:body (handler {:uri "/foo/hi/any/there"}))))
221 (is (= {:foo "hi"
222 :get "there"}
223 (select-keys
224 (handler {:uri "/foo/hi/get/there" :request-method :get})
225 [:foo :get])))
226 (is (= {:foo "hi"
227 :head "there"}
228 (select-keys
229 (handler {:uri "/foo/hi/head/there" :request-method :head})
230 [:foo :head])))
231 (is (= {:status 500
232 :body "hi"}
233 (select-keys
234 (handler {:uri "/foo/hi/put" :request-method :put})
235 [:status :body])))
236 (is (= {:status 200
237 :body "there"}
238 (select-keys
239 (handler {:uri "/foo/hi/post/there" :request-method :post})
240 [:status :body])))
241 (is (= {:status 200
242 :foo "hi"
243 :delete "there"}
244 (select-keys
245 (handler {:uri "/foo/hi/delete/there" :request-method :delete})
246 [:status :foo :delete])))))
247
248 (deftest not-found-test
249 (testing "root not-found handler"
250 (let [handler (routes->handler (not-found "nobody's home, yo"))]
251 (is (= {:status 404
252 :body "nobody's home, yo"}
253 (select-keys
254 (handler {:uri "/hi/there"})
255 [:body :status])))))
256 (testing "nested not-found handler"
257 (let [handler (routes->handler
258 (routes
259 ["/bar" [["" (fn [req] :bar)]
260 (not-found "nothing else under bar!")]]
261 (not-found "nothing else under root!")))]
262 (is (= :bar (handler {:uri "/bar"})))
263 (is (= {:status 404
264 :body "nothing else under bar!"}
265 (select-keys
266 (handler {:uri "/bar/baz"})
267 [:status :body])))
268 (is (= {:status 404
269 :body "nothing else under root!"}
270 (select-keys
271 (handler {:uri "/yo/mang"})
272 [:status :body]))))))
273
274 (deftest regex-test
275 (let [handler (routes->handler
276 ["/foo" [[["/boo/" [#".*" :rest]]
277 (fn [req] (:rest (:route-params req)))]]])]
278 (is (= "hi/there"
279 (handler {:uri "/foo/boo/hi/there"})))))
280
281
282
283