const_conf 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +5 -0
- data/LICENSE +19 -0
- data/README.md +789 -0
- data/Rakefile +35 -0
- data/const_conf.gemspec +35 -0
- data/lib/const_conf/dir_plugin.rb +175 -0
- data/lib/const_conf/env_dir_extension.rb +83 -0
- data/lib/const_conf/errors.rb +93 -0
- data/lib/const_conf/file_plugin.rb +33 -0
- data/lib/const_conf/json_plugin.rb +12 -0
- data/lib/const_conf/railtie.rb +13 -0
- data/lib/const_conf/setting.rb +382 -0
- data/lib/const_conf/setting_accessor.rb +103 -0
- data/lib/const_conf/tree.rb +265 -0
- data/lib/const_conf/version.rb +8 -0
- data/lib/const_conf/yaml_plugin.rb +30 -0
- data/lib/const_conf.rb +401 -0
- data/spec/assets/.env/API_KEY +1 -0
- data/spec/assets/config.json +3 -0
- data/spec/assets/config.yml +1 -0
- data/spec/assets/config_env.yml +7 -0
- data/spec/const_conf/dir_plugin_spec.rb +249 -0
- data/spec/const_conf/env_dir_extension_spec.rb +59 -0
- data/spec/const_conf/file_plugin_spec.rb +173 -0
- data/spec/const_conf/json_plugin_spec.rb +64 -0
- data/spec/const_conf/setting_accessor_spec.rb +114 -0
- data/spec/const_conf/setting_spec.rb +628 -0
- data/spec/const_conf/tree_spec.rb +185 -0
- data/spec/const_conf/yaml_plugin_spec.rb +84 -0
- data/spec/const_conf_spec.rb +216 -0
- data/spec/spec_helper.rb +140 -0
- metadata +240 -0
data/README.md
ADDED
@@ -0,0 +1,789 @@
|
|
1
|
+
# ConstConf
|
2
|
+
|
3
|
+
**ConstConf**: A robust, thread-safe configuration management library for Ruby
|
4
|
+
applications.
|
5
|
+
|
6
|
+
## Description
|
7
|
+
|
8
|
+
ConstConf provides a clean DSL for defining configuration settings with
|
9
|
+
environment variable support, default values, required validation, decoding
|
10
|
+
logic, and descriptive metadata. It offers file-based configuration plugins,
|
11
|
+
XDG Base Directory Specification compliance, and seamless Rails integration.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
To install ConstConf, you can use the following methods:
|
16
|
+
|
17
|
+
1. Type
|
18
|
+
|
19
|
+
```shell
|
20
|
+
gem install const_conf
|
21
|
+
```
|
22
|
+
|
23
|
+
in your terminal.
|
24
|
+
|
25
|
+
1. Or add the line
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'const_conf'
|
29
|
+
```
|
30
|
+
|
31
|
+
to your Gemfile and run `bundle install` in your terminal.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Define configuration in a module:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'const_conf'
|
39
|
+
|
40
|
+
module AppConfig
|
41
|
+
include ConstConf
|
42
|
+
|
43
|
+
plugin ConstConf::FilePlugin # Use FilePlugin
|
44
|
+
plugin ConstConf::DirPlugin # Use DirPlugin
|
45
|
+
|
46
|
+
description 'Application Configuration'
|
47
|
+
prefix '' # Use empty prefix for flat environment variable names
|
48
|
+
|
49
|
+
# Simple environment variable setting
|
50
|
+
DATABASE_URL = set do
|
51
|
+
description 'Database connection string'
|
52
|
+
required true
|
53
|
+
sensitive true
|
54
|
+
end
|
55
|
+
|
56
|
+
# File-based configuration
|
57
|
+
API_KEY = set do
|
58
|
+
prefix 'CONFIG'
|
59
|
+
description 'API key from file'
|
60
|
+
default file('config/api.key', required: true)
|
61
|
+
sensitive true
|
62
|
+
decode(&:chomp)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Directory-based configuration with XDG support
|
66
|
+
CONFIG_FROM_DIR = set do
|
67
|
+
description 'Configuration directory path'
|
68
|
+
default dir('myapp', 'config.yaml', env_var_name: 'XDG_CONFIG_HOME')
|
69
|
+
decode { require 'yaml'; YAML.load(it) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Access settings
|
74
|
+
puts AppConfig::DATABASE_URL # From ENV['DATABASE_URL']
|
75
|
+
puts AppConfig::API_KEY # Default file config/api.key, or ENV['CONFIG_API_KEY']
|
76
|
+
puts AppConfig::CONFIG_FROM_DIR # From directory structure at ENV['XDG_CONFIG_HOME'] + "/myapp"
|
77
|
+
# =>
|
78
|
+
# postgresql://user:pass@localhost/myapp
|
79
|
+
# sk-1234567890abcdef1234567890abcdef
|
80
|
+
# {"host" => "localhost", "port" => 3000}
|
81
|
+
```
|
82
|
+
|
83
|
+
In Rails it is best to require your configuration in `config/app_config.rb` as soon
|
84
|
+
as the bundled gems were loaded, so subsequent initiallizers can access the
|
85
|
+
created setting constants:
|
86
|
+
init
|
87
|
+
```ruby
|
88
|
+
Bundler.require(*Rails.groups)
|
89
|
+
require_relative 'app_config.rb'
|
90
|
+
|
91
|
+
module MyApp
|
92
|
+
…
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
### Note that **Predicate Methods (`?`)** are defined
|
97
|
+
|
98
|
+
If a setting is `active?`, the predicate method returns a truthy value, which
|
99
|
+
then can be used like this:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# Check if active?
|
103
|
+
if database_url = AppConfig::DATABASE_URL?
|
104
|
+
connect_to_database with: database_url
|
105
|
+
else
|
106
|
+
STDERR.puts "No DATABASE_URL configured for app!"
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
Or `nil` is returned, which can then be handled accordingly.
|
111
|
+
|
112
|
+
### Configuration View Explanation
|
113
|
+
|
114
|
+
The `AppConfig.view` output shows the complete configuration hierarchy with
|
115
|
+
detailed metadata for each setting. Sensitive values are displayed as 🤫 to
|
116
|
+
protect confidential information like passwords and API keys.
|
117
|
+
|
118
|
+
Key indicators in the view:
|
119
|
+
|
120
|
+
|
121
|
+
| Yes | No | Purpose |
|
122
|
+
|:---:|:---:|----------------------|
|
123
|
+
| 🔒 | ⚪ | Sensitive data (e.g., passwords, tokens)|
|
124
|
+
| 🤫 | … | Suppressed output of value if sensitive|
|
125
|
+
| 🔴 | ⚪ | Required settings that must be configured|
|
126
|
+
| 🔧 | ⚪ | Configured values from ENV |
|
127
|
+
| 🙈 | ⚪ | ENV var was ignored |
|
128
|
+
| 🟢 | ⚪ | Setting is active (? method returns truty value) |
|
129
|
+
| ⚙️ | ⚪ | Decoding logic applied to transform raw input |
|
130
|
+
| ✅ ┊ ☑️ | ❌ | Validation checks have passed or failed |
|
131
|
+
|
132
|
+
The view helps developers understand how configuration is resolved from
|
133
|
+
multiple sources and validate that settings are properly configured while
|
134
|
+
protecting sensitive data.
|
135
|
+
|
136
|
+
```
|
137
|
+
AppConfig # Application Configuration
|
138
|
+
├─ prefix ""
|
139
|
+
├─ 3 settings
|
140
|
+
├─ AppConfig::DATABASE_URL # Database connection string
|
141
|
+
│ ├─ prefix ""
|
142
|
+
│ ├─ env var name DATABASE_URL
|
143
|
+
│ ├─ env var (orig.) 🤫
|
144
|
+
│ ├─ default 🤫
|
145
|
+
│ ├─ value 🤫
|
146
|
+
│ ├─ sensitive 🔒
|
147
|
+
│ ├─ required 🔴
|
148
|
+
│ ├─ configured 🔧
|
149
|
+
│ ├─ ignored ⚪
|
150
|
+
│ ├─ active 🟢
|
151
|
+
│ ├─ decoding ⚪
|
152
|
+
│ └─ checked ☑️
|
153
|
+
├─ AppConfig::API_KEY # API key from file
|
154
|
+
│ ├─ prefix "CONFIG"
|
155
|
+
│ ├─ env var name CONFIG_API_KEY
|
156
|
+
│ ├─ env var (orig.) 🤫
|
157
|
+
│ ├─ default 🤫
|
158
|
+
│ ├─ value 🤫
|
159
|
+
│ ├─ sensitive 🔒
|
160
|
+
│ ├─ required ⚪
|
161
|
+
│ ├─ configured ⚪
|
162
|
+
│ ├─ ignored ⚪
|
163
|
+
│ ├─ active 🟢
|
164
|
+
│ ├─ decoding ⚙️
|
165
|
+
│ └─ checked ☑️
|
166
|
+
└─ AppConfig::CONFIG_FROM_DIR # Configuration directory path
|
167
|
+
├─ prefix ""
|
168
|
+
├─ env var name CONFIG_FROM_DIR
|
169
|
+
├─ env var (orig.) nil
|
170
|
+
├─ default "host: 'localhost'\nport: 3000\n"
|
171
|
+
├─ value {"host" => "localhost", "port" => 3000}
|
172
|
+
├─ sensitive ⚪
|
173
|
+
├─ required ⚪
|
174
|
+
├─ configured ⚪
|
175
|
+
├─ ignored ⚪
|
176
|
+
├─ active 🟢
|
177
|
+
├─ decoding ⚙️
|
178
|
+
└─ checked ☑️
|
179
|
+
|
180
|
+
```
|
181
|
+
|
182
|
+
### Use of Plugins
|
183
|
+
|
184
|
+
ConstConf provides extensible plugin architecture for adding configuration
|
185
|
+
sources and behaviors, as can be seen in the example above. The library
|
186
|
+
includes two main built-in plugins:
|
187
|
+
|
188
|
+
```mermaid
|
189
|
+
graph TD
|
190
|
+
A[ConstConf Module] --> B[set do Block]
|
191
|
+
A --> C[Plugin System]
|
192
|
+
|
193
|
+
C --> D[FilePlugin]
|
194
|
+
C --> E[DirPlugin]
|
195
|
+
C --> F[JSONPlugin]
|
196
|
+
C --> G[YAMLPlugin]
|
197
|
+
C --> H[Custom Plugins]
|
198
|
+
|
199
|
+
B --> I[DSL Methods]
|
200
|
+
I --> J[file method]
|
201
|
+
I --> K[dir method]
|
202
|
+
I --> L[json method]
|
203
|
+
I --> M[yaml method]
|
204
|
+
I --> N[Core DSL methods]
|
205
|
+
|
206
|
+
D --> O[File-based Configuration]
|
207
|
+
E --> P[Directory-based Configuration]
|
208
|
+
F --> Q[JSON Configuration]
|
209
|
+
G --> R[YAML Configuration]
|
210
|
+
H --> S[Custom Configuration Sources]
|
211
|
+
|
212
|
+
O --> T[File Reading Logic]
|
213
|
+
P --> U[Directory Traversal Logic]
|
214
|
+
Q --> V[JSON Parsing Logic]
|
215
|
+
R --> W[YAML Parsing Logic]
|
216
|
+
S --> X[Custom Logic]
|
217
|
+
|
218
|
+
I --> Y[Configuration Resolution Process]
|
219
|
+
Y --> Z[ENV Variable Lookup]
|
220
|
+
Y --> AA[Default Value Handling]
|
221
|
+
Y --> AB[Validation & Processing]
|
222
|
+
|
223
|
+
style A fill:#e1f5fe
|
224
|
+
style B fill:#f3e5f5
|
225
|
+
style C fill:#e8f5e9
|
226
|
+
style D fill:#fff3e0
|
227
|
+
style E fill:#fce4ec
|
228
|
+
style F fill:#f1f8e9
|
229
|
+
style G fill:#e0f2f1
|
230
|
+
style H fill:#f1f8e9
|
231
|
+
style I fill:#fafafa
|
232
|
+
style Y fill:#ffebee
|
233
|
+
```
|
234
|
+
|
235
|
+
#### FilePlugin
|
236
|
+
|
237
|
+
Enables file-based configuration through the `file()` method:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
API_KEY = set do
|
241
|
+
default file('config/api.key', required: true)
|
242
|
+
…
|
243
|
+
end
|
244
|
+
```
|
245
|
+
|
246
|
+
#### DirPlugin
|
247
|
+
|
248
|
+
Enables directory-based configuration with XDG compliance through the `dir()`
|
249
|
+
method:
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
CONFIG_FROM_DIR = set do
|
253
|
+
default dir('myapp', 'config.yaml', env_var_name: 'XDG_CONFIG_HOME')
|
254
|
+
…
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
#### JSONPlugin
|
259
|
+
|
260
|
+
Enables JSON-based configuration through the `json()` method:
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
CONFIG = set do
|
264
|
+
default json('config.json')
|
265
|
+
# or with custom object class:
|
266
|
+
# default json('config.json', object_class: MyCustomClass)
|
267
|
+
…
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
#### YAMLPlugin
|
272
|
+
|
273
|
+
Enables YAML-based configuration through the `yaml()` method:
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
CONFIG = set do
|
277
|
+
default yaml('config.yaml')
|
278
|
+
# or with environment-specific loading:
|
279
|
+
# default yaml('config.yaml', env: true)
|
280
|
+
# or with explicit environment:
|
281
|
+
# default yaml('config.yaml', env: 'production')
|
282
|
+
…
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
Plugins are registered using the `plugin` method at the module level. Multiple
|
287
|
+
plugins can be combined to provide flexible configuration sources.
|
288
|
+
|
289
|
+
When a plugin is loaded, it extends the DSL with additional methods and
|
290
|
+
behaviors that can be used within `set do` blocks. The plugin system allows for
|
291
|
+
easy extension of ConstConf's capabilities without modifying core
|
292
|
+
functionality.
|
293
|
+
|
294
|
+
### Configuration Concepts Explained
|
295
|
+
|
296
|
+
#### **Description**
|
297
|
+
|
298
|
+
- **Purpose**: Human-readable explanation of what the setting is for (📝 is
|
299
|
+
always required to be provided)
|
300
|
+
- **Implementation**: The `description` accessor stores a string explaining the
|
301
|
+
setting's purpose
|
302
|
+
- **Usage**: `description 'Database connection string'`
|
303
|
+
- **Indicator**: Shows in view output as descriptive text next to each setting
|
304
|
+
|
305
|
+
#### **Prefix**
|
306
|
+
|
307
|
+
- **Purpose**: Namespace prefix used to construct environment variable names
|
308
|
+
- **Implementation**: The `prefix` accessor determines how environment
|
309
|
+
variables are named
|
310
|
+
- **Usage**: `prefix 'APP'` makes `API_KEY` become `APP_API_KEY`
|
311
|
+
- **Indicator**: Shows as prefix value in view output
|
312
|
+
|
313
|
+
#### **Decoding** (`decoding?`)
|
314
|
+
|
315
|
+
- **Purpose**: Transforms raw input values using a Proc
|
316
|
+
- **Implementation**: The `decode` accessor stores a Proc that processes the
|
317
|
+
value
|
318
|
+
- **Usage**: `decode(&:chomp)` removes whitespace, `decode { YAML.load(it) }`
|
319
|
+
parses YAML
|
320
|
+
- **Indicator**: Shows ⚙️ in view output when active
|
321
|
+
|
322
|
+
#### **Required** (`required?`)
|
323
|
+
|
324
|
+
- **Purpose**: Determines if a setting must have a valid value
|
325
|
+
- **Implementation**: Can be boolean (`true/false`) or Proc for conditional
|
326
|
+
validation
|
327
|
+
- **Usage**: `required true` (always required) or `required { !Rails.env.test?
|
328
|
+
}` (conditional)
|
329
|
+
- **Indicator**: Shows 🔴 in view output when required and not satisfied
|
330
|
+
|
331
|
+
#### **Configured** (`configured?`)
|
332
|
+
|
333
|
+
- **Purpose**: Indicates whether a value was actually provided (not just
|
334
|
+
default)
|
335
|
+
- **Implementation**: Checks if environment variable is set or default is used
|
336
|
+
- **Usage**: `configured?` returns true when `ENV['VAR_NAME']` exists or
|
337
|
+
default is non-nil
|
338
|
+
- **Indicator**: Shows 🔧 in view output when value is explicitly provided
|
339
|
+
|
340
|
+
#### **Checked** (`checked?`)
|
341
|
+
|
342
|
+
- **Purpose**: Validates that custom validation logic passes
|
343
|
+
- **Implementation**: The `check` accessor stores validation logic as Proc
|
344
|
+
- **Usage**: `check ->(setting) { setting.value >= 1024 }` for port validation
|
345
|
+
- **Indicator Logic**:
|
346
|
+
- **☑️** = No custom check defined (passes by default - `:unchecked_true`)
|
347
|
+
- **✅** = Custom check explicitly passes (returns `true`)
|
348
|
+
- **❌** = Custom check explicitly fails (returns `false`)
|
349
|
+
|
350
|
+
#### **Active** (`active?`)
|
351
|
+
|
352
|
+
- **Purpose**: Determines if the setting should be considered usable/active and
|
353
|
+
determines if the result of the AppConfig::FOOBAR? method is the
|
354
|
+
AppConfig::FOOBAR value if true.
|
355
|
+
- **Implementation**: Evaluates the `activated` property (defaults to
|
356
|
+
`:present?`)
|
357
|
+
- **Usage**: Can be `true`, `false`, `Symbol`, or `Proc` for custom activation
|
358
|
+
logic
|
359
|
+
- **Indicator**: Shows 🟢 in view output when active
|
360
|
+
|
361
|
+
#### **Ignored** (`ignored?`)
|
362
|
+
|
363
|
+
- **Purpose**: Skips processing this setting during configuration resolution
|
364
|
+
- **Implementation**: The `ignored` accessor prevents reading from ENV
|
365
|
+
variables
|
366
|
+
- **Usage**: Set to `true` to skip environment variable lookup for this setting
|
367
|
+
- **Indicator**: Shows 🙈 in view output when ignored
|
368
|
+
|
369
|
+
These concepts work together to provide a comprehensive configuration
|
370
|
+
management system that tracks the complete lifecycle and status of each setting
|
371
|
+
from definition through validation and usage.
|
372
|
+
|
373
|
+
### Advanced Usage Examples
|
374
|
+
|
375
|
+
#### Nested Configuration Modules
|
376
|
+
|
377
|
+
ConstConf supports elegant nested module organization where prefixes are
|
378
|
+
automatically inherited and combined. When you define nested modules, the
|
379
|
+
library automatically constructs full environment variable names by combining
|
380
|
+
parent module prefixes with child module names. This creates a clean,
|
381
|
+
hierarchical configuration structure that's both maintainable and predictable.
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
require 'const_conf'
|
385
|
+
|
386
|
+
module AppConfig # default prefix 'APP_CONFIG'
|
387
|
+
include ConstConf
|
388
|
+
|
389
|
+
description 'Application Configuration'
|
390
|
+
|
391
|
+
module Database # default prefix 'APP_CONFIG_DATABASE'
|
392
|
+
|
393
|
+
description 'Database settings'
|
394
|
+
|
395
|
+
URL = set do # from ENV['APP_CONFIG_DATABASE_URL'] via default prefix
|
396
|
+
description 'Database connection URL'
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
402
|
+
To access the nested configuration values in Ruby, you would reference them
|
403
|
+
through the full module path:
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
# Access the database URL setting
|
407
|
+
db_url = AppConfig::Database::URL
|
408
|
+
|
409
|
+
# Use it in your application
|
410
|
+
connect_to_database(db_url)
|
411
|
+
|
412
|
+
# Check if it's active/available
|
413
|
+
if db_url = AppConfig::Database::URL?
|
414
|
+
# Use the database connection
|
415
|
+
connect_to_database(db_url)
|
416
|
+
end
|
417
|
+
```
|
418
|
+
|
419
|
+
This hierarchical approach makes configuration organization intuitive while
|
420
|
+
maintaining clear access patterns through Ruby's constant resolution mechanism.
|
421
|
+
|
422
|
+
#### Validation and Checks
|
423
|
+
|
424
|
+
ConstConf provides powerful validation capabilities through custom check blocks
|
425
|
+
that allow you to enforce business logic and data integrity rules. These checks
|
426
|
+
are evaluated during configuration resolution and appear in the view output
|
427
|
+
with visual indicators.
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
require 'const_conf'
|
431
|
+
|
432
|
+
module AppConfig
|
433
|
+
include ConstConf
|
434
|
+
|
435
|
+
description 'Application Configuration'
|
436
|
+
prefix '' # Use empty prefix for flat environment variable names
|
437
|
+
|
438
|
+
PORT = set do # from ENV['PORT']
|
439
|
+
description 'Server port'
|
440
|
+
default 3000
|
441
|
+
decode(&:to_i)
|
442
|
+
check { value >= 1024 } # Port must be >= 1024
|
443
|
+
end
|
444
|
+
|
445
|
+
HOST = set do # from ENV['APP_HOST']
|
446
|
+
description 'Host name'
|
447
|
+
prefix 'APP'
|
448
|
+
required { !Rails.env.test? }
|
449
|
+
check -> setting { setting.value.present? } # Must not be blank
|
450
|
+
end
|
451
|
+
end
|
452
|
+
```
|
453
|
+
|
454
|
+
The output of `AppConfig.view` is:
|
455
|
+
|
456
|
+
```
|
457
|
+
AppConfig # Application Configuration
|
458
|
+
├─ prefix ""
|
459
|
+
├─ 2 settings
|
460
|
+
├─ AppConfig::PORT # Server port
|
461
|
+
│ ├─ prefix ""
|
462
|
+
│ ├─ env var name PORT
|
463
|
+
│ ├─ env var (orig.) nil
|
464
|
+
│ ├─ default 3000
|
465
|
+
│ ├─ value 3000
|
466
|
+
│ ├─ sensitive ⚪
|
467
|
+
│ ├─ required ⚪
|
468
|
+
│ ├─ configured ⚪
|
469
|
+
│ ├─ ignored ⚪
|
470
|
+
│ ├─ active 🟢
|
471
|
+
│ ├─ decoding ⚙️
|
472
|
+
│ └─ checked ✅
|
473
|
+
└─ AppConfig::HOST # Host name
|
474
|
+
├─ prefix "APP"
|
475
|
+
├─ env var name APP_HOST
|
476
|
+
├─ env var (orig.) "www.example.com"
|
477
|
+
├─ default nil
|
478
|
+
├─ value "www.example.com"
|
479
|
+
├─ sensitive ⚪
|
480
|
+
├─ required 🔴
|
481
|
+
├─ configured 🔧
|
482
|
+
├─ ignored ⚪
|
483
|
+
├─ active 🟢
|
484
|
+
├─ decoding ⚪
|
485
|
+
└─ checked ✅
|
486
|
+
```
|
487
|
+
|
488
|
+
##### **How Validation Works:**
|
489
|
+
|
490
|
+
- **PORT**: The `check { value >= 1024 }` ensures that if a PORT environment
|
491
|
+
variable is set, it must be at least 1024 (a privileged port range)
|
492
|
+
- **HOST**: The `required { !Rails.env.test? }` makes this setting required
|
493
|
+
only in non-test environments, and the check validates that the value isn't
|
494
|
+
blank
|
495
|
+
|
496
|
+
The validation system ensures that your configuration meets both technical
|
497
|
+
requirements (like port ranges) and business rules (like non-empty hostnames),
|
498
|
+
making it much harder to deploy applications with invalid configurations.
|
499
|
+
|
500
|
+
**When validations fail during confirmation, ConstConf raises specific exceptions:**
|
501
|
+
|
502
|
+
- `ConstConf::RequiredValueNotConfigured` for missing required values
|
503
|
+
- `ConstConf::SettingCheckFailed` for failed custom checks
|
504
|
+
- These errors are raised immediately during configuration loading to prevent
|
505
|
+
runtime issues
|
506
|
+
|
507
|
+
#### Complex Settings Examples
|
508
|
+
|
509
|
+
```ruby
|
510
|
+
require 'const_conf'
|
511
|
+
|
512
|
+
module AppConfig
|
513
|
+
include ConstConf
|
514
|
+
|
515
|
+
description 'Application Configuration'
|
516
|
+
|
517
|
+
# Version validation
|
518
|
+
REVISION = set do # from ENV['REVISION']
|
519
|
+
description 'Current software revision'
|
520
|
+
prefix ''
|
521
|
+
required { Rails.env.production? }
|
522
|
+
check { value.blank? || value.to_s =~ /\A\h{7}\z/ }
|
523
|
+
end
|
524
|
+
|
525
|
+
# Host validation with regex
|
526
|
+
HOST = set do # from ENV['APP_CONFIG_HOST']
|
527
|
+
description 'HOST name the application can be reached under'
|
528
|
+
required { Rails.env.production? }
|
529
|
+
check { value.blank? || value =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && value.size <= 253 }
|
530
|
+
end
|
531
|
+
|
532
|
+
# Multi-value validation
|
533
|
+
HOSTS_ALLOWED = set do # from ENV['APP_CONFIG_HOSTS_ALLOWED']
|
534
|
+
description 'Connections under these hostnames are allowed in Rails.'
|
535
|
+
default ''
|
536
|
+
decode { it.split(?,).map(&:strip) }
|
537
|
+
check { value.all? { |host| host =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && host.size <= 253 } }
|
538
|
+
end
|
539
|
+
|
540
|
+
# Sensitive configuration with validation
|
541
|
+
GITHUB_PERSONAL_ACCESS_TOKEN = set do # from ENV['GITHUB_PERSONAL_ACCESS_TOKEN']
|
542
|
+
description 'GitHub Personal Access Token for repo access'
|
543
|
+
prefix ''
|
544
|
+
required { !Rails.env.test? }
|
545
|
+
sensitive true
|
546
|
+
check { value.to_s =~ /\Aghp_[A-Za-z0-9]{36}\z/ }
|
547
|
+
end
|
548
|
+
|
549
|
+
# URI validation
|
550
|
+
REDIS_URL = set do # from ENV['REDIS_URL']
|
551
|
+
description 'Redis server URL'
|
552
|
+
prefix ''
|
553
|
+
default 'redis://localhost:6379/1'
|
554
|
+
sensitive true
|
555
|
+
check { URI.parse(value).scheme == 'redis' rescue false }
|
556
|
+
end
|
557
|
+
end
|
558
|
+
```
|
559
|
+
|
560
|
+
The output of `AppConfig.view` is:
|
561
|
+
|
562
|
+
```
|
563
|
+
AppConfig # Application Configuration
|
564
|
+
├─ prefix "APP_CONFIG"
|
565
|
+
├─ 5 settings
|
566
|
+
├─ AppConfig::REVISION # Current software revision
|
567
|
+
│ ├─ prefix ""
|
568
|
+
│ ├─ env var name REVISION
|
569
|
+
│ ├─ env var (orig.) "b781318"
|
570
|
+
│ ├─ default nil
|
571
|
+
│ ├─ value "b781318"
|
572
|
+
│ ├─ sensitive ⚪
|
573
|
+
│ ├─ required ⚪
|
574
|
+
│ ├─ configured 🔧
|
575
|
+
│ ├─ ignored ⚪
|
576
|
+
│ ├─ active 🟢
|
577
|
+
│ ├─ decoding ⚪
|
578
|
+
│ └─ checked ✅
|
579
|
+
├─ AppConfig::HOST # HOST name the application can be reached under
|
580
|
+
│ ├─ prefix "APP_CONFIG"
|
581
|
+
│ ├─ env var name APP_CONFIG_HOST
|
582
|
+
│ ├─ env var (orig.) "www.example.com"
|
583
|
+
│ ├─ default nil
|
584
|
+
│ ├─ value "www.example.com"
|
585
|
+
│ ├─ sensitive ⚪
|
586
|
+
│ ├─ required ⚪
|
587
|
+
│ ├─ configured 🔧
|
588
|
+
│ ├─ ignored ⚪
|
589
|
+
│ ├─ active 🟢
|
590
|
+
│ ├─ decoding ⚪
|
591
|
+
│ └─ checked ✅
|
592
|
+
├─ AppConfig::HOSTS_ALLOWED # Connections under these hostnames are allowed in Rails.
|
593
|
+
│ ├─ prefix "APP_CONFIG"
|
594
|
+
│ ├─ env var name APP_CONFIG_HOSTS_ALLOWED
|
595
|
+
│ ├─ env var (orig.) "www.example.com,example.com…
|
596
|
+
│ ├─ default ""
|
597
|
+
│ ├─ value ["www.example.com", "example…
|
598
|
+
│ ├─ sensitive ⚪
|
599
|
+
│ ├─ required ⚪
|
600
|
+
│ ├─ configured 🔧
|
601
|
+
│ ├─ ignored ⚪
|
602
|
+
│ ├─ active 🟢
|
603
|
+
│ ├─ decoding ⚙️
|
604
|
+
│ └─ checked ✅
|
605
|
+
├─ AppConfig::GITHUB_PERSONAL_ACCESS_TOKEN # GitHub Personal Access Token for repo access
|
606
|
+
│ ├─ prefix ""
|
607
|
+
│ ├─ env var name GITHUB_PERSONAL_ACCESS_TOKEN
|
608
|
+
│ ├─ env var (orig.) 🤫
|
609
|
+
│ ├─ default 🤫
|
610
|
+
│ ├─ value 🤫
|
611
|
+
│ ├─ sensitive 🔒
|
612
|
+
│ ├─ required 🔴
|
613
|
+
│ ├─ configured 🔧
|
614
|
+
│ ├─ ignored ⚪
|
615
|
+
│ ├─ active 🟢
|
616
|
+
│ ├─ decoding ⚪
|
617
|
+
│ └─ checked ✅
|
618
|
+
└─ AppConfig::REDIS_URL # Redis server URL
|
619
|
+
├─ prefix ""
|
620
|
+
├─ env var name REDIS_URL
|
621
|
+
├─ env var (orig.) 🤫
|
622
|
+
├─ default 🤫
|
623
|
+
├─ value 🤫
|
624
|
+
├─ sensitive 🔒
|
625
|
+
├─ required ⚪
|
626
|
+
├─ configured 🔧
|
627
|
+
├─ ignored ⚪
|
628
|
+
├─ active 🟢
|
629
|
+
├─ decoding ⚪
|
630
|
+
└─ checked ✅
|
631
|
+
```
|
632
|
+
|
633
|
+
##### 1. Version Validation (`REVISION`)
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
REVISION = set do
|
637
|
+
description 'Current software revision'
|
638
|
+
prefix ''
|
639
|
+
required { Rails.env.production? }
|
640
|
+
check { value.blank? || value.to_s =~ /\A\h{7}\z/ }
|
641
|
+
end
|
642
|
+
```
|
643
|
+
|
644
|
+
**Explanation:**
|
645
|
+
|
646
|
+
- **Purpose**: Validates that the revision is either blank (not set) or a valid
|
647
|
+
7-character hexadecimal string
|
648
|
+
- **Conditional Required**: Only required in production environment
|
649
|
+
- **Validation Logic**: Uses regex `/\A\h{7}\z/` to match exactly 7 hexadecimal
|
650
|
+
characters (0-9, a-f)
|
651
|
+
- **Indicator**: Shows `🔴` when required and not satisfied, `✅` when valid
|
652
|
+
|
653
|
+
##### 2. Host Validation with Regex (`HOST`)
|
654
|
+
|
655
|
+
```ruby
|
656
|
+
HOST = set do
|
657
|
+
description 'HOST name the application can be reached under'
|
658
|
+
required { Rails.env.production? }
|
659
|
+
check { value.blank? || value =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && value.size <= 253 }
|
660
|
+
end
|
661
|
+
```
|
662
|
+
|
663
|
+
**Explanation:**
|
664
|
+
|
665
|
+
- **Purpose**: Validates domain names with proper format and length constraints
|
666
|
+
- **Conditional Required**: Production-only requirement
|
667
|
+
- **Regex Pattern**:
|
668
|
+
- `/\A[a-z\-]+\.[a-z\-\.]+\z/` - Matches lowercase letters, hyphens, dots in
|
669
|
+
hostnames
|
670
|
+
- Ensures valid DNS format (e.g., "example.com")
|
671
|
+
- **Length Check**: Maximum 253 characters per RFC 1035
|
672
|
+
- **Indicator**: Shows validation status with visual cues
|
673
|
+
|
674
|
+
##### 3. Multi-value Validation (`HOSTS_ALLOWED`)
|
675
|
+
|
676
|
+
```ruby
|
677
|
+
HOSTS_ALLOWED = set do
|
678
|
+
description 'Connections under these hostnames are allowed in Rails.'
|
679
|
+
default ''
|
680
|
+
decode { it.split(?,).map(&:strip) }
|
681
|
+
check { value.all? { |host| host =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && host.size <= 253 } }
|
682
|
+
end
|
683
|
+
```
|
684
|
+
|
685
|
+
**Explanation:**
|
686
|
+
|
687
|
+
- **Purpose**: Validates a comma-separated list of hostnames for allowed
|
688
|
+
connections
|
689
|
+
- **Default Value**: Empty string (no hosts allowed by default)
|
690
|
+
- **Decoding Logic**: Splits on commas and strips whitespace from each hostname
|
691
|
+
- **Validation**: Ensures ALL hosts in the list pass the same regex validation
|
692
|
+
as single hosts
|
693
|
+
- **Indicator**: Shows `⚙️` for decoding, `✅` for valid multi-value
|
694
|
+
|
695
|
+
##### 4. Sensitive Configuration with Validation (`GITHUB_PERSONAL_ACCESS_TOKEN`)
|
696
|
+
|
697
|
+
```ruby
|
698
|
+
GITHUB_PERSONAL_ACCESS_TOKEN = set do
|
699
|
+
description 'GitHub Personal Access Token for repo access'
|
700
|
+
prefix ''
|
701
|
+
required { !Rails.env.test? }
|
702
|
+
sensitive true
|
703
|
+
check { value.to_s =~ /\Aghp_[A-Za-z0-9]{36}\z/ }
|
704
|
+
end
|
705
|
+
```
|
706
|
+
|
707
|
+
**Explanation:**
|
708
|
+
|
709
|
+
- **Purpose**: Validates GitHub personal access tokens with strict format
|
710
|
+
requirements
|
711
|
+
- **Conditional Required**: Not required in test environment
|
712
|
+
- **Sensitive Flag**: Masks the actual value in views (`🤫`)
|
713
|
+
- **Token Validation**:
|
714
|
+
- Must start with `ghp_`
|
715
|
+
- Followed by exactly 36 alphanumeric characters
|
716
|
+
- Matches GitHub's token format specification
|
717
|
+
- **Indicator**: Shows both `🔒` (sensitive) and `✅` (validated)
|
718
|
+
|
719
|
+
##### 5. URI Validation (`REDIS_URL`)
|
720
|
+
|
721
|
+
```ruby
|
722
|
+
REDIS_URL = set do
|
723
|
+
description 'Redis server URL'
|
724
|
+
prefix ''
|
725
|
+
default 'redis://localhost:6379/1'
|
726
|
+
sensitive true
|
727
|
+
check { URI.parse(value).scheme == 'redis' rescue false }
|
728
|
+
end
|
729
|
+
```
|
730
|
+
|
731
|
+
**Explanation:**
|
732
|
+
|
733
|
+
- **Purpose**: Validates Redis connection URLs are properly formatted
|
734
|
+
- **Default Value**: Safe localhost configuration
|
735
|
+
- **Sensitive Flag**: Masks the URL in views
|
736
|
+
- **Validation Logic**: Attempts to parse as URI and checks for 'redis' scheme
|
737
|
+
- **Error Handling**: Uses `rescue false` to gracefully handle invalid URLs
|
738
|
+
- **Indicator**: Shows `✅` when valid, handles malformed inputs gracefully
|
739
|
+
|
740
|
+
##### Key Technical Details
|
741
|
+
|
742
|
+
**Conditional Validation**: The use of Procs like `{ Rails.env.production? }`
|
743
|
+
allows dynamic validation rules based on runtime conditions.
|
744
|
+
|
745
|
+
**Safe Error Handling**: The URI parsing example demonstrates proper error
|
746
|
+
handling with rescue clauses to prevent configuration failures due to malformed
|
747
|
+
inputs.
|
748
|
+
|
749
|
+
**Regex Patterns**: All validations use precise regex patterns that match RFC
|
750
|
+
standards or specific format requirements, ensuring data integrity.
|
751
|
+
|
752
|
+
These examples showcase how ConstConf can handle complex business logic while
|
753
|
+
maintaining clean, readable configuration definitions. The combination of
|
754
|
+
conditional requirements, multi-value validation, and sensitive data protection
|
755
|
+
makes it suitable for production environments with strict security
|
756
|
+
requirements.
|
757
|
+
|
758
|
+
## Rails Integration
|
759
|
+
|
760
|
+
ConstConf automatically integrates with Rails:
|
761
|
+
|
762
|
+
- Configuration is reloaded when the application prepares configuration
|
763
|
+
- Works seamlessly with Rails environment variables
|
764
|
+
|
765
|
+
## Debugging and Inspection
|
766
|
+
|
767
|
+
View configuration hierarchies:
|
768
|
+
|
769
|
+
```ruby
|
770
|
+
# Show all configuration settings
|
771
|
+
AppConfig.view
|
772
|
+
|
773
|
+
# Show specific setting details
|
774
|
+
AppConfig::DATABASE_URL!.view
|
775
|
+
```
|
776
|
+
|
777
|
+
## Download
|
778
|
+
|
779
|
+
The homepage of this library is located at
|
780
|
+
|
781
|
+
* https://github.com/flori/const_conf
|
782
|
+
|
783
|
+
## Author
|
784
|
+
|
785
|
+
**ConstConf** was written by Florian Frank [Florian Frank](mailto:flori@ping.de)
|
786
|
+
|
787
|
+
## License
|
788
|
+
|
789
|
+
[MIT License](./LICENSE)
|