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,164 @@
1
+ require 'dry/initializer'
2
+ require 'dry/equalizer'
3
+
4
+ module Dry
5
+ module Schema
6
+ # Processing result
7
+ #
8
+ # @see Processor#call
9
+ #
10
+ # @api public
11
+ class Result
12
+ include Dry::Equalizer(:output, :errors)
13
+
14
+ extend Dry::Initializer
15
+
16
+ # @api private
17
+ param :output
18
+ alias_method :to_h, :output
19
+ alias_method :to_hash, :output
20
+
21
+ # @api private
22
+ param :results, default: -> { EMPTY_ARRAY.dup }
23
+
24
+ # @api private
25
+ option :message_compiler
26
+
27
+ # @api private
28
+ def self.new(*args)
29
+ result = super
30
+ yield(result)
31
+ result.freeze
32
+ end
33
+
34
+ # @api private
35
+ def replace(hash)
36
+ @output = hash
37
+ self
38
+ end
39
+
40
+ # @api private
41
+ def concat(other)
42
+ results.concat(other)
43
+ result_ast.concat(other.map(&:to_ast))
44
+ self
45
+ end
46
+
47
+ # Read value from the output hash
48
+ #
49
+ # @param [Symbol] name
50
+ #
51
+ # @return [Object]
52
+ #
53
+ # @api public
54
+ def [](name)
55
+ output[name]
56
+ end
57
+
58
+ # Check if a given key is present in the output
59
+ #
60
+ # @param [Symbol] name
61
+ #
62
+ # @return [Boolean]
63
+ #
64
+ # @api public
65
+ def key?(name)
66
+ output.key?(name)
67
+ end
68
+
69
+ # Check if a given key resulted in an error
70
+ #
71
+ # @param [Symbol] name
72
+ #
73
+ # @return [Boolean]
74
+ #
75
+ # @api public
76
+ def error?(name)
77
+ errors.key?(name)
78
+ end
79
+
80
+ # Check if the result is successful
81
+ #
82
+ # @return [Boolean]
83
+ #
84
+ # @api public
85
+ def success?
86
+ results.empty?
87
+ end
88
+
89
+ # Check if the result is not successful
90
+ #
91
+ # @return [Boolean]
92
+ #
93
+ # @api public
94
+ def failure?
95
+ !success?
96
+ end
97
+
98
+ # Get human-readable error representation
99
+ #
100
+ # @see #message_set
101
+ #
102
+ # @return [Hash<Symbol=>Array>]
103
+ #
104
+ # @api public
105
+ def errors(options = EMPTY_HASH)
106
+ message_set(options.merge(hints: false)).dump
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
129
+ end
130
+
131
+ # Return the message set
132
+ #
133
+ # @param [Hash] options
134
+ # @option options [Symbol] :locale Alternative locale (default is :en)
135
+ # @option options [Boolean] :hints Whether to include hint messages or not
136
+ # @option options [Boolean] :full Whether to generate messages that include key names
137
+ #
138
+ # @return [MessageSet]
139
+ #
140
+ # @api public
141
+ def message_set(options = EMPTY_HASH)
142
+ message_compiler.with(options).(result_ast)
143
+ end
144
+
145
+ # Return a string representation of the result
146
+ #
147
+ # @return [String]
148
+ #
149
+ # @api public
150
+ def inspect
151
+ "#<#{self.class}#{to_h.inspect} errors=#{errors.inspect}>"
152
+ end
153
+
154
+ private
155
+
156
+ # A list of failure ASTs produced by rule result objects
157
+ #
158
+ # @api private
159
+ def result_ast
160
+ @__result__ast ||= results.map(&:to_ast)
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,45 @@
1
+ require 'dry/initializer'
2
+
3
+ require 'dry/schema/constants'
4
+ require 'dry/schema/config'
5
+ require 'dry/schema/result'
6
+ require 'dry/schema/messages'
7
+ require 'dry/schema/message_compiler'
8
+
9
+ module Dry
10
+ module Schema
11
+ # Applies rules defined within the DSL
12
+ #
13
+ # @api private
14
+ class RuleApplier
15
+ extend Dry::Initializer
16
+
17
+ # @api private
18
+ param :rules
19
+
20
+ # @api private
21
+ option :config, default: proc { Config.new }
22
+
23
+ # @api private
24
+ option :message_compiler, default: proc { MessageCompiler.new(Messages.setup(config)) }
25
+
26
+ # @api private
27
+ def call(input)
28
+ results = EMPTY_ARRAY.dup
29
+
30
+ rules.each do |name, rule|
31
+ next if input.error?(name)
32
+ result = rule.(input)
33
+ results << result if result.failure?
34
+ end
35
+
36
+ input.concat(results)
37
+ end
38
+
39
+ # @api private
40
+ def to_ast
41
+ [:set, rules.values.map(&:to_ast)]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,103 @@
1
+ require 'dry/schema/constants'
2
+ require 'dry/schema/compiler'
3
+ require 'dry/schema/predicate'
4
+
5
+ module Dry
6
+ module Schema
7
+ # Captures predicates defined within the DSL
8
+ #
9
+ # @api private
10
+ class Trace < BasicObject
11
+ INVALID_PREDICATES = %i[key?].freeze
12
+
13
+ include ::Dry::Equalizer(:compiler, :captures)
14
+
15
+ undef eql?
16
+
17
+ # @api private
18
+ attr_reader :compiler
19
+
20
+ # @api private
21
+ attr_reader :captures
22
+
23
+ # @api private
24
+ def initialize(compiler = Compiler.new)
25
+ @compiler = compiler
26
+ @captures = []
27
+ end
28
+
29
+ # @api private
30
+ def evaluate(*predicates, **opts, &block)
31
+ predicates.each do |predicate|
32
+ if predicate.respond_to?(:call)
33
+ append(predicate)
34
+ elsif predicate.is_a?(::Array)
35
+ append(predicate.map { |pred| __send__(pred) }.reduce(:|))
36
+ else
37
+ append(__send__(predicate))
38
+ end
39
+ end
40
+
41
+ opts.each do |predicate, *args|
42
+ append(__send__(predicate, *args))
43
+ end
44
+
45
+ self
46
+ end
47
+
48
+ # @api private
49
+ def append(op)
50
+ captures << op
51
+ self
52
+ end
53
+ alias_method :<<, :append
54
+
55
+ # @api private
56
+ def to_rule(name = nil)
57
+ return if captures.empty?
58
+
59
+ if name
60
+ compiler.visit([:key, [name, to_ast]])
61
+ else
62
+ reduced_rule
63
+ end
64
+ end
65
+
66
+ # @api private
67
+ def to_ast
68
+ reduced_rule.to_ast
69
+ end
70
+
71
+ # @api private
72
+ def class
73
+ ::Dry::Schema::Trace
74
+ end
75
+
76
+ private
77
+
78
+ # @api private
79
+ def reduced_rule
80
+ captures.map(&:to_ast).map(&compiler.method(:visit)).reduce(:and)
81
+ end
82
+
83
+ # @api private
84
+ def method_missing(meth, *args, &block)
85
+ if meth.to_s.end_with?(QUESTION_MARK)
86
+ if ::Dry::Schema::Trace::INVALID_PREDICATES.include?(meth)
87
+ ::Kernel.raise InvalidSchemaError, "#{meth} predicate cannot be used in this context"
88
+ end
89
+
90
+ unless compiler.supports?(meth)
91
+ ::Kernel.raise ::ArgumentError, "#{meth} predicate is not defined"
92
+ end
93
+
94
+ predicate = Predicate.new(compiler, meth, args, block)
95
+ predicate.ensure_valid
96
+ predicate
97
+ else
98
+ super
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,42 @@
1
+ require 'dry/schema/constants'
2
+ require 'dry/schema/types'
3
+
4
+ module Dry
5
+ module Schema
6
+ # A simple wrapper around Dry::Types registry
7
+ #
8
+ # This is used internally by specialized processor sub-classes
9
+ #
10
+ # @api private
11
+ class TypeRegistry
12
+ # @api private
13
+ attr_reader :types
14
+
15
+ # @api private
16
+ attr_reader :namespace
17
+
18
+ # @api private
19
+ def self.new(types = Dry::Types, namespace = nil)
20
+ super
21
+ end
22
+
23
+ # @api private
24
+ def initialize(types, namespace = nil)
25
+ @types = types
26
+ @namespace = namespace
27
+ end
28
+
29
+ # @api private
30
+ def namespaced(ns)
31
+ self.class.new(types, ns)
32
+ end
33
+
34
+ # @api private
35
+ def [](name)
36
+ key = [namespace, name].compact.join(DOT)
37
+ type = types.registered?(key) ? types[key] : types[name.to_s]
38
+ type
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ require 'dry/types'
2
+
3
+ module Dry
4
+ module Schema
5
+ # Schema's own type registry
6
+ #
7
+ # @api public
8
+ module Types
9
+ include Dry::Types.module
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+ require 'dry/initializer'
2
+
3
+ module Dry
4
+ module Schema
5
+ # Used by the processors to coerce values in the input hash
6
+ #
7
+ # @api private
8
+ class ValueCoercer
9
+ extend Dry::Initializer
10
+
11
+ # @api private
12
+ param :type_schema
13
+
14
+ # @api private
15
+ def call(input)
16
+ if input.success?
17
+ type_schema[Hash(input)]
18
+ else
19
+ type_schema.member_types.reduce(EMPTY_HASH.dup) do |hash, (name, type)|
20
+ hash[name] = input.error?(name) ? input[name] : type[input[name]]
21
+ hash
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module Dry
2
+ module Schema
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,255 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Solnica
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-configurable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 0.1.3
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '0.1'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.3
47
+ - !ruby/object:Gem::Dependency
48
+ name: dry-equalizer
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.2'
61
+ - !ruby/object:Gem::Dependency
62
+ name: dry-initializer
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.4'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.4'
75
+ - !ruby/object:Gem::Dependency
76
+ name: dry-logic
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 0.5.0
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '0.5'
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 0.5.0
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '0.5'
95
+ - !ruby/object:Gem::Dependency
96
+ name: dry-types
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 0.14.0
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0.14'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 0.14.0
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '0.14'
115
+ - !ruby/object:Gem::Dependency
116
+ name: dry-core
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '0.2'
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 0.2.1
125
+ type: :runtime
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.2'
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: 0.2.1
135
+ - !ruby/object:Gem::Dependency
136
+ name: bundler
137
+ requirement: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ type: :development
143
+ prerelease: false
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ - !ruby/object:Gem::Dependency
150
+ name: rake
151
+ requirement: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ type: :development
157
+ prerelease: false
158
+ version_requirements: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ - !ruby/object:Gem::Dependency
164
+ name: rspec
165
+ requirement: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ type: :development
171
+ prerelease: false
172
+ version_requirements: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ description:
178
+ email:
179
+ - piotr.solnica@gmail.com
180
+ executables: []
181
+ extensions: []
182
+ extra_rdoc_files: []
183
+ files:
184
+ - CHANGELOG.md
185
+ - LICENSE
186
+ - README.md
187
+ - config/errors.yml
188
+ - lib/dry-schema.rb
189
+ - lib/dry/schema.rb
190
+ - lib/dry/schema/compiler.rb
191
+ - lib/dry/schema/config.rb
192
+ - lib/dry/schema/constants.rb
193
+ - lib/dry/schema/dsl.rb
194
+ - lib/dry/schema/extensions.rb
195
+ - lib/dry/schema/extensions/monads.rb
196
+ - lib/dry/schema/json.rb
197
+ - lib/dry/schema/key.rb
198
+ - lib/dry/schema/key_coercer.rb
199
+ - lib/dry/schema/key_map.rb
200
+ - lib/dry/schema/macros.rb
201
+ - lib/dry/schema/macros/core.rb
202
+ - lib/dry/schema/macros/dsl.rb
203
+ - lib/dry/schema/macros/each.rb
204
+ - lib/dry/schema/macros/filled.rb
205
+ - lib/dry/schema/macros/hash.rb
206
+ - lib/dry/schema/macros/key.rb
207
+ - lib/dry/schema/macros/maybe.rb
208
+ - lib/dry/schema/macros/optional.rb
209
+ - lib/dry/schema/macros/required.rb
210
+ - lib/dry/schema/macros/value.rb
211
+ - lib/dry/schema/message.rb
212
+ - lib/dry/schema/message_compiler.rb
213
+ - lib/dry/schema/message_compiler/visitor_opts.rb
214
+ - lib/dry/schema/message_set.rb
215
+ - lib/dry/schema/messages.rb
216
+ - lib/dry/schema/messages/abstract.rb
217
+ - lib/dry/schema/messages/i18n.rb
218
+ - lib/dry/schema/messages/namespaced.rb
219
+ - lib/dry/schema/messages/yaml.rb
220
+ - lib/dry/schema/params.rb
221
+ - lib/dry/schema/predicate.rb
222
+ - lib/dry/schema/predicate_inferrer.rb
223
+ - lib/dry/schema/predicate_registry.rb
224
+ - lib/dry/schema/processor.rb
225
+ - lib/dry/schema/result.rb
226
+ - lib/dry/schema/rule_applier.rb
227
+ - lib/dry/schema/trace.rb
228
+ - lib/dry/schema/type_registry.rb
229
+ - lib/dry/schema/types.rb
230
+ - lib/dry/schema/value_coercer.rb
231
+ - lib/dry/schema/version.rb
232
+ homepage: https://github.com/dry-rb/dry-schema
233
+ licenses:
234
+ - MIT
235
+ metadata: {}
236
+ post_install_message:
237
+ rdoc_options: []
238
+ require_paths:
239
+ - lib
240
+ required_ruby_version: !ruby/object:Gem::Requirement
241
+ requirements:
242
+ - - ">="
243
+ - !ruby/object:Gem::Version
244
+ version: '0'
245
+ required_rubygems_version: !ruby/object:Gem::Requirement
246
+ requirements:
247
+ - - ">="
248
+ - !ruby/object:Gem::Version
249
+ version: '0'
250
+ requirements: []
251
+ rubygems_version: 3.0.2
252
+ signing_key:
253
+ specification_version: 4
254
+ summary: Schema coercion and validation
255
+ test_files: []