anyway_config 2.3.1 → 2.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|