This repository has been archived by the owner on Jan 1, 2022. It is now read-only.
forked from shopsmart/clj-foundation
/
config.clj
68 lines (52 loc) · 3.23 KB
/
config.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
(ns clj-foundation.config
"Read configuration from an EDN file. The EDN file's location is specified using an environment
variable (for specifying its location during test, staging or production deployments) as well as
a literal path or URL for specifying its location during development. If the environment variable
is present, it overrides any absolute path.
In addition, the EDN file being used for configuration may contain template variables as defined
by the [[templates]] namespace. Template variables may be resolved through the environment or through
keys and values specified in code."
(:require [clojure.string :as str]
[clojure.edn :as edn]
[clojure.pprint :refer [pprint]]
[schema.core :as s :refer [=> =>* defschema]]
[clj-foundation.templates :as template]
[clj-foundation.patterns :as p]
[clj-foundation.io :as io]
[clj-foundation.templates :as templates])
(:gen-class))
(s/defn read-config :- s/Any
"Reads config object via the path represented by keys and returns the resulting object."
[config & keys]
(try
(let [result (reduce get config keys)]
(if (nil? result)
(throw (IllegalStateException. (str "Error finding config: " keys)))
result))
(catch NullPointerException e (throw (IllegalStateException.
(str "Error finding config: " keys))))))
(defmacro read-settings-file [config-file-envar default-config-resource substitutions]
`(edn/read-string (io/read-template ~config-file-envar ~default-config-resource ~@substitutions)))
(defmacro defconfig
"Defines a configuration lookup function of type (=> s/Any [s/Keyword]) that returns configuration
values from the EDN file specified by variable or the default resource. File resolution is done
in the following precedence:
* A Java system variable with its name matching config-file-location-envar.
* An operating system environment variable with its name matching config-file-location-envar.
* A Java resource with a relative path specified by default-config-resource.
The configuration file itself should represent a single Clojure map with keywords as the keys and
any value (including another map) as the value. Thus calling a config function generated by
defconfig might look like:
(config :topic :subtopic :entry-name)
An optional third parameter can either be a map of type {s/Keyword s/Any} or a seq of keywords and
values representing default values that will be substitued into the configuration file using the same
resolution rules above. E.g.: The developer can choose to have either separate dev and prod config
files or to define variables inside the configuration file with default values for dev that will be
overridden in prod via Java system properties or environment variables."
[config-fn-name config-file-location-envar default-config-resource & default-kvs]
`(def ~config-fn-name
(fn [& keys#]
(let [read-settings# #(read-settings-file ~config-file-location-envar
~default-config-resource
~default-kvs)]
(apply read-config (read-settings#) keys#)))))