Skip to content

Instantly share code, notes, and snippets.

@mbezjak
Created December 2, 2022 15:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbezjak/c1baeece563b8ed734692938e6d1a36f to your computer and use it in GitHub Desktop.
Save mbezjak/c1baeece563b8ed734692938e6d1a36f to your computer and use it in GitHub Desktop.
Error model
(ns my.company.error
"Error carries error information as a hash-map.
A hash-map can carry any information. Some information is system wide (thus
used by the system) and will be promoted as qualified keywords not to conflict
with user defined ones. Those are:
- `:code` [qualified keyword] - error code
- `:args` [collection] - arguments for `format` to build the error message
- `:message` [string] - a human error message
- `:breadcrumbs` [collection] - a path to the source that caused the error
- `:suggested-http-code` [int] - HTTP status code of the response
To construct an error, use one of constructor functions:
- `make`
- `from-message`
Only access system wide information using the accessor functions below and
never as a fully qualified keyword because that is an implementation detail
and might be subject to change."
(:require
[clojure.set :as set]
[clojure.string :as string]))
(def ^:private promoted-attributes
{:code ::code
:args ::args
:message ::message
:breadcrumbs ::breadcrumbs
:suggested-http-code ::suggested-http-code})
(defn code [e]
(::code e))
(defn args [e]
(::args e))
(defn message [e]
(::message e))
(defn breadcrumbs [e]
(::breadcrumbs e))
(defn suggested-http-code [e]
(::suggested-http-code e))
(defn- ensure-code-is-qualified-keyword! [e]
(let [c (code e)]
(when (and c (not (qualified-keyword? c)))
(throw (IllegalArgumentException. (format "Code must be qualified keyword, but was: %s with type %s"
c (type c)))))))
(defn make [e]
(let [promoted (merge {}
(set/rename-keys e promoted-attributes)
(apply dissoc e (keys promoted-attributes)))]
(ensure-code-is-qualified-keyword! promoted)
promoted))
(defn from-message [message]
(make {:message message}))
(defn set-suggested-http-code [e http-code]
(make (assoc e :suggested-http-code http-code)))
(defn format-args [e]
(map (fn [a]
(cond
;; Want extensibility? Use defprotocol
(nil? a) "null"
(string? a) a
(coll? a) (string/join ", " (sort a))
:else a))
(args e)))
(defn- ensure-message-template-exists! [tpl e]
(when-not tpl
(throw (ex-info (format "Message template doesn't exist for code: %s" (code e))
{:error e}))))
(defn with-message [e template-map]
(if (message e)
e
(let [tpl (get template-map (code e))
_ (ensure-message-template-exists! tpl e)
as (seq (format-args e))
msg (if as (apply format tpl as) tpl)]
(make (assoc e :message msg)))))
(ns my.company.errors
"Errors is a collection of `error`."
(:require
[my.company.error :as error]))
(defn make [es]
(mapv error/make es))
(defn make-1 [e]
(make [e]))
(defn codes [es]
(mapv error/code es))
(defn messages [es]
(mapv error/message es))
(defn set-suggested-http-code [http-code es]
(mapv #(error/set-suggested-http-code % http-code) es))
(defn suggested-http-code [es]
(->> es
(map error/suggested-http-code)
(remove nil?)
(first)))
(defn with-message [template-map es]
(mapv #(error/with-message % template-map) es))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment