dry-schema 1.5.5 → 1.7.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: 442f6c2c04925f14ec6f0230394ad8e009518e9a2d7c1db22ddf68416324d530
4
- data.tar.gz: 8f210323df91ba5ebf8a947b5888907b6f6f7cac504f93fbec80af8678dbdeb1
3
+ metadata.gz: 305c1a8710478195cc35c3de75a1f50834c2cc044b0a6d62bb7c95845ff48db8
4
+ data.tar.gz: 62254023ae1e6ee82ecf7023e3900906a75ebda86676c59dfa24269d337af7f8
5
5
  SHA512:
6
- metadata.gz: 87c2dcc7e169f2ff4c474038a0821a77e9f71420d40d2a4fe59f1d0ab722bcbbc3b9a60dcb33107d34d77cd303a82c33ebb3d084c350a49fc13e7b4cd73666f8
7
- data.tar.gz: 59442e66361d9a1daf9de31480681bf0414875f22b9cdd440224db7b1a5fde818bdc479e2296cea558b00e9b698df56645aa65a55da59bb2bde2d67b4b979120
6
+ metadata.gz: '08c894bf7682d81654ecd9034ae71e14c10e314997196cf11ee39ddaadf799aabeba33e7398aeb54961ba4b19a67e0d68cb3142ebccc182df18dd64ebca3c725'
7
+ data.tar.gz: 947264e7d824feededff8608e6e090592287503cb699d11ab31183955c963a01c1ceaeaa977c19d8add8e719a329ed1afbe72d8dd51f86603750c2502fd41a98
data/CHANGELOG.md CHANGED
@@ -1,3 +1,87 @@
1
+ <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
+
3
+ ## 1.7.0 2021-06-29
4
+
5
+ This release ships with a bunch of internal refactorings that should improve performance but if you see any unexpected behavior please do report issues.
6
+
7
+ ### Fixed
8
+
9
+ - Handle arrays of hashes where Array constructor coerces non-Hash input (#351 fixed via #354) (@ojab)
10
+ - Run outer schema processor steps before inner ones (issue #350 fixed via #361) (@ojab)
11
+ - Fix key validator false negatives on empty collections (see #363) (@Drenmi)
12
+ - Prevent error message YAML files from being parsed multiple times (issue #352 via #364) (@alassek)
13
+ - Using constructor types should work fine now ie `required(:foo).filled(Types::Params::Integer.constructor(&:succ))` (issue #280 fixed via #365) (@solnic)
14
+ - Handle non-Hash to Hash transformation in `before(:key_coercer)` (issue #350 fixed via #362) (@ojab)
15
+
16
+ ### Changed
17
+
18
+ - [internal] `Dry::Schema::Path` clean up and performance improvements (via #358) (@ojab)
19
+ - [internal] simplify and speed up handling of steps in nested schemas (via #360) (@ojab)
20
+
21
+ [Compare v1.6.2...v1.7.0](https://github.com/dry-rb/dry-schema/compare/v1.6.2...v1.7.0)
22
+
23
+ ## 1.6.2 2021-04-15
24
+
25
+
26
+ ### Added
27
+
28
+ - A default error message for `respond_to?` predicate (@rindek)
29
+
30
+ ### Fixed
31
+
32
+ - Using `respond_to?` predicate in blocks works now (@rindek)
33
+
34
+
35
+ [Compare v1.6.1...v1.6.2](https://github.com/dry-rb/dry-schema/compare/v1.6.1...v1.6.2)
36
+
37
+ ## 1.6.1 2021-02-02
38
+
39
+
40
+ ### Fixed
41
+
42
+ - Messages#[] handles meta/no meta cases more gracefully and has better interoperability with the I18n backend. This brings MessageCompiler#visit_unexpected_key up to parity with MessageCompiler#visit_predicate. Uses visit_predicate as basis for visit_unexpected_key. (@robhanlon22)
43
+
44
+
45
+ [Compare v1.6.0...v1.6.1](https://github.com/dry-rb/dry-schema/compare/v1.6.0...v1.6.1)
46
+
47
+ ## 1.6.0 2021-01-21
48
+
49
+
50
+ ### Fixed
51
+
52
+ - Using sum types with a i18n backend no longer crashes (issue #328 fixes via #331) (@tylerhunt)
53
+
54
+ ### Changed
55
+
56
+ - Inferring predicates from class names is deprecated. It's very unlikely your code depends on it,
57
+ however, if it does, you'll get an exception with instructions. (@flash-gordon)
58
+
59
+ If you see an exception and don't rely on inferring, just disable it with:
60
+
61
+ ```ruby
62
+ Dry::Schema::PredicateInferrer::Compiler.infer_predicate_by_class_name false
63
+ ```
64
+
65
+ Otherwise, enable it explicitly:
66
+
67
+ ```ruby
68
+ Dry::Schema::PredicateInferrer::Compiler.infer_predicate_by_class_name true
69
+ ```
70
+
71
+ See https://github.com/dry-rb/dry-schema/issues/335 for rationale.
72
+
73
+ [Compare v1.5.6...v1.6.0](https://github.com/dry-rb/dry-schema/compare/v1.5.6...v1.6.0)
74
+
75
+ ## 1.5.6 2020-10-21
76
+
77
+
78
+ ### Fixed
79
+
80
+ - Fixed stack error which was a regression introduced in 1.5.5 (issue #322 fixed via #323) (@flash-gordon)
81
+
82
+
83
+ [Compare v1.5.5...v1.5.6](https://github.com/dry-rb/dry-schema/compare/v1.5.5...v1.5.6)
84
+
1
85
  ## 1.5.5 2020-10-08
2
86
 
3
87
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2020 dry-rb team
3
+ Copyright (c) 2015-2021 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ <!--- this file is synced from dry-rb/template-gem project -->
1
2
  [gem]: https://rubygems.org/gems/dry-schema
2
3
  [actions]: https://github.com/dry-rb/dry-schema/actions
3
4
  [codacy]: https://www.codacy.com/gh/dry-rb/dry-schema
@@ -14,15 +15,15 @@
14
15
 
15
16
  ## Links
16
17
 
17
- * [User documentation](http://dry-rb.org/gems/dry-schema)
18
+ * [User documentation](https://dry-rb.org/gems/dry-schema)
18
19
  * [API documentation](http://rubydoc.info/gems/dry-schema)
19
20
 
20
21
  ## Supported Ruby versions
21
22
 
22
23
  This library officially supports the following Ruby versions:
23
24
 
24
- * MRI >= `2.4`
25
- * jruby >= `9.2`
25
+ * MRI `>= 2.6.0`
26
+ * ~~jruby~~ `>= 9.3` (we are waiting for [2.6 support](https://github.com/jruby/jruby/issues/6161))
26
27
 
27
28
  ## License
28
29
 
data/config/errors.yml CHANGED
@@ -87,6 +87,8 @@ en:
87
87
 
88
88
  type?: "must be %{type}"
89
89
 
90
+ respond_to?: "must respond to %{method}"
91
+
90
92
  size?:
91
93
  arg:
92
94
  default: "size must be %{size}"
data/dry-schema.gemspec CHANGED
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
- # this file is managed by dry-rb/devtools project
3
2
 
4
- lib = File.expand_path('lib', __dir__)
3
+ # this file is synced from dry-rb/template-gem project
4
+
5
+ lib = File.expand_path("lib", __dir__)
5
6
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
- require 'dry/schema/version'
7
+ require "dry/schema/version"
7
8
 
8
9
  Gem::Specification.new do |spec|
9
- spec.name = 'dry-schema'
10
+ spec.name = "dry-schema"
10
11
  spec.authors = ["Piotr Solnica"]
11
12
  spec.email = ["piotr.solnica@gmail.com"]
12
- spec.license = 'MIT'
13
+ spec.license = "MIT"
13
14
  spec.version = Dry::Schema::VERSION.dup
14
15
 
15
16
  spec.summary = "Coercion and validation for data structures"
@@ -17,28 +18,28 @@ Gem::Specification.new do |spec|
17
18
  dry-schema provides a DSL for defining schemas with keys and rules that should be applied to
18
19
  values. It supports coercion, input sanitization, custom types and localized error messages
19
20
  (with or without I18n gem). It's also used as the schema engine in dry-validation.
21
+
20
22
  TEXT
21
- spec.homepage = 'https://dry-rb.org/gems/dry-schema'
23
+ spec.homepage = "https://dry-rb.org/gems/dry-schema"
22
24
  spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-schema.gemspec", "lib/**/*", "config/*.yml"]
23
- spec.bindir = 'bin'
25
+ spec.bindir = "bin"
24
26
  spec.executables = []
25
- spec.require_paths = ['lib']
27
+ spec.require_paths = ["lib"]
26
28
 
27
- spec.metadata['allowed_push_host'] = 'https://rubygems.org'
28
- spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-schema/blob/master/CHANGELOG.md'
29
- spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-schema'
30
- spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-schema/issues'
29
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
30
+ spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-schema/blob/master/CHANGELOG.md"
31
+ spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-schema"
32
+ spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-schema/issues"
31
33
 
32
- spec.required_ruby_version = ">= 2.4.0"
34
+ spec.required_ruby_version = ">= 2.6.0"
33
35
 
34
36
  # to update dependencies edit project.yml
35
37
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
36
38
  spec.add_runtime_dependency "dry-configurable", "~> 0.8", ">= 0.8.3"
37
- spec.add_runtime_dependency "dry-core", "~> 0.4"
38
- spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
39
+ spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
39
40
  spec.add_runtime_dependency "dry-initializer", "~> 3.0"
40
41
  spec.add_runtime_dependency "dry-logic", "~> 1.0"
41
- spec.add_runtime_dependency "dry-types", "~> 1.4"
42
+ spec.add_runtime_dependency "dry-types", "~> 1.5"
42
43
 
43
44
  spec.add_development_dependency "bundler"
44
45
  spec.add_development_dependency "rake"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/equalizer"
3
+ require "dry/core/equalizer"
4
4
  require "dry/configurable"
5
5
 
6
6
  require "dry/schema/constants"
@@ -151,7 +151,11 @@ module Dry
151
151
  # @api private
152
152
  def write(source, target)
153
153
  read(source) { |value|
154
- target[coerced_name] = value.is_a?(::Array) ? value.map { |el| member.write(el) } : value
154
+ target[coerced_name] = if value.is_a?(::Array)
155
+ value.map { |el| el.is_a?(::Hash) ? member.write(el) : el }
156
+ else
157
+ value
158
+ end
155
159
  }
156
160
  end
157
161
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/core/cache"
4
- require "dry/equalizer"
4
+ require "dry/core/equalizer"
5
5
 
6
6
  module Dry
7
7
  module Schema
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/equalizer"
3
+ require "dry/core/equalizer"
4
4
  require "dry/core/cache"
5
5
  require "dry/schema/constants"
6
6
  require "dry/schema/key"
@@ -51,9 +51,14 @@ module Dry
51
51
  hash.flat_map { |key, _|
52
52
  case (value = hash[key])
53
53
  when Hash
54
+ next key.to_s if value.empty?
55
+
54
56
  [key].product(key_paths(hash[key])).map { |keys| keys.join(DOT) }
55
57
  when Array
56
- hashes_or_arrays = value.select { |e| e.is_a?(Array) || e.is_a?(Hash) }
58
+ hashes_or_arrays = value.select { |e| (e.is_a?(Array) || e.is_a?(Hash)) && !e.empty? }
59
+
60
+ next key.to_s if hashes_or_arrays.empty?
61
+
57
62
  hashes_or_arrays.flat_map.with_index { |el, idx|
58
63
  key_paths(el).map { |path| ["#{key}[#{idx}]", *path].join(DOT) }
59
64
  }
@@ -17,6 +17,7 @@ module Dry
17
17
 
18
18
  undef :eql?
19
19
  undef :nil?
20
+ undef :respond_to?
20
21
 
21
22
  # @!attribute [r] chain
22
23
  # Indicate if the macro should append its rules to the provided trace
@@ -11,9 +11,14 @@ module Dry
11
11
  # @api private
12
12
  class Value < DSL
13
13
  # @api private
14
- def call(*predicates, **opts, &block)
14
+ def call(*args, **opts, &block)
15
+ types, predicates = args.partition { |arg| arg.is_a?(Dry::Types::Type) }
16
+
17
+ constructor = types.select { |type| type.is_a?(Dry::Types::Constructor) }.reduce(:>>)
15
18
  schema = predicates.detect { |predicate| predicate.is_a?(Processor) }
16
19
 
20
+ schema_dsl.set_type(name, constructor) if constructor
21
+
17
22
  type_spec = opts[:type_spec]
18
23
 
19
24
  if schema
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/initializer"
4
- require "dry/equalizer"
4
+ require "dry/core/equalizer"
5
5
 
6
6
  require "dry/schema/path"
7
7
  require "dry/schema/message/or"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/equalizer"
3
+ require "dry/core/equalizer"
4
4
 
5
5
  require "dry/schema/message/or/abstract"
6
6
  require "dry/schema/path"
@@ -38,7 +38,7 @@ module Dry
38
38
  #
39
39
  # @api public
40
40
  def dump
41
- @dump ||= "#{left.dump} #{messages[:or][:text]} #{right.dump}"
41
+ @dump ||= "#{left.dump} #{messages[:or]} #{right.dump}"
42
42
  end
43
43
  alias_method :to_s, :dump
44
44
 
@@ -4,8 +4,9 @@ require "dry/initializer"
4
4
 
5
5
  require "dry/schema/constants"
6
6
  require "dry/schema/message"
7
- require "dry/schema/message_set"
8
7
  require "dry/schema/message_compiler/visitor_opts"
8
+ require "dry/schema/message_set"
9
+ require "dry/schema/path"
9
10
 
10
11
  module Dry
11
12
  module Schema
@@ -109,18 +110,8 @@ module Dry
109
110
  end
110
111
 
111
112
  # @api private
112
- def visit_unexpected_key(node, _opts)
113
- path, input = node
114
-
115
- msg = messages.translate("errors.unexpected_key")
116
-
117
- Message.new(
118
- path: path,
119
- meta: msg[:meta] || EMPTY_HASH,
120
- text: msg[:text],
121
- predicate: nil,
122
- input: input
123
- )
113
+ def visit_unexpected_key(node, opts)
114
+ visit_predicate([:unexpected_key, []], opts.dup.update(path: Path[node.first]))
124
115
  end
125
116
 
126
117
  # @api private
@@ -131,7 +122,10 @@ module Dry
131
122
 
132
123
  # @api private
133
124
  def or_translator
134
- @or_translator ||= proc { |k| messages.translate(k, **default_lookup_options) }
125
+ @or_translator ||= proc { |k|
126
+ message = messages.translate(k, **default_lookup_options)
127
+ message.is_a?(Hash) ? message[:text] : message
128
+ }
135
129
  end
136
130
 
137
131
  # @api private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/equalizer"
3
+ require "dry/core/equalizer"
4
4
 
5
5
  module Dry
6
6
  module Schema
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "set"
4
4
  require "concurrent/map"
5
- require "dry/equalizer"
5
+ require "dry/core/equalizer"
6
6
  require "dry/configurable"
7
7
 
8
8
  require "dry/schema/constants"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/initializer"
4
- require "dry/equalizer"
4
+ require "dry/core/equalizer"
5
5
 
6
6
  require "dry/schema/constants"
7
7
 
@@ -3,7 +3,7 @@
3
3
  require "yaml"
4
4
  require "pathname"
5
5
 
6
- require "dry/equalizer"
6
+ require "dry/core/equalizer"
7
7
  require "dry/schema/constants"
8
8
  require "dry/schema/messages/abstract"
9
9
 
@@ -71,6 +71,11 @@ module Dry
71
71
  @cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
72
72
  end
73
73
 
74
+ # @api private
75
+ def self.source_cache
76
+ @source_cache ||= Concurrent::Map.new
77
+ end
78
+
74
79
  # @api private
75
80
  def initialize(data: EMPTY_HASH, config: nil)
76
81
  super()
@@ -179,7 +184,9 @@ module Dry
179
184
 
180
185
  # @api private
181
186
  def load_translations(path)
182
- data = self.class.flat_hash(YAML.load_file(path))
187
+ data = self.class.source_cache.fetch_or_store(path) do
188
+ self.class.flat_hash(YAML.load_file(path)).freeze
189
+ end
183
190
 
184
191
  return data unless custom_top_namespace?(path)
185
192
 
@@ -32,10 +32,10 @@ module Dry
32
32
  new(spec.split(DOT).map(&:to_sym))
33
33
  when Hash
34
34
  new(keys_from_hash(spec))
35
- when Path
35
+ when self
36
36
  spec
37
37
  else
38
- raise ArgumentError, "+spec+ must be either a Symbol, Array, Hash or a Path"
38
+ raise ArgumentError, "+spec+ must be either a Symbol, Array, Hash or a #{name}"
39
39
  end
40
40
  end
41
41
 
@@ -60,24 +60,9 @@ module Dry
60
60
 
61
61
  # @api private
62
62
  def to_h(value = EMPTY_ARRAY.dup)
63
- curr_idx = 0
64
- last_idx = keys.size - 1
65
- hash = EMPTY_HASH.dup
66
- node = hash
67
-
68
- while curr_idx <= last_idx
69
- node =
70
- node[keys[curr_idx]] =
71
- if curr_idx == last_idx
72
- value.is_a?(Array) ? value : [value]
73
- else
74
- EMPTY_HASH.dup
75
- end
76
-
77
- curr_idx += 1
78
- end
63
+ value = [value] unless value.is_a?(Array)
79
64
 
80
- hash
65
+ keys.reverse_each.reduce(value) { |result, key| {key => result} }
81
66
  end
82
67
 
83
68
  # @api private
@@ -86,61 +71,26 @@ module Dry
86
71
  end
87
72
 
88
73
  # @api private
89
- def index(key)
90
- keys.index(key)
91
- end
92
-
93
- def without_index
94
- self.class.new([*to_a[0..-2]])
95
- end
96
-
97
- # @api private
98
- #
99
- # rubocop:disable Metrics/PerceivedComplexity
100
74
  def include?(other)
101
- return false unless same_root?(other)
102
- return last.equal?(other.last) if index? && other.index?
103
- return without_index.include?(other) if index?
104
-
105
- if !index? && other.index?
106
- path = key_matches(other, :select)
107
-
108
- return false unless path.size > 1
109
-
110
- self.class.new(path).include?(other)
111
- end
112
-
113
- self >= other && !other.key_matches(self).include?(nil)
75
+ keys[0, other.keys.length].eql?(other.keys)
114
76
  end
115
- # rubocop:enable Metrics/PerceivedComplexity
116
77
 
117
78
  # @api private
118
79
  def <=>(other)
119
- raise ArgumentError, "Can't compare paths from different branches" unless same_root?(other)
120
-
121
- return 0 if keys.eql?(other.keys)
80
+ return keys.length <=> other.keys.length if include?(other) || other.include?(self)
122
81
 
123
- res = key_matches(other).compact.reject { |value| value.equal?(false) }
82
+ first_uncommon_index = (self & other).keys.length
124
83
 
125
- res.size < count ? 1 : -1
84
+ keys[first_uncommon_index] <=> other.keys[first_uncommon_index]
126
85
  end
127
86
 
128
87
  # @api private
129
88
  def &(other)
130
- unless same_root?(other)
131
- raise ArgumentError, "#{other.inspect} doesn't have the same root #{inspect}"
132
- end
133
-
134
89
  self.class.new(
135
- key_matches(other, :select).compact.reject { |value| value.equal?(false) }
90
+ keys.take_while.with_index { |key, index| other.keys[index].eql?(key) }
136
91
  )
137
92
  end
138
93
 
139
- # @api private
140
- def key_matches(other, meth = :map)
141
- public_send(meth) { |key| (idx = other.index(key)) && keys[idx].equal?(key) }
142
- end
143
-
144
94
  # @api private
145
95
  def last
146
96
  keys.last
@@ -151,10 +101,7 @@ module Dry
151
101
  root.equal?(other.root)
152
102
  end
153
103
 
154
- # @api private
155
- def index?
156
- last.is_a?(Integer)
157
- end
104
+ EMPTY = new(EMPTY_ARRAY).freeze
158
105
  end
159
106
  end
160
107
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/equalizer"
3
+ require "dry/core/equalizer"
4
4
  require "dry/logic/operators"
5
5
 
6
6
  module Dry
@@ -84,14 +84,14 @@ module Dry
84
84
  #
85
85
  # @api public
86
86
  def call(input)
87
- Result.new(input, input: input, message_compiler: message_compiler) do |result|
87
+ Result.new(input.dup, message_compiler: message_compiler) do |result|
88
88
  steps.call(result)
89
89
  end
90
90
  end
91
91
  alias_method :[], :call
92
92
 
93
93
  # @api public
94
- def xor(other)
94
+ def xor(_other)
95
95
  raise NotImplementedError, "composing schemas using `xor` operator is not supported yet"
96
96
  end
97
97
  alias_method :^, :xor
@@ -87,6 +87,7 @@ module Dry
87
87
  def after(name, &block)
88
88
  after_steps[name] ||= EMPTY_ARRAY.dup
89
89
  after_steps[name] << Step.new(type: :after, name: name, executor: block)
90
+ after_steps[name].sort_by!(&:path)
90
91
  self
91
92
  end
92
93
 
@@ -100,6 +101,7 @@ module Dry
100
101
  def before(name, &block)
101
102
  before_steps[name] ||= EMPTY_ARRAY.dup
102
103
  before_steps[name] << Step.new(type: :before, name: name, executor: block)
104
+ before_steps[name].sort_by!(&:path)
103
105
  self
104
106
  end
105
107
 
@@ -120,18 +122,20 @@ module Dry
120
122
  # @api private
121
123
  def merge_callbacks(left, right)
122
124
  left.merge(right) do |_key, oldval, newval|
123
- oldval + newval
125
+ (oldval + newval).sort_by(&:path)
124
126
  end
125
127
  end
126
128
 
127
129
  # @api private
128
130
  def import_callbacks(path, other)
129
131
  other.before_steps.each do |name, steps|
130
- (before_steps[name] ||= []).concat(steps.map { |step| step.scoped(path) })
132
+ before_steps[name] ||= []
133
+ before_steps[name].concat(steps.map { |step| step.scoped(path) }).sort_by!(&:path)
131
134
  end
132
135
 
133
136
  other.after_steps.each do |name, steps|
134
- (after_steps[name] ||= []).concat(steps.map { |step| step.scoped(path) })
137
+ after_steps[name] ||= []
138
+ after_steps[name].concat(steps.map { |step| step.scoped(path) }).sort_by!(&:path)
135
139
  end
136
140
  end
137
141
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/initializer"
4
- require "dry/equalizer"
4
+ require "dry/core/equalizer"
5
5
 
6
6
  require "dry/schema/path"
7
7
 
@@ -15,24 +15,21 @@ module Dry
15
15
  class Result
16
16
  include Dry::Equalizer(:output, :errors)
17
17
 
18
- extend Dry::Initializer
18
+ extend Dry::Initializer[undefined: false]
19
19
 
20
20
  # @api private
21
- param :output
21
+ param :output, reader: false
22
22
 
23
- # Dump result to a hash returning processed and validated data
23
+ # A list of failure ASTs produced by rule result objects
24
24
  #
25
- # @return [Hash]
26
- alias_method :to_h, :output
27
-
28
25
  # @api private
29
- param :results, default: -> { EMPTY_ARRAY.dup }
26
+ option :result_ast, default: -> { EMPTY_ARRAY.dup }
30
27
 
31
28
  # @api private
32
29
  option :message_compiler
33
30
 
34
31
  # @api private
35
- option :parent, default: -> { nil }
32
+ option :path, optional: true, reader: false
36
33
 
37
34
  # @api private
38
35
  def self.new(*, **)
@@ -48,8 +45,8 @@ module Dry
48
45
  # @return [Result]
49
46
  #
50
47
  # @api private
51
- def at(path, &block)
52
- new(Path[path].reduce(output) { |a, e| a[e] }, parent: self, &block)
48
+ def at(at_path, &block)
49
+ new(@output, path: Path.new([*path, *Path[at_path]]), &block)
53
50
  end
54
51
 
55
52
  # @api private
@@ -57,7 +54,7 @@ module Dry
57
54
  self.class.new(
58
55
  output,
59
56
  message_compiler: message_compiler,
60
- results: results,
57
+ result_ast: result_ast,
61
58
  **opts,
62
59
  &block
63
60
  )
@@ -70,14 +67,35 @@ module Dry
70
67
  end
71
68
 
72
69
  # @api private
73
- def replace(hash)
74
- @output = hash
70
+ def path
71
+ @path || Path::EMPTY
72
+ end
73
+
74
+ # Dump result to a hash returning processed and validated data
75
+ #
76
+ # @return [Hash]
77
+ def output
78
+ path.equal?(Path::EMPTY) ? @output : @output.dig(*path)
79
+ end
80
+ alias_method :to_h, :output
81
+
82
+ # @api private
83
+ def replace(value)
84
+ if value.is_a?(output.class)
85
+ output.replace(value)
86
+ elsif path.equal?(Path::EMPTY)
87
+ @output = value
88
+ else
89
+ value_holder = path.keys.length > 1 ? @output.dig(*path.to_a[0..-2]) : @output
90
+
91
+ value_holder[path.last] = value
92
+ end
93
+
75
94
  self
76
95
  end
77
96
 
78
97
  # @api private
79
98
  def concat(other)
80
- results.concat(other)
81
99
  result_ast.concat(other.map(&:to_ast))
82
100
  self
83
101
  end
@@ -164,7 +182,7 @@ module Dry
164
182
  #
165
183
  # @api public
166
184
  def inspect
167
- "#<#{self.class}#{to_h.inspect} errors=#{errors.to_h.inspect}>"
185
+ "#<#{self.class}#{to_h.inspect} errors=#{errors.to_h.inspect} path=#{path.keys.inspect}>"
168
186
  end
169
187
 
170
188
  if RUBY_VERSION >= "2.7"
@@ -182,15 +200,6 @@ module Dry
182
200
  def add_error(node)
183
201
  result_ast << node
184
202
  end
185
-
186
- private
187
-
188
- # A list of failure ASTs produced by rule result objects
189
- #
190
- # @api private
191
- def result_ast
192
- @result_ast ||= results.map(&:to_ast)
193
- end
194
203
  end
195
204
  end
196
205
  end
@@ -17,53 +17,34 @@ module Dry
17
17
  attr_reader :executor
18
18
 
19
19
  # @api private
20
- class Scoped
21
- # @api private
22
- attr_reader :path
23
-
24
- # @api private
25
- attr_reader :step
26
-
27
- # @api private
28
- def initialize(path, step)
29
- @path = Path[path]
30
- @step = step
31
- end
32
-
33
- # @api private
34
- def scoped(new_path)
35
- self.class.new(Path[[*new_path, *path]], step)
36
- end
37
-
38
- # @api private
39
- def call(result)
40
- result.at(path) do |scoped_result|
41
- output = step.(scoped_result).to_h
42
- target = Array(path)[0..-2].reduce(result) { |a, e| a[e] }
43
-
44
- target.update(path.last => output)
45
- end
46
- end
47
- end
20
+ attr_reader :path
48
21
 
49
22
  # @api private
50
- def initialize(type:, name:, executor:)
23
+ def initialize(type:, name:, executor:, path: Path::EMPTY)
51
24
  @type = type
52
25
  @name = name
53
26
  @executor = executor
27
+ @path = path
54
28
  validate_name(name)
55
29
  end
56
30
 
57
31
  # @api private
58
32
  def call(result)
59
- output = executor.(result)
60
- result.replace(output) if output.is_a?(Hash)
33
+ scoped_result = path.equal?(Path::EMPTY) ? result : result.at(path)
34
+
35
+ output = executor.(scoped_result)
36
+ scoped_result.replace(output) if output.is_a?(Hash)
61
37
  output
62
38
  end
63
39
 
64
40
  # @api private
65
- def scoped(path)
66
- Scoped.new(path, self)
41
+ def scoped(parent_path)
42
+ self.class.new(
43
+ type: type,
44
+ name: name,
45
+ executor: executor,
46
+ path: Path.new([*parent_path, *path])
47
+ )
67
48
  end
68
49
 
69
50
  private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/equalizer"
3
+ require "dry/core/equalizer"
4
4
  require "dry/initializer"
5
5
 
6
6
  module Dry
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Schema
5
- VERSION = "1.5.5"
5
+ VERSION = "1.7.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.5.5
4
+ version: 1.7.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: 2020-10-08 00:00:00.000000000 Z
11
+ date: 2021-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -50,28 +50,20 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.4'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '0.4'
61
- - !ruby/object:Gem::Dependency
62
- name: dry-equalizer
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
53
+ version: '0.5'
54
+ - - ">="
66
55
  - !ruby/object:Gem::Version
67
- version: '0.2'
56
+ version: '0.5'
68
57
  type: :runtime
69
58
  prerelease: false
70
59
  version_requirements: !ruby/object:Gem::Requirement
71
60
  requirements:
72
61
  - - "~>"
73
62
  - !ruby/object:Gem::Version
74
- version: '0.2'
63
+ version: '0.5'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0.5'
75
67
  - !ruby/object:Gem::Dependency
76
68
  name: dry-initializer
77
69
  requirement: !ruby/object:Gem::Requirement
@@ -106,14 +98,14 @@ dependencies:
106
98
  requirements:
107
99
  - - "~>"
108
100
  - !ruby/object:Gem::Version
109
- version: '1.4'
101
+ version: '1.5'
110
102
  type: :runtime
111
103
  prerelease: false
112
104
  version_requirements: !ruby/object:Gem::Requirement
113
105
  requirements:
114
106
  - - "~>"
115
107
  - !ruby/object:Gem::Version
116
- version: '1.4'
108
+ version: '1.5'
117
109
  - !ruby/object:Gem::Dependency
118
110
  name: bundler
119
111
  requirement: !ruby/object:Gem::Requirement
@@ -156,10 +148,11 @@ dependencies:
156
148
  - - ">="
157
149
  - !ruby/object:Gem::Version
158
150
  version: '0'
159
- description: |
151
+ description: |+
160
152
  dry-schema provides a DSL for defining schemas with keys and rules that should be applied to
161
153
  values. It supports coercion, input sanitization, custom types and localized error messages
162
154
  (with or without I18n gem). It's also used as the schema engine in dry-validation.
155
+
163
156
  email:
164
157
  - piotr.solnica@gmail.com
165
158
  executables: []
@@ -253,14 +246,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
253
246
  requirements:
254
247
  - - ">="
255
248
  - !ruby/object:Gem::Version
256
- version: 2.4.0
249
+ version: 2.6.0
257
250
  required_rubygems_version: !ruby/object:Gem::Requirement
258
251
  requirements:
259
252
  - - ">="
260
253
  - !ruby/object:Gem::Version
261
254
  version: '0'
262
255
  requirements: []
263
- rubygems_version: 3.0.3
256
+ rubygems_version: 3.1.6
264
257
  signing_key:
265
258
  specification_version: 4
266
259
  summary: Coercion and validation for data structures