Skip to content

Instantly share code, notes, and snippets.

@emidln
Created August 10, 2018 17:07
Show Gist options
  • Save emidln/c413dfd4301786393523204a47ae0968 to your computer and use it in GitHub Desktop.
Save emidln/c413dfd4301786393523204a47ae0968 to your computer and use it in GitHub Desktop.
(ns spec.settings
"Organizes and validates your settings with clojure.spec and spec-tools."
(:require [clojure.string :as str]
[clojure.spec.alpha :as s]
[spec-tools.spec :as spec]
[spec-tools.core :as st]
[spec-tools.data-spec :as ds]))
(defmacro for-map
"Like for, but returns a map. Expects the result to be a pair or map."
{:style/indent 1
:private true}
[& body]
`(->> (for ~@body)
(into {})))
(defn env
"SampleAPI settings from the environment"
[& [filter-prefix]]
(for-map [[s v] (System/getenv)
:when (and filter-prefix (str/starts-with? s filter-prefix))]
{(-> (str/lower-case s)
(str/replace "sampleapi_" "")
(str/replace "_" "-")
keyword)
v}))
(defn props
"SampleAPI settings from properties"
[& [filter-prefix]]
(for-map [[s v] (System/getProperties)
:when (and filter-prefix (str/starts-with? s filter-prefix))]
{(-> (str/lower-case s)
(str/replace-first "sampleapi." "")
(str/replace "." "-")
keyword)
v}))
(defn defaults
"SampleAPI settings defaults"
[]
(for-map [[setting {:keys [default] :as config}] settings-config
:when (not= (config :default ::not-set) ::not-set)]
{setting default}))
(defn all-settings
"SampleAPI settings. If given a filter prefix"
[& [filter-prefix]]
(merge (env (when filter-prefix (str/upper-case filter-prefix)))
(props (when filter-prefix (str/lower-case filter-prefix)))
(defaults)))
(defn validate-settings-fn
"Validates all-settings meet the settings-spec. Exits the process with explanation on failure."
[settings-spec all-settings]
(st/decode settings-spec all-settings st/string-transformer))
(defn validate-settings!
([settings-spec]
(validate-settings! settings-spec (all-settings)))
([settings-spec all-settings]
(let [settings (validate-settings-fn settings-spec all-settings)]
(if (= ::s/invalid settings)
(do
(println "Invalid settings configuration. See spec output:")
(println
(with-out-str
(st/explain settings-spec all-settings st/string-transformer)))
(System/exit 1))
settings))))
(defn settings-spec
[spec-name settings-config]
(->> {:spec ~(for-map [[setting {:keys [spec]}] settings-config]
{setting spec})
:name ::settings}
ds/spec))
(comment
(require '[clojure.spec.alpha :as s])
(require '[spec-tools.spec :as spec])
(require '[spec.settings :as ss])
;; declare your configuration
(def settings-config
{:debug {:spec spec/boolean?
:default false}
:num-processes {:spec spec/integer?
:default 4}})
;; specify your config
(s/def ::settings (ss/settings-spec ::settings settings-config))
;; validate your settings
(ss/validate-settings! ::settings (all-settings "sampleapp"))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment