dry-schema 1.7.0 → 1.9.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 +43 -0
- data/README.md +2 -2
- data/dry-schema.gemspec +2 -2
- data/lib/dry/schema/compiler.rb +1 -1
- data/lib/dry/schema/config.rb +9 -9
- data/lib/dry/schema/dsl.rb +7 -4
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +9 -4
- data/lib/dry/schema/extensions/hints.rb +11 -9
- data/lib/dry/schema/extensions/info/schema_compiler.rb +10 -1
- data/lib/dry/schema/extensions/json_schema/schema_compiler.rb +232 -0
- data/lib/dry/schema/extensions/json_schema.rb +29 -0
- data/lib/dry/schema/extensions/struct.rb +1 -1
- data/lib/dry/schema/extensions.rb +4 -0
- data/lib/dry/schema/key.rb +75 -74
- data/lib/dry/schema/key_coercer.rb +2 -2
- data/lib/dry/schema/key_validator.rb +44 -23
- data/lib/dry/schema/macros/array.rb +4 -0
- data/lib/dry/schema/macros/core.rb +1 -1
- data/lib/dry/schema/macros/dsl.rb +17 -15
- data/lib/dry/schema/macros/hash.rb +1 -1
- data/lib/dry/schema/macros/key.rb +2 -2
- data/lib/dry/schema/macros/schema.rb +2 -0
- data/lib/dry/schema/macros/value.rb +7 -0
- data/lib/dry/schema/message/or/multi_path.rb +7 -5
- data/lib/dry/schema/message_compiler.rb +13 -10
- data/lib/dry/schema/messages/abstract.rb +9 -9
- data/lib/dry/schema/messages/i18n.rb +98 -96
- data/lib/dry/schema/messages/namespaced.rb +6 -0
- data/lib/dry/schema/messages/yaml.rb +165 -158
- data/lib/dry/schema/predicate.rb +2 -2
- data/lib/dry/schema/predicate_inferrer.rb +2 -0
- data/lib/dry/schema/primitive_inferrer.rb +2 -0
- data/lib/dry/schema/processor.rb +4 -4
- data/lib/dry/schema/result.rb +5 -7
- data/lib/dry/schema/trace.rb +5 -1
- data/lib/dry/schema/type_registry.rb +1 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +9 -7
@@ -5,125 +5,127 @@ require "dry/schema/messages/abstract"
|
|
5
5
|
|
6
6
|
module Dry
|
7
7
|
module Schema
|
8
|
-
|
9
|
-
|
10
|
-
# @api public
|
11
|
-
class Messages::I18n < Messages::Abstract
|
12
|
-
# Translation function
|
13
|
-
#
|
14
|
-
# @return [Method]
|
15
|
-
attr_reader :t
|
16
|
-
|
17
|
-
# @api private
|
18
|
-
def initialize
|
19
|
-
super
|
20
|
-
@t = I18n.method(:t)
|
21
|
-
end
|
22
|
-
|
23
|
-
# Get a message for the given key and its options
|
24
|
-
#
|
25
|
-
# @param [Symbol] key
|
26
|
-
# @param [Hash] options
|
27
|
-
#
|
28
|
-
# @return [String]
|
8
|
+
module Messages
|
9
|
+
# I18n message backend
|
29
10
|
#
|
30
11
|
# @api public
|
31
|
-
|
32
|
-
|
12
|
+
class I18n < Abstract
|
13
|
+
# Translation function
|
14
|
+
#
|
15
|
+
# @return [Method]
|
16
|
+
attr_reader :t
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def initialize
|
20
|
+
super
|
21
|
+
@t = ::I18n.method(:t)
|
22
|
+
end
|
33
23
|
|
34
|
-
|
24
|
+
# Get a message for the given key and its options
|
25
|
+
#
|
26
|
+
# @param [Symbol] key
|
27
|
+
# @param [Hash] options
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
def get(key, options = EMPTY_HASH)
|
33
|
+
return unless key
|
34
|
+
|
35
|
+
result = t.(key, locale: options.fetch(:locale, default_locale))
|
36
|
+
|
37
|
+
if result.is_a?(Hash)
|
38
|
+
text = result[:text]
|
39
|
+
meta = result.dup.tap { |h| h.delete(:text) }
|
40
|
+
else
|
41
|
+
text = result
|
42
|
+
meta = EMPTY_HASH.dup
|
43
|
+
end
|
35
44
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
text = result
|
41
|
-
meta = EMPTY_HASH.dup
|
45
|
+
{
|
46
|
+
text: text,
|
47
|
+
meta: meta
|
48
|
+
}
|
42
49
|
end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
# @api public
|
55
|
-
def key?(key, options)
|
56
|
-
I18n.exists?(key, options.fetch(:locale, default_locale)) ||
|
57
|
-
I18n.exists?(key, I18n.default_locale)
|
58
|
-
end
|
51
|
+
# Check if given key is defined
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def key?(key, options)
|
57
|
+
::I18n.exists?(key, options.fetch(:locale, default_locale)) ||
|
58
|
+
::I18n.exists?(key, ::I18n.default_locale)
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
61
|
+
# Merge messages from an additional path
|
62
|
+
#
|
63
|
+
# @param [String, Array<String>] paths
|
64
|
+
#
|
65
|
+
# @return [Messages::I18n]
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def merge(paths)
|
69
|
+
prepare(paths)
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
# @api private
|
73
|
+
def default_locale
|
74
|
+
super || ::I18n.locale || ::I18n.default_locale
|
75
|
+
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
# @api private
|
78
|
+
def prepare(paths = config.load_paths)
|
79
|
+
paths.each do |path|
|
80
|
+
data = ::YAML.load_file(path)
|
80
81
|
|
81
|
-
|
82
|
-
|
82
|
+
if custom_top_namespace?(path)
|
83
|
+
top_namespace = config.top_namespace
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
85
|
+
mapped_data = data.transform_values { |v|
|
86
|
+
{top_namespace => v[DEFAULT_MESSAGES_ROOT]}
|
87
|
+
}
|
87
88
|
|
88
|
-
|
89
|
-
|
90
|
-
|
89
|
+
store_translations(mapped_data)
|
90
|
+
else
|
91
|
+
store_translations(data)
|
92
|
+
end
|
91
93
|
end
|
92
|
-
end
|
93
94
|
|
94
|
-
|
95
|
-
|
95
|
+
self
|
96
|
+
end
|
96
97
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
# @api private
|
99
|
+
def interpolatable_data(_key, _options, **data)
|
100
|
+
data
|
101
|
+
end
|
101
102
|
|
102
|
-
|
103
|
-
|
104
|
-
|
103
|
+
# @api private
|
104
|
+
def interpolate(key, options, **data)
|
105
|
+
text_key = "#{key}.text"
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
107
|
+
opts = {
|
108
|
+
locale: default_locale,
|
109
|
+
**options,
|
110
|
+
**data
|
111
|
+
}
|
111
112
|
|
112
|
-
|
113
|
+
resolved_key = key?(text_key, opts) ? text_key : key
|
113
114
|
|
114
|
-
|
115
|
-
|
115
|
+
t.(resolved_key, **opts)
|
116
|
+
end
|
116
117
|
|
117
|
-
|
118
|
+
private
|
118
119
|
|
119
|
-
|
120
|
-
|
121
|
-
|
120
|
+
# @api private
|
121
|
+
def store_translations(data)
|
122
|
+
locales = data.keys.map(&:to_sym)
|
122
123
|
|
123
|
-
|
124
|
+
::I18n.available_locales |= locales
|
124
125
|
|
125
|
-
|
126
|
-
|
126
|
+
locales.each do |locale|
|
127
|
+
::I18n.backend.store_translations(locale, data[locale.to_s])
|
128
|
+
end
|
127
129
|
end
|
128
130
|
end
|
129
131
|
end
|
@@ -21,6 +21,7 @@ module Dry
|
|
21
21
|
|
22
22
|
# @api private
|
23
23
|
def initialize(namespace, messages)
|
24
|
+
super()
|
24
25
|
@config = messages.config
|
25
26
|
@namespace = namespace
|
26
27
|
@messages = messages
|
@@ -79,6 +80,11 @@ module Dry
|
|
79
80
|
def interpolate(key, options, **data)
|
80
81
|
messages.interpolate(key, options, **data)
|
81
82
|
end
|
83
|
+
|
84
|
+
# @api private
|
85
|
+
def translate(key, **args)
|
86
|
+
messages.translate(key, **args)
|
87
|
+
end
|
82
88
|
end
|
83
89
|
end
|
84
90
|
end
|
@@ -9,195 +9,202 @@ require "dry/schema/messages/abstract"
|
|
9
9
|
|
10
10
|
module Dry
|
11
11
|
module Schema
|
12
|
-
|
13
|
-
|
14
|
-
# @api public
|
15
|
-
class Messages::YAML < Messages::Abstract
|
16
|
-
LOCALE_TOKEN = "%<locale>s"
|
17
|
-
TOKEN_REGEXP = /%{(\w*)}/.freeze
|
18
|
-
EMPTY_CONTEXT = Object.new.tap { |ctx|
|
19
|
-
def ctx.context
|
20
|
-
binding
|
21
|
-
end
|
22
|
-
}.freeze.context
|
23
|
-
|
24
|
-
include Dry::Equalizer(:data)
|
25
|
-
|
26
|
-
# Loaded localized message templates
|
12
|
+
module Messages
|
13
|
+
# Plain YAML message backend
|
27
14
|
#
|
28
|
-
# @
|
29
|
-
|
15
|
+
# @api public
|
16
|
+
class YAML < Abstract
|
17
|
+
LOCALE_TOKEN = "%<locale>s"
|
18
|
+
TOKEN_REGEXP = /%{(\w*)}/.freeze
|
19
|
+
EMPTY_CONTEXT = Object.new.tap { |ctx|
|
20
|
+
def ctx.context
|
21
|
+
binding
|
22
|
+
end
|
23
|
+
}.freeze.context
|
30
24
|
|
31
|
-
|
32
|
-
#
|
33
|
-
# @return [Proc]
|
34
|
-
attr_reader :t
|
25
|
+
include ::Dry::Equalizer(:data)
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
27
|
+
# Loaded localized message templates
|
28
|
+
#
|
29
|
+
# @return [Hash]
|
30
|
+
attr_reader :data
|
40
31
|
|
41
|
-
|
32
|
+
# Translation function
|
33
|
+
#
|
34
|
+
# @return [Proc]
|
35
|
+
attr_reader :t
|
42
36
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
37
|
+
# @api private
|
38
|
+
def self.build(options = EMPTY_HASH)
|
39
|
+
super do |config|
|
40
|
+
config.default_locale = :en unless config.default_locale
|
48
41
|
|
49
|
-
|
50
|
-
def self.flat_hash(hash, path = [], keys = {})
|
51
|
-
hash.each do |key, value|
|
52
|
-
flat_hash(value, [*path, key], keys) if value.is_a?(Hash)
|
42
|
+
config.root = "%<locale>s.#{config.root}"
|
53
43
|
|
54
|
-
|
55
|
-
|
56
|
-
text: value,
|
57
|
-
meta: EMPTY_HASH
|
58
|
-
}
|
59
|
-
elsif value.is_a?(Hash) && value["text"].is_a?(String)
|
60
|
-
keys[[*path, key].join(DOT)] = {
|
61
|
-
text: value["text"],
|
62
|
-
meta: value.dup.delete_if { |k| k == "text" }.map { |k, v| [k.to_sym, v] }.to_h
|
44
|
+
config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
|
45
|
+
"%<locale>s.#{path}"
|
63
46
|
}
|
64
47
|
end
|
65
48
|
end
|
66
|
-
keys
|
67
|
-
end
|
68
|
-
|
69
|
-
# @api private
|
70
|
-
def self.cache
|
71
|
-
@cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
|
72
|
-
end
|
73
49
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
50
|
+
# @api private
|
51
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
52
|
+
def self.flat_hash(hash, path = EMPTY_ARRAY, keys = {})
|
53
|
+
hash.each do |key, value|
|
54
|
+
flat_hash(value, [*path, key], keys) if value.is_a?(Hash)
|
55
|
+
|
56
|
+
if value.is_a?(String) && hash["text"] != value
|
57
|
+
keys[[*path, key].join(DOT)] = {
|
58
|
+
text: value,
|
59
|
+
meta: EMPTY_HASH
|
60
|
+
}
|
61
|
+
elsif value.is_a?(Hash) && value["text"].is_a?(String)
|
62
|
+
keys[[*path, key].join(DOT)] = {
|
63
|
+
text: value["text"],
|
64
|
+
meta: value.reject { _1.eql?("text") }.transform_keys(&:to_sym)
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
86
68
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
# @param [Hash] options
|
91
|
-
#
|
92
|
-
# @return [String]
|
93
|
-
#
|
94
|
-
# @api public
|
95
|
-
def looked_up_paths(predicate, options)
|
96
|
-
super.map { |path| path % {locale: options[:locale] || default_locale} }
|
97
|
-
end
|
69
|
+
keys
|
70
|
+
end
|
71
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
98
72
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
#
|
104
|
-
# @return [String]
|
105
|
-
#
|
106
|
-
# @api public
|
107
|
-
def get(key, options = EMPTY_HASH)
|
108
|
-
data[evaluated_key(key, options)]
|
109
|
-
end
|
73
|
+
# @api private
|
74
|
+
def self.cache
|
75
|
+
@cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
|
76
|
+
end
|
110
77
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
# @api public
|
116
|
-
def key?(key, options = EMPTY_HASH)
|
117
|
-
data.key?(evaluated_key(key, options))
|
118
|
-
end
|
78
|
+
# @api private
|
79
|
+
def self.source_cache
|
80
|
+
@source_cache ||= Concurrent::Map.new
|
81
|
+
end
|
119
82
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
# @api public
|
127
|
-
def merge(overrides)
|
128
|
-
if overrides.is_a?(Hash)
|
129
|
-
self.class.new(
|
130
|
-
data: data.merge(self.class.flat_hash(overrides)),
|
131
|
-
config: config
|
132
|
-
)
|
133
|
-
else
|
134
|
-
self.class.new(
|
135
|
-
data: Array(overrides).reduce(data) { |a, e| a.merge(load_translations(e)) },
|
136
|
-
config: config
|
137
|
-
)
|
83
|
+
# @api private
|
84
|
+
def initialize(data: EMPTY_HASH, config: nil)
|
85
|
+
super()
|
86
|
+
@data = data
|
87
|
+
@config = config if config
|
88
|
+
@t = proc { |key, locale: default_locale| get("%<locale>s.#{key}", locale: locale) }
|
138
89
|
end
|
139
|
-
end
|
140
90
|
|
141
|
-
|
142
|
-
|
143
|
-
@
|
144
|
-
|
145
|
-
|
91
|
+
# Get an array of looked up paths
|
92
|
+
#
|
93
|
+
# @param [Symbol] predicate
|
94
|
+
# @param [Hash] options
|
95
|
+
#
|
96
|
+
# @return [String]
|
97
|
+
#
|
98
|
+
# @api public
|
99
|
+
def looked_up_paths(predicate, options)
|
100
|
+
super.map { |path| path % {locale: options[:locale] || default_locale} }
|
101
|
+
end
|
146
102
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
103
|
+
# Get a message for the given key and its options
|
104
|
+
#
|
105
|
+
# @param [Symbol] key
|
106
|
+
# @param [Hash] options
|
107
|
+
#
|
108
|
+
# @return [String]
|
109
|
+
#
|
110
|
+
# @api public
|
111
|
+
def get(key, options = EMPTY_HASH)
|
112
|
+
data[evaluated_key(key, options)]
|
113
|
+
end
|
152
114
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
115
|
+
# Check if given key is defined
|
116
|
+
#
|
117
|
+
# @return [Boolean]
|
118
|
+
#
|
119
|
+
# @api public
|
120
|
+
def key?(key, options = EMPTY_HASH)
|
121
|
+
data.key?(evaluated_key(key, options))
|
122
|
+
end
|
158
123
|
|
159
|
-
|
124
|
+
# Merge messages from an additional path
|
125
|
+
#
|
126
|
+
# @param [String] overrides
|
127
|
+
#
|
128
|
+
# @return [Messages::I18n]
|
129
|
+
#
|
130
|
+
# @api public
|
131
|
+
def merge(overrides)
|
132
|
+
if overrides.is_a?(Hash)
|
133
|
+
self.class.new(
|
134
|
+
data: data.merge(self.class.flat_hash(overrides)),
|
135
|
+
config: config
|
136
|
+
)
|
137
|
+
else
|
138
|
+
self.class.new(
|
139
|
+
data: Array(overrides).reduce(data) { |a, e| a.merge(load_translations(e)) },
|
140
|
+
config: config
|
141
|
+
)
|
142
|
+
end
|
143
|
+
end
|
160
144
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
145
|
+
# @api private
|
146
|
+
def prepare
|
147
|
+
@data = config.load_paths.map { |path| load_translations(path) }.reduce({}, :merge)
|
148
|
+
self
|
149
|
+
end
|
166
150
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
151
|
+
# @api private
|
152
|
+
def interpolatable_data(key, options, **data)
|
153
|
+
tokens = evaluation_context(key, options).fetch(:tokens)
|
154
|
+
data.select { |k,| tokens.include?(k) }
|
155
|
+
end
|
172
156
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
157
|
+
# @api private
|
158
|
+
def interpolate(key, options, **data)
|
159
|
+
evaluator = evaluation_context(key, options).fetch(:evaluator)
|
160
|
+
data.empty? ? evaluator.() : evaluator.(**data)
|
177
161
|
end
|
178
|
-
end
|
179
162
|
|
180
|
-
|
181
|
-
|
182
|
-
@
|
183
|
-
|
163
|
+
private
|
164
|
+
|
165
|
+
# @api private
|
166
|
+
def evaluation_context(key, options)
|
167
|
+
cache.fetch_or_store(get(key, options).fetch(:text)) do |input|
|
168
|
+
tokens = input.scan(TOKEN_REGEXP).flatten(1).map(&:to_sym).to_set
|
169
|
+
text = input.gsub("%", "#")
|
170
|
+
|
171
|
+
# rubocop:disable Security/Eval
|
172
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
173
|
+
evaluator = eval(<<~RUBY, EMPTY_CONTEXT, __FILE__, __LINE__ + 1)
|
174
|
+
-> (#{tokens.map { |token| "#{token}:" }.join(", ")}) { "#{text}" }
|
175
|
+
RUBY
|
176
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
177
|
+
# rubocop:enable Security/Eval
|
178
|
+
|
179
|
+
{
|
180
|
+
tokens: tokens,
|
181
|
+
evaluator: evaluator
|
182
|
+
}
|
183
|
+
end
|
184
|
+
end
|
184
185
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
self.class.flat_hash(YAML.load_file(path)).freeze
|
186
|
+
# @api private
|
187
|
+
def cache
|
188
|
+
@cache ||= self.class.cache[self]
|
189
189
|
end
|
190
190
|
|
191
|
-
|
191
|
+
# @api private
|
192
|
+
def load_translations(path)
|
193
|
+
data = self.class.source_cache.fetch_or_store(path) do
|
194
|
+
self.class.flat_hash(::YAML.load_file(path)).freeze
|
195
|
+
end
|
192
196
|
|
193
|
-
|
194
|
-
end
|
197
|
+
return data unless custom_top_namespace?(path)
|
195
198
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
+
data.transform_keys { _1.gsub(DEFAULT_MESSAGES_ROOT, config.top_namespace) }
|
200
|
+
end
|
201
|
+
|
202
|
+
# @api private
|
203
|
+
def evaluated_key(key, options)
|
204
|
+
return key unless key.include?(LOCALE_TOKEN)
|
199
205
|
|
200
|
-
|
206
|
+
key % {locale: options[:locale] || default_locale}
|
207
|
+
end
|
201
208
|
end
|
202
209
|
end
|
203
210
|
end
|
data/lib/dry/schema/predicate.rb
CHANGED
data/lib/dry/schema/processor.rb
CHANGED
@@ -28,8 +28,8 @@ module Dry
|
|
28
28
|
include Dry::Logic::Operators
|
29
29
|
|
30
30
|
setting :key_map_type
|
31
|
-
setting :type_registry_namespace, :strict
|
32
|
-
setting :filter_empty_string, false
|
31
|
+
setting :type_registry_namespace, default: :strict
|
32
|
+
setting :filter_empty_string, default: false
|
33
33
|
|
34
34
|
option :steps, default: -> { ProcessorSteps.new }
|
35
35
|
|
@@ -53,7 +53,7 @@ module Dry
|
|
53
53
|
# @api public
|
54
54
|
def define(&block)
|
55
55
|
@definition ||= DSL.new(
|
56
|
-
processor_type: self, parent: superclass.definition, **config, &block
|
56
|
+
processor_type: self, parent: superclass.definition, **config.to_h, &block
|
57
57
|
)
|
58
58
|
self
|
59
59
|
end
|
@@ -123,7 +123,7 @@ module Dry
|
|
123
123
|
# @api public
|
124
124
|
def inspect
|
125
125
|
<<~STR.strip
|
126
|
-
#<#{self.class.name} keys=#{key_map.map(&:dump)} rules=#{rules.
|
126
|
+
#<#{self.class.name} keys=#{key_map.map(&:dump)} rules=#{rules.transform_values(&:to_s)}>
|
127
127
|
STR
|
128
128
|
end
|
129
129
|
|