anyway_config 2.3.1 → 2.4.1
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 +4 -4
- data/CHANGELOG.md +55 -0
- data/README.md +76 -2
- data/lib/.rbnext/2.6/anyway/ejson_parser.rb +40 -0
- data/lib/.rbnext/2.7/anyway/config.rb +39 -30
- data/lib/.rbnext/2.7/anyway/settings.rb +14 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +8 -2
- data/lib/.rbnext/2.7/anyway/type_casting.rb +14 -1
- data/lib/.rbnext/3.0/anyway/config.rb +39 -30
- data/lib/.rbnext/3.0/anyway/loaders.rb +2 -0
- data/lib/.rbnext/3.0/anyway/tracing.rb +8 -2
- data/lib/.rbnext/3.1/anyway/config.rb +39 -30
- data/lib/.rbnext/3.1/anyway/env.rb +22 -5
- data/lib/.rbnext/3.1/anyway/tracing.rb +8 -2
- data/lib/anyway/config.rb +39 -30
- data/lib/anyway/ejson_parser.rb +40 -0
- data/lib/anyway/env.rb +22 -5
- data/lib/anyway/ext/flatten_names.rb +37 -0
- data/lib/anyway/ext/hash.rb +8 -1
- data/lib/anyway/ext/string_constantize.rb +24 -0
- data/lib/anyway/loaders/doppler.rb +63 -0
- data/lib/anyway/loaders/ejson.rb +89 -0
- data/lib/anyway/loaders.rb +2 -0
- data/lib/anyway/rails/settings.rb +22 -14
- data/lib/anyway/settings.rb +14 -0
- data/lib/anyway/tracing.rb +8 -2
- data/lib/anyway/type_casting.rb +11 -1
- data/lib/anyway/utils/which.rb +18 -0
- data/lib/anyway/version.rb +1 -1
- data/lib/anyway_config.rb +7 -0
- data/sig/anyway_config.rbs +7 -2
- metadata +37 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd660b7594fd85e041d4f00a4abbadef72085256e250df638cd239a3512db58c
|
4
|
+
data.tar.gz: 94fa4463eaf4bf2d64cddba05ba1fac18e635f62ff89620f8a460c9cf0162535
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1585d343611b50d1ca021dfbd2dfb49f92b8aad835c7f3c48aabeab4d5cc905877db4ef57a86e51b59c53f9b1192f69c7b58af4ec6c1b182447b146e19b4a3b
|
7
|
+
data.tar.gz: 983a3079303037e6cda97057ad51ef2479000baa1e975d481a7c2f4cd2510605fd36227d3692b1a82f7a85db333a7909e5610515286e1c10adc0aeb0e11f3006
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,57 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 2.4.1 (2023-05-04)
|
6
|
+
|
7
|
+
- Add custom namespace support via `ejson_namespace` ([@bessey])
|
8
|
+
|
9
|
+
- Add arbitrary custom loader options support via `loader_options` ([@bessey])
|
10
|
+
|
11
|
+
## 2.4.0 (2023-04-04)
|
12
|
+
|
13
|
+
- Added `Confi#as_env` to convert config into a ENV-like Hash. ([@tagirahmad][])
|
14
|
+
|
15
|
+
- Added experimental support for sub-configs via coercion. ([@palkan][])
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class AnotherConfig < Anyway::Config
|
19
|
+
attr_config foo: "bar"
|
20
|
+
end
|
21
|
+
|
22
|
+
class MyConfig < Anyway::Config
|
23
|
+
attr_config :another_config
|
24
|
+
|
25
|
+
coerce_types another_config: "AnotherConfig"
|
26
|
+
end
|
27
|
+
|
28
|
+
MyConfig.new.another_config.foo #=> "bar"
|
29
|
+
|
30
|
+
ENV["MY_ANOTHER_CONFIG__FOO"] = "baz"
|
31
|
+
MyConfig.new.another_config.foo #=> "baz"
|
32
|
+
```
|
33
|
+
|
34
|
+
- Define predicate methods when `:boolean` type is specified for the attribute. ([@palkan][])
|
35
|
+
|
36
|
+
- Add support for nested required config attributes. ([@palkan][])
|
37
|
+
|
38
|
+
The API is inspired by Rails permitted params:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class AppConfig < Anyway::Config
|
42
|
+
attr_config :assets_host, database: {host: nil, port: nil}
|
43
|
+
|
44
|
+
required :assets_host, database: [:host, :port]
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
- Add support for using `env_prefix ""` to load from unprefixed env vars. ([@palkan][])
|
49
|
+
|
50
|
+
See [#118](https://github.com/palkan/anyway_config/issues/118).
|
51
|
+
|
52
|
+
- Added EJSON support. ([@inner-whisper])
|
53
|
+
|
54
|
+
- Add Doppler loader. ([@prog-supdex][]).
|
55
|
+
|
5
56
|
## 2.3.1 (2023-01-17)
|
6
57
|
|
7
58
|
- [Fixes [#110](https://github.com/palkan/anyway_config/issues/110)] Fix setting up autoloader for the same folder. ([@palkan][])
|
@@ -469,3 +520,7 @@ No we're dependency-free!
|
|
469
520
|
[@progapandist]: https://github.com/progapandist
|
470
521
|
[@skryukov]: https://github.com/skryukov
|
471
522
|
[@fargelus]: https://github.com/fargelus
|
523
|
+
[@prog-supdex]: https://github.com/prog-supdex
|
524
|
+
[@inner-whisper]: https://github.com/inner-whisper
|
525
|
+
[@tagirahmad]: https://github.com/tagirahmad
|
526
|
+
[@bessey]: https://github.com/bessey
|
data/README.md
CHANGED
@@ -45,6 +45,9 @@ For version 1.x see the [1-4-stable branch](https://github.com/palkan/anyway_con
|
|
45
45
|
- [Type coercion](#type-coercion)
|
46
46
|
- [Local configuration](#local-files)
|
47
47
|
- [Data loaders](#data-loaders)
|
48
|
+
- [Doppler integration](#doppler-integration)
|
49
|
+
- [EJSON support](#ejson-support)
|
50
|
+
- [Custom loaders](#custom-loaders)
|
48
51
|
- [Source tracing](#tracing)
|
49
52
|
- [Pattern matching](#pattern-matching)
|
50
53
|
- [Test helpers](#test-helpers)
|
@@ -269,7 +272,7 @@ This feature is similar to `Rails.application.config_for` but more powerful:
|
|
269
272
|
| Load data from `secrets` | ❌ | ✅ |
|
270
273
|
| Load data from `credentials` | ❌ | ✅ |
|
271
274
|
| Load data from environment | ❌ | ✅ |
|
272
|
-
| Load data from [
|
275
|
+
| Load data from [other sources](#data-loaders) | ❌ | ✅ |
|
273
276
|
| Local config files | ❌ | ✅ |
|
274
277
|
| Type coercion | ❌ | ✅ |
|
275
278
|
| [Source tracing](#tracing) | ❌ | ✅ |
|
@@ -541,6 +544,8 @@ Would you like to generate a heroku.yml file? (Y/n) n
|
|
541
544
|
You can also specify the `--app` option to put the newly created class into `app/configs` folder.
|
542
545
|
Alternatively, you can call `rails g anyway:app_config name param1 param2 ...`.
|
543
546
|
|
547
|
+
**NOTE:** The generated `ApplicationConfig` class uses a singleton pattern along with `delegate_missing_to` to re-use the same instance across the application. However, the delegation can lead to unexpected behaviour and break Anyway Config internals if you have attributes named as `Anyway::Config` class methods. See [#120](https://github.com/palkan/anyway_config/issues/120).
|
548
|
+
|
544
549
|
## Using with Ruby
|
545
550
|
|
546
551
|
The default data loading mechanism for non-Rails applications is the following (ordered by priority from low to high):
|
@@ -733,6 +738,74 @@ Don't forget to add `*.local.yml` (and `config/credentials/local.*`) to your `.g
|
|
733
738
|
|
734
739
|
## Data loaders
|
735
740
|
|
741
|
+
### Doppler integration
|
742
|
+
|
743
|
+
Anyway Config can pull configuration data from [Doppler](https://www.doppler.com/). All you need is to specify the `DOPPLER_TOKEN` environment variable with the **service token**, associated with the specific content (read more about [service tokens](https://docs.doppler.com/docs/service-tokens)).
|
744
|
+
|
745
|
+
You can also configure Doppler loader manually if needed:
|
746
|
+
|
747
|
+
```ruby
|
748
|
+
# Add loader
|
749
|
+
Anyway.loaders.append :Doppler, Anyway::Loaders::Doppler
|
750
|
+
|
751
|
+
# Configure API URL and token (defaults are shown)
|
752
|
+
Anyway::Loaders::Doppler.download_url = "https://api.doppler.com/v3/configs/config/secrets/download"
|
753
|
+
Anyway::Loaders::Doppler.token = ENV["DOPPLER_TOKEN"]
|
754
|
+
```
|
755
|
+
|
756
|
+
**NOTE:** You can opt-out from Doppler loader by specifying the`ANYWAY_CONFIG_DISABLE_DOPPLER=true` env var (in case you have the `DOPPLER_TOKEN` env var, but don't want to use it with Anyway Config).
|
757
|
+
|
758
|
+
### EJSON support
|
759
|
+
|
760
|
+
Anyway Config allows you to keep your configuration also in encrypted `.ejson` files. More information
|
761
|
+
about EJSON format you can read [here](https://github.com/Shopify/ejson).
|
762
|
+
|
763
|
+
Configuration will be loaded only if you have `ejson` executable in your PATH. Easiest way to do this - install `ejson` as a gem into project:
|
764
|
+
|
765
|
+
```ruby
|
766
|
+
# Gemfile
|
767
|
+
gem "ejson"
|
768
|
+
```
|
769
|
+
|
770
|
+
Loading order of configuration is next:
|
771
|
+
|
772
|
+
- `config/secrets.local.ejson` (see [Local files](#local-files) for more information)
|
773
|
+
- `config/<environment>/secrets.ejson` (if you have any multi-environment setup, e.g Rails environments)
|
774
|
+
- `config/secrets.ejson`
|
775
|
+
|
776
|
+
Example of `config/secrets.ejson` file content for your `MyConfig`:
|
777
|
+
|
778
|
+
```json
|
779
|
+
{
|
780
|
+
"_public_key": "0843d33f0eee994adc66b939fe4ef569e4c97db84e238ff581934ee599e19d1a",
|
781
|
+
"my":
|
782
|
+
{
|
783
|
+
"_username": "root",
|
784
|
+
"password": "EJ[1:IC1d347GkxLXdZ0KrjGaY+ljlsK1BmK7CobFt6iOLgE=:Z55OYS1+On0xEBvxUaIOdv/mE2r6lp44:T7bE5hkAbazBnnH6M8bfVcv8TOQJAgUDQffEgw==]"
|
785
|
+
}
|
786
|
+
}
|
787
|
+
```
|
788
|
+
|
789
|
+
To debug any problems with loading configurations from `.ejson` files you can directly call `ejson decrypt`:
|
790
|
+
|
791
|
+
```sh
|
792
|
+
ejson decrypt config/secrets.ejson
|
793
|
+
```
|
794
|
+
|
795
|
+
You can customize the JSON namespace under which a loader searches for configuration via `loader_options`:
|
796
|
+
|
797
|
+
```ruby
|
798
|
+
class MyConfig < Anyway::Config
|
799
|
+
# To look under the key "foo" instead of the default key of "my"
|
800
|
+
loader_options ejson_namespace: "foo"
|
801
|
+
|
802
|
+
# Or to disable namespacing entirely, and instead search in the root object
|
803
|
+
loader_options ejson_namespace: false
|
804
|
+
end
|
805
|
+
```
|
806
|
+
|
807
|
+
### Custom loaders
|
808
|
+
|
736
809
|
You can provide your own data loaders or change the existing ones using the Loaders API (which is very similar to Rack middleware builder):
|
737
810
|
|
738
811
|
```ruby
|
@@ -751,7 +824,8 @@ def call(
|
|
751
824
|
name:, # config name
|
752
825
|
env_prefix:, # prefix for env vars if any
|
753
826
|
config_path:, # path to YML config
|
754
|
-
local
|
827
|
+
local:, # true|false, whether to load local configuration
|
828
|
+
**options # custom options can be passed via Anyway::Config.loader_options example: "custom", option: "blah"
|
755
829
|
)
|
756
830
|
#=> must return Hash with configuration data
|
757
831
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "anyway/ext/hash"
|
5
|
+
|
6
|
+
using Anyway::Ext::Hash
|
7
|
+
|
8
|
+
module Anyway
|
9
|
+
class EJSONParser
|
10
|
+
attr_reader :bin_path
|
11
|
+
|
12
|
+
def initialize(bin_path = "ejson")
|
13
|
+
@bin_path = bin_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(file_path)
|
17
|
+
return unless File.exist?(file_path)
|
18
|
+
|
19
|
+
raw_content = nil
|
20
|
+
|
21
|
+
stdout, stderr, status = Open3.capture3("#{bin_path} decrypt #{file_path}")
|
22
|
+
|
23
|
+
if status.success?
|
24
|
+
raw_content = JSON.parse(stdout.chomp)
|
25
|
+
else
|
26
|
+
Kernel.warn "Failed to decrypt #{file_path}: #{stderr}"
|
27
|
+
end
|
28
|
+
|
29
|
+
return unless raw_content
|
30
|
+
|
31
|
+
raw_content.deep_transform_keys do |key|
|
32
|
+
if key[0] == "_"
|
33
|
+
key[1..-1]
|
34
|
+
else
|
35
|
+
key
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -8,6 +8,7 @@ module Anyway # :nodoc:
|
|
8
8
|
using Anyway::Ext::DeepDup
|
9
9
|
using Anyway::Ext::DeepFreeze
|
10
10
|
using Anyway::Ext::Hash
|
11
|
+
using Anyway::Ext::FlattenNames
|
11
12
|
|
12
13
|
using(Module.new do
|
13
14
|
refine Object do
|
@@ -26,6 +27,7 @@ module Anyway # :nodoc:
|
|
26
27
|
RESERVED_NAMES = %i[
|
27
28
|
config_name
|
28
29
|
env_prefix
|
30
|
+
as_env
|
29
31
|
values
|
30
32
|
class
|
31
33
|
clear
|
@@ -47,8 +49,6 @@ module Anyway # :nodoc:
|
|
47
49
|
__type_caster__
|
48
50
|
].freeze
|
49
51
|
|
50
|
-
ENV_OPTION_EXCLUDE_KEY = :except
|
51
|
-
|
52
52
|
class Error < StandardError; end
|
53
53
|
|
54
54
|
class ValidationError < Error; end
|
@@ -128,34 +128,14 @@ module Anyway # :nodoc:
|
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
131
|
-
def required(*names, env: nil)
|
132
|
-
unknown_names = names - config_attributes
|
131
|
+
def required(*names, env: nil, **nested)
|
132
|
+
unknown_names = names + nested.keys - config_attributes
|
133
133
|
raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
|
134
134
|
|
135
|
-
|
136
|
-
required_attributes.push(*names)
|
137
|
-
end
|
138
|
-
|
139
|
-
def filter_by_env(names, env)
|
140
|
-
return names if env.nil? || env.to_s == current_env
|
141
|
-
|
142
|
-
filtered_names = if env.is_a?(Hash)
|
143
|
-
names_with_exclude_env_option(names, env)
|
144
|
-
elsif env.is_a?(Array)
|
145
|
-
names if env.flat_map(&:to_s).include?(current_env)
|
146
|
-
end
|
147
|
-
|
148
|
-
filtered_names || []
|
149
|
-
end
|
150
|
-
|
151
|
-
def current_env
|
152
|
-
Settings.current_environment.to_s
|
153
|
-
end
|
135
|
+
return unless Settings.matching_env?(env)
|
154
136
|
|
155
|
-
|
156
|
-
|
157
|
-
excluded_envs = [envs].flat_map(&:to_s)
|
158
|
-
names if excluded_envs.none?(current_env)
|
137
|
+
required_attributes.push(*names)
|
138
|
+
required_attributes.push(*nested.flatten_names)
|
159
139
|
end
|
160
140
|
|
161
141
|
def required_attributes
|
@@ -219,10 +199,28 @@ module Anyway # :nodoc:
|
|
219
199
|
end
|
220
200
|
end
|
221
201
|
|
202
|
+
def loader_options(val = nil)
|
203
|
+
return (@loader_options = val) unless val.nil?
|
204
|
+
|
205
|
+
return @loader_options if instance_variable_defined?(:@loader_options)
|
206
|
+
|
207
|
+
@loader_options = if superclass < Anyway::Config
|
208
|
+
superclass.loader_options
|
209
|
+
else
|
210
|
+
{}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
222
214
|
def new_empty_config() ; {}; end
|
223
215
|
|
224
216
|
def coerce_types(mapping)
|
225
217
|
Utils.deep_merge!(coercion_mapping, mapping)
|
218
|
+
|
219
|
+
mapping.each do |key, val|
|
220
|
+
next unless val == :boolean || (val.is_a?(::Hash) && val[:type] == :boolean)
|
221
|
+
|
222
|
+
alias_method :"#{key}?", :"#{key}"
|
223
|
+
end
|
226
224
|
end
|
227
225
|
|
228
226
|
def coercion_mapping
|
@@ -268,7 +266,7 @@ module Anyway # :nodoc:
|
|
268
266
|
names.each do |name|
|
269
267
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
270
268
|
def #{name}=(val)
|
271
|
-
__trace__&.record_value(val,
|
269
|
+
__trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
|
272
270
|
values[:#{name}] = val
|
273
271
|
end
|
274
272
|
|
@@ -357,7 +355,13 @@ module Anyway # :nodoc:
|
|
357
355
|
|
358
356
|
config_path = resolve_config_path(config_name, env_prefix)
|
359
357
|
|
360
|
-
load_from_sources(
|
358
|
+
load_from_sources(
|
359
|
+
base_config,
|
360
|
+
name: config_name,
|
361
|
+
env_prefix: env_prefix,
|
362
|
+
config_path: config_path,
|
363
|
+
**self.class.loader_options
|
364
|
+
)
|
361
365
|
|
362
366
|
if overrides
|
363
367
|
Tracing.trace!(:load) { overrides }
|
@@ -430,13 +434,18 @@ module Anyway # :nodoc:
|
|
430
434
|
end
|
431
435
|
end
|
432
436
|
|
437
|
+
def as_env
|
438
|
+
Env.from_hash(to_h, prefix: env_prefix)
|
439
|
+
end
|
440
|
+
|
433
441
|
private
|
434
442
|
|
435
443
|
attr_reader :values, :__trace__
|
436
444
|
|
437
445
|
def validate_required_attributes!
|
438
446
|
self.class.required_attributes.select do |name|
|
439
|
-
values
|
447
|
+
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
448
|
+
val.nil? || (val.is_a?(String) && val.empty?)
|
440
449
|
end.then do |missing|
|
441
450
|
next if missing.empty?
|
442
451
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
@@ -80,6 +80,20 @@ module Anyway
|
|
80
80
|
def default_environmental_key?
|
81
81
|
!default_environmental_key.nil?
|
82
82
|
end
|
83
|
+
|
84
|
+
def matching_env?(env)
|
85
|
+
return true if env.nil? || env.to_s == current_environment
|
86
|
+
|
87
|
+
if env.is_a?(::Hash)
|
88
|
+
envs = env[:except]
|
89
|
+
excluded_envs = [envs].flat_map(&:to_s)
|
90
|
+
excluded_envs.none?(current_environment)
|
91
|
+
elsif env.is_a?(::Array)
|
92
|
+
env.flat_map(&:to_s).include?(current_environment)
|
93
|
+
else
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
83
97
|
end
|
84
98
|
|
85
99
|
# By default, use local files only in development (that's the purpose if the local files)
|
@@ -19,7 +19,7 @@ module Anyway
|
|
19
19
|
def initialize(type = :trace, value = UNDEF, **source)
|
20
20
|
@type = type
|
21
21
|
@source = source
|
22
|
-
@value = value == UNDEF ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
22
|
+
@value = (value == UNDEF) ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
23
23
|
end
|
24
24
|
|
25
25
|
def dig(*__rest__, &__block__)
|
@@ -35,7 +35,7 @@ module Anyway
|
|
35
35
|
end
|
36
36
|
|
37
37
|
target_trace = path.empty? ? self : value.dig(*path)
|
38
|
-
target_trace.
|
38
|
+
target_trace.record_key(key.to_s, trace)
|
39
39
|
|
40
40
|
val
|
41
41
|
end
|
@@ -54,6 +54,12 @@ module Anyway
|
|
54
54
|
hash
|
55
55
|
end
|
56
56
|
|
57
|
+
def record_key(key, key_trace)
|
58
|
+
@value = Hash.new { |h, k| h[k] = Trace.new(:trace) } unless value.is_a?(::Hash)
|
59
|
+
|
60
|
+
value[key] = key_trace
|
61
|
+
end
|
62
|
+
|
57
63
|
def merge!(another_trace)
|
58
64
|
raise ArgumentError, "You can only merge into a :trace type, and this is :#{type}" unless trace?
|
59
65
|
raise ArgumentError, "You can only merge a :trace type, but trying :#{type}" unless another_trace.trace?
|
@@ -90,6 +90,11 @@ module Anyway
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
+
unless "".respond_to?(:safe_constantize)
|
94
|
+
require "anyway/ext/string_constantize"
|
95
|
+
using Anyway::Ext::StringConstantize
|
96
|
+
end
|
97
|
+
|
93
98
|
# TypeCaster is an object responsible for type-casting.
|
94
99
|
# It uses a provided types registry and mapping, and also
|
95
100
|
# accepts a fallback typecaster.
|
@@ -109,8 +114,16 @@ module Anyway
|
|
109
114
|
return fallback.coerce(key, val) unless caster_config
|
110
115
|
|
111
116
|
case; when ((__m__ = caster_config)) && false
|
112
|
-
when (((array, type) = nil) || ((__m__.respond_to?(:deconstruct_keys) && (((__m_hash__src__ = __m__.deconstruct_keys(nil)) || true) && (Hash === __m_hash__src__ || Kernel.raise(TypeError, "#deconstruct_keys must return Hash"))) && (__m_hash__ = __m_hash__src__.dup)) && ((__m_hash__.key?(:array) && __m_hash__.key?(:type)) && (((array = __m_hash__.delete(:array)) || true) && (((type = __m_hash__.delete(:type)) || true) && __m_hash__.empty?)))))
|
117
|
+
when (((array, type) = nil) || ((Hash === __m__) && ((__m__.respond_to?(:deconstruct_keys) && (((__m_hash__src__ = __m__.deconstruct_keys(nil)) || true) && (Hash === __m_hash__src__ || Kernel.raise(TypeError, "#deconstruct_keys must return Hash"))) && (__m_hash__ = __m_hash__src__.dup)) && ((__m_hash__.key?(:array) && __m_hash__.key?(:type)) && (((array = __m_hash__.delete(:array)) || true) && (((type = __m_hash__.delete(:type)) || true) && __m_hash__.empty?))))))
|
113
118
|
registry.deserialize(val, type, array: array)
|
119
|
+
when (((subconfig,) = nil) || ((Hash === __m__) && (__m__.respond_to?(:deconstruct_keys) && (((__m_hash__ = __m__.deconstruct_keys([:config])) || true) && (Hash === __m_hash__ || Kernel.raise(TypeError, "#deconstruct_keys must return Hash"))) && (__m_hash__.key?(:config) && ((subconfig = __m_hash__[:config]) || true)))))
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
subconfig = subconfig.safe_constantize if subconfig.is_a?(::String)
|
124
|
+
raise ArgumentError, "Config is not found: #{subconfig}" unless subconfig
|
125
|
+
|
126
|
+
subconfig.new(val)
|
114
127
|
when (Hash === __m__)
|
115
128
|
|
116
129
|
|
@@ -8,6 +8,7 @@ module Anyway # :nodoc:
|
|
8
8
|
using Anyway::Ext::DeepDup
|
9
9
|
using Anyway::Ext::DeepFreeze
|
10
10
|
using Anyway::Ext::Hash
|
11
|
+
using Anyway::Ext::FlattenNames
|
11
12
|
|
12
13
|
using(Module.new do
|
13
14
|
refine Object do
|
@@ -26,6 +27,7 @@ module Anyway # :nodoc:
|
|
26
27
|
RESERVED_NAMES = %i[
|
27
28
|
config_name
|
28
29
|
env_prefix
|
30
|
+
as_env
|
29
31
|
values
|
30
32
|
class
|
31
33
|
clear
|
@@ -47,8 +49,6 @@ module Anyway # :nodoc:
|
|
47
49
|
__type_caster__
|
48
50
|
].freeze
|
49
51
|
|
50
|
-
ENV_OPTION_EXCLUDE_KEY = :except
|
51
|
-
|
52
52
|
class Error < StandardError; end
|
53
53
|
|
54
54
|
class ValidationError < Error; end
|
@@ -128,34 +128,14 @@ module Anyway # :nodoc:
|
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
131
|
-
def required(*names, env: nil)
|
132
|
-
unknown_names = names - config_attributes
|
131
|
+
def required(*names, env: nil, **nested)
|
132
|
+
unknown_names = names + nested.keys - config_attributes
|
133
133
|
raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
|
134
134
|
|
135
|
-
|
136
|
-
required_attributes.push(*names)
|
137
|
-
end
|
138
|
-
|
139
|
-
def filter_by_env(names, env)
|
140
|
-
return names if env.nil? || env.to_s == current_env
|
141
|
-
|
142
|
-
filtered_names = if env.is_a?(Hash)
|
143
|
-
names_with_exclude_env_option(names, env)
|
144
|
-
elsif env.is_a?(Array)
|
145
|
-
names if env.flat_map(&:to_s).include?(current_env)
|
146
|
-
end
|
147
|
-
|
148
|
-
filtered_names || []
|
149
|
-
end
|
150
|
-
|
151
|
-
def current_env
|
152
|
-
Settings.current_environment.to_s
|
153
|
-
end
|
135
|
+
return unless Settings.matching_env?(env)
|
154
136
|
|
155
|
-
|
156
|
-
|
157
|
-
excluded_envs = [envs].flat_map(&:to_s)
|
158
|
-
names if excluded_envs.none?(current_env)
|
137
|
+
required_attributes.push(*names)
|
138
|
+
required_attributes.push(*nested.flatten_names)
|
159
139
|
end
|
160
140
|
|
161
141
|
def required_attributes
|
@@ -219,10 +199,28 @@ module Anyway # :nodoc:
|
|
219
199
|
end
|
220
200
|
end
|
221
201
|
|
202
|
+
def loader_options(val = nil)
|
203
|
+
return (@loader_options = val) unless val.nil?
|
204
|
+
|
205
|
+
return @loader_options if instance_variable_defined?(:@loader_options)
|
206
|
+
|
207
|
+
@loader_options = if superclass < Anyway::Config
|
208
|
+
superclass.loader_options
|
209
|
+
else
|
210
|
+
{}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
222
214
|
def new_empty_config() ; {}; end
|
223
215
|
|
224
216
|
def coerce_types(mapping)
|
225
217
|
Utils.deep_merge!(coercion_mapping, mapping)
|
218
|
+
|
219
|
+
mapping.each do |key, val|
|
220
|
+
next unless val == :boolean || (val.is_a?(::Hash) && val[:type] == :boolean)
|
221
|
+
|
222
|
+
alias_method :"#{key}?", :"#{key}"
|
223
|
+
end
|
226
224
|
end
|
227
225
|
|
228
226
|
def coercion_mapping
|
@@ -268,7 +266,7 @@ module Anyway # :nodoc:
|
|
268
266
|
names.each do |name|
|
269
267
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
270
268
|
def #{name}=(val)
|
271
|
-
__trace__&.record_value(val,
|
269
|
+
__trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
|
272
270
|
values[:#{name}] = val
|
273
271
|
end
|
274
272
|
|
@@ -357,7 +355,13 @@ module Anyway # :nodoc:
|
|
357
355
|
|
358
356
|
config_path = resolve_config_path(config_name, env_prefix)
|
359
357
|
|
360
|
-
load_from_sources(
|
358
|
+
load_from_sources(
|
359
|
+
base_config,
|
360
|
+
name: config_name,
|
361
|
+
env_prefix: env_prefix,
|
362
|
+
config_path: config_path,
|
363
|
+
**self.class.loader_options
|
364
|
+
)
|
361
365
|
|
362
366
|
if overrides
|
363
367
|
Tracing.trace!(:load) { overrides }
|
@@ -430,13 +434,18 @@ module Anyway # :nodoc:
|
|
430
434
|
end
|
431
435
|
end
|
432
436
|
|
437
|
+
def as_env
|
438
|
+
Env.from_hash(to_h, prefix: env_prefix)
|
439
|
+
end
|
440
|
+
|
433
441
|
private
|
434
442
|
|
435
443
|
attr_reader :values, :__trace__
|
436
444
|
|
437
445
|
def validate_required_attributes!
|
438
446
|
self.class.required_attributes.select do |name|
|
439
|
-
values
|
447
|
+
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
448
|
+
val.nil? || (val.is_a?(String) && val.empty?)
|
440
449
|
end.then do |missing|
|
441
450
|
next if missing.empty?
|
442
451
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
@@ -19,7 +19,7 @@ module Anyway
|
|
19
19
|
def initialize(type = :trace, value = UNDEF, **source)
|
20
20
|
@type = type
|
21
21
|
@source = source
|
22
|
-
@value = value == UNDEF ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
22
|
+
@value = (value == UNDEF) ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
23
23
|
end
|
24
24
|
|
25
25
|
def dig(...)
|
@@ -35,7 +35,7 @@ module Anyway
|
|
35
35
|
end
|
36
36
|
|
37
37
|
target_trace = path.empty? ? self : value.dig(*path)
|
38
|
-
target_trace.
|
38
|
+
target_trace.record_key(key.to_s, trace)
|
39
39
|
|
40
40
|
val
|
41
41
|
end
|
@@ -54,6 +54,12 @@ module Anyway
|
|
54
54
|
hash
|
55
55
|
end
|
56
56
|
|
57
|
+
def record_key(key, key_trace)
|
58
|
+
@value = Hash.new { |h, k| h[k] = Trace.new(:trace) } unless value.is_a?(::Hash)
|
59
|
+
|
60
|
+
value[key] = key_trace
|
61
|
+
end
|
62
|
+
|
57
63
|
def merge!(another_trace)
|
58
64
|
raise ArgumentError, "You can only merge into a :trace type, and this is :#{type}" unless trace?
|
59
65
|
raise ArgumentError, "You can only merge a :trace type, but trying :#{type}" unless another_trace.trace?
|