dry-schema 1.4.2 → 1.5.3
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 +217 -78
- data/LICENSE +1 -1
- data/README.md +4 -4
- data/config/errors.yml +4 -0
- data/dry-schema.gemspec +46 -0
- data/lib/dry-schema.rb +1 -1
- data/lib/dry/schema.rb +19 -6
- data/lib/dry/schema/compiler.rb +5 -5
- data/lib/dry/schema/config.rb +15 -6
- data/lib/dry/schema/constants.rb +16 -7
- data/lib/dry/schema/dsl.rb +87 -27
- data/lib/dry/schema/extensions.rb +10 -2
- data/lib/dry/schema/extensions/hints.rb +15 -8
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +1 -1
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +0 -47
- data/lib/dry/schema/extensions/info.rb +27 -0
- data/lib/dry/schema/extensions/info/schema_compiler.rb +105 -0
- data/lib/dry/schema/extensions/monads.rb +1 -1
- data/lib/dry/schema/extensions/struct.rb +32 -0
- data/lib/dry/schema/json.rb +1 -1
- data/lib/dry/schema/key.rb +16 -1
- data/lib/dry/schema/key_coercer.rb +4 -4
- data/lib/dry/schema/key_map.rb +9 -4
- data/lib/dry/schema/key_validator.rb +67 -0
- data/lib/dry/schema/macros.rb +8 -8
- data/lib/dry/schema/macros/array.rb +17 -4
- data/lib/dry/schema/macros/core.rb +9 -4
- data/lib/dry/schema/macros/dsl.rb +34 -19
- data/lib/dry/schema/macros/each.rb +4 -4
- data/lib/dry/schema/macros/filled.rb +5 -5
- data/lib/dry/schema/macros/hash.rb +21 -3
- data/lib/dry/schema/macros/key.rb +9 -9
- data/lib/dry/schema/macros/maybe.rb +4 -5
- data/lib/dry/schema/macros/optional.rb +1 -1
- data/lib/dry/schema/macros/required.rb +1 -1
- data/lib/dry/schema/macros/schema.rb +23 -2
- data/lib/dry/schema/macros/value.rb +34 -7
- data/lib/dry/schema/message.rb +35 -9
- data/lib/dry/schema/message/or.rb +18 -39
- data/lib/dry/schema/message/or/abstract.rb +28 -0
- data/lib/dry/schema/message/or/multi_path.rb +37 -0
- data/lib/dry/schema/message/or/single_path.rb +64 -0
- data/lib/dry/schema/message_compiler.rb +55 -19
- data/lib/dry/schema/message_compiler/visitor_opts.rb +2 -2
- data/lib/dry/schema/message_set.rb +26 -37
- data/lib/dry/schema/messages.rb +6 -6
- data/lib/dry/schema/messages/abstract.rb +54 -56
- data/lib/dry/schema/messages/i18n.rb +29 -27
- data/lib/dry/schema/messages/namespaced.rb +12 -2
- data/lib/dry/schema/messages/template.rb +19 -44
- data/lib/dry/schema/messages/yaml.rb +61 -14
- data/lib/dry/schema/params.rb +1 -1
- data/lib/dry/schema/path.rb +44 -5
- data/lib/dry/schema/predicate.rb +4 -2
- data/lib/dry/schema/predicate_inferrer.rb +4 -184
- data/lib/dry/schema/predicate_registry.rb +2 -2
- data/lib/dry/schema/primitive_inferrer.rb +16 -0
- data/lib/dry/schema/processor.rb +49 -28
- data/lib/dry/schema/processor_steps.rb +50 -27
- data/lib/dry/schema/result.rb +52 -5
- data/lib/dry/schema/rule_applier.rb +7 -7
- data/lib/dry/schema/step.rb +79 -0
- data/lib/dry/schema/trace.rb +5 -4
- data/lib/dry/schema/type_container.rb +3 -3
- data/lib/dry/schema/type_registry.rb +2 -2
- data/lib/dry/schema/types.rb +1 -1
- data/lib/dry/schema/value_coercer.rb +2 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +21 -7
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/dry-schema
|
2
2
|
[actions]: https://github.com/dry-rb/dry-schema/actions
|
3
|
-
[
|
3
|
+
[codacy]: https://www.codacy.com/gh/dry-rb/dry-schema
|
4
4
|
[chat]: https://dry-rb.zulipchat.com
|
5
5
|
[inchpages]: http://inch-ci.org/github/dry-rb/dry-schema
|
6
6
|
|
@@ -8,8 +8,8 @@
|
|
8
8
|
|
9
9
|
[][gem]
|
10
10
|
[][actions]
|
11
|
-
[][codacy]
|
12
|
+
[][codacy]
|
13
13
|
[][inchpages]
|
14
14
|
|
15
15
|
## Links
|
@@ -19,7 +19,7 @@
|
|
19
19
|
|
20
20
|
## Supported Ruby versions
|
21
21
|
|
22
|
-
This library officially supports following Ruby versions:
|
22
|
+
This library officially supports the following Ruby versions:
|
23
23
|
|
24
24
|
* MRI >= `2.4`
|
25
25
|
* jruby >= `9.2`
|
data/config/errors.yml
CHANGED
@@ -3,6 +3,8 @@ en:
|
|
3
3
|
or: "or"
|
4
4
|
|
5
5
|
errors:
|
6
|
+
unexpected_key: "is not allowed"
|
7
|
+
|
6
8
|
array?: "must be an array"
|
7
9
|
|
8
10
|
empty?: "must be empty"
|
@@ -101,5 +103,7 @@ en:
|
|
101
103
|
default: "must be %{size} bytes long"
|
102
104
|
range: "must be within %{size_left} - %{size_right} bytes long"
|
103
105
|
|
106
|
+
uuid_v4?: "is not a valid UUID"
|
107
|
+
|
104
108
|
not:
|
105
109
|
empty?: "cannot be empty"
|
data/dry-schema.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# this file is managed by dry-rb/devtools project
|
3
|
+
|
4
|
+
lib = File.expand_path('lib', __dir__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require 'dry/schema/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'dry-schema'
|
10
|
+
spec.authors = ["Piotr Solnica"]
|
11
|
+
spec.email = ["piotr.solnica@gmail.com"]
|
12
|
+
spec.license = 'MIT'
|
13
|
+
spec.version = Dry::Schema::VERSION.dup
|
14
|
+
|
15
|
+
spec.summary = "Coercion and validation for data structures"
|
16
|
+
spec.description = <<~TEXT
|
17
|
+
dry-schema provides a DSL for defining schemas with keys and rules that should be applied to
|
18
|
+
values. It supports coercion, input sanitization, custom types and localized error messages
|
19
|
+
(with or without I18n gem). It's also used as the schema engine in dry-validation.
|
20
|
+
TEXT
|
21
|
+
spec.homepage = 'https://dry-rb.org/gems/dry-schema'
|
22
|
+
spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-schema.gemspec", "lib/**/*", "config/*.yml"]
|
23
|
+
spec.bindir = 'bin'
|
24
|
+
spec.executables = []
|
25
|
+
spec.require_paths = ['lib']
|
26
|
+
|
27
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
28
|
+
spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-schema/blob/master/CHANGELOG.md'
|
29
|
+
spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-schema'
|
30
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-schema/issues'
|
31
|
+
|
32
|
+
spec.required_ruby_version = ">= 2.4.0"
|
33
|
+
|
34
|
+
# to update dependencies edit project.yml
|
35
|
+
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
36
|
+
spec.add_runtime_dependency "dry-configurable", "~> 0.8", ">= 0.8.3"
|
37
|
+
spec.add_runtime_dependency "dry-core", "~> 0.4"
|
38
|
+
spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
|
39
|
+
spec.add_runtime_dependency "dry-initializer", "~> 3.0"
|
40
|
+
spec.add_runtime_dependency "dry-logic", "~> 1.0"
|
41
|
+
spec.add_runtime_dependency "dry-types", "~> 1.4"
|
42
|
+
|
43
|
+
spec.add_development_dependency "bundler"
|
44
|
+
spec.add_development_dependency "rake"
|
45
|
+
spec.add_development_dependency "rspec"
|
46
|
+
end
|
data/lib/dry-schema.rb
CHANGED
data/lib/dry/schema.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/core/extensions"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
5
|
+
require "dry/schema/config"
|
6
|
+
require "dry/schema/constants"
|
7
|
+
require "dry/schema/dsl"
|
8
|
+
require "dry/schema/params"
|
9
|
+
require "dry/schema/json"
|
9
10
|
|
10
11
|
module Dry
|
11
12
|
# Main interface
|
@@ -14,6 +15,18 @@ module Dry
|
|
14
15
|
module Schema
|
15
16
|
extend Dry::Core::Extensions
|
16
17
|
|
18
|
+
# Configuration
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# Dry::Schema.config.messages.backend = :i18n
|
22
|
+
#
|
23
|
+
# @return [Config]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def self.config
|
27
|
+
@config ||= Config.new
|
28
|
+
end
|
29
|
+
|
17
30
|
# Define a schema
|
18
31
|
#
|
19
32
|
# @example
|
@@ -74,4 +87,4 @@ module Dry
|
|
74
87
|
end
|
75
88
|
end
|
76
89
|
|
77
|
-
require
|
90
|
+
require "dry/schema/extensions"
|
data/lib/dry/schema/compiler.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "dry/logic/rule_compiler"
|
4
|
+
require "dry/schema/namespaced_rule"
|
5
|
+
require "dry/schema/predicate_registry"
|
6
6
|
|
7
7
|
module Dry
|
8
8
|
module Schema
|
@@ -30,12 +30,12 @@ module Dry
|
|
30
30
|
# used as nested schemas
|
31
31
|
#
|
32
32
|
# @param [Array] node
|
33
|
-
# @param [Hash]
|
33
|
+
# @param [Hash] _opts Unused
|
34
34
|
#
|
35
35
|
# @return [NamespacedRule]
|
36
36
|
#
|
37
37
|
# @api private
|
38
|
-
def visit_namespace(node,
|
38
|
+
def visit_namespace(node, _opts = EMPTY_HASH)
|
39
39
|
namespace, rest = node
|
40
40
|
NamespacedRule.new(namespace, visit(rest))
|
41
41
|
end
|
data/lib/dry/schema/config.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "dry/equalizer"
|
4
|
+
require "dry/configurable"
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
6
|
+
require "dry/schema/constants"
|
7
|
+
require "dry/schema/predicate_registry"
|
8
|
+
require "dry/schema/type_container"
|
9
9
|
|
10
10
|
module Dry
|
11
11
|
module Schema
|
@@ -51,6 +51,15 @@ module Dry
|
|
51
51
|
setting(:default_locale, nil)
|
52
52
|
end
|
53
53
|
|
54
|
+
# @!method validate_keys
|
55
|
+
#
|
56
|
+
# On/off switch for key validator
|
57
|
+
#
|
58
|
+
# @return [Boolean]
|
59
|
+
#
|
60
|
+
# @api public
|
61
|
+
setting(:validate_keys, false)
|
62
|
+
|
54
63
|
# @api private
|
55
64
|
def respond_to_missing?(meth, include_private = false)
|
56
65
|
super || config.respond_to?(meth, include_private)
|
@@ -58,7 +67,7 @@ module Dry
|
|
58
67
|
|
59
68
|
# @api private
|
60
69
|
def inspect
|
61
|
-
"#<#{self.class} #{to_h.map { |k,v| ["#{k}=", v.inspect] }.map(&:join).join(
|
70
|
+
"#<#{self.class} #{to_h.map { |k, v| ["#{k}=", v.inspect] }.map(&:join).join(" ")}>"
|
62
71
|
end
|
63
72
|
|
64
73
|
private
|
data/lib/dry/schema/constants.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "pathname"
|
4
|
+
require "dry/core/constants"
|
5
5
|
|
6
6
|
module Dry
|
7
7
|
# Common constants used across the library
|
@@ -10,15 +10,24 @@ module Dry
|
|
10
10
|
module Schema
|
11
11
|
include Core::Constants
|
12
12
|
|
13
|
-
LIST_SEPARATOR =
|
14
|
-
QUESTION_MARK =
|
15
|
-
DOT =
|
13
|
+
LIST_SEPARATOR = ", "
|
14
|
+
QUESTION_MARK = "?"
|
15
|
+
DOT = "."
|
16
|
+
|
17
|
+
# core processor steps in the default execution order
|
18
|
+
STEPS_IN_ORDER = %i[
|
19
|
+
key_validator
|
20
|
+
key_coercer
|
21
|
+
filter_schema
|
22
|
+
value_coercer
|
23
|
+
rule_applier
|
24
|
+
].freeze
|
16
25
|
|
17
26
|
# Path to the default set of localized messages bundled within the gem
|
18
|
-
DEFAULT_MESSAGES_PATH = Pathname(__dir__).join(
|
27
|
+
DEFAULT_MESSAGES_PATH = Pathname(__dir__).join("../../../config/errors.yml").realpath.freeze
|
19
28
|
|
20
29
|
# Default namespace used for localized messages in YAML files
|
21
|
-
DEFAULT_MESSAGES_ROOT =
|
30
|
+
DEFAULT_MESSAGES_ROOT = "dry_schema"
|
22
31
|
|
23
32
|
# An error raised when DSL is used in an incorrect way
|
24
33
|
InvalidSchemaError = Class.new(StandardError)
|
data/lib/dry/schema/dsl.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
|
11
|
-
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
3
|
+
require "dry/initializer"
|
4
|
+
|
5
|
+
require "dry/schema/constants"
|
6
|
+
require "dry/schema/path"
|
7
|
+
require "dry/schema/config"
|
8
|
+
require "dry/schema/compiler"
|
9
|
+
require "dry/schema/types"
|
10
|
+
require "dry/schema/macros"
|
11
|
+
|
12
|
+
require "dry/schema/processor"
|
13
|
+
require "dry/schema/processor_steps"
|
14
|
+
require "dry/schema/key_map"
|
15
|
+
require "dry/schema/key_coercer"
|
16
|
+
require "dry/schema/key_validator"
|
17
|
+
require "dry/schema/value_coercer"
|
18
|
+
require "dry/schema/rule_applier"
|
17
19
|
|
18
20
|
module Dry
|
19
21
|
module Schema
|
@@ -67,10 +69,13 @@ module Dry
|
|
67
69
|
option :parent, Types::Coercible::Array, default: -> { EMPTY_ARRAY.dup }, as: :parents
|
68
70
|
|
69
71
|
# @return [Config] Configuration object exposed via `#configure` method
|
70
|
-
option :config, optional: true, default: proc {
|
72
|
+
option :config, optional: true, default: proc { default_config }
|
71
73
|
|
72
74
|
# @return [ProcessorSteps] Steps for the processor
|
73
|
-
option :steps, default: proc {
|
75
|
+
option :steps, default: proc { ProcessorSteps.new }
|
76
|
+
|
77
|
+
# @return [Path, Array] Path under which the schema is defined
|
78
|
+
option :path, -> *args { Path[*args] if args.any? }, default: proc { EMPTY_ARRAY }
|
74
79
|
|
75
80
|
# Build a new DSL object and evaluate provided block
|
76
81
|
#
|
@@ -171,7 +176,7 @@ module Dry
|
|
171
176
|
def key(name, macro:, &block)
|
172
177
|
raise ArgumentError, "Key +#{name}+ is not a symbol" unless name.is_a?(::Symbol)
|
173
178
|
|
174
|
-
set_type(name, Types::Any)
|
179
|
+
set_type(name, Types::Any.meta(default: true))
|
175
180
|
|
176
181
|
macro = macro.new(
|
177
182
|
name: name,
|
@@ -191,12 +196,31 @@ module Dry
|
|
191
196
|
#
|
192
197
|
# @api private
|
193
198
|
def call
|
194
|
-
steps[
|
195
|
-
|
196
|
-
|
197
|
-
|
199
|
+
all_steps = parents.map(&:steps) + [steps]
|
200
|
+
|
201
|
+
result_steps = all_steps.inject { |result, steps| result.merge(steps) }
|
202
|
+
|
203
|
+
result_steps[:key_validator] = key_validator if config.validate_keys
|
204
|
+
result_steps[:key_coercer] = key_coercer
|
205
|
+
result_steps[:value_coercer] = value_coercer
|
206
|
+
result_steps[:rule_applier] = rule_applier
|
207
|
+
result_steps[:filter_schema] = filter_schema.rule_applier if filter_rules?
|
208
|
+
|
209
|
+
processor_type.new(schema_dsl: self, steps: result_steps)
|
210
|
+
end
|
198
211
|
|
199
|
-
|
212
|
+
# Merge with another dsl
|
213
|
+
#
|
214
|
+
# @return [DSL]
|
215
|
+
#
|
216
|
+
# @api private
|
217
|
+
def merge(other)
|
218
|
+
new(
|
219
|
+
parent: parents + other.parents,
|
220
|
+
macros: macros + other.macros,
|
221
|
+
types: types.merge(other.types),
|
222
|
+
steps: steps.merge(other.steps)
|
223
|
+
)
|
200
224
|
end
|
201
225
|
|
202
226
|
# Cast this DSL into a rule object
|
@@ -215,7 +239,7 @@ module Dry
|
|
215
239
|
#
|
216
240
|
# @api public
|
217
241
|
def array
|
218
|
-
-> member_type { type_registry[
|
242
|
+
-> member_type { type_registry["array"].of(resolve_type(member_type)) }
|
219
243
|
end
|
220
244
|
|
221
245
|
# Method allows steps injection to the processor
|
@@ -263,7 +287,7 @@ module Dry
|
|
263
287
|
#
|
264
288
|
# @api private
|
265
289
|
def type_schema
|
266
|
-
our_schema = type_registry[
|
290
|
+
our_schema = type_registry["hash"].schema(types).lax
|
267
291
|
schemas = [*parents.map(&:type_schema), our_schema]
|
268
292
|
schemas.inject { |result, schema| result.schema(schema.to_a) }
|
269
293
|
end
|
@@ -287,11 +311,20 @@ module Dry
|
|
287
311
|
# @api private
|
288
312
|
def set_type(name, spec)
|
289
313
|
type = resolve_type(spec)
|
290
|
-
meta = {
|
314
|
+
meta = {required: false, maybe: type.optional?}
|
291
315
|
|
292
316
|
types[name] = type.meta(meta)
|
293
317
|
end
|
294
318
|
|
319
|
+
# Check if a custom type was set under provided key name
|
320
|
+
#
|
321
|
+
# @return [Bool]
|
322
|
+
#
|
323
|
+
# @api private
|
324
|
+
def custom_type?(name)
|
325
|
+
!types[name].meta[:default].equal?(true)
|
326
|
+
end
|
327
|
+
|
295
328
|
# Resolve type object from the provided spec
|
296
329
|
#
|
297
330
|
# @param [Symbol, Array<Symbol>, Dry::Types::Type] spec
|
@@ -326,7 +359,7 @@ module Dry
|
|
326
359
|
#
|
327
360
|
# @api private
|
328
361
|
def filter_rules?
|
329
|
-
if instance_variable_defined?(
|
362
|
+
if instance_variable_defined?("@filter_schema_dsl") && !filter_schema_dsl.macros.empty?
|
330
363
|
return true
|
331
364
|
end
|
332
365
|
|
@@ -374,6 +407,15 @@ module Dry
|
|
374
407
|
parents.select(&:filter_rules?).map(&:filter_schema)
|
375
408
|
end
|
376
409
|
|
410
|
+
# Build a key validator
|
411
|
+
#
|
412
|
+
# @return [KeyValidator]
|
413
|
+
#
|
414
|
+
# @api private
|
415
|
+
def key_validator
|
416
|
+
KeyValidator.new(key_map: key_map + parent_key_map)
|
417
|
+
end
|
418
|
+
|
377
419
|
# Build a key coercer
|
378
420
|
#
|
379
421
|
# @return [KeyCoercer]
|
@@ -411,13 +453,19 @@ module Dry
|
|
411
453
|
|
412
454
|
# Build a key spec needed by the key map
|
413
455
|
#
|
456
|
+
# TODO: we need a key-map compiler using Types AST
|
457
|
+
#
|
414
458
|
# @api private
|
415
459
|
def key_spec(name, type)
|
416
460
|
if type.respond_to?(:keys)
|
417
|
-
{
|
461
|
+
{name => key_map(type.name_key_map)}
|
418
462
|
elsif type.respond_to?(:member)
|
419
463
|
kv = key_spec(name, type.member)
|
420
464
|
kv.equal?(name) ? name : kv.flatten(1)
|
465
|
+
elsif type.meta[:maybe] && type.respond_to?(:right)
|
466
|
+
key_spec(name, type.right)
|
467
|
+
elsif type.respond_to?(:type)
|
468
|
+
key_spec(name, type.type)
|
421
469
|
else
|
422
470
|
name
|
423
471
|
end
|
@@ -432,6 +480,18 @@ module Dry
|
|
432
480
|
def parent_key_map
|
433
481
|
parents.reduce([]) { |key_map, parent| parent.key_map + key_map }
|
434
482
|
end
|
483
|
+
|
484
|
+
# @api private
|
485
|
+
def default_config
|
486
|
+
parents.each_cons(2) do |left, right|
|
487
|
+
unless left.config == right.config
|
488
|
+
raise ArgumentError,
|
489
|
+
"Parent configs differ, left=#{left.inspect}, right=#{right.inspect}"
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
(parent || Schema).config.dup
|
494
|
+
end
|
435
495
|
end
|
436
496
|
end
|
437
497
|
end
|