dry-schema 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1867ad3cc77ca7f40338a9af423ebbdeb226c677e54fa2681af2efe43cc098e7
4
- data.tar.gz: 2b0a981aaaab80619e4e79b5428961fa8f68e47796c171771531fa9263f3a487
3
+ metadata.gz: 5a4bd19052082cf526e57fca967abe2bbfbdcdfd4c1439c09acfb19bc3b25562
4
+ data.tar.gz: 82ffd3bf33409876cf372406e480ca25de8776dfad334ff634b0b041f2ca33ed
5
5
  SHA512:
6
- metadata.gz: 8296a4a2e9d5972fcac050407a76c6c9ac477415f2abfeb2d5005f71a3ac6ee32101edfb785ef51048fdd1d6bf70ec24fcc53c5883004169ec20af2291e138b5
7
- data.tar.gz: 88d64217d50a50c642a23713cd4f2a865ed16dd7da73a73dc085f7324daae9869c06c1e4024ecc2b973aea7097daae8037020fce7121cafab08732faa9362fa2
6
+ metadata.gz: dfde0da5889a34f7ef68699048cd755462f49d9d6249435fd5e0006a8d482dfa3a130e73397f02a83bbb11a4ad2c6e5c039e53154a0072d7f89e4a71ee89748b
7
+ data.tar.gz: dbc0e2f57947b69241c34451980af16b2c60be5cffb770c510f9ddce91daf58a9e6d900eac2884ca88f30c88dce415a4d6bd629d97922a3c79458458fd3ec289
@@ -1,3 +1,21 @@
1
+ # v1.1.0 2019-05-30
2
+
3
+ ### Added
4
+
5
+ * `Config` exposes `predicates` setting too (solnic)
6
+
7
+ ### Fixed
8
+
9
+ * `filled` macro behavior results in `must be filled` error messages when appropriate - see PR #141 for more information (issue #134) (solnic)
10
+ * Filter rules no longer cause keys to be added to input (issue #142) (solnic)
11
+ * Filter rules work now with inheritance (solnic)
12
+ * Inherited type schemas used by coercion are now properly configured as `lax` type (solnic)
13
+ * `Config` is now finalized before instantiating schemas and properly dupped when its inherited (flash-gordon + solnic)
14
+ * `Config#eql?` works as expected (solnic)
15
+ * Predicates are properly inferred from array with a member type spec, ie `array[:integer]` results in `array? + each(:integer?)` (issue #140) (solnic)
16
+
17
+ [Compare v1.0.3...v1.1.0](https://github.com/dry-rb/dry-schema/compare/v1.0.3...v1.1.0)
18
+
1
19
  # v1.0.3 2019-05-21
2
20
 
3
21
  ### Fixed
@@ -15,33 +15,50 @@ module Dry
15
15
  # @api public
16
16
  class Config
17
17
  include Dry::Configurable
18
- include Dry::Equalizer(:predicates, :messages)
18
+ include Dry::Equalizer(:to_h, inspect: false)
19
19
 
20
+ # @!method predicates
21
+ #
22
+ # Return configured predicate registry
23
+ #
24
+ # @return [Schema::PredicateRegistry]
25
+ #
26
+ # @api public
20
27
  setting(:predicates, Schema::PredicateRegistry.new)
21
28
 
29
+ # @!method messages
30
+ #
31
+ # Return configuration for message backend
32
+ #
33
+ # @return [Dry::Configurable::Config]
34
+ #
35
+ # @api public
22
36
  setting(:messages) do
23
37
  setting(:backend, :yaml)
24
38
  setting(:namespace)
25
39
  setting(:load_paths, Set[DEFAULT_MESSAGES_PATH], &:dup)
26
40
  setting(:top_namespace, DEFAULT_MESSAGES_ROOT)
41
+ setting(:default_locale, nil)
27
42
  end
28
43
 
29
- # Return configured predicate registry
30
- #
31
- # @return [Schema::PredicateRegistry]
32
- #
33
- # @api public
34
- def predicates
35
- config.predicates
44
+ # @api private
45
+ def respond_to_missing?(meth, include_private = false)
46
+ super || config.respond_to?(meth, include_private)
36
47
  end
37
48
 
38
- # Return configuration for message backend
39
- #
40
- # @return [Dry::Configurable::Config]
49
+ # @api private
50
+ def inspect
51
+ "#<#{self.class} #{to_h.map { |k,v| ["#{k}=", v.inspect] }.map(&:join).join(' ')}>"
52
+ end
53
+
54
+ private
55
+
56
+ # Forward to the underlying config object
41
57
  #
42
- # @api public
43
- def messages
44
- config.messages
58
+ # @api private
59
+ def method_missing(meth, *args, &block)
60
+ super unless config.respond_to?(meth)
61
+ config.public_send(meth, *args)
45
62
  end
46
63
  end
47
64
  end
@@ -111,6 +111,17 @@ module Dry
111
111
  self
112
112
  end
113
113
 
114
+ # Return a macro with the provided name
115
+ #
116
+ # @param [Symbol] name
117
+ #
118
+ # @return [Macros::Core]
119
+ #
120
+ # @api public
121
+ def [](name)
122
+ macros.detect { |macro| macro.name.equal?(name) }
123
+ end
124
+
114
125
  # Define a required key
115
126
  #
116
127
  # @example
@@ -164,7 +175,7 @@ module Dry
164
175
  name: name,
165
176
  compiler: compiler,
166
177
  schema_dsl: self,
167
- filter_schema: filter_schema
178
+ filter_schema_dsl: filter_schema_dsl
168
179
  )
169
180
 
170
181
  macro.value(&block) if block
@@ -174,7 +185,7 @@ module Dry
174
185
 
175
186
  # Build a processor based on DSL's definitions
176
187
  #
177
- # @return [Processor]
188
+ # @return [Processor, Params, JSON]
178
189
  #
179
190
  # @api private
180
191
  def call
@@ -182,7 +193,7 @@ module Dry
182
193
  steps << filter_schema.rule_applier if filter_rules?
183
194
  steps << value_coercer << rule_applier
184
195
 
185
- processor_type.new { |processor| steps.each { |step| processor << step } }
196
+ processor_type.new(schema_dsl: self, steps: steps)
186
197
  end
187
198
 
188
199
  # Cast this DSL into a rule object
@@ -210,11 +221,8 @@ module Dry
210
221
  #
211
222
  # @api private
212
223
  def type_schema
213
- if parent
214
- parent.type_schema.schema(types)
215
- else
216
- type_registry['hash'].schema(types).lax
217
- end
224
+ schema = type_registry['hash'].schema(types).lax
225
+ parent ? parent.type_schema.schema(schema.to_a) : schema
218
226
  end
219
227
 
220
228
  # Return a new DSL instance using the same processor type
@@ -222,8 +230,8 @@ module Dry
222
230
  # @return [Dry::Types::Safe]
223
231
  #
224
232
  # @api private
225
- def new(&block)
226
- self.class.new(processor_type: processor_type, &block)
233
+ def new(options = EMPTY_HASH, &block)
234
+ self.class.new(options.merge(processor_type: processor_type), &block)
227
235
  end
228
236
 
229
237
  # Set a type for the given key name
@@ -257,6 +265,27 @@ module Dry
257
265
  end
258
266
  end
259
267
 
268
+ # @api private
269
+ def filter_schema
270
+ filter_schema_dsl.call
271
+ end
272
+
273
+ # Build an input schema DSL used by `filter` API
274
+ #
275
+ # @see Macros::Value#filter
276
+ #
277
+ # @api private
278
+ def filter_schema_dsl
279
+ @filter_schema_dsl ||= new(parent: parent_filter_schema)
280
+ end
281
+
282
+ # Check if any filter rules were defined
283
+ #
284
+ # @api private
285
+ def filter_rules?
286
+ (instance_variable_defined?('@filter_schema_dsl') && !filter_schema_dsl.macros.empty?) || parent&.filter_rules?
287
+ end
288
+
260
289
  protected
261
290
 
262
291
  # Build a rule applier
@@ -265,7 +294,7 @@ module Dry
265
294
  #
266
295
  # @api protected
267
296
  def rule_applier
268
- RuleApplier.new(rules, config: config)
297
+ RuleApplier.new(rules, config: config.finalize!)
269
298
  end
270
299
 
271
300
  # Build rules from defined macros
@@ -293,20 +322,11 @@ module Dry
293
322
 
294
323
  private
295
324
 
296
- # Check if any filter rules were defined
297
- #
298
325
  # @api private
299
- def filter_rules?
300
- instance_variable_defined?('@__filter_schema__') && !filter_schema.macros.empty?
301
- end
326
+ def parent_filter_schema
327
+ return unless parent
302
328
 
303
- # Build an input schema DSL used by `filter` API
304
- #
305
- # @see Macros::Value#filter
306
- #
307
- # @api private
308
- def filter_schema
309
- @__filter_schema__ ||= new
329
+ parent.filter_schema if parent.filter_rules?
310
330
  end
311
331
 
312
332
  # Build a key coercer
@@ -13,6 +13,7 @@ module Dry
13
13
  class JSON < Processor
14
14
  config.key_map_type = :stringified
15
15
  config.type_registry = config.type_registry.namespaced(:json)
16
+ config.filter_empty_string = false
16
17
  end
17
18
  end
18
19
  end
@@ -66,9 +66,10 @@ module Dry
66
66
  # @return [Macros::Core]
67
67
  #
68
68
  # @api public
69
- def filled(*args, &block)
69
+ def filled(*args, **opts, &block)
70
70
  append_macro(Macros::Filled) do |macro|
71
- macro.call(*args, &block)
71
+ filter(:filled?) if opts[:type_spec] && macro.filter?
72
+ macro.call(*args, **opts, &block)
72
73
  end
73
74
  end
74
75
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'dry/schema/primitive_inferrer'
3
4
  require 'dry/schema/macros/value'
4
5
 
5
6
  module Dry
@@ -9,8 +10,30 @@ module Dry
9
10
  #
10
11
  # @api private
11
12
  class Filled < Value
13
+ # @!attribute [r] primitive_inferrer
14
+ # PrimitiveInferrer used to get a list of primitive classes from configured type
15
+ # @return [PrimitiveInferrer]
16
+ # @api private
17
+ option :primitive_inferrer, default: proc { PrimitiveInferrer.new }
18
+
12
19
  # @api private
13
20
  def call(*predicates, **opts, &block)
21
+ ensure_valid_predicates(predicates)
22
+
23
+ if opts[:type_spec]
24
+ if filter?
25
+ value(*predicates, **opts, &block)
26
+ else
27
+ value(predicates[0], :filled?, *predicates[1..predicates.size - 1], **opts, &block)
28
+ end
29
+ else
30
+ value(:filled?, *predicates, **opts, &block)
31
+ end
32
+ end
33
+
34
+ # @api private
35
+ # rubocop:disable Style/GuardClause
36
+ def ensure_valid_predicates(predicates)
14
37
  if predicates.include?(:empty?)
15
38
  raise ::Dry::Schema::InvalidSchemaError, 'Using filled with empty? predicate is invalid'
16
39
  end
@@ -18,12 +41,27 @@ module Dry
18
41
  if predicates.include?(:filled?)
19
42
  raise ::Dry::Schema::InvalidSchemaError, 'Using filled with filled? is redundant'
20
43
  end
44
+ end
45
+ # rubocop:enable Style/GuardClause
21
46
 
22
- if opts[:type_spec]
23
- value(predicates[0], :filled?, *predicates[1..predicates.size - 1], **opts, &block)
24
- else
25
- value(:filled?, *predicates, **opts, &block)
26
- end
47
+ # @api private
48
+ def filter?
49
+ !primitives.include?(NilClass) && processor_config.filter_empty_string
50
+ end
51
+
52
+ # @api private
53
+ def processor_config
54
+ schema_dsl.processor_type.config
55
+ end
56
+
57
+ # @api private
58
+ def primitives
59
+ primitive_inferrer[schema_type]
60
+ end
61
+
62
+ # @api private
63
+ def schema_type
64
+ schema_dsl.types[name]
27
65
  end
28
66
  end
29
67
  end
@@ -12,9 +12,10 @@ module Dry
12
12
  #
13
13
  # @api public
14
14
  class Key < DSL
15
- # @return [Schema::DSL]
16
- # @api private
17
- option :filter_schema, optional: true, default: proc { schema_dsl&.new }
15
+ # @!attribute [r] filter_schema_dsl
16
+ # @return [Schema::DSL]
17
+ # @api private
18
+ option :filter_schema_dsl, default: proc { schema_dsl&.filter_schema_dsl }
18
19
 
19
20
  # Specify predicates that should be applied before coercion
20
21
  #
@@ -27,7 +28,7 @@ module Dry
27
28
  #
28
29
  # @api public
29
30
  def filter(*args, &block)
30
- filter_schema.optional(name).value(*args, &block)
31
+ (filter_schema_dsl[name] || filter_schema_dsl.optional(name)).value(*args, &block)
31
32
  self
32
33
  end
33
34
 
@@ -54,7 +55,7 @@ module Dry
54
55
  # @api public
55
56
  def value(*args, **opts, &block)
56
57
  extract_type_spec(*args) do |*predicates, type_spec:|
57
- super(*predicates, **opts, &block)
58
+ super(*predicates, type_spec: type_spec, **opts, &block)
58
59
  end
59
60
  end
60
61
 
@@ -33,6 +33,9 @@ module Dry
33
33
  raise ArgumentError, 'wrong number of arguments (given 0, expected at least 1)'
34
34
  end
35
35
 
36
+ type_spec = opts[:type_spec]
37
+ each(type_spec.type.member) if type_spec.respond_to?(:member)
38
+
36
39
  self
37
40
  end
38
41
 
@@ -18,6 +18,7 @@ module Dry
18
18
  include Dry::Configurable
19
19
  include Dry::Equalizer(:config)
20
20
 
21
+ setting :default_locale, nil
21
22
  setting :load_paths, Set[DEFAULT_MESSAGES_PATH]
22
23
  setting :top_namespace, DEFAULT_MESSAGES_ROOT
23
24
  setting :root, 'errors'
@@ -163,7 +164,7 @@ module Dry
163
164
 
164
165
  # @api private
165
166
  def default_locale
166
- :en
167
+ config.default_locale
167
168
  end
168
169
 
169
170
  private
@@ -55,7 +55,7 @@ module Dry
55
55
 
56
56
  # @api private
57
57
  def default_locale
58
- I18n.locale || I18n.default_locale || super
58
+ super || I18n.locale || I18n.default_locale
59
59
  end
60
60
 
61
61
  # @api private
@@ -30,6 +30,8 @@ module Dry
30
30
  # @api private
31
31
  def self.build(options = EMPTY_HASH)
32
32
  super do |config|
33
+ config.default_locale = :en unless config.default_locale
34
+
33
35
  config.root = "%<locale>s.#{config.root}"
34
36
 
35
37
  config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
@@ -13,6 +13,7 @@ module Dry
13
13
  class Params < Processor
14
14
  config.key_map_type = :stringified
15
15
  config.type_registry = config.type_registry.namespaced(:params)
16
+ config.filter_empty_string = true
16
17
  end
17
18
  end
18
19
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/cache'
4
+
5
+ module Dry
6
+ module Schema
7
+ # PrimitiveInferrer is used internally by `Macros::Filled`
8
+ # for inferring a list of possible primitives that a given
9
+ # type can handle.
10
+ #
11
+ # @api private
12
+ class PrimitiveInferrer
13
+ extend Dry::Core::Cache
14
+
15
+ # Compiler reduces type AST into a list of primitives
16
+ #
17
+ # @api private
18
+ class Compiler
19
+ # @api private
20
+ def visit(node)
21
+ meth, rest = node
22
+ public_send(:"visit_#{meth}", rest)
23
+ end
24
+
25
+ # @api private
26
+ def visit_nominal(node)
27
+ type, _ = node
28
+ type
29
+ end
30
+
31
+ # @api private
32
+ def visit_hash(_)
33
+ Hash
34
+ end
35
+
36
+ # @api private
37
+ def visit_array(_)
38
+ Array
39
+ end
40
+
41
+ # @api private
42
+ def visit_lax(node)
43
+ visit(node)
44
+ end
45
+
46
+ # @api private
47
+ def visit_constructor(node)
48
+ other, * = node
49
+ visit(other)
50
+ end
51
+
52
+ # @api private
53
+ def visit_enum(node)
54
+ other, * = node
55
+ visit(other)
56
+ end
57
+
58
+ # @api private
59
+ def visit_sum(node)
60
+ left, right = node
61
+
62
+ [visit(left), visit(right)].flatten(1)
63
+ end
64
+
65
+ # @api private
66
+ def visit_constrained(node)
67
+ other, * = node
68
+ visit(other)
69
+ end
70
+
71
+ # @api private
72
+ def visit_any(_)
73
+ Object
74
+ end
75
+ end
76
+
77
+ # @return [Compiler]
78
+ # @api private
79
+ attr_reader :compiler
80
+
81
+ # @api private
82
+ def initialize
83
+ @compiler = Compiler.new
84
+ end
85
+
86
+ # Infer predicate identifier from the provided type
87
+ #
88
+ # @return [Symbol]
89
+ #
90
+ # @api private
91
+ def [](type)
92
+ self.class.fetch_or_store(type.hash) do
93
+ Array(compiler.visit(type.to_ast)).freeze
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -29,8 +29,11 @@ module Dry
29
29
 
30
30
  setting :key_map_type
31
31
  setting :type_registry, TypeRegistry.new
32
+ setting :filter_empty_string, false
32
33
 
33
- param :steps, default: -> { EMPTY_ARRAY.dup }
34
+ option :steps, default: -> { EMPTY_ARRAY.dup }
35
+
36
+ option :schema_dsl
34
37
 
35
38
  class << self
36
39
  # Return DSL configured via #define
@@ -60,9 +63,11 @@ module Dry
60
63
  # @return [Processor]
61
64
  #
62
65
  # @api public
63
- def new(&block)
64
- if block
65
- super.tap(&block)
66
+ def new(options = nil, &block)
67
+ if options || block
68
+ processor = super
69
+ yield(processor) if block
70
+ processor
66
71
  elsif definition
67
72
  definition.call
68
73
  else
@@ -177,6 +182,20 @@ module Dry
177
182
  @rule_applier ||= steps.last
178
183
  end
179
184
  alias_method :to_rule, :rule_applier
185
+
186
+ # Check if there are filter rules
187
+ #
188
+ # @api private
189
+ def filter_rules?
190
+ @filter_rules_predicate ||= schema_dsl.filter_rules?
191
+ end
192
+
193
+ # Return filter schema
194
+ #
195
+ # @api private
196
+ def filter_schema
197
+ @filter_schema ||= schema_dsl.filter_schema
198
+ end
180
199
  end
181
200
  end
182
201
  end
@@ -22,6 +22,9 @@ module Dry
22
22
  else
23
23
  type_schema.each_with_object(EMPTY_HASH.dup) do |key, hash|
24
24
  name = key.name
25
+
26
+ next unless input.key?(name)
27
+
25
28
  value = input[name]
26
29
 
27
30
  hash[name] = input.error?(name) ? value : key[value]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Schema
5
- VERSION = '1.0.3'
5
+ VERSION = '1.1.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-21 00:00:00.000000000 Z
11
+ date: 2019-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -28,22 +28,22 @@ dependencies:
28
28
  name: dry-configurable
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 0.8.0
34
31
  - - "~>"
35
32
  - !ruby/object:Gem::Version
36
33
  version: '0.8'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 0.8.3
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- version: 0.8.0
44
41
  - - "~>"
45
42
  - !ruby/object:Gem::Version
46
43
  version: '0.8'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.8.3
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: dry-core
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -217,6 +217,7 @@ files:
217
217
  - lib/dry/schema/predicate.rb
218
218
  - lib/dry/schema/predicate_inferrer.rb
219
219
  - lib/dry/schema/predicate_registry.rb
220
+ - lib/dry/schema/primitive_inferrer.rb
220
221
  - lib/dry/schema/processor.rb
221
222
  - lib/dry/schema/result.rb
222
223
  - lib/dry/schema/rule_applier.rb