dry-schema 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/LICENSE +20 -0
  4. data/README.md +21 -0
  5. data/config/errors.yml +91 -0
  6. data/lib/dry-schema.rb +1 -0
  7. data/lib/dry/schema.rb +51 -0
  8. data/lib/dry/schema/compiler.rb +31 -0
  9. data/lib/dry/schema/config.rb +52 -0
  10. data/lib/dry/schema/constants.rb +13 -0
  11. data/lib/dry/schema/dsl.rb +382 -0
  12. data/lib/dry/schema/extensions.rb +3 -0
  13. data/lib/dry/schema/extensions/monads.rb +18 -0
  14. data/lib/dry/schema/json.rb +16 -0
  15. data/lib/dry/schema/key.rb +166 -0
  16. data/lib/dry/schema/key_coercer.rb +37 -0
  17. data/lib/dry/schema/key_map.rb +133 -0
  18. data/lib/dry/schema/macros.rb +6 -0
  19. data/lib/dry/schema/macros/core.rb +51 -0
  20. data/lib/dry/schema/macros/dsl.rb +74 -0
  21. data/lib/dry/schema/macros/each.rb +18 -0
  22. data/lib/dry/schema/macros/filled.rb +24 -0
  23. data/lib/dry/schema/macros/hash.rb +46 -0
  24. data/lib/dry/schema/macros/key.rb +137 -0
  25. data/lib/dry/schema/macros/maybe.rb +37 -0
  26. data/lib/dry/schema/macros/optional.rb +17 -0
  27. data/lib/dry/schema/macros/required.rb +17 -0
  28. data/lib/dry/schema/macros/value.rb +41 -0
  29. data/lib/dry/schema/message.rb +103 -0
  30. data/lib/dry/schema/message_compiler.rb +193 -0
  31. data/lib/dry/schema/message_compiler/visitor_opts.rb +30 -0
  32. data/lib/dry/schema/message_set.rb +123 -0
  33. data/lib/dry/schema/messages.rb +42 -0
  34. data/lib/dry/schema/messages/abstract.rb +143 -0
  35. data/lib/dry/schema/messages/i18n.rb +60 -0
  36. data/lib/dry/schema/messages/namespaced.rb +53 -0
  37. data/lib/dry/schema/messages/yaml.rb +82 -0
  38. data/lib/dry/schema/params.rb +16 -0
  39. data/lib/dry/schema/predicate.rb +80 -0
  40. data/lib/dry/schema/predicate_inferrer.rb +49 -0
  41. data/lib/dry/schema/predicate_registry.rb +38 -0
  42. data/lib/dry/schema/processor.rb +151 -0
  43. data/lib/dry/schema/result.rb +164 -0
  44. data/lib/dry/schema/rule_applier.rb +45 -0
  45. data/lib/dry/schema/trace.rb +103 -0
  46. data/lib/dry/schema/type_registry.rb +42 -0
  47. data/lib/dry/schema/types.rb +12 -0
  48. data/lib/dry/schema/value_coercer.rb +27 -0
  49. data/lib/dry/schema/version.rb +5 -0
  50. 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