dry-schema 0.1.0 → 1.0.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 +166 -0
- data/LICENSE +1 -1
- data/README.md +13 -4
- data/config/errors.yml +59 -54
- data/lib/dry/schema/compiler.rb +24 -0
- data/lib/dry/schema/config.rb +23 -27
- data/lib/dry/schema/constants.rb +27 -3
- data/lib/dry/schema/dsl.rb +36 -49
- data/lib/dry/schema/extensions/hints/compiler_methods.rb +19 -0
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +84 -0
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +44 -0
- data/lib/dry/schema/extensions/hints/result_methods.rb +47 -0
- data/lib/dry/schema/extensions/hints.rb +56 -0
- data/lib/dry/schema/extensions/monads.rb +15 -4
- data/lib/dry/schema/extensions.rb +6 -0
- data/lib/dry/schema/json.rb +4 -2
- data/lib/dry/schema/key.rb +8 -10
- data/lib/dry/schema/key_coercer.rb +6 -2
- data/lib/dry/schema/key_map.rb +5 -4
- data/lib/dry/schema/macros/array.rb +39 -0
- data/lib/dry/schema/macros/core.rb +2 -0
- data/lib/dry/schema/macros/dsl.rb +149 -4
- data/lib/dry/schema/macros/each.rb +12 -1
- data/lib/dry/schema/macros/filled.rb +14 -7
- data/lib/dry/schema/macros/hash.rb +6 -32
- data/lib/dry/schema/macros/key.rb +38 -60
- data/lib/dry/schema/macros/maybe.rb +5 -3
- data/lib/dry/schema/macros/optional.rb +8 -1
- data/lib/dry/schema/macros/required.rb +3 -1
- data/lib/dry/schema/macros/schema.rb +46 -0
- data/lib/dry/schema/macros/value.rb +18 -2
- data/lib/dry/schema/macros.rb +4 -0
- data/lib/dry/schema/message/or.rb +54 -0
- data/lib/dry/schema/message.rb +56 -72
- data/lib/dry/schema/message_compiler/visitor_opts.rb +28 -1
- data/lib/dry/schema/message_compiler.rb +85 -74
- data/lib/dry/schema/message_set.rb +82 -50
- data/lib/dry/schema/messages/abstract.rb +84 -44
- data/lib/dry/schema/messages/i18n.rb +45 -9
- data/lib/dry/schema/messages/namespaced.rb +22 -5
- data/lib/dry/schema/messages/template.rb +68 -0
- data/lib/dry/schema/messages/yaml.rb +76 -22
- data/lib/dry/schema/messages.rb +19 -24
- data/lib/dry/schema/namespaced_rule.rb +37 -0
- data/lib/dry/schema/params.rb +4 -2
- data/lib/dry/schema/path.rb +102 -0
- data/lib/dry/schema/predicate.rb +15 -0
- data/lib/dry/schema/predicate_inferrer.rb +116 -23
- data/lib/dry/schema/predicate_registry.rb +2 -0
- data/lib/dry/schema/processor.rb +70 -39
- data/lib/dry/schema/result.rb +16 -31
- data/lib/dry/schema/rule_applier.rb +10 -3
- data/lib/dry/schema/trace.rb +16 -3
- data/lib/dry/schema/type_registry.rb +5 -3
- data/lib/dry/schema/types.rb +3 -1
- data/lib/dry/schema/value_coercer.rb +10 -4
- data/lib/dry/schema/version.rb +3 -1
- data/lib/dry/schema.rb +39 -13
- data/lib/dry-schema.rb +2 -0
- metadata +43 -47
data/lib/dry/schema/params.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/schema/processor'
|
|
2
4
|
|
|
3
5
|
module Dry
|
|
@@ -5,7 +7,7 @@ module Dry
|
|
|
5
7
|
# Params schema type
|
|
6
8
|
#
|
|
7
9
|
# @see Processor
|
|
8
|
-
# @see Schema
|
|
10
|
+
# @see Schema#Params
|
|
9
11
|
#
|
|
10
12
|
# @api public
|
|
11
13
|
class Params < Processor
|
|
@@ -13,4 +15,4 @@ module Dry
|
|
|
13
15
|
config.type_registry = config.type_registry.namespaced(:params)
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
|
-
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry/schema/constants'
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
# Path represents a list of keys in a hash
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
class Path
|
|
11
|
+
include Comparable
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
14
|
+
# @return [Array<Symbol>]
|
|
15
|
+
attr_reader :keys
|
|
16
|
+
|
|
17
|
+
alias_method :root, :first
|
|
18
|
+
|
|
19
|
+
# Coerce a spec into a path object
|
|
20
|
+
#
|
|
21
|
+
# @param [Path, Symbol, String, Hash, Array<Symbol>] spec
|
|
22
|
+
#
|
|
23
|
+
# @return [Path]
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
def self.[](spec)
|
|
27
|
+
case spec
|
|
28
|
+
when Symbol, Array
|
|
29
|
+
new(Array[*spec])
|
|
30
|
+
when String
|
|
31
|
+
new(spec.split(DOT).map(&:to_sym))
|
|
32
|
+
when Hash
|
|
33
|
+
new(keys_from_hash(spec))
|
|
34
|
+
when Path
|
|
35
|
+
spec
|
|
36
|
+
else
|
|
37
|
+
raise ArgumentError, '+spec+ must be either a Symbol, Array, Hash or a Path'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Extract a list of keys from a hash
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
44
|
+
def self.keys_from_hash(hash)
|
|
45
|
+
hash.inject([]) { |a, (k, v)|
|
|
46
|
+
v.is_a?(Hash) ? a.concat([k, *keys_from_hash(v)]) : a.concat([k, v])
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @api private
|
|
51
|
+
def initialize(keys)
|
|
52
|
+
@keys = keys
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @api private
|
|
56
|
+
def each(&block)
|
|
57
|
+
keys.each(&block)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @api private
|
|
61
|
+
def index(key)
|
|
62
|
+
keys.index(key)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @api private
|
|
66
|
+
def include?(other)
|
|
67
|
+
return false unless same_root?(other)
|
|
68
|
+
return false if index? && other.index? && !last.equal?(other.last)
|
|
69
|
+
self >= other
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
def <=>(other)
|
|
74
|
+
raise ArgumentError, "Can't compare paths from different branches" unless same_root?(other)
|
|
75
|
+
|
|
76
|
+
return 0 if keys.eql?(other.keys)
|
|
77
|
+
|
|
78
|
+
res =
|
|
79
|
+
map { |key| (idx = other.index(key)) && keys[idx].equal?(key) }
|
|
80
|
+
.compact
|
|
81
|
+
.reject { |value| value.equal?(false) }
|
|
82
|
+
|
|
83
|
+
res.size < count ? 1 : -1
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @api private
|
|
87
|
+
def last
|
|
88
|
+
keys.last
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @api private
|
|
92
|
+
def same_root?(other)
|
|
93
|
+
root.equal?(other.root)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @api private
|
|
97
|
+
def index?
|
|
98
|
+
last.is_a?(Integer)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/dry/schema/predicate.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/equalizer'
|
|
2
4
|
require 'dry/logic/operators'
|
|
3
5
|
|
|
@@ -19,6 +21,10 @@ module Dry
|
|
|
19
21
|
@predicate = predicate
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
# Dump negated predicate to an AST
|
|
25
|
+
#
|
|
26
|
+
# @return [Array]
|
|
27
|
+
#
|
|
22
28
|
# @api private
|
|
23
29
|
def to_ast(*args)
|
|
24
30
|
[:not, predicate.to_ast(*args)]
|
|
@@ -51,6 +57,9 @@ module Dry
|
|
|
51
57
|
|
|
52
58
|
# Negate a predicate
|
|
53
59
|
#
|
|
60
|
+
# @example
|
|
61
|
+
# required(:name).value(:string) { !empty? }
|
|
62
|
+
#
|
|
54
63
|
# @return [Negation]
|
|
55
64
|
#
|
|
56
65
|
# @api public
|
|
@@ -65,11 +74,17 @@ module Dry
|
|
|
65
74
|
end
|
|
66
75
|
end
|
|
67
76
|
|
|
77
|
+
# Compile predicate to a rule object
|
|
78
|
+
#
|
|
68
79
|
# @api private
|
|
69
80
|
def to_rule
|
|
70
81
|
compiler.visit(to_ast)
|
|
71
82
|
end
|
|
72
83
|
|
|
84
|
+
# Dump predicate to an AST
|
|
85
|
+
#
|
|
86
|
+
# @return [Array]
|
|
87
|
+
#
|
|
73
88
|
# @api private
|
|
74
89
|
def to_ast(*)
|
|
75
90
|
[:predicate, [name, compiler.predicates.arg_list(name, *args)]]
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/core/cache'
|
|
2
4
|
|
|
3
5
|
module Dry
|
|
@@ -9,40 +11,131 @@ module Dry
|
|
|
9
11
|
class PredicateInferrer
|
|
10
12
|
extend Dry::Core::Cache
|
|
11
13
|
|
|
12
|
-
TYPE_TO_PREDICATE =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if hash.key?(primitive)
|
|
16
|
-
hash[primitive]
|
|
17
|
-
else
|
|
18
|
-
:"#{primitive.name.split('::').last.downcase}?"
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
TYPE_TO_PREDICATE.update(
|
|
14
|
+
TYPE_TO_PREDICATE = {
|
|
15
|
+
DateTime => :date_time?,
|
|
23
16
|
FalseClass => :false?,
|
|
24
17
|
Integer => :int?,
|
|
25
18
|
NilClass => :nil?,
|
|
26
19
|
String => :str?,
|
|
27
20
|
TrueClass => :true?
|
|
28
|
-
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
REDUCED_TYPES = {
|
|
24
|
+
%i[true? false?] => :bool?
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# Compiler reduces type AST into a list of predicates
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
class Compiler
|
|
31
|
+
# @return [PredicateRegistry]
|
|
32
|
+
# @api private
|
|
33
|
+
attr_reader :registry
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
def initialize(registry)
|
|
37
|
+
@registry = registry
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @api private
|
|
41
|
+
def infer_predicate(type)
|
|
42
|
+
TYPE_TO_PREDICATE.fetch(type) { :"#{type.name.split('::').last.downcase}?" }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @api private
|
|
46
|
+
def visit(node)
|
|
47
|
+
meth, rest = node
|
|
48
|
+
public_send(:"visit_#{meth}", rest)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @api private
|
|
52
|
+
def visit_nominal(node)
|
|
53
|
+
type = node[0]
|
|
54
|
+
predicate = infer_predicate(type)
|
|
55
|
+
|
|
56
|
+
if registry.key?(predicate)
|
|
57
|
+
predicate
|
|
58
|
+
else
|
|
59
|
+
{ type?: type }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @api private
|
|
64
|
+
def visit_hash(_)
|
|
65
|
+
:hash?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @api private
|
|
69
|
+
def visit_array(_)
|
|
70
|
+
:array?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @api private
|
|
74
|
+
def visit_lax(node)
|
|
75
|
+
visit(node)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @api private
|
|
79
|
+
def visit_constructor(node)
|
|
80
|
+
other, * = node
|
|
81
|
+
visit(other)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @api private
|
|
85
|
+
def visit_enum(node)
|
|
86
|
+
other, * = node
|
|
87
|
+
visit(other)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @api private
|
|
91
|
+
def visit_sum(node)
|
|
92
|
+
left, right = node
|
|
93
|
+
|
|
94
|
+
predicates = [visit(left), visit(right)]
|
|
95
|
+
|
|
96
|
+
if predicates.first == :nil?
|
|
97
|
+
predicates[1..predicates.size - 1]
|
|
98
|
+
else
|
|
99
|
+
predicates
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @api private
|
|
104
|
+
def visit_constrained(node)
|
|
105
|
+
other, * = node
|
|
106
|
+
visit(other)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @api private
|
|
110
|
+
def visit_any(_)
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [Compiler]
|
|
116
|
+
# @api private
|
|
117
|
+
attr_reader :compiler
|
|
118
|
+
|
|
119
|
+
# @api private
|
|
120
|
+
def initialize(registry)
|
|
121
|
+
@compiler = Compiler.new(registry)
|
|
122
|
+
end
|
|
29
123
|
|
|
30
124
|
# Infer predicate identifier from the provided type
|
|
31
125
|
#
|
|
32
126
|
# @return [Symbol]
|
|
33
127
|
#
|
|
34
128
|
# @api private
|
|
35
|
-
def
|
|
36
|
-
fetch_or_store(type.hash)
|
|
37
|
-
predicates =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
129
|
+
def [](type)
|
|
130
|
+
self.class.fetch_or_store(type.hash) do
|
|
131
|
+
predicates = compiler.visit(type.to_ast)
|
|
132
|
+
|
|
133
|
+
if predicates.is_a?(Hash)
|
|
134
|
+
predicates
|
|
135
|
+
else
|
|
136
|
+
Array(REDUCED_TYPES[predicates] || predicates).flatten
|
|
137
|
+
end
|
|
138
|
+
end
|
|
46
139
|
end
|
|
47
140
|
end
|
|
48
141
|
end
|
data/lib/dry/schema/processor.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/configurable'
|
|
2
4
|
require 'dry/initializer'
|
|
3
5
|
|
|
@@ -15,7 +17,7 @@ module Dry
|
|
|
15
17
|
# 1. Prepare input hash using a key map
|
|
16
18
|
# 2. Apply pre-coercion filtering rules (optional step, used only when `filter` was used)
|
|
17
19
|
# 3. Apply value coercions based on type specifications
|
|
18
|
-
# 4. Apply rules
|
|
20
|
+
# 4. Apply rules
|
|
19
21
|
#
|
|
20
22
|
# @see Params
|
|
21
23
|
# @see JSON
|
|
@@ -30,42 +32,42 @@ module Dry
|
|
|
30
32
|
|
|
31
33
|
param :steps, default: -> { EMPTY_ARRAY.dup }
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
#
|
|
40
|
-
# @api public
|
|
41
|
-
def self.define(&block)
|
|
42
|
-
@__definition__ ||= DSL.new(
|
|
43
|
-
processor_type: self, parent: superclass.definition, **config, &block
|
|
44
|
-
)
|
|
45
|
-
self
|
|
46
|
-
end
|
|
35
|
+
class << self
|
|
36
|
+
# Return DSL configured via #define
|
|
37
|
+
#
|
|
38
|
+
# @return [DSL]
|
|
39
|
+
# @api private
|
|
40
|
+
attr_reader :definition
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@
|
|
55
|
-
|
|
42
|
+
# Define a schema for your processor class
|
|
43
|
+
#
|
|
44
|
+
# @see Schema#define
|
|
45
|
+
# @see Schema#Params
|
|
46
|
+
# @see Schema#JSON
|
|
47
|
+
#
|
|
48
|
+
# @return [Class]
|
|
49
|
+
#
|
|
50
|
+
# @api public
|
|
51
|
+
def define(&block)
|
|
52
|
+
@definition ||= DSL.new(
|
|
53
|
+
processor_type: self, parent: superclass.definition, **config, &block
|
|
54
|
+
)
|
|
55
|
+
self
|
|
56
|
+
end
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
# Build a new processor object
|
|
59
|
+
#
|
|
60
|
+
# @return [Processor]
|
|
61
|
+
#
|
|
62
|
+
# @api public
|
|
63
|
+
def new(&block)
|
|
64
|
+
if block
|
|
65
|
+
super.tap(&block)
|
|
66
|
+
elsif definition
|
|
67
|
+
definition.call
|
|
68
|
+
else
|
|
69
|
+
raise ArgumentError, 'Cannot create a schema without a definition'
|
|
70
|
+
end
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
|
|
@@ -94,6 +96,16 @@ module Dry
|
|
|
94
96
|
end
|
|
95
97
|
end
|
|
96
98
|
end
|
|
99
|
+
alias_method :[], :call
|
|
100
|
+
|
|
101
|
+
# Return a proc that acts like a schema object
|
|
102
|
+
#
|
|
103
|
+
# @return [Proc]
|
|
104
|
+
#
|
|
105
|
+
# @api public
|
|
106
|
+
def to_proc
|
|
107
|
+
->(input) { call(input) }
|
|
108
|
+
end
|
|
97
109
|
|
|
98
110
|
# Return the key map
|
|
99
111
|
#
|
|
@@ -101,7 +113,18 @@ module Dry
|
|
|
101
113
|
#
|
|
102
114
|
# @api public
|
|
103
115
|
def key_map
|
|
104
|
-
@
|
|
116
|
+
@key_map ||= steps.detect { |s| s.is_a?(KeyCoercer) }.key_map
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Return string represntation
|
|
120
|
+
#
|
|
121
|
+
# @return [String]
|
|
122
|
+
#
|
|
123
|
+
# @api public
|
|
124
|
+
def inspect
|
|
125
|
+
<<~STR.strip
|
|
126
|
+
#<#{self.class.name} keys=#{key_map.map(&:dump)} rules=#{rules.map { |k, v| [k, v.to_s] }.to_h}>
|
|
127
|
+
STR
|
|
105
128
|
end
|
|
106
129
|
|
|
107
130
|
# Return the type schema
|
|
@@ -110,7 +133,16 @@ module Dry
|
|
|
110
133
|
#
|
|
111
134
|
# @api private
|
|
112
135
|
def type_schema
|
|
113
|
-
@
|
|
136
|
+
@type_schema ||= steps.detect { |s| s.is_a?(ValueCoercer) }.type_schema
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Return the rules config
|
|
140
|
+
#
|
|
141
|
+
# @return [Dry::Types::Config]
|
|
142
|
+
#
|
|
143
|
+
# @api private
|
|
144
|
+
def config
|
|
145
|
+
@config ||= rule_applier.config
|
|
114
146
|
end
|
|
115
147
|
|
|
116
148
|
# Return AST representation of the rules
|
|
@@ -142,8 +174,7 @@ module Dry
|
|
|
142
174
|
#
|
|
143
175
|
# @api private
|
|
144
176
|
def rule_applier
|
|
145
|
-
|
|
146
|
-
@__rule_applier__ ||= steps.last
|
|
177
|
+
@rule_applier ||= steps.last
|
|
147
178
|
end
|
|
148
179
|
alias_method :to_rule, :rule_applier
|
|
149
180
|
end
|
data/lib/dry/schema/result.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/initializer'
|
|
2
4
|
require 'dry/equalizer'
|
|
3
5
|
|
|
6
|
+
require 'dry/schema/path'
|
|
7
|
+
|
|
4
8
|
module Dry
|
|
5
9
|
module Schema
|
|
6
10
|
# Processing result
|
|
@@ -15,8 +19,11 @@ module Dry
|
|
|
15
19
|
|
|
16
20
|
# @api private
|
|
17
21
|
param :output
|
|
22
|
+
|
|
23
|
+
# Dump result to a hash returning processed and validated data
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash]
|
|
18
26
|
alias_method :to_h, :output
|
|
19
|
-
alias_method :to_hash, :output
|
|
20
27
|
|
|
21
28
|
# @api private
|
|
22
29
|
param :results, default: -> { EMPTY_ARRAY.dup }
|
|
@@ -66,15 +73,15 @@ module Dry
|
|
|
66
73
|
output.key?(name)
|
|
67
74
|
end
|
|
68
75
|
|
|
69
|
-
# Check if
|
|
76
|
+
# Check if there's an error for the provided spec
|
|
70
77
|
#
|
|
71
|
-
# @param [Symbol]
|
|
78
|
+
# @param [Symbol, Hash<Symbol=>Symbol>] spec
|
|
72
79
|
#
|
|
73
80
|
# @return [Boolean]
|
|
74
81
|
#
|
|
75
82
|
# @api public
|
|
76
|
-
def error?(
|
|
77
|
-
|
|
83
|
+
def error?(spec)
|
|
84
|
+
message_set.any? { |msg| Path[msg.path].include?(Path[spec]) }
|
|
78
85
|
end
|
|
79
86
|
|
|
80
87
|
# Check if the result is successful
|
|
@@ -99,33 +106,11 @@ module Dry
|
|
|
99
106
|
#
|
|
100
107
|
# @see #message_set
|
|
101
108
|
#
|
|
102
|
-
# @return [
|
|
109
|
+
# @return [MessageSet]
|
|
103
110
|
#
|
|
104
111
|
# @api public
|
|
105
112
|
def errors(options = EMPTY_HASH)
|
|
106
|
-
message_set(options
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Get all messages including hints
|
|
110
|
-
#
|
|
111
|
-
# @see #message_set
|
|
112
|
-
#
|
|
113
|
-
# @return [Hash<Symbol=>Array>]
|
|
114
|
-
#
|
|
115
|
-
# @api public
|
|
116
|
-
def messages(options = EMPTY_HASH)
|
|
117
|
-
message_set(options.merge(hints: true)).dump
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Get hints exclusively without errors
|
|
121
|
-
#
|
|
122
|
-
# @see #message_set
|
|
123
|
-
#
|
|
124
|
-
# @return [Hash<Symbol=>Array>]
|
|
125
|
-
#
|
|
126
|
-
# @api public
|
|
127
|
-
def hints(options = EMPTY_HASH)
|
|
128
|
-
message_set(options.merge(failures: false)).dump
|
|
113
|
+
message_set(options)
|
|
129
114
|
end
|
|
130
115
|
|
|
131
116
|
# Return the message set
|
|
@@ -148,7 +133,7 @@ module Dry
|
|
|
148
133
|
#
|
|
149
134
|
# @api public
|
|
150
135
|
def inspect
|
|
151
|
-
"#<#{self.class}#{to_h.inspect} errors=#{errors.inspect}>"
|
|
136
|
+
"#<#{self.class}#{to_h.inspect} errors=#{errors.to_h.inspect}>"
|
|
152
137
|
end
|
|
153
138
|
|
|
154
139
|
private
|
|
@@ -157,7 +142,7 @@ module Dry
|
|
|
157
142
|
#
|
|
158
143
|
# @api private
|
|
159
144
|
def result_ast
|
|
160
|
-
@
|
|
145
|
+
@result_ast ||= results.map(&:to_ast)
|
|
161
146
|
end
|
|
162
147
|
end
|
|
163
148
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/initializer'
|
|
2
4
|
|
|
3
5
|
require 'dry/schema/constants'
|
|
@@ -18,10 +20,10 @@ module Dry
|
|
|
18
20
|
param :rules
|
|
19
21
|
|
|
20
22
|
# @api private
|
|
21
|
-
option :config, default:
|
|
23
|
+
option :config, default: -> { Config.new }
|
|
22
24
|
|
|
23
25
|
# @api private
|
|
24
|
-
option :message_compiler, default:
|
|
26
|
+
option :message_compiler, default: -> { MessageCompiler.new(Messages.setup(config.messages)) }
|
|
25
27
|
|
|
26
28
|
# @api private
|
|
27
29
|
def call(input)
|
|
@@ -29,6 +31,7 @@ module Dry
|
|
|
29
31
|
|
|
30
32
|
rules.each do |name, rule|
|
|
31
33
|
next if input.error?(name)
|
|
34
|
+
|
|
32
35
|
result = rule.(input)
|
|
33
36
|
results << result if result.failure?
|
|
34
37
|
end
|
|
@@ -38,7 +41,11 @@ module Dry
|
|
|
38
41
|
|
|
39
42
|
# @api private
|
|
40
43
|
def to_ast
|
|
41
|
-
|
|
44
|
+
if config.messages.namespace
|
|
45
|
+
[:namespace, [config.messages.namespace, [:set, rules.values.map(&:to_ast)]]]
|
|
46
|
+
else
|
|
47
|
+
[:set, rules.values.map(&:to_ast)]
|
|
48
|
+
end
|
|
42
49
|
end
|
|
43
50
|
end
|
|
44
51
|
end
|
data/lib/dry/schema/trace.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/schema/constants'
|
|
2
4
|
require 'dry/schema/compiler'
|
|
3
5
|
require 'dry/schema/predicate'
|
|
@@ -27,10 +29,15 @@ module Dry
|
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
# @api private
|
|
30
|
-
def evaluate(*predicates, **opts
|
|
32
|
+
def evaluate(*predicates, **opts)
|
|
33
|
+
pred_opts = opts.dup
|
|
34
|
+
pred_opts.delete(:type_spec)
|
|
35
|
+
|
|
31
36
|
predicates.each do |predicate|
|
|
32
37
|
if predicate.respond_to?(:call)
|
|
33
38
|
append(predicate)
|
|
39
|
+
elsif predicate.is_a?(::Hash)
|
|
40
|
+
evaluate_hash_predicates(predicate)
|
|
34
41
|
elsif predicate.is_a?(::Array)
|
|
35
42
|
append(predicate.map { |pred| __send__(pred) }.reduce(:|))
|
|
36
43
|
else
|
|
@@ -38,10 +45,16 @@ module Dry
|
|
|
38
45
|
end
|
|
39
46
|
end
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
evaluate_hash_predicates(pred_opts)
|
|
49
|
+
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @api private
|
|
54
|
+
def evaluate_hash_predicates(predicates)
|
|
55
|
+
predicates.each do |predicate, *args|
|
|
42
56
|
append(__send__(predicate, *args))
|
|
43
57
|
end
|
|
44
|
-
|
|
45
58
|
self
|
|
46
59
|
end
|
|
47
60
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'dry/schema/constants'
|
|
2
4
|
require 'dry/schema/types'
|
|
3
5
|
|
|
@@ -16,12 +18,12 @@ module Dry
|
|
|
16
18
|
attr_reader :namespace
|
|
17
19
|
|
|
18
20
|
# @api private
|
|
19
|
-
def self.new(types = Dry::Types, namespace =
|
|
21
|
+
def self.new(types = Dry::Types, namespace = :nominal)
|
|
20
22
|
super
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
# @api private
|
|
24
|
-
def initialize(types, namespace =
|
|
26
|
+
def initialize(types, namespace = :nominal)
|
|
25
27
|
@types = types
|
|
26
28
|
@namespace = namespace
|
|
27
29
|
end
|
|
@@ -39,4 +41,4 @@ module Dry
|
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
|
-
end
|
|
44
|
+
end
|