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.
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.