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.
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