We often need to deal with deeply nested conditions, complex error-handling and potentially missing data which induces software entropy. This talk covers a non-traditional approach to deal with errors where functional purity is important.
[email-id] :as new-story}] (if-not (valid-email-id? email-id) {:tag :bad-input :message "Invalid email-id"} (try (let [story-id (util/clean-uuid)] (do (db/save-story owner-id story-id new-story) {:story-id story-id})) (catch SQLException e (throw (ex-info "Unable to save new story" {:owner-id owner-id})))))) Service errors & DB call Valid? Input data Insert data in DB
be expressed as (prom/fail failure), for example: (ns demo-blog.web (:require [promenade.core :as prom])) (prom/fail {:error "Story not found" :type :not-found}) Any regular value that is not a Failure is considered Success. REPL Output #promenade.internal.Failure {:failure {:error "Story not found", :type :not-found}}
macro acting on the result of the previous step A non-vector expression (list-stories) is treated as a success-handler, which is invoked if the previous step was a success A failure-handler is specified in a vector form: [failure-handler success- handler] (failure->resp), which is invoked if list-stories was a failure (prom/either->> owner-id list-stories [failure->resp respond-200])
owner-id validate-input list-stories kebab->camel [failure->resp respond-200]) Valid? Input JSON Case conversion Similarly we can chain together operations using macros: either-> & either-as->