(PE-9181) Rename to `comidi`
Chris Price
9 years ago
0 | # pl-bidi | |
0 | # comidi | |
1 | 1 | |
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) | |
3 | 3 | |
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" | |
3 | 3 | :dependencies [[org.clojure/clojure "1.6.0"] |
4 | 4 | [bidi "1.18.9"] |
5 | 5 | [compojure "1.3.2"] |
6 | [prismatic/schema "0.2.2"] | |
6 | [prismatic/schema "0.4.0"] | |
7 | 7 | [puppetlabs/kitchensink "1.1.0"]]) |
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 | (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 |