dry-schema 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|