David James Humphreys (Juxter) [@davidjhumphreys]
/davidjameshumphreys/patterns-pitfalls-liberator/ [slides]
A brilliant library that follows the HTTP graph
... just find the parts that you need to hook in.
There is a lot to configure to set up a basic route.
* if you are really impatient!
The returned map is merged into the functions that occur afterwards.*
(def simple-get
(resource {:available-media-types ["text/html"]
;; do your db call in here
:exists? (fn [ctx] {:data "My data"})
;; the keys are merged in
:handle-ok (fn [{:keys [data] :as ctx}]
data)}))
* with a few exceptions and ways to prevent this.
(fn render [args])
(defn- json? [ctx]
(-> ctx
:representation
:media-type
(= "application/json")))
(def simple-get-with-type
(resource {:available-media-types ["text/html" "application/json"]
:allowed-methods [:get]
:exists? (fn exists [ctx] {:data "My data"})
:handle-ok (fn ok [{:keys [data] :as ctx}]
(if (json? ctx)
{:data data
:is-json true}
data))}))
/Prismatic/schema is a great choice
*Use some middleware to make coercion easier
Schema/coerce & Liberator
(defn make-malformed-coercer
"A wrapper for checking the malformed state using Schema coercers.
Liberator malformed expects [malformed? {:some data}] to be returned
from the function."
[coerce-fn]
(fn malformed? [ctx]
(let [result (coerce-fn ctx)]
(log/info result)
(if (error? result)
[true (merge result
(try (negotiate-media-type ctx)
(catch ProtocolException _
{:representation
{:media-type "application/json"}})))]
[false {:coerced-params result}]))))
little hack to render a result
(defn do-get
"A simple get endpoint."
[check-vals-coercer exists? render & {:as overrides}]
(merge {:available-media-types
["text/html" "application/json"]
:allowed-methods [:get]
:malformed?
(make-malformed-coercer check-vals-coercer)
:exists? exists?
:handle-ok render}
overrides))
(resource
(do-get
(fn [ctx] (-> ctx
:request
:params
(coercer/coerce {:expected schema/Str} {mapping})))
(fn exists? [{:keys [request]}]
{:data (get-data (:database request))})
render-fn))
Using a similar pattern
(defn do-post
"A simple post endpoint."
[check-vals-coercer exists? post! post-redirect? & {:as overrides}]
(merge post
{:malformed? (make-malformed-coercer check-vals-coercer)
:exists? exists?
:post! post!
:post-redirect? post-redirect?}
overrides))
Add all of your context-sensitive references into request
Use middleware to do it.
No *database*
globals please
(defn the-application
"All of the webapp routes and middleware. By defining in this way we
can pass in various settings (i.e. to add a database create a middleware
to wrap the request)"
[component-settings]
(-> #'server-routes
(wrap-trace :ui true) ;; <- liberator trace
(wrap-renderer (-> component-settings :render :global-vars))
(wrap-database (-> component-settings :database))
wrap-keyword-params ;; <- param helpers for schema validation
wrap-params
wrap-json-params
(wrap-bidi-handlers build-routes handlers)))
Maybe use components
service-available?
happens first, what about long requests?
:as-response (fn [this ctx] build-ring-response)
/davidjameshumphreys/patterns-pitfalls-liberator/ [slides]