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.
(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)It's a tree. Remember, you can't go back.
/davidjameshumphreys/patterns-pitfalls-liberator/ [slides]