dry-schema 0.1.1 → 0.2.0
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 +29 -0
- data/config/errors.yml +56 -55
- data/lib/dry/schema/compiler.rb +17 -0
- data/lib/dry/schema/dsl.rb +1 -1
- data/lib/dry/schema/extensions.rb +4 -0
- data/lib/dry/schema/extensions/hints.rb +52 -0
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +72 -0
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +29 -0
- data/lib/dry/schema/extensions/hints/result_methods.rb +38 -0
- data/lib/dry/schema/macros.rb +1 -0
- data/lib/dry/schema/macros/dsl.rb +22 -1
- data/lib/dry/schema/macros/filled.rb +8 -4
- data/lib/dry/schema/macros/hash.rb +3 -30
- data/lib/dry/schema/macros/key.rb +6 -6
- data/lib/dry/schema/macros/schema.rb +44 -0
- data/lib/dry/schema/macros/value.rb +9 -0
- data/lib/dry/schema/message.rb +13 -25
- data/lib/dry/schema/message_compiler.rb +31 -32
- data/lib/dry/schema/message_compiler/visitor_opts.rb +26 -0
- data/lib/dry/schema/message_set.rb +7 -47
- data/lib/dry/schema/messages.rb +5 -3
- data/lib/dry/schema/messages/abstract.rb +14 -3
- data/lib/dry/schema/messages/i18n.rb +15 -1
- data/lib/dry/schema/messages/namespaced.rb +16 -1
- data/lib/dry/schema/messages/yaml.rb +13 -4
- data/lib/dry/schema/namespaced_rule.rb +23 -0
- data/lib/dry/schema/result.rb +1 -23
- data/lib/dry/schema/rule_applier.rb +5 -1
- data/lib/dry/schema/trace.rb +4 -1
- data/lib/dry/schema/version.rb +1 -1
- metadata +9 -3
@@ -35,10 +35,21 @@ module Dry
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
# Specify a nested
|
38
|
+
# Specify a nested hash without enforced hash? type-check
|
39
39
|
#
|
40
40
|
# @api public
|
41
41
|
def schema(*args, &block)
|
42
|
+
append_macro(Macros::Schema) do |macro|
|
43
|
+
macro.call(*args, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Specify a nested hash with enforced hash? type-check
|
48
|
+
#
|
49
|
+
# @see #schema
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def hash(*args, &block)
|
42
53
|
append_macro(Macros::Hash) do |macro|
|
43
54
|
macro.call(*args, &block)
|
44
55
|
end
|
@@ -53,6 +64,16 @@ module Dry
|
|
53
64
|
end
|
54
65
|
end
|
55
66
|
|
67
|
+
# Like `each`, but prepends `array?` check
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def array(*args, &block)
|
71
|
+
value(:array)
|
72
|
+
append_macro(Macros::Each) do |macro|
|
73
|
+
macro.value(*args, &block)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
56
77
|
private
|
57
78
|
|
58
79
|
# @api private
|
@@ -7,16 +7,20 @@ module Dry
|
|
7
7
|
#
|
8
8
|
# @api public
|
9
9
|
class Filled < Value
|
10
|
-
def call(*
|
11
|
-
if
|
10
|
+
def call(*predicates, **opts, &block)
|
11
|
+
if predicates.include?(:empty?)
|
12
12
|
raise ::Dry::Schema::InvalidSchemaError, "Using filled with empty? predicate is invalid"
|
13
13
|
end
|
14
14
|
|
15
|
-
if
|
15
|
+
if predicates.include?(:filled?)
|
16
16
|
raise ::Dry::Schema::InvalidSchemaError, "Using filled with filled? is redundant"
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
if opts[:type_spec].equal?(true)
|
20
|
+
value(predicates[0], :filled?, *predicates[1..predicates.size-1], **opts, &block)
|
21
|
+
else
|
22
|
+
value(:filled?, *predicates, **opts, &block)
|
23
|
+
end
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'dry/schema/macros/
|
1
|
+
require 'dry/schema/macros/schema'
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module Schema
|
@@ -6,38 +6,11 @@ module Dry
|
|
6
6
|
# Macro used to specify a nested schema
|
7
7
|
#
|
8
8
|
# @api public
|
9
|
-
class Hash <
|
9
|
+
class Hash < Schema
|
10
10
|
# @api private
|
11
11
|
def call(*args, &block)
|
12
12
|
trace << hash?
|
13
|
-
|
14
|
-
super(*args) unless args.empty?
|
15
|
-
|
16
|
-
if block
|
17
|
-
definition = schema_dsl.new(&block)
|
18
|
-
|
19
|
-
parent_type = schema_dsl.types[name]
|
20
|
-
definition_schema = definition.type_schema
|
21
|
-
|
22
|
-
schema_type =
|
23
|
-
if parent_type.respond_to?(:of)
|
24
|
-
parent_type.of(definition_schema)
|
25
|
-
else
|
26
|
-
definition_schema
|
27
|
-
end
|
28
|
-
|
29
|
-
final_type =
|
30
|
-
if schema_dsl.maybe?(parent_type)
|
31
|
-
schema_type.optional
|
32
|
-
else
|
33
|
-
schema_type
|
34
|
-
end
|
35
|
-
|
36
|
-
schema_dsl.set_type(name, final_type)
|
37
|
-
|
38
|
-
trace << definition.to_rule
|
39
|
-
end
|
40
|
-
|
13
|
+
super(*args, &block)
|
41
14
|
self
|
42
15
|
end
|
43
16
|
end
|
@@ -35,7 +35,7 @@ module Dry
|
|
35
35
|
#
|
36
36
|
# @api public
|
37
37
|
def value(*args, **opts, &block)
|
38
|
-
extract_type_spec(*args) do |*predicates
|
38
|
+
extract_type_spec(*args) do |*predicates, type_spec:|
|
39
39
|
super(*predicates, **opts, &block)
|
40
40
|
end
|
41
41
|
end
|
@@ -48,8 +48,8 @@ module Dry
|
|
48
48
|
#
|
49
49
|
# @api public
|
50
50
|
def filled(*args, **opts, &block)
|
51
|
-
extract_type_spec(*args) do |*predicates
|
52
|
-
super(*predicates, **opts, &block)
|
51
|
+
extract_type_spec(*args) do |*predicates, type_spec:|
|
52
|
+
super(*predicates, type_spec: type_spec, **opts, &block)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
@@ -61,7 +61,7 @@ module Dry
|
|
61
61
|
#
|
62
62
|
# @api public
|
63
63
|
def maybe(*args, **opts, &block)
|
64
|
-
extract_type_spec(*args, nullable: true) do |*predicates
|
64
|
+
extract_type_spec(*args, nullable: true) do |*predicates, type_spec:|
|
65
65
|
append_macro(Macros::Maybe) do |macro|
|
66
66
|
macro.call(*predicates, **opts, &block)
|
67
67
|
end
|
@@ -104,7 +104,7 @@ module Dry
|
|
104
104
|
def extract_type_spec(*args, nullable: false)
|
105
105
|
type_spec = args[0]
|
106
106
|
|
107
|
-
if type_spec.kind_of?(Schema::Processor) || type_spec.is_a?(Symbol) && type_spec.to_s.end_with?(QUESTION_MARK)
|
107
|
+
if type_spec.kind_of?(Dry::Schema::Processor) || type_spec.is_a?(Symbol) && type_spec.to_s.end_with?(QUESTION_MARK)
|
108
108
|
type_spec = nil
|
109
109
|
end
|
110
110
|
|
@@ -129,7 +129,7 @@ module Dry
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
132
|
-
yield(*predicates)
|
132
|
+
yield(*predicates, type_spec: !type_spec.nil?)
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'dry/schema/macros/value'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
module Macros
|
6
|
+
# Macro used to specify a nested schema
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Schema < Value
|
10
|
+
# @api private
|
11
|
+
def call(*args, &block)
|
12
|
+
super(*args) unless args.empty?
|
13
|
+
|
14
|
+
if block
|
15
|
+
definition = schema_dsl.new(&block)
|
16
|
+
|
17
|
+
parent_type = schema_dsl.types[name]
|
18
|
+
definition_schema = definition.type_schema
|
19
|
+
|
20
|
+
schema_type =
|
21
|
+
if parent_type.respond_to?(:of)
|
22
|
+
parent_type.of(definition_schema)
|
23
|
+
else
|
24
|
+
definition_schema
|
25
|
+
end
|
26
|
+
|
27
|
+
final_type =
|
28
|
+
if schema_dsl.maybe?(parent_type)
|
29
|
+
schema_type.optional
|
30
|
+
else
|
31
|
+
schema_type
|
32
|
+
end
|
33
|
+
|
34
|
+
schema_dsl.set_type(name, final_type)
|
35
|
+
|
36
|
+
trace << definition.to_rule
|
37
|
+
end
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -9,6 +9,15 @@ module Dry
|
|
9
9
|
class Value < DSL
|
10
10
|
# @api private
|
11
11
|
def call(*predicates, **opts, &block)
|
12
|
+
schema = predicates.detect { |predicate| predicate.is_a?(Processor) }
|
13
|
+
|
14
|
+
if schema
|
15
|
+
current_type = schema_dsl.types[name]
|
16
|
+
updated_type = current_type.respond_to?(:of) ? current_type.of(schema.type_schema) : schema.type_schema
|
17
|
+
|
18
|
+
schema_dsl.set_type(name, updated_type)
|
19
|
+
end
|
20
|
+
|
12
21
|
trace.evaluate(*predicates, **opts, &block)
|
13
22
|
|
14
23
|
trace.append(new(chain: false).instance_exec(&block)) if block
|
data/lib/dry/schema/message.rb
CHANGED
@@ -14,6 +14,8 @@ module Dry
|
|
14
14
|
#
|
15
15
|
# @api public
|
16
16
|
class Or
|
17
|
+
include Enumerable
|
18
|
+
|
17
19
|
# @api private
|
18
20
|
attr_reader :left
|
19
21
|
|
@@ -34,16 +36,21 @@ module Dry
|
|
34
36
|
@path = left.path
|
35
37
|
end
|
36
38
|
|
37
|
-
# @api private
|
38
|
-
def hint?
|
39
|
-
false
|
40
|
-
end
|
41
|
-
|
42
39
|
# Return a string representation of the message
|
43
40
|
#
|
44
41
|
# @api public
|
45
42
|
def to_s
|
46
|
-
|
43
|
+
uniq.join(" #{messages[:or].()} ")
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
def each(&block)
|
48
|
+
to_a.each(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
def to_a
|
53
|
+
[left, right]
|
47
54
|
end
|
48
55
|
end
|
49
56
|
|
@@ -75,29 +82,10 @@ module Dry
|
|
75
82
|
text
|
76
83
|
end
|
77
84
|
|
78
|
-
# @api private
|
79
|
-
def hint?
|
80
|
-
false
|
81
|
-
end
|
82
|
-
|
83
85
|
# @api private
|
84
86
|
def eql?(other)
|
85
87
|
other.is_a?(String) ? text == other : super
|
86
88
|
end
|
87
89
|
end
|
88
|
-
|
89
|
-
# A hint message sub-type
|
90
|
-
#
|
91
|
-
# @api private
|
92
|
-
class Hint < Message
|
93
|
-
def self.[](predicate, path, text, options)
|
94
|
-
Hint.new(predicate, path, text, options)
|
95
|
-
end
|
96
|
-
|
97
|
-
# @api private
|
98
|
-
def hint?
|
99
|
-
true
|
100
|
-
end
|
101
|
-
end
|
102
90
|
end
|
103
91
|
end
|
@@ -19,9 +19,8 @@ module Dry
|
|
19
19
|
@messages = messages
|
20
20
|
@options = options
|
21
21
|
@full = @options.fetch(:full, false)
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@default_lookup_options = { locale: locale }
|
22
|
+
@locale = @options[:locale]
|
23
|
+
@default_lookup_options = @locale ? { locale: locale } : EMPTY_HASH
|
25
24
|
end
|
26
25
|
|
27
26
|
# @api private
|
@@ -29,11 +28,6 @@ module Dry
|
|
29
28
|
@full
|
30
29
|
end
|
31
30
|
|
32
|
-
# @api private
|
33
|
-
def hints?
|
34
|
-
@hints
|
35
|
-
end
|
36
|
-
|
37
31
|
# @api private
|
38
32
|
def with(new_options)
|
39
33
|
return self if new_options.empty?
|
@@ -42,40 +36,35 @@ module Dry
|
|
42
36
|
|
43
37
|
# @api private
|
44
38
|
def call(ast)
|
45
|
-
|
39
|
+
current_messages = EMPTY_ARRAY.dup
|
40
|
+
compiled_messages = ast.map { |node| visit(node, EMPTY_OPTS.dup(current_messages)) }
|
41
|
+
|
42
|
+
MessageSet[compiled_messages, failures: options.fetch(:failures, true)]
|
46
43
|
end
|
47
44
|
|
48
45
|
# @api private
|
49
|
-
def visit(node,
|
50
|
-
__send__(:"visit_#{node[0]}", node[1],
|
46
|
+
def visit(node, opts = EMPTY_OPTS.dup)
|
47
|
+
__send__(:"visit_#{node[0]}", node[1], opts)
|
51
48
|
end
|
52
49
|
|
53
50
|
# @api private
|
54
|
-
def visit_failure(node, opts
|
51
|
+
def visit_failure(node, opts)
|
55
52
|
rule, other = node
|
56
53
|
visit(other, opts.(rule: rule))
|
57
54
|
end
|
58
55
|
|
59
56
|
# @api private
|
60
|
-
def visit_hint(node, opts
|
61
|
-
|
62
|
-
visit(node, opts.(message_type: :hint))
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# @api private
|
67
|
-
def visit_each(node, opts = EMPTY_OPTS.dup)
|
68
|
-
# TODO: we can still generate a hint for elements here!
|
69
|
-
[]
|
57
|
+
def visit_hint(node, opts)
|
58
|
+
nil
|
70
59
|
end
|
71
60
|
|
72
61
|
# @api private
|
73
|
-
def visit_not(node, opts
|
62
|
+
def visit_not(node, opts)
|
74
63
|
visit(node, opts.(not: true))
|
75
64
|
end
|
76
65
|
|
77
66
|
# @api private
|
78
|
-
def visit_and(node, opts
|
67
|
+
def visit_and(node, opts)
|
79
68
|
left, right = node.map { |n| visit(n, opts) }
|
80
69
|
|
81
70
|
if right
|
@@ -86,7 +75,7 @@ module Dry
|
|
86
75
|
end
|
87
76
|
|
88
77
|
# @api private
|
89
|
-
def visit_or(node, opts
|
78
|
+
def visit_or(node, opts)
|
90
79
|
left, right = node.map { |n| visit(n, opts) }
|
91
80
|
|
92
81
|
if [left, right].flatten.map(&:path).uniq.size == 1
|
@@ -99,7 +88,14 @@ module Dry
|
|
99
88
|
end
|
100
89
|
|
101
90
|
# @api private
|
102
|
-
def
|
91
|
+
def visit_namespace(node, opts)
|
92
|
+
ns, rest = node
|
93
|
+
self.class.new(messages.namespaced(ns), options).visit(rest, opts)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def visit_predicate(node, opts)
|
98
|
+
base_opts = opts.dup
|
103
99
|
predicate, args = node
|
104
100
|
|
105
101
|
*arg_vals, val = args.map(&:last)
|
@@ -121,9 +117,7 @@ module Dry
|
|
121
117
|
|
122
118
|
text = message_text(rule, template, tokens, options)
|
123
119
|
|
124
|
-
|
125
|
-
|
126
|
-
message_class[
|
120
|
+
message_type(options)[
|
127
121
|
predicate, path, text,
|
128
122
|
args: arg_vals,
|
129
123
|
input: input,
|
@@ -132,13 +126,18 @@ module Dry
|
|
132
126
|
end
|
133
127
|
|
134
128
|
# @api private
|
135
|
-
def
|
129
|
+
def message_type(*)
|
130
|
+
Message
|
131
|
+
end
|
132
|
+
|
133
|
+
# @api private
|
134
|
+
def visit_key(node, opts)
|
136
135
|
name, other = node
|
137
136
|
visit(other, opts.(path: name))
|
138
137
|
end
|
139
138
|
|
140
139
|
# @api private
|
141
|
-
def visit_set(node, opts
|
140
|
+
def visit_set(node, opts)
|
142
141
|
node.map { |el| visit(el, opts) }
|
143
142
|
end
|
144
143
|
|
@@ -149,7 +148,7 @@ module Dry
|
|
149
148
|
end
|
150
149
|
|
151
150
|
# @api private
|
152
|
-
def visit_xor(node, opts
|
151
|
+
def visit_xor(node, opts)
|
153
152
|
left, right = node
|
154
153
|
[visit(left, opts), visit(right, opts)].uniq
|
155
154
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'dry/schema/constants'
|
2
|
+
require 'dry/schema/message'
|
3
|
+
|
1
4
|
module Dry
|
2
5
|
module Schema
|
3
6
|
# @api private
|
@@ -12,6 +15,7 @@ module Dry
|
|
12
15
|
opts[:path] = EMPTY_ARRAY
|
13
16
|
opts[:rule] = nil
|
14
17
|
opts[:message_type] = :failure
|
18
|
+
opts[:current_messages] = EMPTY_ARRAY.dup
|
15
19
|
opts
|
16
20
|
end
|
17
21
|
|
@@ -24,6 +28,28 @@ module Dry
|
|
24
28
|
def call(other)
|
25
29
|
merge(other.update(path: [*path, *other[:path]]))
|
26
30
|
end
|
31
|
+
|
32
|
+
def dup(current_messages = EMPTY_ARRAY.dup)
|
33
|
+
opts = super()
|
34
|
+
opts[:current_messages] = current_messages
|
35
|
+
opts
|
36
|
+
end
|
37
|
+
|
38
|
+
def key_failure?(path)
|
39
|
+
failures.any? { |f| f.path == path && f.predicate.equal?(:key?) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def failures
|
43
|
+
current_messages.reject(&:hint?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def hints
|
47
|
+
current_messages.select(&:hint?)
|
48
|
+
end
|
49
|
+
|
50
|
+
def current_messages
|
51
|
+
self[:current_messages]
|
52
|
+
end
|
27
53
|
end
|
28
54
|
end
|
29
55
|
end
|