anyway_config 2.0.6 → 2.2.2
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 +241 -181
- data/README.md +237 -12
- data/lib/.rbnext/1995.next/anyway/config.rb +438 -0
- data/lib/.rbnext/1995.next/anyway/dynamic_config.rb +31 -0
- data/lib/.rbnext/1995.next/anyway/env.rb +56 -0
- data/lib/.rbnext/1995.next/anyway/loaders/base.rb +21 -0
- data/lib/.rbnext/1995.next/anyway/tracing.rb +181 -0
- data/lib/.rbnext/2.7/anyway/auto_cast.rb +39 -19
- data/lib/.rbnext/2.7/anyway/config.rb +60 -15
- data/lib/.rbnext/2.7/anyway/rails/loaders/yaml.rb +30 -0
- data/lib/.rbnext/2.7/anyway/rbs.rb +92 -0
- data/lib/.rbnext/2.7/anyway/settings.rb +79 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +1 -1
- data/lib/.rbnext/2.7/anyway/type_casting.rb +143 -0
- data/lib/.rbnext/3.0/anyway/auto_cast.rb +53 -0
- data/lib/.rbnext/{2.8 → 3.0}/anyway/config.rb +60 -15
- data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders/base.rb +0 -0
- data/lib/.rbnext/{2.8 → 3.0}/anyway/loaders.rb +0 -0
- data/lib/.rbnext/{2.8 → 3.0}/anyway/tracing.rb +1 -1
- data/lib/anyway/auto_cast.rb +39 -19
- data/lib/anyway/config.rb +74 -29
- data/lib/anyway/dynamic_config.rb +6 -2
- data/lib/anyway/env.rb +1 -1
- data/lib/anyway/ext/deep_dup.rb +12 -0
- data/lib/anyway/ext/hash.rb +0 -12
- data/lib/anyway/loaders/base.rb +1 -1
- data/lib/anyway/loaders/env.rb +3 -1
- data/lib/anyway/loaders/yaml.rb +9 -5
- data/lib/anyway/option_parser_builder.rb +1 -3
- data/lib/anyway/optparse_config.rb +5 -7
- data/lib/anyway/rails/loaders/credentials.rb +4 -4
- data/lib/anyway/rails/loaders/secrets.rb +6 -8
- data/lib/anyway/rails/loaders/yaml.rb +11 -0
- data/lib/anyway/rails/settings.rb +9 -2
- data/lib/anyway/rbs.rb +92 -0
- data/lib/anyway/settings.rb +52 -2
- data/lib/anyway/tracing.rb +6 -6
- data/lib/anyway/type_casting.rb +134 -0
- data/lib/anyway/utils/deep_merge.rb +21 -0
- data/lib/anyway/version.rb +1 -1
- data/lib/anyway_config.rb +4 -0
- data/sig/anyway_config.rbs +129 -0
- metadata +42 -15
- data/lib/.rbnext/2.7/anyway/option_parser_builder.rb +0 -31
data/lib/anyway/env.rb
CHANGED
@@ -49,7 +49,7 @@ module Anyway
|
|
49
49
|
path = key.sub(/^#{prefix}_/, "").downcase
|
50
50
|
|
51
51
|
paths = path.split("__")
|
52
|
-
trace!(:env, *paths, key:
|
52
|
+
trace!(:env, *paths, key:) { data.bury(type_cast.call(val), *paths) }
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
data/lib/anyway/ext/deep_dup.rb
CHANGED
data/lib/anyway/ext/hash.rb
CHANGED
@@ -5,18 +5,6 @@ module Anyway
|
|
5
5
|
# Extend Hash through refinements
|
6
6
|
module Hash
|
7
7
|
refine ::Hash do
|
8
|
-
# From ActiveSupport http://api.rubyonrails.org/classes/Hash.html#method-i-deep_merge
|
9
|
-
def deep_merge!(other_hash)
|
10
|
-
merge!(other_hash) do |key, this_value, other_value|
|
11
|
-
if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
|
12
|
-
this_value.deep_merge!(other_value)
|
13
|
-
this_value
|
14
|
-
else
|
15
|
-
other_value
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
8
|
def stringify_keys!
|
21
9
|
keys.each do |key|
|
22
10
|
value = delete(key)
|
data/lib/anyway/loaders/base.rb
CHANGED
data/lib/anyway/loaders/env.rb
CHANGED
@@ -6,7 +6,9 @@ module Anyway
|
|
6
6
|
module Loaders
|
7
7
|
class Env < Base
|
8
8
|
def call(env_prefix:, **_options)
|
9
|
-
|
9
|
+
env = ::Anyway::Env.new(type_cast: ::Anyway::NoCast)
|
10
|
+
|
11
|
+
env.fetch_with_trace(env_prefix).then do |(conf, trace)|
|
10
12
|
Tracing.current_trace&.merge!(trace)
|
11
13
|
conf
|
12
14
|
end
|
data/lib/anyway/loaders/yaml.rb
CHANGED
@@ -17,7 +17,7 @@ module Anyway
|
|
17
17
|
local_path = local_config_path(config_path)
|
18
18
|
local_config = trace!(:yml, path: relative_config_path(local_path).to_s) { load_local_yml(local_path) }
|
19
19
|
|
20
|
-
|
20
|
+
Utils.deep_merge!(base_config, local_config)
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
@@ -25,15 +25,19 @@ module Anyway
|
|
25
25
|
def parse_yml(path)
|
26
26
|
return {} unless File.file?(path)
|
27
27
|
require "yaml" unless defined?(::YAML)
|
28
|
+
|
29
|
+
# By default, YAML load will return `false` when the yaml document is
|
30
|
+
# empty. When this occurs, we return an empty hash instead, to match
|
31
|
+
# the interface when no config file is present.
|
28
32
|
if defined?(ERB)
|
29
|
-
::YAML.load(ERB.new(File.read(path)).result) # rubocop:disable Security/YAMLLoad
|
33
|
+
::YAML.load(ERB.new(File.read(path)).result) || {} # rubocop:disable Security/YAMLLoad
|
30
34
|
else
|
31
|
-
::YAML.load_file(path)
|
35
|
+
::YAML.load_file(path) || {}
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
35
|
-
|
36
|
-
|
39
|
+
alias_method :load_base_yml, :parse_yml
|
40
|
+
alias_method :load_local_yml, :parse_yml
|
37
41
|
|
38
42
|
def local_config_path(path)
|
39
43
|
path.sub(/\.yml/, ".local.yml")
|
@@ -8,8 +8,6 @@ module Anyway # :nodoc:
|
|
8
8
|
class << self
|
9
9
|
def call(options)
|
10
10
|
OptionParser.new do |opts|
|
11
|
-
opts.accept(AutoCast) { AutoCast.call(_1) }
|
12
|
-
|
13
11
|
options.each do |key, descriptor|
|
14
12
|
opts.on(*option_parser_on_args(key, **descriptor)) do |val|
|
15
13
|
yield [key, val]
|
@@ -20,7 +18,7 @@ module Anyway # :nodoc:
|
|
20
18
|
|
21
19
|
private
|
22
20
|
|
23
|
-
def option_parser_on_args(key, flag: false, desc: nil, type:
|
21
|
+
def option_parser_on_args(key, flag: false, desc: nil, type: ::String)
|
24
22
|
on_args = ["--#{key.to_s.tr("_", "-")}#{flag ? "" : " VALUE"}"]
|
25
23
|
on_args << type unless flag
|
26
24
|
on_args << desc unless desc.nil?
|
@@ -70,13 +70,11 @@ module Anyway
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def option_parser
|
73
|
-
@option_parser ||=
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
extension.call(parser, self)
|
79
|
-
end
|
73
|
+
@option_parser ||= OptionParserBuilder.call(self.class.option_parser_options) do |key, val|
|
74
|
+
write_config_attr(key, val)
|
75
|
+
end.tap do |parser|
|
76
|
+
self.class.option_parser_extensions.map do |extension|
|
77
|
+
extension.call(parser, self)
|
80
78
|
end
|
81
79
|
end
|
82
80
|
end
|
@@ -22,15 +22,15 @@ module Anyway
|
|
22
22
|
:credentials,
|
23
23
|
store: credentials_path
|
24
24
|
) do
|
25
|
-
::Rails.application.credentials.
|
25
|
+
::Rails.application.credentials.config[name.to_sym]
|
26
26
|
end.then do |creds|
|
27
|
-
|
27
|
+
Utils.deep_merge!(config, creds) if creds
|
28
28
|
end
|
29
29
|
|
30
30
|
if use_local?
|
31
31
|
trace!(:credentials, store: LOCAL_CONTENT_PATH) do
|
32
32
|
local_credentials(name)
|
33
|
-
end.then { |creds|
|
33
|
+
end.then { |creds| Utils.deep_merge!(config, creds) if creds }
|
34
34
|
end
|
35
35
|
|
36
36
|
config
|
@@ -48,7 +48,7 @@ module Anyway
|
|
48
48
|
key_path: ::Rails.root.join("config/credentials/local.key")
|
49
49
|
)
|
50
50
|
|
51
|
-
creds.
|
51
|
+
creds.config[name.to_sym]
|
52
52
|
end
|
53
53
|
|
54
54
|
def credentials_path
|
@@ -15,7 +15,7 @@ module Anyway
|
|
15
15
|
trace!(:secrets) do
|
16
16
|
secrets.public_send(name)
|
17
17
|
end.then do |secrets|
|
18
|
-
|
18
|
+
Utils.deep_merge!(config, secrets) if secrets
|
19
19
|
end
|
20
20
|
|
21
21
|
config
|
@@ -24,13 +24,11 @@ module Anyway
|
|
24
24
|
private
|
25
25
|
|
26
26
|
def secrets
|
27
|
-
@secrets ||=
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
::Rails.application.remove_instance_variable(:@secrets)
|
33
|
-
end
|
27
|
+
@secrets ||= ::Rails.application.secrets.tap do |_|
|
28
|
+
# Reset secrets state if the app hasn't been initialized
|
29
|
+
# See https://github.com/palkan/anyway_config/issues/14
|
30
|
+
next if ::Rails.application.initialized?
|
31
|
+
::Rails.application.remove_instance_variable(:@secrets)
|
34
32
|
end
|
35
33
|
end
|
36
34
|
end
|
@@ -5,11 +5,22 @@ module Anyway
|
|
5
5
|
module Loaders
|
6
6
|
class YAML < Anyway::Loaders::YAML
|
7
7
|
def load_base_yml(*)
|
8
|
+
parsed_yml = super
|
9
|
+
return parsed_yml unless environmental?(parsed_yml)
|
10
|
+
|
8
11
|
super[::Rails.env] || {}
|
9
12
|
end
|
10
13
|
|
11
14
|
private
|
12
15
|
|
16
|
+
def environmental?(parsed_yml)
|
17
|
+
return true unless Settings.future.unwrap_known_environments
|
18
|
+
# likely
|
19
|
+
return true if parsed_yml.key?(::Rails.env)
|
20
|
+
# less likely
|
21
|
+
::Rails.application.config.anyway_config.known_environments.any? { parsed_yml.key?(_1) }
|
22
|
+
end
|
23
|
+
|
13
24
|
def relative_config_path(path)
|
14
25
|
Pathname.new(path).relative_path_from(::Rails.root)
|
15
26
|
end
|
@@ -9,8 +9,13 @@ end
|
|
9
9
|
|
10
10
|
module Anyway
|
11
11
|
class Settings
|
12
|
+
class Future
|
13
|
+
setting :unwrap_known_environments, true
|
14
|
+
end
|
15
|
+
|
12
16
|
class << self
|
13
17
|
attr_reader :autoload_static_config_path, :autoloader
|
18
|
+
attr_accessor :known_environments
|
14
19
|
|
15
20
|
if defined?(::Zeitwerk)
|
16
21
|
def autoload_static_config_path=(val)
|
@@ -22,10 +27,11 @@ module Anyway
|
|
22
27
|
|
23
28
|
@autoload_static_config_path = val
|
24
29
|
|
25
|
-
# See https://github.com/rails/rails/blob/8ab4fd12f18203b83d0f252db96d10731485ff6a/railties/lib/rails/autoloaders.rb#L10
|
30
|
+
# See Rails 6 https://github.com/rails/rails/blob/8ab4fd12f18203b83d0f252db96d10731485ff6a/railties/lib/rails/autoloaders.rb#L10
|
31
|
+
# and Rails 7 https://github.com/rails/rails/blob/5462fbd5de1900c1b1ce1c9dc11c1a2d8cdcd809/railties/lib/rails/autoloaders.rb#L15
|
26
32
|
@autoloader = Zeitwerk::Loader.new.tap do |loader|
|
27
33
|
loader.tag = "anyway.config"
|
28
|
-
loader.inflector = ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector
|
34
|
+
loader.inflector = defined?(ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector) ? ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector : ::Rails::Autoloaders::Inflector
|
29
35
|
loader.push_dir(::Rails.root.join(val))
|
30
36
|
loader.setup
|
31
37
|
end
|
@@ -56,5 +62,6 @@ module Anyway
|
|
56
62
|
end
|
57
63
|
|
58
64
|
self.default_config_path = ->(name) { ::Rails.root.join("config", "#{name}.yml") }
|
65
|
+
self.known_environments = %w[test development production]
|
59
66
|
end
|
60
67
|
end
|
data/lib/anyway/rbs.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
module RBSGenerator
|
5
|
+
TYPE_TO_CLASS = {
|
6
|
+
string: "String",
|
7
|
+
integer: "Integer",
|
8
|
+
float: "Float",
|
9
|
+
date: "Date",
|
10
|
+
datetime: "DateTime",
|
11
|
+
uri: "URI",
|
12
|
+
boolean: "bool"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# Generate RBS signature from a config class
|
16
|
+
def to_rbs
|
17
|
+
*namespace, class_name = name.split("::")
|
18
|
+
|
19
|
+
buf = []
|
20
|
+
indent = 0
|
21
|
+
interface_name = "_Config"
|
22
|
+
|
23
|
+
if namespace.empty?
|
24
|
+
interface_name = "_#{class_name}"
|
25
|
+
else
|
26
|
+
buf << "module #{namespace.join("::")}"
|
27
|
+
indent += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
# Using interface emulates a module we include to provide getters and setters
|
31
|
+
# (thus making `super` possible)
|
32
|
+
buf << "#{" " * indent}interface #{interface_name}"
|
33
|
+
indent += 1
|
34
|
+
|
35
|
+
# Generating setters and getters for config attributes
|
36
|
+
config_attributes.each do |param|
|
37
|
+
type = coercion_mapping[param] || defaults[param.to_s]
|
38
|
+
|
39
|
+
type =
|
40
|
+
case type
|
41
|
+
in NilClass
|
42
|
+
"untyped"
|
43
|
+
in Symbol
|
44
|
+
TYPE_TO_CLASS.fetch(type) { defaults[param] ? "Symbol" : "untyped" }
|
45
|
+
in Array
|
46
|
+
"Array[untyped]"
|
47
|
+
in array:, type:, **nil
|
48
|
+
"Array[#{TYPE_TO_CLASS.fetch(type, "untyped")}]"
|
49
|
+
in Hash
|
50
|
+
"Hash[string,untyped]"
|
51
|
+
in TrueClass | FalseClass
|
52
|
+
"bool"
|
53
|
+
else
|
54
|
+
type.class.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
getter_type = type
|
58
|
+
getter_type = "#{type}?" unless required_attributes.include?(param)
|
59
|
+
|
60
|
+
buf << "#{" " * indent}def #{param}: () -> #{getter_type}"
|
61
|
+
buf << "#{" " * indent}def #{param}=: (#{type}) -> void"
|
62
|
+
|
63
|
+
if type == "bool" || type == "bool?"
|
64
|
+
buf << "#{" " * indent}def #{param}?: () -> #{getter_type}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
indent -= 1
|
69
|
+
buf << "#{" " * indent}end"
|
70
|
+
|
71
|
+
buf << ""
|
72
|
+
|
73
|
+
buf << "#{" " * indent}class #{class_name} < #{superclass.name}"
|
74
|
+
indent += 1
|
75
|
+
|
76
|
+
buf << "#{" " * indent}include #{interface_name}"
|
77
|
+
|
78
|
+
indent -= 1
|
79
|
+
buf << "#{" " * indent}end"
|
80
|
+
|
81
|
+
unless namespace.empty?
|
82
|
+
buf << "end"
|
83
|
+
end
|
84
|
+
|
85
|
+
buf << ""
|
86
|
+
|
87
|
+
buf.join("\n")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Config.extend RBSGenerator
|
92
|
+
end
|
data/lib/anyway/settings.rb
CHANGED
@@ -5,16 +5,66 @@ module Anyway
|
|
5
5
|
#
|
6
6
|
# Settings contain the library-wide configuration.
|
7
7
|
class Settings
|
8
|
+
# Future encapsulates settings that will be introduced in the upcoming version
|
9
|
+
# with the default values, which could break compatibility
|
10
|
+
class Future
|
11
|
+
class << self
|
12
|
+
def setting(name, default_value)
|
13
|
+
settings[name] = default_value
|
14
|
+
|
15
|
+
define_method(name) do
|
16
|
+
store[name]
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method(:"#{name}=") do |val|
|
20
|
+
store[name] = val
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def settings
|
25
|
+
@settings ||= {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@store = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def use(*names)
|
34
|
+
store.clear
|
35
|
+
names.each { store[_1] = self.class.settings[_1] }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :store
|
41
|
+
end
|
42
|
+
|
8
43
|
class << self
|
9
44
|
# Define whether to load data from
|
10
45
|
# *.yml.local (or credentials/local.yml.enc)
|
11
46
|
attr_accessor :use_local_files
|
12
47
|
|
13
|
-
#
|
14
|
-
|
48
|
+
# A proc returning a path to YML config file given the config name
|
49
|
+
attr_reader :default_config_path
|
50
|
+
|
51
|
+
def default_config_path=(val)
|
52
|
+
if val.is_a?(Proc)
|
53
|
+
@default_config_path = val
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
val = val.to_s
|
58
|
+
|
59
|
+
@default_config_path = ->(name) { File.join(val, "#{name}.yml") }
|
60
|
+
end
|
15
61
|
|
16
62
|
# Enable source tracing
|
17
63
|
attr_accessor :tracing_enabled
|
64
|
+
|
65
|
+
def future
|
66
|
+
@future ||= Future.new
|
67
|
+
end
|
18
68
|
end
|
19
69
|
|
20
70
|
# By default, use local files only in development (that's the purpose if the local files)
|
data/lib/anyway/tracing.rb
CHANGED
@@ -34,11 +34,11 @@ module Anyway
|
|
34
34
|
|
35
35
|
def record_value(val, *path, **opts)
|
36
36
|
key = path.pop
|
37
|
-
if val.is_a?(Hash)
|
37
|
+
trace = if val.is_a?(Hash)
|
38
38
|
Trace.new.tap { _1.merge_values(val, **opts) }
|
39
39
|
else
|
40
40
|
Trace.new(:value, val, **opts)
|
41
|
-
end
|
41
|
+
end
|
42
42
|
|
43
43
|
target_trace = path.empty? ? self : value.dig(*path)
|
44
44
|
target_trace.value[key.to_s] = trace
|
@@ -86,7 +86,7 @@ module Anyway
|
|
86
86
|
if trace?
|
87
87
|
value.transform_values(&:to_h).tap { _1.default_proc = nil }
|
88
88
|
else
|
89
|
-
{value
|
89
|
+
{value, source}
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -141,7 +141,7 @@ module Anyway
|
|
141
141
|
|
142
142
|
def current_trace() = trace_stack.last
|
143
143
|
|
144
|
-
|
144
|
+
alias_method :tracing?, :current_trace
|
145
145
|
|
146
146
|
def source_stack
|
147
147
|
(Thread.current[:__anyway__trace_source_stack__] ||= [])
|
@@ -171,9 +171,9 @@ module Anyway
|
|
171
171
|
return yield unless Tracing.tracing?
|
172
172
|
val = yield
|
173
173
|
if val.is_a?(Hash)
|
174
|
-
Tracing.current_trace.merge_values(val, type
|
174
|
+
Tracing.current_trace.merge_values(val, type:, **opts)
|
175
175
|
else
|
176
|
-
Tracing.current_trace.record_value(val, *path, type
|
176
|
+
Tracing.current_trace.record_value(val, *path, type:, **opts)
|
177
177
|
end
|
178
178
|
val
|
179
179
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
# Contains a mapping between type IDs/names and deserializers
|
5
|
+
class TypeRegistry
|
6
|
+
class << self
|
7
|
+
def default
|
8
|
+
@default ||= TypeRegistry.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@registry = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def accept(name_or_object, &block)
|
17
|
+
if !block && !name_or_object.respond_to?(:call)
|
18
|
+
raise ArgumentError, "Please, provide a type casting block or an object implementing #call(val) method"
|
19
|
+
end
|
20
|
+
|
21
|
+
registry[name_or_object] = block || name_or_object
|
22
|
+
end
|
23
|
+
|
24
|
+
def deserialize(raw, type_id, array: false)
|
25
|
+
return if raw.nil?
|
26
|
+
|
27
|
+
caster =
|
28
|
+
if type_id.is_a?(Symbol) || type_id.nil?
|
29
|
+
registry.fetch(type_id) { raise ArgumentError, "Unknown type: #{type_id}" }
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Type must implement #call(val): #{type_id}" unless type_id.respond_to?(:call)
|
32
|
+
type_id
|
33
|
+
end
|
34
|
+
|
35
|
+
if array
|
36
|
+
raw_arr = raw.is_a?(String) ? raw.split(/\s*,\s*/) : Array(raw)
|
37
|
+
raw_arr.map { caster.call(_1) }
|
38
|
+
else
|
39
|
+
caster.call(raw)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def dup
|
44
|
+
new_obj = self.class.allocate
|
45
|
+
new_obj.instance_variable_set(:@registry, registry.dup)
|
46
|
+
new_obj
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :registry
|
52
|
+
end
|
53
|
+
|
54
|
+
TypeRegistry.default.tap do |obj|
|
55
|
+
obj.accept(nil, &:itself)
|
56
|
+
obj.accept(:string, &:to_s)
|
57
|
+
obj.accept(:integer, &:to_i)
|
58
|
+
obj.accept(:float, &:to_f)
|
59
|
+
|
60
|
+
obj.accept(:date) do
|
61
|
+
require "date" unless defined?(::Date)
|
62
|
+
|
63
|
+
next _1 if _1.is_a?(::Date)
|
64
|
+
|
65
|
+
next _1.to_date if _1.respond_to?(:to_date)
|
66
|
+
|
67
|
+
::Date.parse(_1)
|
68
|
+
end
|
69
|
+
|
70
|
+
obj.accept(:datetime) do
|
71
|
+
require "date" unless defined?(::Date)
|
72
|
+
|
73
|
+
next _1 if _1.is_a?(::DateTime)
|
74
|
+
|
75
|
+
next _1.to_datetime if _1.respond_to?(:to_datetime)
|
76
|
+
|
77
|
+
::DateTime.parse(_1)
|
78
|
+
end
|
79
|
+
|
80
|
+
obj.accept(:uri) do
|
81
|
+
require "uri" unless defined?(::URI)
|
82
|
+
|
83
|
+
next _1 if _1.is_a?(::URI)
|
84
|
+
|
85
|
+
::URI.parse(_1)
|
86
|
+
end
|
87
|
+
|
88
|
+
obj.accept(:boolean) do
|
89
|
+
_1.to_s.match?(/\A(true|t|yes|y|1)\z/i)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# TypeCaster is an object responsible for type-casting.
|
94
|
+
# It uses a provided types registry and mapping, and also
|
95
|
+
# accepts a fallback typecaster.
|
96
|
+
class TypeCaster
|
97
|
+
using Ext::DeepDup
|
98
|
+
using Ext::Hash
|
99
|
+
|
100
|
+
def initialize(mapping, registry: TypeRegistry.default, fallback: ::Anyway::AutoCast)
|
101
|
+
@mapping = mapping.deep_dup
|
102
|
+
@registry = registry
|
103
|
+
@fallback = fallback
|
104
|
+
end
|
105
|
+
|
106
|
+
def coerce(key, val, config: mapping)
|
107
|
+
caster_config = config[key.to_sym]
|
108
|
+
|
109
|
+
return fallback.coerce(key, val) unless caster_config
|
110
|
+
|
111
|
+
case caster_config
|
112
|
+
in array:, type:, **nil
|
113
|
+
registry.deserialize(val, type, array: array)
|
114
|
+
in Hash
|
115
|
+
return val unless val.is_a?(Hash)
|
116
|
+
|
117
|
+
caster_config.each do |k, v|
|
118
|
+
ks = k.to_s
|
119
|
+
next unless val.key?(ks)
|
120
|
+
|
121
|
+
val[ks] = coerce(k, val[ks], config: caster_config)
|
122
|
+
end
|
123
|
+
|
124
|
+
val
|
125
|
+
else
|
126
|
+
registry.deserialize(val, caster_config)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
attr_reader :mapping, :registry, :fallback
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
using Anyway::Ext::DeepDup
|
5
|
+
|
6
|
+
module Utils
|
7
|
+
def self.deep_merge!(source, other)
|
8
|
+
other.each do |key, other_value|
|
9
|
+
this_value = source[key]
|
10
|
+
|
11
|
+
if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
|
12
|
+
deep_merge!(this_value, other_value)
|
13
|
+
else
|
14
|
+
source[key] = other_value.deep_dup
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
source
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/anyway/version.rb
CHANGED
data/lib/anyway_config.rb
CHANGED
@@ -11,12 +11,16 @@ require "anyway/ext/deep_dup"
|
|
11
11
|
require "anyway/ext/deep_freeze"
|
12
12
|
require "anyway/ext/hash"
|
13
13
|
|
14
|
+
require "anyway/utils/deep_merge"
|
15
|
+
|
14
16
|
require "anyway/settings"
|
15
17
|
require "anyway/tracing"
|
16
18
|
require "anyway/config"
|
17
19
|
require "anyway/auto_cast"
|
20
|
+
require "anyway/type_casting"
|
18
21
|
require "anyway/env"
|
19
22
|
require "anyway/loaders"
|
23
|
+
require "anyway/rbs"
|
20
24
|
|
21
25
|
module Anyway # :nodoc:
|
22
26
|
class << self
|