dry-schema 1.4.2 → 1.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,9 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Dry::Schema.register_extension(:monads) do
|
4
|
-
require
|
4
|
+
require "dry/schema/extensions/monads"
|
5
5
|
end
|
6
6
|
|
7
7
|
Dry::Schema.register_extension(:hints) do
|
8
|
-
require
|
8
|
+
require "dry/schema/extensions/hints"
|
9
|
+
end
|
10
|
+
|
11
|
+
Dry::Schema.register_extension(:struct) do
|
12
|
+
require "dry/schema/extensions/struct"
|
13
|
+
end
|
14
|
+
|
15
|
+
Dry::Schema.register_extension(:info) do
|
16
|
+
require "dry/schema/extensions/info"
|
9
17
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "dry/schema/compiler"
|
4
|
+
require "dry/schema/message"
|
5
|
+
require "dry/schema/message_compiler"
|
6
6
|
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
7
|
+
require "dry/schema/extensions/hints/compiler_methods"
|
8
|
+
require "dry/schema/extensions/hints/message_compiler_methods"
|
9
|
+
require "dry/schema/extensions/hints/message_set_methods"
|
10
|
+
require "dry/schema/extensions/hints/result_methods"
|
11
11
|
|
12
12
|
module Dry
|
13
13
|
module Schema
|
@@ -22,7 +22,14 @@ module Dry
|
|
22
22
|
# @see Message::Or
|
23
23
|
#
|
24
24
|
# @api public
|
25
|
-
class Or
|
25
|
+
class Or::SinglePath
|
26
|
+
# @api private
|
27
|
+
def hint?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Or::MultiPath
|
26
33
|
# @api private
|
27
34
|
def hint?
|
28
35
|
false
|
@@ -37,53 +37,6 @@ module Dry
|
|
37
37
|
@to_h ||= failures ? messages_map : messages_map(hints)
|
38
38
|
end
|
39
39
|
alias_method :to_hash, :to_h
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
# @api private
|
44
|
-
def unique_paths
|
45
|
-
messages.uniq(&:path).map(&:path)
|
46
|
-
end
|
47
|
-
|
48
|
-
# @api private
|
49
|
-
def messages_map(messages = self.messages)
|
50
|
-
return EMPTY_HASH if empty?
|
51
|
-
|
52
|
-
messages.reduce(placeholders) { |hash, msg|
|
53
|
-
node = msg.path.reduce(hash) { |a, e| a.is_a?(Hash) ? a[e] : a.last[e] }
|
54
|
-
(node[0].is_a?(::Array) ? node[0] : node) << msg.dump
|
55
|
-
hash
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
# @api private
|
60
|
-
#
|
61
|
-
# rubocop:disable Metrics/AbcSize
|
62
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
63
|
-
def initialize_placeholders!
|
64
|
-
@placeholders = unique_paths.each_with_object(EMPTY_HASH.dup) { |path, hash|
|
65
|
-
curr_idx = 0
|
66
|
-
last_idx = path.size - 1
|
67
|
-
node = hash
|
68
|
-
|
69
|
-
while curr_idx <= last_idx
|
70
|
-
key = path[curr_idx]
|
71
|
-
|
72
|
-
next_node =
|
73
|
-
if node.is_a?(Array) && key.is_a?(Symbol)
|
74
|
-
node_hash = (node << [] << {}).last
|
75
|
-
node_hash[key] || (node_hash[key] = curr_idx < last_idx ? {} : [])
|
76
|
-
else
|
77
|
-
node[key] || (node[key] = curr_idx < last_idx ? {} : [])
|
78
|
-
end
|
79
|
-
|
80
|
-
node = next_node
|
81
|
-
curr_idx += 1
|
82
|
-
end
|
83
|
-
}
|
84
|
-
end
|
85
|
-
# rubocop:enable Metrics/AbcSize
|
86
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
87
40
|
end
|
88
41
|
end
|
89
42
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema/extensions/info/schema_compiler"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Schema
|
7
|
+
# Info extension
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
module Info
|
11
|
+
module SchemaMethods
|
12
|
+
# Return information about keys and types
|
13
|
+
#
|
14
|
+
# @return [Hash<Symbol=>Hash>]
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def info
|
18
|
+
compiler = SchemaCompiler.new
|
19
|
+
compiler.call(to_ast)
|
20
|
+
compiler.to_h
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Processor.include(Info::SchemaMethods)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema/constants"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Schema
|
7
|
+
# @api private
|
8
|
+
module Info
|
9
|
+
# @api private
|
10
|
+
class SchemaCompiler
|
11
|
+
PREDICATE_TO_TYPE = {
|
12
|
+
array?: "array",
|
13
|
+
bool?: "bool",
|
14
|
+
date?: "date",
|
15
|
+
date_time?: "date_time",
|
16
|
+
decimal?: "float",
|
17
|
+
float?: "float",
|
18
|
+
hash?: "hash",
|
19
|
+
int?: "integer",
|
20
|
+
nil?: "nil",
|
21
|
+
str?: "string",
|
22
|
+
time?: "time"
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
attr_reader :keys
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
def initialize
|
30
|
+
@keys = EMPTY_HASH.dup
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
def to_h
|
35
|
+
{keys: keys}
|
36
|
+
end
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
def call(ast)
|
40
|
+
visit(ast)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def visit(node, opts = EMPTY_HASH)
|
45
|
+
meth, rest = node
|
46
|
+
public_send(:"visit_#{meth}", rest, opts)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
def visit_set(node, opts = EMPTY_HASH)
|
51
|
+
target = (key = opts[:key]) ? self.class.new : self
|
52
|
+
|
53
|
+
node.each { |child| target.visit(child, opts) }
|
54
|
+
|
55
|
+
return unless key
|
56
|
+
|
57
|
+
target_info = opts[:member] ? {member: target.to_h} : target.to_h
|
58
|
+
type = opts[:member] ? "array" : "hash"
|
59
|
+
|
60
|
+
keys.update(key => {**keys[key], type: type, **target_info})
|
61
|
+
end
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
def visit_and(node, opts = EMPTY_HASH)
|
65
|
+
left, right = node
|
66
|
+
|
67
|
+
visit(left, opts)
|
68
|
+
visit(right, opts)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @api private
|
72
|
+
def visit_implication(node, opts = EMPTY_HASH)
|
73
|
+
node.each do |el|
|
74
|
+
visit(el, opts.merge(required: false))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def visit_each(node, opts = EMPTY_HASH)
|
80
|
+
visit(node, opts.merge(member: true))
|
81
|
+
end
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
def visit_key(node, opts = EMPTY_HASH)
|
85
|
+
name, rest = node
|
86
|
+
visit(rest, opts.merge(key: name, required: true))
|
87
|
+
end
|
88
|
+
|
89
|
+
# @api private
|
90
|
+
def visit_predicate(node, opts = EMPTY_HASH)
|
91
|
+
name, rest = node
|
92
|
+
|
93
|
+
key = opts[:key]
|
94
|
+
|
95
|
+
if name.equal?(:key?)
|
96
|
+
keys[rest[0][1]] = {required: opts.fetch(:required, true)}
|
97
|
+
else
|
98
|
+
type = PREDICATE_TO_TYPE[name]
|
99
|
+
keys[key][:type] = type if type
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/struct"
|
4
|
+
require "dry/schema/predicate_inferrer"
|
5
|
+
require "dry/schema/primitive_inferrer"
|
6
|
+
require "dry/schema/macros/dsl"
|
7
|
+
require "dry/schema/macros/hash"
|
8
|
+
|
9
|
+
module Dry
|
10
|
+
module Schema
|
11
|
+
module Macros
|
12
|
+
Hash.prepend(::Module.new {
|
13
|
+
def call(*args)
|
14
|
+
if args.size >= 1 && args[0].is_a?(::Class) && args[0] <= ::Dry::Struct
|
15
|
+
if block_given?
|
16
|
+
raise ArgumentError, "blocks are not supported when using "\
|
17
|
+
"a struct class (#{name.inspect} => #{args[0]})"
|
18
|
+
end
|
19
|
+
|
20
|
+
super(args[0].schema, *args.drop(1))
|
21
|
+
type(schema_dsl.types[name].constructor(args[0]))
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
PredicateInferrer::Compiler.send(:alias_method, :visit_struct, :visit_hash)
|
30
|
+
PrimitiveInferrer::Compiler.send(:alias_method, :visit_struct, :visit_hash)
|
31
|
+
end
|
32
|
+
end
|
data/lib/dry/schema/json.rb
CHANGED
data/lib/dry/schema/key.rb
CHANGED
@@ -64,6 +64,11 @@ module Dry
|
|
64
64
|
new(name: name.to_s)
|
65
65
|
end
|
66
66
|
|
67
|
+
# @api private
|
68
|
+
def to_dot_notation
|
69
|
+
[name.to_s]
|
70
|
+
end
|
71
|
+
|
67
72
|
# @api private
|
68
73
|
def new(**new_opts)
|
69
74
|
self.class.new(id, name: name, coercer: coercer, **new_opts)
|
@@ -118,9 +123,14 @@ module Dry
|
|
118
123
|
new(name: name.to_s, members: members.stringified)
|
119
124
|
end
|
120
125
|
|
126
|
+
# @api private
|
127
|
+
def to_dot_notation
|
128
|
+
[name].product(members.flat_map(&:to_dot_notation)).map { |e| e.join(DOT) }
|
129
|
+
end
|
130
|
+
|
121
131
|
# @api private
|
122
132
|
def dump
|
123
|
-
{
|
133
|
+
{name => members.map(&:dump)}
|
124
134
|
end
|
125
135
|
end
|
126
136
|
|
@@ -155,6 +165,11 @@ module Dry
|
|
155
165
|
new(name: name.to_s, member: member.stringified)
|
156
166
|
end
|
157
167
|
|
168
|
+
# @api private
|
169
|
+
def to_dot_notation
|
170
|
+
[:"#{name}[]"].product(member.to_dot_notation).map { |el| el.join(DOT) }
|
171
|
+
end
|
172
|
+
|
158
173
|
# @api private
|
159
174
|
def dump
|
160
175
|
[name, member.dump]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "dry/core/cache"
|
4
|
+
require "dry/equalizer"
|
5
5
|
|
6
6
|
module Dry
|
7
7
|
module Schema
|
@@ -32,8 +32,8 @@ module Dry
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# @api private
|
35
|
-
def call(
|
36
|
-
key_map.write(
|
35
|
+
def call(result)
|
36
|
+
key_map.write(result.to_h)
|
37
37
|
end
|
38
38
|
alias_method :[], :call
|
39
39
|
end
|
data/lib/dry/schema/key_map.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "dry/equalizer"
|
4
|
+
require "dry/core/cache"
|
5
|
+
require "dry/schema/constants"
|
6
|
+
require "dry/schema/key"
|
7
7
|
|
8
8
|
module Dry
|
9
9
|
module Schema
|
@@ -100,6 +100,11 @@ module Dry
|
|
100
100
|
self.class.new(map(&:stringified))
|
101
101
|
end
|
102
102
|
|
103
|
+
# @api private
|
104
|
+
def to_dot_notation
|
105
|
+
@to_dot_notation ||= map(&:to_dot_notation).flatten
|
106
|
+
end
|
107
|
+
|
103
108
|
# Iterate over keys
|
104
109
|
#
|
105
110
|
# @api public
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/initializer"
|
4
|
+
require "dry/schema/constants"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Schema
|
8
|
+
# @api private
|
9
|
+
class KeyValidator
|
10
|
+
extend Dry::Initializer
|
11
|
+
|
12
|
+
INDEX_REGEX = /\[\d+\]/.freeze
|
13
|
+
DIGIT_REGEX = /\A\d+\z/.freeze
|
14
|
+
BRACKETS = "[]"
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
option :key_map
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
def call(result)
|
21
|
+
input = result.to_h
|
22
|
+
|
23
|
+
input_paths = key_paths(input)
|
24
|
+
key_paths = key_map.to_dot_notation
|
25
|
+
|
26
|
+
input_paths.each do |path|
|
27
|
+
error_path =
|
28
|
+
if path[INDEX_REGEX]
|
29
|
+
key = path.gsub(INDEX_REGEX, BRACKETS)
|
30
|
+
|
31
|
+
if key_paths.none? { |key_path| key_path.include?(key) }
|
32
|
+
arr = path.gsub(INDEX_REGEX) { |m| ".#{m[1]}" }
|
33
|
+
arr.split(DOT).map { |s| DIGIT_REGEX.match?(s) ? s.to_i : s.to_sym }
|
34
|
+
end
|
35
|
+
elsif !key_paths.include?(path)
|
36
|
+
path
|
37
|
+
end
|
38
|
+
|
39
|
+
next unless error_path
|
40
|
+
|
41
|
+
result.add_error([:unexpected_key, [error_path, input]])
|
42
|
+
end
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
def key_paths(hash)
|
51
|
+
hash.flat_map { |key, _|
|
52
|
+
case (value = hash[key])
|
53
|
+
when Hash
|
54
|
+
[key].product(key_paths(hash[key])).map { |keys| keys.join(DOT) }
|
55
|
+
when Array
|
56
|
+
hashes_or_arrays = value.select { |e| e.is_a?(Array) || e.is_a?(Hash) }
|
57
|
+
hashes_or_arrays.flat_map.with_index { |el, idx|
|
58
|
+
key_paths(el).map { |path| ["#{key}[#{idx}]", *path].join(DOT) }
|
59
|
+
}
|
60
|
+
else
|
61
|
+
key.to_s
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|