├── .gitignore ├── LICENSE ├── README.md ├── project.clj ├── src ├── data_readers.clj └── immuconf │ └── config.clj └── test ├── fixtures ├── test-a.edn ├── test-a2.edn ├── test-b.edn ├── test-d.edn └── test-o.edn └── immuconf └── config_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # immuconf 2 | 3 | A small library for explicitly managing configuration files in a 4 | Clojure program. 5 | 6 | ## Installation 7 | 8 | [![Clojars Project](http://clojars.org/levand/immuconf/latest-version.svg)](http://clojars.org/levand/immuconf) 9 | 10 | ## Rationale 11 | 12 | Every application needs configuration files. There are many different 13 | approaches. The purpose of this library is twofold: the first is to 14 | codify a set of decisions that have served me well on several 15 | projects, and the second to provide a canonical implementation of the 16 | pattern so it doesn't need to be continually re-implemented. 17 | 18 | **Characteristics:** 19 | 20 | - Explicit. Configurations are intended to be passed through the code 21 | explicitly as function arguments, not globally or ambiently. Note 22 | that I can't *stop* you from binding the config to a global or 23 | thread-local var but from personal experience its a bad idea. 24 | 25 | - Lightweight. As non-intrusive as possible. Configs are normal 26 | Clojure maps. 27 | 28 | - Multiplicity. Configuration data can be specified at multiple 29 | levels, embracing multi-target, multi-environment, multi-user 30 | applications. 31 | 32 | - Safe. Error messages are clear and informative. There are no bare 33 | null pointer exceptions on missing configuration keys. The 34 | application will warn on possibly unintended overrides, or when a 35 | configuration is missing keys that it requires. 36 | 37 | ## Concept 38 | 39 | There are different classes of configuration data. Some examples 40 | include (but are not limited to): 41 | 42 | - Basic configuration that is used everywhere. Usually it is checked 43 | into source control and edited like code. 44 | - Private configuration that contains sensitive data which cannot be 45 | added to source control. 46 | - Configuration that applies only to specific environments. 47 | - Configuration that applies only to specific users in a development 48 | setting. 49 | 50 | Immuconf is built around the concept of using a seperate configuration 51 | file for each of these concerns, which are ultimately merged together 52 | into a single configuration map. Configuration files are loaded in a 53 | constant order. "Upstream" config files can define overridable values 54 | which *must* be overridden in a downstream file, or the configuration 55 | is invalid. If two config files specify different values, the value in 56 | the "downstream" file wins. 57 | 58 | As an example, one might specify this sequence of config files: 59 | 60 | ["resources/config.edn" "resources/staging.edn" "~/.private/config.edn" "luke-dev.edn"] 61 | 62 | - `resources/config.edn` is checked into git. It defines certain 63 | config items that always apply. It also defines _overridable 64 | values_. For example, it could specify that there must be a database 65 | connection defined in subsequent config files. 66 | - `resources/staging.edn` is also checked into git. Presumably there 67 | are also `resources/prod.edn` and `resources/dev.edn` which could be 68 | used instead. This file might specify a database connection, and all 69 | the non-sensitive parameters for the database connection: the URI, 70 | etc. It would also specify that certain sensitive DB parameters 71 | (e.g, the password) must be present, but would not actually provide them. 72 | - `~/.private/config.edn` would contain the actual sensitive 73 | information, such as database passwords, and would not be checked 74 | into git. 75 | - `luke-dev.edn` could contain developer overrides, for any values, 76 | specifically for an individual developer's environments. 77 | 78 | If a key is unexpectedly overridden, it logs a warning. This helps 79 | prevent accidental overrides, but allows developers to deliberately 80 | override certain values. 81 | 82 | ### Config File Syntax 83 | 84 | Config files are valid EDN files. 85 | 86 | The basic syntax is standard nested maps. Nested collections other than 87 | maps are not supported - vectors, sets and list may be terminal 88 | "leaves" but they will not be merged when loading multiple config 89 | files. 90 | 91 | Overrides are implemented via the `immuconf/override` reader literal, 92 | and may occur in value position: 93 | 94 | ```clojure 95 | {:database {:uri "jdbc:postgresql://db-server.com:1234/my-database" 96 | :user "system" 97 | :password #immuconf/override "Specify the database password here. 98 | Ask Jim if you don't have it."}} 99 | ``` 100 | 101 | Values may also specify a *default*, using the `immuconf/default` 102 | reader literal. Default values may be overridden without any warning 103 | message, but will simply resolve to the given value without a warning 104 | if they are *not* overidden. 105 | 106 | ```clojure 107 | {:environment {:cache-size #immuconf/default 32}} 108 | ``` 109 | 110 | ### API 111 | 112 | #### Load 113 | 114 | The primary api is the `immuconf.config/load` function. It takes any 115 | number of arguments. Each argument must be resolvable to a config 116 | file, and can be of any type that is a valid argument for 117 | `clojure.java.io/reader`. `load` returns a standard Clojure map, but 118 | will throw an error if the config file is invalid (that is, if it 119 | contains any values that were specified to have been overridden, but 120 | were not). 121 | 122 | ```clojure 123 | (def my-cfg (immuconf.config/load "resources/config.edn" "resources/dev.edn" 124 | "~/.private/config.edn" "luke-dev.edn")) 125 | ``` 126 | 127 | If `load` is passed *no* arguments, it will first attempt to read an 128 | `.immuconf.edn` file (relative to the project path), which should be 129 | an EDN file containing a sequence of config file paths. 130 | 131 | If there is no `.immuconf.edn` file, it will fall back to reading the 132 | value of the `IMMUCONF_CFG` system environment variable, which should 133 | be a colon-delimited list of config file paths. 134 | 135 | If there is neither an `.immuconf.edn` file nor an `IMMUCONF_CFG` 136 | environment variable, the system will throw an error. 137 | 138 | #### Get 139 | 140 | The only other public function in the API is 141 | `immuconf.config/get`. Its first argument is a config map, any 142 | additional arguments are keys (as would be passed to 143 | `clojure.core/get-in`). 144 | 145 | ```clojure 146 | (immuconf.config/get my-cfg :database :uri) 147 | ``` 148 | 149 | The only differences between `immuconf.config/get` and 150 | `clojure.core/get` are that the Immuconf version takes varargs instead 151 | of a sequence of keys and that, if the key is missing, it will 152 | throw an info-bearing exception explaining what key was expected but 153 | not found instead of just returning `nil` (which virtually ensures a 154 | `NullPointerExcption` somewhere downstream). 155 | 156 | ## Logging 157 | 158 | Logging of warnings and errors is an important part of Immuconf. 159 | 160 | Immuconf logs using `clojure.tools.logging`, which in turn will detect 161 | and use most JVM logging systems. If this doesn't meet your needs, 162 | please file a Github issue and I will see how hard it is to add 163 | support for your logging system. 164 | 165 | ## License 166 | 167 | Copyright © Luke VanderHart 2015 168 | 169 | Distributed under the Eclipse Public License either version 1.0 or (at 170 | your option) any later version. 171 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject levand/immuconf "0.1.0" 2 | :description "A small library for loding configuration files in a 3 | Clojure application." 4 | :url "http://github.com/levand/immuconf" 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | :dependencies [[org.clojure/clojure "1.6.0"] 8 | [org.clojure/tools.logging "0.3.1"]]) 9 | -------------------------------------------------------------------------------- /src/data_readers.clj: -------------------------------------------------------------------------------- 1 | {immuconf/override immuconf.config/->Override 2 | immuconf/default immuconf.config/->Default} 3 | -------------------------------------------------------------------------------- /src/immuconf/config.clj: -------------------------------------------------------------------------------- 1 | (ns immuconf.config 2 | (:refer-clojure :exclude [load get]) 3 | (:require [clojure.walk :as w] 4 | [clojure.edn :as edn] 5 | [clojure.string :as s] 6 | [clojure.tools.logging :as log])) 7 | 8 | (ns-unmap *ns* 'Override) 9 | 10 | (defrecord Override [doc]) 11 | (defrecord Default [value]) 12 | 13 | (defn- override-warning 14 | "log an override warning, given two config values (as [file value] 15 | tuples).. Do not log if the value is supposed to be overridden." 16 | [[base-file base-value] [override-file override-value] path] 17 | (when-not (or (instance? Override base-value) 18 | (instance? Default base-value)) 19 | (log/warn "Value at" path 20 | "in" base-file 21 | "overridden by value in" override-file))) 22 | 23 | (declare merge-cfgs) 24 | (defn- merge-vals 25 | "Merge two values. Each value is represented as a tuple of [file 26 | value]." 27 | [values path] 28 | (cond 29 | ;; When equal, use any of the values with no warning 30 | (apply = (map second values)) (second (last values)) 31 | ;; when they are all maps, merge! 32 | (every? (comp map? second) values) (merge-cfgs values path) 33 | ;; Otherwise, warn on override and return the last one 34 | :else (do (override-warning (first values) (last values) path) 35 | (second (last values))))) 36 | 37 | (defn- merge-cfgs 38 | "Merge a sequence of configuration maps. Each config map is 39 | represented as a tuple of [file cfg]" 40 | ([cfgs] (merge-cfgs cfgs [])) 41 | ([cfgs path] 42 | (let [files (map first cfgs) 43 | maps (map second cfgs) 44 | keys (into #{} (mapcat keys maps))] 45 | (into {} 46 | (for [k keys] 47 | (let [vals (map #(clojure.core/get % k ::none) maps) 48 | val-tuples (map vector files vals) 49 | actual-vals (filter #(not= ::none (second %)) val-tuples)] 50 | [k (merge-vals actual-vals (conj path k))])))))) 51 | 52 | 53 | 54 | (defn- validate-cfg 55 | "Validate that a configuration map has no overrides that were not 56 | overridden. Also replaces any default value placeholders with the 57 | actual default value they specify. 58 | 59 | If successful, returns the configuration map. If not, throws an 60 | error." 61 | ([cfg-map] (validate-cfg cfg-map [])) 62 | ([value path] 63 | (cond 64 | (instance? Override value) (throw (ex-info (format "Config specified that value at %s should have been overridden, but it was not" path) 65 | {:override value 66 | :path path})) 67 | (instance? Default value) (:value value) 68 | (map? value) (into {} (map (fn [[k v]] 69 | [k (validate-cfg v (conj path k))]) 70 | value)) 71 | :else value))) 72 | 73 | (defn- load-cfg-file 74 | "Load a single config file into a config map. Returns a tuple of the 75 | given filename and the resulting config " 76 | [file] 77 | (try 78 | [file (edn/read-string {:readers *data-readers*} (slurp file))] 79 | (catch Throwable t 80 | (throw (ex-info (format "Error loading config file: %s" file) 81 | {:file file} t))))) 82 | 83 | (declare load) 84 | (defn- load-from-file 85 | "Attempt to load config files defined in an .immuconf.edn 86 | file. Return nil if no such file exists." 87 | [] 88 | (try 89 | (let [s (slurp ".immuconf.edn") 90 | cfgs (edn/read-string s)] 91 | (when-not (empty? cfgs) 92 | (log/info "Using configuration files specified in .immuconf.edn") 93 | (apply load cfgs))) 94 | (catch Exception e 95 | nil))) 96 | 97 | (defn- load-from-env 98 | "Attempt to load config files from an IMMUCONF_CFG environment 99 | variable. Return nil if no such variable is defined." 100 | [] 101 | (let [val (System/getenv "IMMUCONF_CFG")] 102 | (when-not (empty? val) 103 | (let [cfgs (s/split val #":")] 104 | (when-not (empty? cfgs) 105 | (log/info "Using configuration files specified in $IMMUCONF_CFG") 106 | (apply load cfgs)))))) 107 | 108 | (defn- expand-home [filename] 109 | "Useful on *nix systems to that ~ (home) dirs work" 110 | (if (.startsWith filename "~") 111 | (clojure.string/replace-first filename "~" (System/getProperty "user.home")) 112 | filename)) 113 | 114 | (defn load 115 | "If no arguments are specified, attempt to load config files from a 116 | `.immuconf.edn` file or an IMMUCONF_CFG environment variable, in that 117 | order. 118 | 119 | If arguments are specified, attempts to load each argument as a 120 | config file using `clojure.java.io/reader`, and merge them together. 121 | 122 | Returns a valid configuration map. 123 | 124 | If the configuration map specified by the inputs is not valid, 125 | throws an exception." 126 | ([] (or 127 | (load-from-file) 128 | (load-from-env) 129 | (throw (ex-info "No configuration files were specified, and neither an .immuconf.edn file nor an IMMUCONF_CFG environment variable was found" {})))) 130 | ([& cfg-files] 131 | (validate-cfg 132 | (merge-cfgs 133 | (map load-cfg-file 134 | (map expand-home cfg-files)))))) 135 | 136 | (defn get 137 | "Look up the given keys in the specified config. If the key is not 138 | present, throw an error explaining which key was missing." 139 | [cfg & keys] 140 | (let [v (get-in cfg keys ::none)] 141 | (if (= v ::none) 142 | (throw (ex-info (format "Attempted to read undefined configuration value %s" keys) {:keys keys})) 143 | v))) 144 | -------------------------------------------------------------------------------- /test/fixtures/test-a.edn: -------------------------------------------------------------------------------- 1 | {:a {:b {:c 1}}} 2 | -------------------------------------------------------------------------------- /test/fixtures/test-a2.edn: -------------------------------------------------------------------------------- 1 | {:a {:b {:c 2}}} 2 | -------------------------------------------------------------------------------- /test/fixtures/test-b.edn: -------------------------------------------------------------------------------- 1 | {:a {:b {:d 2}}} 2 | -------------------------------------------------------------------------------- /test/fixtures/test-d.edn: -------------------------------------------------------------------------------- 1 | {:a {:b {:c #immuconf/default 42}}} 2 | -------------------------------------------------------------------------------- /test/fixtures/test-o.edn: -------------------------------------------------------------------------------- 1 | {:a {:b {:c #immuconf/override "Override me!"}}} 2 | -------------------------------------------------------------------------------- /test/immuconf/config_test.clj: -------------------------------------------------------------------------------- 1 | (ns immuconf.config-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.tools.logging :as log] 4 | [immuconf.config :as cfg])) 5 | 6 | (deftest basic-merging 7 | (is (= (cfg/load "test/fixtures/test-a.edn" "test/fixtures/test-b.edn") 8 | {:a {:b {:c 1 :d 2}}}))) 9 | 10 | (deftest basic-override 11 | (is (= (cfg/load "test/fixtures/test-o.edn" "test/fixtures/test-a.edn") 12 | {:a {:b {:c 1}}}))) 13 | 14 | (deftest warn-on-unexpected-override 15 | (let [warning (atom "")] 16 | (with-redefs [log/log* 17 | (fn [_ _ _ msg] 18 | (reset! warning msg))] 19 | (is (= (cfg/load "test/fixtures/test-a.edn" "test/fixtures/test-a2.edn") 20 | {:a {:b {:c 2}}})) 21 | (is (re-find #"\[:a :b :c\]" @warning))))) 22 | 23 | (deftest unsatisfied-override 24 | (is (thrown-with-msg? clojure.lang.ExceptionInfo #"should have been overridden" 25 | (cfg/load "test/fixtures/test-o.edn")))) 26 | 27 | (deftest default-values 28 | (is (= (cfg/load "test/fixtures/test-d.edn") 29 | {:a {:b {:c 42}}}))) 30 | 31 | (deftest overriding-defaults-does-not-warn 32 | (let [did-warn (atom false)] 33 | (with-redefs [log/log* 34 | (fn [& args] 35 | (reset! did-warn true))] 36 | (is (= (cfg/load "test/fixtures/test-d.edn" "test/fixtures/test-a.edn") 37 | {:a {:b {:c 1}}})) 38 | (is (false? @did-warn))))) 39 | 40 | (deftest get-basic-value 41 | (let [cfg (cfg/load "test/fixtures/test-a.edn" "test/fixtures/test-b.edn")] 42 | (is (= 1 (cfg/get cfg :a :b :c))))) 43 | 44 | (deftest friendly-error-on-missing-key 45 | (let [cfg (cfg/load "test/fixtures/test-a.edn" "test/fixtures/test-b.edn")] 46 | (is (thrown-with-msg? clojure.lang.ExceptionInfo 47 | #"\(:a :x :c\)" 48 | (cfg/get cfg :a :x :c))))) 49 | 50 | (deftest path-with-tilde 51 | (System/setProperty "user.home" (str (System/getProperty "user.dir") "/test")) 52 | (is (cfg/load "~/fixtures/test-a.edn"))) 53 | 54 | 55 | 56 | 57 | --------------------------------------------------------------------------------