initial commit
This commit is contained in:
commit
bd038047db
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
.DS_Store
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Gered King
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# clj-webtoolbox
|
||||||
|
|
||||||
|
Miscellaneous helper functions and macros for Ring and Compojure.
|
||||||
|
|
||||||
|
## Leiningen
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
[clj-webtoolbox "0.0.1"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2014 Gered King
|
||||||
|
|
||||||
|
Distributed under the the MIT License. See LICENSE for more details.
|
10
project.clj
Normal file
10
project.clj
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
(defproject clj-webtoolbox "0.0.1"
|
||||||
|
:description "Miscellaneous helper functions and macros for Ring and Compojure."
|
||||||
|
:url "https://github.com/gered/clj-webtoolbox"
|
||||||
|
:license {:name "MIT License"
|
||||||
|
:url "http://opensource.org/licenses/MIT"}
|
||||||
|
|
||||||
|
:dependencies [[org.clojure/clojure "1.6.0"]
|
||||||
|
[compojure "1.3.1" :scope "provided"]
|
||||||
|
[ring/ring-core "1.3.1" :scope "provided"]
|
||||||
|
[cheshire "5.3.1"]])
|
126
src/clj_webtoolbox/response.clj
Normal file
126
src/clj_webtoolbox/response.clj
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
(ns clj-webtoolbox.response
|
||||||
|
"Response helpers to compliment those provided by ring.util.response. Some
|
||||||
|
of these are mostly just copies with maybe slight tweaks here and there
|
||||||
|
just so that application code need not include both namespaces unless
|
||||||
|
absolutely needed for the not-so-commonly-used functions."
|
||||||
|
(:require
|
||||||
|
[cheshire.core :as json]))
|
||||||
|
|
||||||
|
(def ^:private base-response
|
||||||
|
{:status 200
|
||||||
|
:headers {}
|
||||||
|
:body nil})
|
||||||
|
|
||||||
|
(defn content
|
||||||
|
"Returns a Ring response where the body is set to the value given."
|
||||||
|
([body] (content base-response body))
|
||||||
|
([resp body]
|
||||||
|
(assoc resp :body body)))
|
||||||
|
|
||||||
|
(defn status
|
||||||
|
"Returns a Ring response where the HTTP status is set to the status code given."
|
||||||
|
([http-status] (status base-response http-status))
|
||||||
|
([resp http-status]
|
||||||
|
(assoc resp :status http-status)))
|
||||||
|
|
||||||
|
(defn header
|
||||||
|
"Returns a Ring response where the HTTP header/value is added to the existing
|
||||||
|
set of headers in the response. The header name given should be a string."
|
||||||
|
([name value] (header base-response name value))
|
||||||
|
([resp name value]
|
||||||
|
(update-in resp [:headers] assoc name value)))
|
||||||
|
|
||||||
|
(defn headers
|
||||||
|
"Returns a Ring response where the map of HTTP headers is added to the existing
|
||||||
|
set of headers in the response. The map should contain string keys."
|
||||||
|
([m] (headers base-response m))
|
||||||
|
([resp m]
|
||||||
|
(update-in resp [:headers] merge m)))
|
||||||
|
|
||||||
|
(defn content-type
|
||||||
|
"Returns a Ring response where the content type is set to the value given."
|
||||||
|
([type] (content-type base-response type))
|
||||||
|
([resp type]
|
||||||
|
(header resp "Content-Type" type)))
|
||||||
|
|
||||||
|
(defn plain-text
|
||||||
|
"Returns a Ring response containing plain text content."
|
||||||
|
[body]
|
||||||
|
(-> (content-type "text/plain; charset=utf-8")
|
||||||
|
(content body)))
|
||||||
|
|
||||||
|
(defn html
|
||||||
|
"Returns a Ring response containing HTML content."
|
||||||
|
[body]
|
||||||
|
(-> (content-type "text/html; charset=utf-8")
|
||||||
|
(content body)))
|
||||||
|
|
||||||
|
(defn xml
|
||||||
|
"Returns a Ring response containing XML content."
|
||||||
|
[body]
|
||||||
|
(-> (content-type "text/xml; charset=utf-8")
|
||||||
|
(content body)))
|
||||||
|
|
||||||
|
(defn json
|
||||||
|
"Returns a Ring response containing JSON content. The body passed in will
|
||||||
|
be automatically converted to a JSON format equivalent."
|
||||||
|
[body]
|
||||||
|
(-> (content-type "application/json; charset=utf-8")
|
||||||
|
(content (json/generate-string body))))
|
||||||
|
|
||||||
|
(defn edn
|
||||||
|
"Returns a Ring response containing EDN content. The body passed in should
|
||||||
|
be a Clojure data structure and will be serialized using pr-str."
|
||||||
|
[body]
|
||||||
|
(-> (content-type "application/edn; charset=utf-8")
|
||||||
|
(content (pr-str body))))
|
||||||
|
|
||||||
|
(defn cookie
|
||||||
|
"Returns a Ring response where a cookie is appended to any existing
|
||||||
|
cookies set in the existing response."
|
||||||
|
[resp name value & [opts]]
|
||||||
|
(assoc-in resp [:cookies name] (merge (:value value opts))))
|
||||||
|
|
||||||
|
(defn session
|
||||||
|
"Returns an updated Ring response with session information added. This will
|
||||||
|
overwrite existing session data."
|
||||||
|
[resp session]
|
||||||
|
(assoc resp :session session))
|
||||||
|
|
||||||
|
(defn using-session
|
||||||
|
"Returns an updated Ring response with session information added from the
|
||||||
|
Ring request. You can use this to set the initial session to be modified
|
||||||
|
by subsequent functions to modify the response's session map (so as to
|
||||||
|
modify the existing session rather then overwrite it completely)."
|
||||||
|
[resp request]
|
||||||
|
(assoc resp :session (:session request)))
|
||||||
|
|
||||||
|
(defn session-assoc
|
||||||
|
"Returns an updated Ring response with the new key/value set in the
|
||||||
|
response's session map."
|
||||||
|
[resp k v]
|
||||||
|
(assoc-in resp [:session k] v))
|
||||||
|
|
||||||
|
(defn session-dissoc
|
||||||
|
"Returns an updated Ring response with the key removed from the response's
|
||||||
|
session map."
|
||||||
|
[resp k]
|
||||||
|
(update-in resp [:session] dissoc k))
|
||||||
|
|
||||||
|
(defn session-assoc-in
|
||||||
|
"Returns an updated Ring response with the new key/value set in the
|
||||||
|
response's session map. ks should be a vector of keywords referring
|
||||||
|
to a nested value to set in the session. Any levels that do not exist
|
||||||
|
will be created."
|
||||||
|
[resp ks v]
|
||||||
|
(assoc-in resp (concat [:session] ks) v))
|
||||||
|
|
||||||
|
(defn session-update-in
|
||||||
|
"Returns an updated Ring response where a specific value within the
|
||||||
|
existing response's session map is 'updated' using function f which
|
||||||
|
takes the existing value along with any supplied args and should
|
||||||
|
return the new value. ks should be a vector of keywords referring
|
||||||
|
to a nested value to update. Any levels that do not exist will be
|
||||||
|
created."
|
||||||
|
[resp ks f & args]
|
||||||
|
(apply update-in resp (concat [:session] ks) f args))
|
60
src/clj_webtoolbox/routes/checked.clj
Normal file
60
src/clj_webtoolbox/routes/checked.clj
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
(ns clj-webtoolbox.routes.checked
|
||||||
|
(:require
|
||||||
|
[compojure.core :refer [routing]]
|
||||||
|
[ring.util.response :refer [response?]]
|
||||||
|
[clj-webtoolbox.response :as response]
|
||||||
|
[clj-webtoolbox.routes.core :refer [destructure-route-bindings]]))
|
||||||
|
|
||||||
|
(defmacro threaded-checks [request checks fail-response]
|
||||||
|
`(or (some-> ~request ~@checks)
|
||||||
|
(if (response? ~fail-response)
|
||||||
|
~fail-response
|
||||||
|
(~fail-response ~request))))
|
||||||
|
|
||||||
|
(defmacro checked-routes
|
||||||
|
[checks fail-response & routes]
|
||||||
|
`(fn [request#]
|
||||||
|
(let [result# (threaded-checks request# ~checks ~fail-response)]
|
||||||
|
(if (response? result#)
|
||||||
|
result#
|
||||||
|
(routing result# ~@routes)))))
|
||||||
|
|
||||||
|
(def default-check-error-response
|
||||||
|
(-> (response/content "Route checks did not all pass.")
|
||||||
|
(response/status 500)))
|
||||||
|
|
||||||
|
(defmacro checked-route
|
||||||
|
{:arglists '([& body]
|
||||||
|
[:on-fail fail-response & body])}
|
||||||
|
[& body]
|
||||||
|
(let [has-fail-response? (= :on-fail (first body))
|
||||||
|
fail-response (if has-fail-response? (second body) default-check-error-response)
|
||||||
|
body (if has-fail-response? (drop 2 body) body)]
|
||||||
|
`(fn [request#]
|
||||||
|
(threaded-checks request# ~body ~fail-response))))
|
||||||
|
|
||||||
|
(defmacro checked
|
||||||
|
{:arglists '([method-fn path & body]
|
||||||
|
[method-fn path :on-fail fail-response & body])}
|
||||||
|
[method-fn path & body]
|
||||||
|
`(~method-fn ~path []
|
||||||
|
(checked-route ~@body)))
|
||||||
|
|
||||||
|
(defmacro routefn [request args & body]
|
||||||
|
(if (vector? args)
|
||||||
|
`(let [~@(destructure-route-bindings args :safe-params request)] ~@body)
|
||||||
|
`(let [~args ~request] ~@body)))
|
||||||
|
|
||||||
|
(defn safe
|
||||||
|
[request & params]
|
||||||
|
(let [safe-params (select-keys (:params request) params)]
|
||||||
|
(update-in request [:safe-params] merge safe-params)))
|
||||||
|
|
||||||
|
(defn validate
|
||||||
|
[request param f & args]
|
||||||
|
(if (apply f (get-in request [:params param]) args)
|
||||||
|
(safe request param)))
|
||||||
|
|
||||||
|
(defn transform
|
||||||
|
[request param-k f & args]
|
||||||
|
(apply update-in request [:params param-k] f args))
|
41
src/clj_webtoolbox/routes/core.clj
Normal file
41
src/clj_webtoolbox/routes/core.clj
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
(ns clj-webtoolbox.routes.core
|
||||||
|
(:require
|
||||||
|
compojure.core))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------------
|
||||||
|
;; these are taken from compojure.core.
|
||||||
|
|
||||||
|
(defn- assoc-&-binding [binds req params-parent sym]
|
||||||
|
(assoc binds sym `(dissoc (~params-parent ~req)
|
||||||
|
~@(map keyword (keys binds))
|
||||||
|
~@(map str (keys binds)))))
|
||||||
|
|
||||||
|
(defn- assoc-symbol-binding [binds req params-parent sym]
|
||||||
|
(assoc binds sym `(get-in ~req [~params-parent ~(keyword sym)]
|
||||||
|
(get-in ~req [~params-parent ~(str sym)]))))
|
||||||
|
|
||||||
|
(defn destructure-route-bindings
|
||||||
|
"Given a vector of Compojure route parameter bindings, expands them to a vector
|
||||||
|
of symbols that can be used in, e.g. a macro, to provide Compojure-like
|
||||||
|
destructuring elsewhere."
|
||||||
|
([args req] (destructure-route-bindings args :params req))
|
||||||
|
([args params-parent req]
|
||||||
|
(loop [args args, binds {}]
|
||||||
|
(if-let [sym (first args)]
|
||||||
|
(cond
|
||||||
|
(= '& sym) (recur (nnext args) (assoc-&-binding binds req params-parent (second args)))
|
||||||
|
(= :as sym) (recur (nnext args) (assoc binds (second args) req))
|
||||||
|
(symbol? sym) (recur (next args) (assoc-symbol-binding binds req params-parent sym))
|
||||||
|
:else (throw (Exception. (str "Unexpected binding: " sym))))
|
||||||
|
(mapcat identity binds)))))
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(defmacro with-middleware
|
||||||
|
"Applies a sequence of middleware functions to a series of routes wrapped
|
||||||
|
by this macro. For best results, use _within_ a compojure.core/context
|
||||||
|
call. Remember that middleware will be applied in the reverse order
|
||||||
|
that it is specified in."
|
||||||
|
[middlewares & routes]
|
||||||
|
`(-> (compojure.core/routes ~@routes)
|
||||||
|
~@middlewares))
|
Reference in a new issue