Created
April 13, 2016 09:36
-
-
Save josf/6f56bd48e07d3014dadaf8d67a8dc3ec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (defn arg-find | |
| "Helper function for defroute that extracts code from the body and | |
| organizes it as bindings, symbols and body. Function calls within | |
| the body are replaced by their corresponding gensyms. (:symbols is | |
| just a list of the gensyms in :bindings, for convenience)" | |
| [body] | |
| (reduce (fn [acc [k v]] | |
| (if (list? v) | |
| (let [sym (gensym (str (name k) "-"))] | |
| (-> acc | |
| (update-in [:bindings] #(vec (concat % [sym v]))) | |
| (update-in [:symbols] #(conj % sym)) | |
| (assoc-in [:body k] sym))) | |
| (assoc-in [:body k] v))) | |
| {:bindings [] :body {}} | |
| body)) | |
| (defn path-tokens | |
| [path] | |
| (map (fn [part] | |
| (if-let [[_ sym] (re-find #"^\$\{([^}]+)\}$" part)] | |
| (symbol sym) | |
| part)) | |
| (filter (complement empty?) (clojure.string/split path #"/")))) | |
| (defn path-parse | |
| "Returns a list of pairs, mapping symbols to their respective indexs | |
| in the path, so that /part/${arg} would produce [arg 1]." | |
| [tokens] | |
| (filter identity | |
| (map-indexed (fn [idx t] | |
| (when (symbol? t) [t idx])) | |
| tokens))) | |
| (defn node-path | |
| "Rebuild a node compatible path. This is very imperfect of course, | |
| since we're just grabbing with *, but I hope it conveys the | |
| idea..." | |
| [path-tokens] | |
| (clojure.string/join | |
| "/" | |
| (map (fn [part] (if (symbol? part) "*" part)) path-tokens))) | |
| (defn path-bindings | |
| "Produces a vector of bindings to be used in the defroute macro. | |
| Takes the output of path-parse." | |
| [parsed] | |
| (vec | |
| (mapcat (fn [[sym idx]] | |
| `(~sym (nth req-path ~idx))) parsed))) | |
| (defmacro defroute | |
| "body is a map. When values are function calls, they are evaluated | |
| and their return values, which should be maps, are checked | |
| for :server-error or :client-error keys. If either of these is | |
| found, the appropriate error is returned to the client. Otherwise, | |
| the initial map in body is reconstituted with the appropriate values | |
| and returned. " | |
| [method path body] | |
| (let [m-meth (get {'GET '.get | |
| 'POST '.post | |
| 'PUT '.put | |
| 'DELETE '.delete | |
| 'HEAD '.head} method) | |
| {bindings :bindings ret :body syms :symbols} (arg-find body) | |
| path-tks (path-tokens path) | |
| npath (node-path path-tks)] | |
| `(let [resp-fn | |
| (fn [req res] | |
| (let [req-path (.-path req)] | |
| (let ~(path-bindings (path-parse path-tks)) | |
| (let ~bindings | |
| (cond | |
| (some :server-error [~@syms]) (.send (.status res 500) "Server error") | |
| (some :client-error [~@syms]) (.send (.status res 400) "Client error") | |
| true (.send res ~ret))))))] | |
| (~m-meth ~'app ~npath resp-fn)))) | |
| ;;; this expands into this (after removing the namespace cruft): | |
| (macroexpand-1 '(defroute GET "/z1/customer/${id}" {:customer (get-customer id)})) | |
| (let [resp-fn (fn [req res] | |
| (let [req-path (.-path req)] | |
| (let [id (nth req-path 2)] | |
| (let [customer-21529 (get-customer id)] | |
| (cond | |
| (some :server-error [customer-21529]) | |
| (.send (.status res 500) "Server error") | |
| (some :client-error [customer-21529]) | |
| (.send (.status res 400) "Client error") | |
| true (.send res {:customer customer-21529}))))))] | |
| (.get app "z1/customer/*" resp-fn)) | |
| ;;; it allows for multiple internal calls too, so something like this would be possible: | |
| (defroute GET "/z1/customer/${id}/order/${orderid}" | |
| {:customer (get-customer id) | |
| :order (get-order orderid)}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment