hati-config 0.1.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/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +818 -0
- data/hati-config.gemspec +42 -0
- data/lib/hati_config/cache.rb +284 -0
- data/lib/hati_config/configuration.rb +140 -0
- data/lib/hati_config/encryption.rb +244 -0
- data/lib/hati_config/environment.rb +107 -0
- data/lib/hati_config/errors.rb +54 -0
- data/lib/hati_config/hati_configuration.rb +84 -0
- data/lib/hati_config/remote_loader.rb +86 -0
- data/lib/hati_config/schema.rb +213 -0
- data/lib/hati_config/setting.rb +389 -0
- data/lib/hati_config/team.rb +85 -0
- data/lib/hati_config/type_checker.rb +103 -0
- data/lib/hati_config/type_map.rb +72 -0
- data/lib/hati_config/version.rb +5 -0
- data/lib/hati_config.rb +15 -0
- metadata +123 -0
data/README.md
ADDED
@@ -0,0 +1,818 @@
|
|
1
|
+
# HatiConfig
|
2
|
+
|
3
|
+
A Ruby approach to configuration management, inspired by real-world challenges in distributed systems. This gem explores practical solutions for teams dealing with configuration complexity at scale.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Overview & Configuration Patterns](OVERVIEW.md)
|
8
|
+
- [Installation](#installation)
|
9
|
+
- [Basic Usage](#basic-usage)
|
10
|
+
- [Configuration Container Usage](#configuration-container-usage)
|
11
|
+
- [Define with DSL Syntax](#define-with-dsl-syntax)
|
12
|
+
- Distributed Features
|
13
|
+
- [Remote Configuration](#remote-configuration)
|
14
|
+
- [Environment Management](#environment-management)
|
15
|
+
- [Team Isolation](#team-isolation)
|
16
|
+
- [Schema Versioning](#schema-versioning)
|
17
|
+
- [Caching and Refresh](#caching-and-refresh)
|
18
|
+
- [Encryption](#encryption)
|
19
|
+
- Typing
|
20
|
+
- [Configure Type Validation](#configure-type-validation)
|
21
|
+
- [Define Configuration Type Validation](#define-configuration-type-validation)
|
22
|
+
- [Type Schema](#type_schema)
|
23
|
+
- [Built-in Types](#built-in-types)
|
24
|
+
- Import/Export:
|
25
|
+
- [Loading Configurations](#loading-configuration-data)
|
26
|
+
- [Loading from Remote Sources](#loading-from-remote-sources)
|
27
|
+
- [Loading from a JSON String](#loading-from-a-json-string)
|
28
|
+
- [Loading from a YAML File](#loading-from-a-yaml-file)
|
29
|
+
- [Exporting Configurations](#exporting-configuration-data)
|
30
|
+
- [to_h](#to_h)
|
31
|
+
- [to_json](#to_json)
|
32
|
+
- [to_yaml](#to_yaml)
|
33
|
+
- Security
|
34
|
+
- [Encryption](#encryption)
|
35
|
+
- [Encryption Key Providers](#encryption-key-providers)
|
36
|
+
- [Security Features](#security-features)
|
37
|
+
- OSS
|
38
|
+
- [Development](#development)
|
39
|
+
- [Contributing](#contributing)
|
40
|
+
- [License](#license)
|
41
|
+
- [Code of Conduct](#code-of-conduct)
|
42
|
+
|
43
|
+
---
|
44
|
+
|
45
|
+
## Features
|
46
|
+
|
47
|
+
- **Simple Configuration Management**: Easily define, set, and retrieve configuration options.
|
48
|
+
- **Type Validation**: Ensure configurations are correct with built-in type validation.
|
49
|
+
- **Multiple Formats**: Import and export configurations in JSON, YAML, and Hash formats.
|
50
|
+
- **Nested Configurations**: Support for infinite nested configurations for complex applications.
|
51
|
+
- **Classy Access**: Access configurations in a 'classy' manner for better organization and readability.
|
52
|
+
- **Built-in Types**: Utilize various built-in types including basic types, data structures, numeric types, and time types.
|
53
|
+
- **Extensible**: Easily extendable to accommodate custom configuration needs.
|
54
|
+
|
55
|
+
## Recent Updates
|
56
|
+
|
57
|
+
### Version 1.1.0 (Latest)
|
58
|
+
|
59
|
+
- **Fixed**: Encryption functionality now works correctly with the `config(key, value: "secret", encrypted: true)` syntax
|
60
|
+
- **Enhanced**: Improved encryption handling for both inline and hash-based configuration syntax
|
61
|
+
- **Improved**: Better error handling and type validation for encrypted values
|
62
|
+
- **Updated**: Comprehensive encryption documentation with practical examples
|
63
|
+
|
64
|
+
---
|
65
|
+
|
66
|
+
## Installation
|
67
|
+
|
68
|
+
Install the gem and add to the application's Gemfile by executing:
|
69
|
+
|
70
|
+
```bash
|
71
|
+
bundle add hati-config
|
72
|
+
```
|
73
|
+
|
74
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
75
|
+
|
76
|
+
```bash
|
77
|
+
gem install hati-config
|
78
|
+
```
|
79
|
+
|
80
|
+
## Basic Usage
|
81
|
+
|
82
|
+
**Use Case**: You're building a Ruby application that needs clean, type-safe configuration management. You want to avoid scattered constants, magic strings, and configuration bugs that crash production. You need nested configs, type validation, and the ability to export configs for debugging.
|
83
|
+
|
84
|
+
**Why existing tools fall short**:
|
85
|
+
|
86
|
+
- Plain Ruby constants are global and can't be nested cleanly
|
87
|
+
- YAML files lack type safety and runtime validation
|
88
|
+
- Environment variables become unwieldy with complex nested configs
|
89
|
+
- No built-in export/import capabilities for debugging and testing
|
90
|
+
|
91
|
+
**HatiConfig solution**: Clean DSL for defining configs with automatic type validation, nested namespaces, and built-in serialization.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
require 'hati_config'
|
95
|
+
|
96
|
+
module MyApp
|
97
|
+
extend HatiConfig
|
98
|
+
end
|
99
|
+
|
100
|
+
MyApp.configure :settings do
|
101
|
+
config option: 42
|
102
|
+
config.int typed_opt_one: 42
|
103
|
+
config typed_opt_two: 4.2, type: :float
|
104
|
+
end
|
105
|
+
|
106
|
+
MyApp.settings.option # => 42
|
107
|
+
MyApp.settings.typed_opt_one # => 42
|
108
|
+
MyApp.settings.typed_opt_two # => 4.2
|
109
|
+
```
|
110
|
+
|
111
|
+
### With Encryption
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
require 'hati_config'
|
115
|
+
|
116
|
+
# Set up encryption key
|
117
|
+
ENV['HATI_CONFIG_ENCRYPTION_KEY'] = '0' * 32 # 256-bit key
|
118
|
+
|
119
|
+
# Create a setting instance with encryption
|
120
|
+
settings = HatiConfig::Setting.new
|
121
|
+
|
122
|
+
# Configure encryption
|
123
|
+
settings.class.encryption do
|
124
|
+
key_provider :env
|
125
|
+
end
|
126
|
+
|
127
|
+
# Configure encrypted and plain values
|
128
|
+
settings.config :api_key, value: "secret-key", encrypted: true
|
129
|
+
settings.config :public_url, value: "https://api.example.com"
|
130
|
+
|
131
|
+
settings[:api_key] # => "secret-key" (automatically decrypted)
|
132
|
+
settings[:public_url] # => "https://api.example.com" (plain text)
|
133
|
+
```
|
134
|
+
|
135
|
+
### Basic Syntax
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
MyApp.configure :settings do
|
139
|
+
config option: 42
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
## Namespacing
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
MyApp.configure :app do
|
147
|
+
configure :lvl_one do
|
148
|
+
config opt: 100
|
149
|
+
configure :lvl_two do
|
150
|
+
config opt: 200
|
151
|
+
configure :lvl_three do
|
152
|
+
config opt: 300
|
153
|
+
configure :lvl_four do
|
154
|
+
config opt: 400
|
155
|
+
configure :lvl_five do
|
156
|
+
config opt: 500
|
157
|
+
# NOTE: as deep as you want
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
MyApp.app.lvl_one.opt # => 100
|
166
|
+
MyApp.app.lvl_one.lvl_two.opt # => 200
|
167
|
+
MyApp.app.lvl_one.lvl_two.lvl_three.opt # => 300
|
168
|
+
MyApp.app.lvl_one.lvl_two.lvl_three.lvl_four.opt # => 400
|
169
|
+
MyApp.app.lvl_one.lvl_two.lvl_three.lvl_four.lvl_five.opt # => 500
|
170
|
+
```
|
171
|
+
|
172
|
+
### Configure Type Validation
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
MyApp.configure :settings do
|
176
|
+
config custom_typed_opt_one: '42', type: :float
|
177
|
+
end
|
178
|
+
# => HatiConfig::SettingTypeError
|
179
|
+
```
|
180
|
+
|
181
|
+
## Distributed Features
|
182
|
+
|
183
|
+
### Remote Configuration
|
184
|
+
|
185
|
+
**Use Case**: You're running a microservices architecture with 50+ services across multiple regions. Each service needs to know database endpoints, API keys, and feature flags that change frequently. Traditional config files mean redeploying every service when anything changes, causing downtime and deployment bottlenecks.
|
186
|
+
|
187
|
+
**Why existing tools fall short**:
|
188
|
+
|
189
|
+
- Environment variables become unmanageable with hundreds of configs
|
190
|
+
- Config files require application restarts and deployments
|
191
|
+
- Tools like Consul require additional infrastructure and learning curve
|
192
|
+
- Most solutions don't handle automatic refresh or fallback gracefully
|
193
|
+
|
194
|
+
**HatiConfig solution**: Load configurations from HTTP endpoints, S3, or Redis with automatic refresh, caching, and fallback. Update configs without touching code or deployments.
|
195
|
+
|
196
|
+
HatiConfig supports loading configurations from various remote sources:
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
require 'hati_config'
|
200
|
+
|
201
|
+
module MyApp
|
202
|
+
extend HatiConfig
|
203
|
+
|
204
|
+
# Load from HTTP endpoint
|
205
|
+
configure :api_settings, http: {
|
206
|
+
url: 'https://config-server/api-config.json',
|
207
|
+
headers: { 'Authorization' => 'Bearer token' },
|
208
|
+
refresh_interval: 300 # refresh every 5 minutes
|
209
|
+
}
|
210
|
+
|
211
|
+
# Load from S3
|
212
|
+
configure :database_settings, s3: {
|
213
|
+
bucket: 'my-configs',
|
214
|
+
key: 'database.yml',
|
215
|
+
region: 'us-west-2',
|
216
|
+
refresh_interval: 600
|
217
|
+
}
|
218
|
+
|
219
|
+
# Load from Redis
|
220
|
+
configure :feature_flags, redis: {
|
221
|
+
host: 'redis.example.com',
|
222
|
+
key: 'feature_flags',
|
223
|
+
refresh_interval: 60
|
224
|
+
}
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
### Environment Management
|
229
|
+
|
230
|
+
**Use Case**: Your application runs across development, staging, production, and multiple regional production environments. Each environment needs different database URLs, API endpoints, timeout values, and feature flags. Developers accidentally use production configs in development, or staging configs leak into production, causing outages.
|
231
|
+
|
232
|
+
**Why existing tools fall short**:
|
233
|
+
|
234
|
+
- Rails environments are limited and don't handle complex multi-region setups
|
235
|
+
- Multiple config files lead to duplication and inconsistency
|
236
|
+
- No validation that the right configs are loaded in the right environment
|
237
|
+
- Switching between environments requires manual file changes or complex deployment scripts
|
238
|
+
|
239
|
+
**HatiConfig solution**: Define base configurations with environment-specific overrides. Built-in environment detection with validation ensures the right configs are always loaded.
|
240
|
+
|
241
|
+
Easily manage configurations across different environments:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
module MyApp
|
245
|
+
extend HatiConfig
|
246
|
+
|
247
|
+
configure :settings do
|
248
|
+
# Base configuration
|
249
|
+
config :timeout, default: 30
|
250
|
+
config :retries, default: 3
|
251
|
+
|
252
|
+
# Environment-specific overrides
|
253
|
+
environment :development do
|
254
|
+
config :api_url, value: 'http://localhost:3000'
|
255
|
+
config :debug, value: true
|
256
|
+
end
|
257
|
+
|
258
|
+
environment :staging do
|
259
|
+
config :api_url, value: 'https://staging-api.example.com'
|
260
|
+
config :timeout, value: 60
|
261
|
+
end
|
262
|
+
|
263
|
+
environment :production do
|
264
|
+
config :api_url, value: 'https://api.example.com'
|
265
|
+
config :timeout, value: 15
|
266
|
+
config :retries, value: 5
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
### Team Isolation
|
273
|
+
|
274
|
+
**Use Case**: You work at a large tech company with 10+ engineering teams (Frontend, Backend, Mobile, DevOps, ML, etc.). Each team has their own configurations, but they all deploy to shared infrastructure. Teams accidentally override each other's configs, causing mysterious production issues that take hours to debug.
|
275
|
+
|
276
|
+
**Why existing tools fall short**:
|
277
|
+
|
278
|
+
- Shared config files create merge conflicts and accidental overwrites
|
279
|
+
- Namespace collisions are common (multiple teams using "database_url")
|
280
|
+
- No clear ownership or boundaries for configuration sections
|
281
|
+
- Changes by one team can break another team's services
|
282
|
+
|
283
|
+
**HatiConfig solution**: Create isolated namespaces for each team. Teams can safely manage their own configs without affecting others, while still sharing common infrastructure settings.
|
284
|
+
|
285
|
+
Prevent configuration conflicts between teams:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
module MyApp
|
289
|
+
extend HatiConfig
|
290
|
+
|
291
|
+
# Team-specific configuration namespace
|
292
|
+
team :frontend do
|
293
|
+
configure :settings do
|
294
|
+
config :api_endpoint, value: '/api/v1'
|
295
|
+
config :cache_ttl, value: 300
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
team :backend do
|
300
|
+
configure :settings do
|
301
|
+
config :database_pool, value: 5
|
302
|
+
config :worker_threads, value: 10
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
team :mobile do
|
307
|
+
configure :settings do
|
308
|
+
config :push_notifications, value: true
|
309
|
+
config :offline_mode, value: true
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Access team configurations
|
315
|
+
MyApp.frontend.settings.api_endpoint # => '/api/v1'
|
316
|
+
MyApp.backend.settings.database_pool # => 5
|
317
|
+
MyApp.mobile.settings.offline_mode # => true
|
318
|
+
```
|
319
|
+
|
320
|
+
### Schema Versioning
|
321
|
+
|
322
|
+
**Use Case**: Your application has evolved over 2 years. The original config had simple database settings, but now includes complex microservice endpoints, ML model parameters, and feature flags. Old configs are incompatible with new code, but you need to support gradual rollouts and rollbacks without breaking existing deployments.
|
323
|
+
|
324
|
+
**Why existing tools fall short**:
|
325
|
+
|
326
|
+
- No versioning means breaking changes crash old application versions
|
327
|
+
- Manual migration scripts are error-prone and forgotten
|
328
|
+
- No way to validate that configs match the expected schema
|
329
|
+
- Rolling back code requires manually reverting config changes
|
330
|
+
|
331
|
+
**HatiConfig solution**: Version your configuration schemas with automatic migrations. Validate configs against expected schemas and handle version mismatches gracefully.
|
332
|
+
|
333
|
+
Track and validate configuration schema changes:
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
module MyApp
|
337
|
+
extend HatiConfig
|
338
|
+
|
339
|
+
configure :settings, version: '2.0' do
|
340
|
+
# Schema definition with version constraints
|
341
|
+
schema do
|
342
|
+
required :database_url, type: :string, since: '1.0'
|
343
|
+
required :pool_size, type: :integer, since: '1.0'
|
344
|
+
optional :replica_urls, type: [:string], since: '2.0'
|
345
|
+
deprecated :old_setting, since: '2.0', remove_in: '3.0'
|
346
|
+
end
|
347
|
+
|
348
|
+
# Migrations for automatic updates
|
349
|
+
migration '1.0' => '2.0' do |config|
|
350
|
+
config.replica_urls = [config.delete(:backup_url)].compact
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
```
|
355
|
+
|
356
|
+
### Caching and Refresh
|
357
|
+
|
358
|
+
**Use Case**: Your application makes 1000+ requests per second and needs to check feature flags and rate limits on each request. Fetching configs from remote sources every time would crush your config server and add 50ms latency to every request. But configs can change and you need updates within 1 minute for critical flags.
|
359
|
+
|
360
|
+
**Why existing tools fall short**:
|
361
|
+
|
362
|
+
- No caching means every request hits the config server
|
363
|
+
- Simple TTL caching means stale data during config server outages
|
364
|
+
- No intelligent refresh strategies lead to thundering herd problems
|
365
|
+
- Manual cache invalidation is complex and error-prone
|
366
|
+
|
367
|
+
**HatiConfig solution**: Intelligent caching with stale-while-revalidate, background refresh, exponential backoff, and jitter to prevent thundering herds.
|
368
|
+
|
369
|
+
Configure caching behavior and automatic refresh:
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
module MyApp
|
373
|
+
extend HatiConfig
|
374
|
+
|
375
|
+
configure :settings do
|
376
|
+
# Cache configuration
|
377
|
+
cache do
|
378
|
+
adapter :redis, url: 'redis://cache.example.com:6379/0'
|
379
|
+
ttl 300 # 5 minutes
|
380
|
+
stale_while_revalidate true
|
381
|
+
end
|
382
|
+
|
383
|
+
# Refresh strategy
|
384
|
+
refresh do
|
385
|
+
interval 60 # check every minute
|
386
|
+
jitter 10 # add random delay (0-10 seconds)
|
387
|
+
backoff do
|
388
|
+
initial 1
|
389
|
+
multiplier 2
|
390
|
+
max 300
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
```
|
396
|
+
|
397
|
+
### Encryption
|
398
|
+
|
399
|
+
**Use Case**: Your application handles API keys, database passwords, OAuth secrets, and encryption keys that are worth millions if compromised. These secrets are scattered across config files, environment variables, and deployment scripts. A single leaked config file or compromised CI/CD pipeline exposes everything. Compliance requires encryption at rest and audit trails.
|
400
|
+
|
401
|
+
**Why existing tools fall short**:
|
402
|
+
|
403
|
+
- Environment variables are visible in process lists and logs
|
404
|
+
- Config files with secrets get committed to Git accidentally
|
405
|
+
- Kubernetes secrets are base64 encoded, not encrypted
|
406
|
+
- External secret managers add complexity and network dependencies
|
407
|
+
- No transparent encryption/decryption in application code
|
408
|
+
|
409
|
+
**HatiConfig solution**: Automatic encryption of sensitive values with multiple key providers (env, files, AWS KMS). Values are encrypted at rest and decrypted transparently when accessed.
|
410
|
+
|
411
|
+
Secure sensitive configuration values with built-in encryption support:
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
require 'hati_config'
|
415
|
+
|
416
|
+
# Set the encryption key via environment variable
|
417
|
+
ENV['HATI_CONFIG_ENCRYPTION_KEY'] = '0123456789abcdef' * 2 # 32-character key
|
418
|
+
|
419
|
+
# Create a settings instance
|
420
|
+
settings = HatiConfig::Setting.new
|
421
|
+
|
422
|
+
# Configure encryption with environment variable key provider
|
423
|
+
settings.class.encryption do
|
424
|
+
key_provider :env # Uses HATI_CONFIG_ENCRYPTION_KEY environment variable
|
425
|
+
algorithm 'aes' # AES encryption (default)
|
426
|
+
key_size 256 # 256-bit keys (default)
|
427
|
+
mode 'gcm' # GCM mode (default)
|
428
|
+
end
|
429
|
+
|
430
|
+
# Configure settings with encrypted and plain values
|
431
|
+
settings.config :api_key, value: 'secret-api-key', encrypted: true
|
432
|
+
settings.config :database_password, value: 'super-secret-password', encrypted: true
|
433
|
+
|
434
|
+
# Regular unencrypted values
|
435
|
+
settings.config :api_url, value: 'https://api.example.com'
|
436
|
+
|
437
|
+
# Nested configurations with encryption
|
438
|
+
settings.configure :database do
|
439
|
+
config :host, value: 'db.example.com'
|
440
|
+
config :password, value: 'db-secret', encrypted: true
|
441
|
+
config :username, value: 'app_user'
|
442
|
+
end
|
443
|
+
|
444
|
+
# Access values - encrypted values are automatically decrypted
|
445
|
+
settings[:api_key] # => 'secret-api-key' (decrypted)
|
446
|
+
settings.database[:password] # => 'db-secret' (decrypted)
|
447
|
+
settings[:api_url] # => 'https://api.example.com' (plain)
|
448
|
+
```
|
449
|
+
|
450
|
+
#### Encryption Key Providers
|
451
|
+
|
452
|
+
The gem supports multiple key providers for encryption keys:
|
453
|
+
|
454
|
+
```ruby
|
455
|
+
# Environment variable (default)
|
456
|
+
settings.class.encryption do
|
457
|
+
key_provider :env, env_var: 'MY_ENCRYPTION_KEY' # Custom env var name
|
458
|
+
end
|
459
|
+
|
460
|
+
# File-based key
|
461
|
+
settings.class.encryption do
|
462
|
+
key_provider :file, file_path: '/secure/path/to/key.txt'
|
463
|
+
end
|
464
|
+
|
465
|
+
# AWS KMS (requires aws-sdk-kms gem)
|
466
|
+
settings.class.encryption do
|
467
|
+
key_provider :aws_kms, key_id: 'alias/config-key', region: 'us-west-2'
|
468
|
+
end
|
469
|
+
```
|
470
|
+
|
471
|
+
#### Security Features
|
472
|
+
|
473
|
+
- **AES-256-GCM encryption**: Industry-standard encryption with authentication
|
474
|
+
- **Automatic encryption/decryption**: Values are encrypted when stored and decrypted when accessed
|
475
|
+
- **Type safety**: Only string values can be encrypted (enforced at runtime)
|
476
|
+
- **Multiple key providers**: Support for environment variables, files, and AWS KMS
|
477
|
+
- **Secure storage**: Encrypted values are stored as Base64-encoded strings
|
478
|
+
|
479
|
+
## Configuration Container Usage
|
480
|
+
|
481
|
+
```ruby
|
482
|
+
require 'hati_config'
|
483
|
+
|
484
|
+
module MyGem
|
485
|
+
extend HatiConfig
|
486
|
+
end
|
487
|
+
```
|
488
|
+
|
489
|
+
### Declare configurations
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
MyGem.configure :settings do
|
493
|
+
config :option
|
494
|
+
config.int :typed_opt_one
|
495
|
+
config :typed_opt_two, type: Integer
|
496
|
+
# NOTE: declare nested namespace with configure <symbol arg>
|
497
|
+
configure :nested do
|
498
|
+
config :option
|
499
|
+
end
|
500
|
+
end
|
501
|
+
```
|
502
|
+
|
503
|
+
### Define configurations
|
504
|
+
|
505
|
+
```ruby
|
506
|
+
MyGem.settings do
|
507
|
+
config option: 1
|
508
|
+
config typed_opt_one: 2
|
509
|
+
config typed_opt_two: 3
|
510
|
+
# NOTE: access namespace via <.dot_access>
|
511
|
+
config.nested do
|
512
|
+
config option: 4
|
513
|
+
end
|
514
|
+
end
|
515
|
+
```
|
516
|
+
|
517
|
+
### Define with DSL Syntax
|
518
|
+
|
519
|
+
```ruby
|
520
|
+
MyGem.settings do
|
521
|
+
option 'one'
|
522
|
+
typed_opt_one 1
|
523
|
+
typed_opt_two 2
|
524
|
+
# NOTE: access namespace via <block>
|
525
|
+
nested do
|
526
|
+
option 'nested'
|
527
|
+
end
|
528
|
+
end
|
529
|
+
```
|
530
|
+
|
531
|
+
### Get configurations
|
532
|
+
|
533
|
+
```ruby
|
534
|
+
MyGem.settings.option # => 'one'
|
535
|
+
MyGem.settings.typed_opt_one # => 1
|
536
|
+
MyGem.settings.typed_opt_two # => 2
|
537
|
+
MyGem.settings.nested.option # => 'nested'
|
538
|
+
```
|
539
|
+
|
540
|
+
### Define Configuration Type Validation
|
541
|
+
|
542
|
+
```ruby
|
543
|
+
MyGem.settings do
|
544
|
+
config.typed_opt_two: '1'
|
545
|
+
end
|
546
|
+
# => HatiConfig::SettingTypeError
|
547
|
+
|
548
|
+
MyGem.settings do
|
549
|
+
typed_opt_two '1'
|
550
|
+
end
|
551
|
+
# => HatiConfig::SettingTypeError
|
552
|
+
```
|
553
|
+
|
554
|
+
### Union
|
555
|
+
|
556
|
+
```ruby
|
557
|
+
# List one of entries as built-in :symbols or classes
|
558
|
+
|
559
|
+
MyApp.configure :settings do
|
560
|
+
config transaction_fee: 42, type: [Integer, :float, BigDecimal]
|
561
|
+
config vendor_code: 42, type: [String, :int]
|
562
|
+
end
|
563
|
+
```
|
564
|
+
|
565
|
+
### Custom
|
566
|
+
|
567
|
+
```ruby
|
568
|
+
MyApp.configure :settings do
|
569
|
+
config option: CustomClass.new, type: CustomClass
|
570
|
+
end
|
571
|
+
```
|
572
|
+
|
573
|
+
### Callable
|
574
|
+
|
575
|
+
```ruby
|
576
|
+
acc_proc = Proc.new { |val| val.respond_to?(:accounts) }
|
577
|
+
holder_lam = ->(name) { name.length > 5 }
|
578
|
+
|
579
|
+
MyApp.configure :settings do
|
580
|
+
config acc_data: User.new, type: acc_proc
|
581
|
+
config holder_name: 'John Doe', type: holder_lam
|
582
|
+
end
|
583
|
+
```
|
584
|
+
|
585
|
+
## Loading Configuration Data
|
586
|
+
|
587
|
+
**Use Case**: Your application needs to load configuration from existing YAML files, JSON APIs, or hash data from databases. You want to validate the loaded data against expected schemas and handle format errors gracefully. Different environments might use different config sources (files in development, APIs in production).
|
588
|
+
|
589
|
+
**Why existing tools fall short**:
|
590
|
+
|
591
|
+
- Manual YAML/JSON parsing is error-prone and lacks validation
|
592
|
+
- No schema validation means runtime errors from bad config data
|
593
|
+
- Mixing different config sources requires complex custom code
|
594
|
+
- No unified interface for different data formats
|
595
|
+
|
596
|
+
**HatiConfig solution**: Unified interface for loading from YAML, JSON, and Hash sources with optional schema validation and clear error handling.
|
597
|
+
|
598
|
+
The `HatiConfig` module allows you to load configuration data from various sources, including YAML and JSON. Below are the details for each option.
|
599
|
+
|
600
|
+
- `json` (String)
|
601
|
+
- `yaml` (String)
|
602
|
+
- `hash` (Hash)
|
603
|
+
- `schema` (Hash) (Optional) See: [Type Schema](#type_schema) and [Built-in Types](#built-in-types)
|
604
|
+
|
605
|
+
### Loading from a JSON String
|
606
|
+
|
607
|
+
You can load configuration data from a JSON string by passing the `json` option to the `configure` method.
|
608
|
+
|
609
|
+
#### Parameters
|
610
|
+
|
611
|
+
- `json` (String): A JSON string containing the configuration data.
|
612
|
+
- `schema` (Hash) (Optional): A hash representing the type schema for the configuration data.
|
613
|
+
|
614
|
+
#### Error Handling
|
615
|
+
|
616
|
+
- If the JSON format is invalid, a `LoadDataError` will be raised with the message "Invalid JSON format".
|
617
|
+
|
618
|
+
#### Example 1
|
619
|
+
|
620
|
+
```ruby
|
621
|
+
MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}').settings
|
622
|
+
# => #<MyGem::Setting:0x00007f8c1c0b2a80 @options={:opt_one=>1, :opt_two=>2}>
|
623
|
+
```
|
624
|
+
|
625
|
+
#### Example 2
|
626
|
+
|
627
|
+
```ruby
|
628
|
+
MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}', schema: { opt_one: :int, opt_two: :str })
|
629
|
+
# => HatiConfig::SettingTypeError: Expected: <str>. Given: 2 which is <Integer> class.
|
630
|
+
```
|
631
|
+
|
632
|
+
#### Example 3
|
633
|
+
|
634
|
+
```ruby
|
635
|
+
MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}', schema: { opt_one: :int, opt_two: :int })
|
636
|
+
|
637
|
+
MyGem.settings do
|
638
|
+
opt_one 1
|
639
|
+
opt_two "2"
|
640
|
+
end
|
641
|
+
# => HatiConfig::SettingTypeError: Expected: <intstr>. Given: \"2\" which is <String> class.
|
642
|
+
```
|
643
|
+
|
644
|
+
### Loading from a YAML File
|
645
|
+
|
646
|
+
You can also load configuration data from a YAML file by passing the `yaml` option to the `configure` method.
|
647
|
+
|
648
|
+
#### Parameters
|
649
|
+
|
650
|
+
- `yaml` (String): A file path to a YAML file containing the configuration data.
|
651
|
+
- `schema` (Hash) (Optional): A hash representing the type schema for the configuration data.
|
652
|
+
|
653
|
+
#### Error Handling
|
654
|
+
|
655
|
+
- If the specified YAML file is not found, a `LoadDataError` will be raised with the message "YAML file not found".
|
656
|
+
|
657
|
+
##### YAML File
|
658
|
+
|
659
|
+
```yaml
|
660
|
+
# settings.yml
|
661
|
+
|
662
|
+
opt_one: 1
|
663
|
+
opt_two: 2
|
664
|
+
```
|
665
|
+
|
666
|
+
#### Example 1
|
667
|
+
|
668
|
+
```ruby
|
669
|
+
MyGem.configure :settings, yaml: 'settings.yml'
|
670
|
+
# => #<MyGem::Setting:0x00006f8c1c0b2a80 @options={:opt_one=>1, :opt_two=>2}>
|
671
|
+
```
|
672
|
+
|
673
|
+
#### Example 2
|
674
|
+
|
675
|
+
```ruby
|
676
|
+
MyGem.configure :settings, yaml: 'settings.yml', schema: { opt_one: :int, opt_two: :str }
|
677
|
+
# => HatiConfig::SettingTypeError: Expected: <str>. Given: 2 which is <Integer> class.
|
678
|
+
```
|
679
|
+
|
680
|
+
#### Example 3
|
681
|
+
|
682
|
+
```ruby
|
683
|
+
MyGem.configure :settings, yaml: 'settings.yml', schema: { opt_one: :int, opt_two: :int }
|
684
|
+
|
685
|
+
MyGem.settings do
|
686
|
+
opt_one 1
|
687
|
+
opt_two "2"
|
688
|
+
end
|
689
|
+
# => HatiConfig::SettingTypeError: Expected: <intstr>. Given: \"2\" which is <String> class.
|
690
|
+
```
|
691
|
+
|
692
|
+
## Exporting Configuration Data
|
693
|
+
|
694
|
+
You can dump the configuration data in various formats using the following methods:
|
695
|
+
|
696
|
+
### to_h
|
697
|
+
|
698
|
+
```ruby
|
699
|
+
MyGem.configure :settings do
|
700
|
+
config opt_one: 1
|
701
|
+
config opt_two: 2
|
702
|
+
end
|
703
|
+
|
704
|
+
MyGem.settings.to_json # => '{"opt_one":1,"opt_two":2}'
|
705
|
+
```
|
706
|
+
|
707
|
+
### to_json
|
708
|
+
|
709
|
+
```ruby
|
710
|
+
MyGem.configure :settings do
|
711
|
+
config opt_one: 1
|
712
|
+
config opt_two: 2
|
713
|
+
end
|
714
|
+
|
715
|
+
MyGem.settings.to_json # => '{"opt_one":1,"opt_two":2}'
|
716
|
+
```
|
717
|
+
|
718
|
+
### to_yaml
|
719
|
+
|
720
|
+
```ruby
|
721
|
+
MyGem.configure :settings do
|
722
|
+
config opt_one: 1
|
723
|
+
config opt_two: 2
|
724
|
+
end
|
725
|
+
|
726
|
+
MyGem.settings.to_yaml # => "---\nopt_one: 1\nopt_two: 2\n"
|
727
|
+
```
|
728
|
+
|
729
|
+
### type_schema
|
730
|
+
|
731
|
+
```ruby
|
732
|
+
MyGem.configure :settings do
|
733
|
+
config.int opt_one: 1
|
734
|
+
config.str opt_two: "2"
|
735
|
+
end
|
736
|
+
|
737
|
+
MyGem.settings.type_schema # => {:opt_one=>:int, :opt_two=>:str}
|
738
|
+
```
|
739
|
+
|
740
|
+
## Built-in Types Features
|
741
|
+
|
742
|
+
**Use Case**: Your application crashes in production because someone set a timeout to "30 seconds" instead of 30, or a database pool size to "many" instead of 10. You need runtime type validation that catches these errors early and provides clear error messages. Different config values need different types (strings, integers, arrays, custom objects).
|
743
|
+
|
744
|
+
**Why existing tools fall short**:
|
745
|
+
|
746
|
+
- No runtime type checking means silent failures or crashes
|
747
|
+
- Custom validation code is scattered throughout the application
|
748
|
+
- Error messages are unclear ("expected Integer, got String")
|
749
|
+
- No support for complex types like arrays of specific types or custom classes
|
750
|
+
|
751
|
+
**HatiConfig solution**: Comprehensive type system with built-in types, composite types, custom validators, and clear error messages.
|
752
|
+
|
753
|
+
### Base Types
|
754
|
+
|
755
|
+
```ruby
|
756
|
+
:int => Integer
|
757
|
+
:str => String
|
758
|
+
:sym => Symbol
|
759
|
+
:null => NilClass
|
760
|
+
:any => Object
|
761
|
+
:true_class => TrueClass
|
762
|
+
:false_class => FalseClass
|
763
|
+
```
|
764
|
+
|
765
|
+
### Data Structures
|
766
|
+
|
767
|
+
```ruby
|
768
|
+
:hash => Hash
|
769
|
+
:array => Array
|
770
|
+
```
|
771
|
+
|
772
|
+
### Numeric
|
773
|
+
|
774
|
+
```ruby
|
775
|
+
:big_decimal => BigDecimal,
|
776
|
+
:float => Float,
|
777
|
+
:complex => Complex,
|
778
|
+
:rational => Rational,
|
779
|
+
```
|
780
|
+
|
781
|
+
### Time
|
782
|
+
|
783
|
+
```ruby
|
784
|
+
:date => Date,
|
785
|
+
:date_time => DateTime,
|
786
|
+
:time => Time,
|
787
|
+
```
|
788
|
+
|
789
|
+
### Composite
|
790
|
+
|
791
|
+
```ruby
|
792
|
+
:bool => [TrueClass, FalseClass],
|
793
|
+
:numeric => [Integer, Float, BigDecimal],
|
794
|
+
:kernel_num => [Integer, Float, BigDecimal, Complex, Rational],
|
795
|
+
:chrono => [Date, DateTime, Time]
|
796
|
+
```
|
797
|
+
|
798
|
+
## Authors
|
799
|
+
|
800
|
+
- **Mariya Giy** ([@MarieGiy](https://github.com/MarieGiy))
|
801
|
+
|
802
|
+
## Development
|
803
|
+
|
804
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
805
|
+
|
806
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to publish the `.gem` file to [rubygems.org](https://rubygems.org).
|
807
|
+
|
808
|
+
## Contributing
|
809
|
+
|
810
|
+
Bug reports and feature requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
|
811
|
+
|
812
|
+
## License
|
813
|
+
|
814
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
815
|
+
|
816
|
+
## Code of Conduct
|
817
|
+
|
818
|
+
Everyone interacting in the HatiConfig project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
|