dry-schema 0.1.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 +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +20 -0
- data/README.md +21 -0
- data/config/errors.yml +91 -0
- data/lib/dry-schema.rb +1 -0
- data/lib/dry/schema.rb +51 -0
- data/lib/dry/schema/compiler.rb +31 -0
- data/lib/dry/schema/config.rb +52 -0
- data/lib/dry/schema/constants.rb +13 -0
- data/lib/dry/schema/dsl.rb +382 -0
- data/lib/dry/schema/extensions.rb +3 -0
- data/lib/dry/schema/extensions/monads.rb +18 -0
- data/lib/dry/schema/json.rb +16 -0
- data/lib/dry/schema/key.rb +166 -0
- data/lib/dry/schema/key_coercer.rb +37 -0
- data/lib/dry/schema/key_map.rb +133 -0
- data/lib/dry/schema/macros.rb +6 -0
- data/lib/dry/schema/macros/core.rb +51 -0
- data/lib/dry/schema/macros/dsl.rb +74 -0
- data/lib/dry/schema/macros/each.rb +18 -0
- data/lib/dry/schema/macros/filled.rb +24 -0
- data/lib/dry/schema/macros/hash.rb +46 -0
- data/lib/dry/schema/macros/key.rb +137 -0
- data/lib/dry/schema/macros/maybe.rb +37 -0
- data/lib/dry/schema/macros/optional.rb +17 -0
- data/lib/dry/schema/macros/required.rb +17 -0
- data/lib/dry/schema/macros/value.rb +41 -0
- data/lib/dry/schema/message.rb +103 -0
- data/lib/dry/schema/message_compiler.rb +193 -0
- data/lib/dry/schema/message_compiler/visitor_opts.rb +30 -0
- data/lib/dry/schema/message_set.rb +123 -0
- data/lib/dry/schema/messages.rb +42 -0
- data/lib/dry/schema/messages/abstract.rb +143 -0
- data/lib/dry/schema/messages/i18n.rb +60 -0
- data/lib/dry/schema/messages/namespaced.rb +53 -0
- data/lib/dry/schema/messages/yaml.rb +82 -0
- data/lib/dry/schema/params.rb +16 -0
- data/lib/dry/schema/predicate.rb +80 -0
- data/lib/dry/schema/predicate_inferrer.rb +49 -0
- data/lib/dry/schema/predicate_registry.rb +38 -0
- data/lib/dry/schema/processor.rb +151 -0
- data/lib/dry/schema/result.rb +164 -0
- data/lib/dry/schema/rule_applier.rb +45 -0
- data/lib/dry/schema/trace.rb +103 -0
- data/lib/dry/schema/type_registry.rb +42 -0
- data/lib/dry/schema/types.rb +12 -0
- data/lib/dry/schema/value_coercer.rb +27 -0
- data/lib/dry/schema/version.rb +5 -0
- metadata +255 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'dry/schema/macros/dsl'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
module Macros
|
6
|
+
# Macro used to specify predicates for each element of an array
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Each < DSL
|
10
|
+
# @api private
|
11
|
+
def to_ast(*)
|
12
|
+
[:each, trace.to_ast]
|
13
|
+
end
|
14
|
+
alias_method :ast, :to_ast
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'dry/schema/macros/value'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
module Macros
|
6
|
+
# Macro used to prepend `:filled?` predicate
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Filled < Value
|
10
|
+
def call(*args, &block)
|
11
|
+
if args.include?(:empty?)
|
12
|
+
raise ::Dry::Schema::InvalidSchemaError, "Using filled with empty? predicate is invalid"
|
13
|
+
end
|
14
|
+
|
15
|
+
if args.include?(:filled?)
|
16
|
+
raise ::Dry::Schema::InvalidSchemaError, "Using filled with filled? is redundant"
|
17
|
+
end
|
18
|
+
|
19
|
+
value(:filled?, *args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
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 Hash < Value
|
10
|
+
# @api private
|
11
|
+
def call(*args, &block)
|
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
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'dry/schema/predicate_inferrer'
|
2
|
+
require 'dry/schema/processor'
|
3
|
+
require 'dry/schema/macros/dsl'
|
4
|
+
require 'dry/schema/constants'
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module Schema
|
8
|
+
module Macros
|
9
|
+
# Base macro for specifying rules applied to a value found under the key
|
10
|
+
#
|
11
|
+
# @see DSL#key
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
class Key < DSL
|
15
|
+
option :filter_schema, optional: true, default: proc { schema_dsl&.new }
|
16
|
+
|
17
|
+
# Specify predicates that should be used to filter out values
|
18
|
+
# before coercion is applied
|
19
|
+
#
|
20
|
+
# @see Macros::DSL#value
|
21
|
+
#
|
22
|
+
# @return [Macros::Key]
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def filter(*args, &block)
|
26
|
+
filter_schema.optional(name).value(*args, &block)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set type specification and predicates
|
31
|
+
#
|
32
|
+
# @see Macros::DSL#value
|
33
|
+
#
|
34
|
+
# @return [Macros::Key]
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def value(*args, **opts, &block)
|
38
|
+
extract_type_spec(*args) do |*predicates|
|
39
|
+
super(*predicates, **opts, &block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set type specification and predicates for a filled value
|
44
|
+
#
|
45
|
+
# @see Macros::DSL#value
|
46
|
+
#
|
47
|
+
# @return [Macros::Key]
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def filled(*args, **opts, &block)
|
51
|
+
extract_type_spec(*args) do |*predicates|
|
52
|
+
super(*predicates, **opts, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set type specification and predicates for a maybe value
|
57
|
+
#
|
58
|
+
# @see Macros::DSL#value
|
59
|
+
#
|
60
|
+
# @return [Macros::Key]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def maybe(*args, **opts, &block)
|
64
|
+
extract_type_spec(*args, nullable: true) do |*predicates|
|
65
|
+
append_macro(Macros::Maybe) do |macro|
|
66
|
+
macro.call(*predicates, **opts, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set type spec
|
72
|
+
#
|
73
|
+
# @param [Symbol, Array, Dry::Types::Type]
|
74
|
+
#
|
75
|
+
# @return [Macros::Key]
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def type(spec)
|
79
|
+
schema_dsl.set_type(name, spec)
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Coerce macro to a rule
|
84
|
+
#
|
85
|
+
# @return [Dry::Logic::Rule]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
def to_rule
|
89
|
+
if trace.captures.empty?
|
90
|
+
super
|
91
|
+
else
|
92
|
+
[super, trace.to_rule(name)].reduce(operation)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def to_ast
|
98
|
+
[:predicate, [:key?, [[:name, name], [:input, Undefined]]]]
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
def extract_type_spec(*args, nullable: false)
|
105
|
+
type_spec = args[0]
|
106
|
+
|
107
|
+
if type_spec.kind_of?(Schema::Processor) || type_spec.is_a?(Symbol) && type_spec.to_s.end_with?(QUESTION_MARK)
|
108
|
+
type_spec = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
predicates = Array(type_spec ? args[1..-1] : args)
|
112
|
+
|
113
|
+
if type_spec
|
114
|
+
type(nullable && !type_spec.is_a?(::Array) ? [:nil, type_spec] : type_spec)
|
115
|
+
type_predicates = PredicateInferrer[schema_dsl.types[name]]
|
116
|
+
|
117
|
+
unless predicates.include?(type_predicates)
|
118
|
+
type_predicates.each do |pred|
|
119
|
+
unless compiler.supports?(pred)
|
120
|
+
raise ArgumentError, "Predicate +#{pred.inspect}+ inferred from #{type_spec.inspect} type spec is not supported"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if type_predicates.size.equal?(1)
|
125
|
+
predicates.unshift(type_predicates[0])
|
126
|
+
else
|
127
|
+
predicates.unshift(type_predicates)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
yield(*predicates)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'dry/schema/macros/dsl'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
module Macros
|
6
|
+
# Macro used to specify predicates for a value that can be `nil`
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Maybe < DSL
|
10
|
+
# @api private
|
11
|
+
def call(*args, **opts, &block)
|
12
|
+
if args.include?(:empty?)
|
13
|
+
raise ::Dry::Schema::InvalidSchemaError, "Using maybe with empty? predicate is invalid"
|
14
|
+
end
|
15
|
+
|
16
|
+
if args.include?(:nil?)
|
17
|
+
raise ::Dry::Schema::InvalidSchemaError, "Using maybe with nil? predicate is redundant"
|
18
|
+
end
|
19
|
+
|
20
|
+
value(*args, **opts, &block)
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def to_ast
|
27
|
+
[:implication,
|
28
|
+
[
|
29
|
+
[:not, [:predicate, [:nil?, [[:input, Undefined]]]]],
|
30
|
+
trace.to_rule.to_ast
|
31
|
+
]
|
32
|
+
]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'dry/schema/macros/key'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
module Macros
|
6
|
+
# A Key specialization used for keys that can be skipped
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Optional < Key
|
10
|
+
# @api private
|
11
|
+
def operation
|
12
|
+
:then
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'dry/schema/macros/key'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
module Macros
|
6
|
+
# A Key specialization used for keys that must be present
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Required < Key
|
10
|
+
# @api private
|
11
|
+
def operation
|
12
|
+
:and
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'dry/schema/macros/dsl'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
module Macros
|
6
|
+
# A macro used for specifying predicates to be applied to values from a hash
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Value < DSL
|
10
|
+
# @api private
|
11
|
+
def call(*predicates, **opts, &block)
|
12
|
+
trace.evaluate(*predicates, **opts, &block)
|
13
|
+
|
14
|
+
trace.append(new(chain: false).instance_exec(&block)) if block
|
15
|
+
|
16
|
+
if trace.captures.empty?
|
17
|
+
raise ArgumentError, 'wrong number of arguments (given 0, expected at least 1)'
|
18
|
+
end
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def respond_to_missing?(meth, include_private = false)
|
25
|
+
super || meth.to_s.end_with?(QUESTION_MARK)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def method_missing(meth, *args, &block)
|
32
|
+
if meth.to_s.end_with?(QUESTION_MARK)
|
33
|
+
trace.__send__(meth, *args, &block)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'dry/equalizer'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Schema
|
5
|
+
# Message objects used by message sets
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
class Message
|
9
|
+
include Dry::Equalizer(:predicate, :path, :text, :options)
|
10
|
+
|
11
|
+
attr_reader :predicate, :path, :text, :rule, :args, :options
|
12
|
+
|
13
|
+
# A message sub-type used by OR operations
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
class Or
|
17
|
+
# @api private
|
18
|
+
attr_reader :left
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
attr_reader :right
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
attr_reader :path
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
attr_reader :messages
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def initialize(left, right, messages)
|
31
|
+
@left = left
|
32
|
+
@right = right
|
33
|
+
@messages = messages
|
34
|
+
@path = left.path
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def hint?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return a string representation of the message
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def to_s
|
46
|
+
[left, right].uniq.join(" #{messages[:or]} ")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Build a new message object
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
def self.[](predicate, path, text, options)
|
54
|
+
Message.new(predicate, path, text, options)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
def initialize(predicate, path, text, options)
|
59
|
+
@predicate = predicate
|
60
|
+
@path = path.dup
|
61
|
+
@text = text
|
62
|
+
@options = options
|
63
|
+
@rule = options[:rule]
|
64
|
+
@args = options[:args] || EMPTY_ARRAY
|
65
|
+
|
66
|
+
if predicate == :key?
|
67
|
+
@path << rule
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Return a string representation of the message
|
72
|
+
#
|
73
|
+
# @api public
|
74
|
+
def to_s
|
75
|
+
text
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def hint?
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
def eql?(other)
|
85
|
+
other.is_a?(String) ? text == other : super
|
86
|
+
end
|
87
|
+
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
|
+
end
|
103
|
+
end
|