dry-schema 1.0.3 → 1.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.
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