blood_contracts-core 0.4.2 → 0.4.3

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: 5a95e70463f9422ef654272494ae21d3b2440f6df9f2ec93fce292d1760bf8a5
4
- data.tar.gz: 38cae2fd0e55b1e4a32fbbafa455659add37c78ed6c03c1f250166d426bff362
3
+ metadata.gz: cf9fc4121fbf8d1f0b7ccbef5feea8337bddaffe58035359a0eb8ff0617337c9
4
+ data.tar.gz: e697e1e0c1ef8fd849a243e083b45a1dc0ea18a9998feb3fd63c4d709b220b12
5
5
  SHA512:
6
- metadata.gz: ee8f1b8795c6906757a188e9714869420c31bdab2b77a43ef8ca299a31c9e95269c4d254e63f1cd6f425ad50fccd6d6568c7f0ace662e1c29eb1e28a8e7671b3
7
- data.tar.gz: c7e613e87f3c9038094c15f0f0eda3bee294b69d55998ceed794d9c918065d7faff6a77bdb23760ae36e8ebc3b581de74fa26cc5b2dae34fc7deb9125c661355
6
+ metadata.gz: 12cad0640c0a2eab92e5189ff7a658bf0e93b055e0ef4fac15e16c3d6083a9e4463673d1be916a73b9e32b626c7c2e98d0f4e0af34164081296c0a7114284fea
7
+ data.tar.gz: 251436c1bbef4e1736140f0dd01625ede3ab08f689ad0af0f91ad1e32a6dd7758720ba8a5e11cefed729915bf3c98fac8692c34f8613eb9bd60d7f6983876a34
data/CHANGELOG.md CHANGED
@@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
- ## master
8
+ ## [0.4.3] - [2019-08-30]
9
+
10
+ Features/Fixes:
11
+ - Changed the way we treat context in Sum and Tuple. Each validation of those aggregations now uses its own context.
12
+ In other word, each validation path has its own context, which will not be corrupted in parallel validaions.
13
+ If validation succeded we return the only valid context, otherwise if we failed on Tuple or Sum we return set of
14
+ contexts, to inspect why did validation fail.
15
+
16
+ ## [0.4.2] - [2019-08-29]
9
17
 
10
18
  Features:
11
19
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "blood_contracts-core"
3
- gem.version = "0.4.2"
3
+ gem.version = "0.4.3"
4
4
  gem.authors = ["Sergey Dolganov (sclinede)"]
5
5
  gem.email = ["sclinede@evilmartians.com"]
6
6
 
@@ -8,6 +8,7 @@ module BloodContracts
8
8
  require_relative "./core/pipe.rb"
9
9
  require_relative "./core/contract.rb"
10
10
  require_relative "./core/sum.rb"
11
+ require_relative "./core/sum_contract_failure.rb"
11
12
  require_relative "./core/tuple.rb"
12
13
  require_relative "./core/tuple_contract_failure.rb"
13
14
 
@@ -13,6 +13,17 @@ module BloodContracts::Core
13
13
  @context[:errors] = (@context[:errors].to_a << @value.to_h)
14
14
  end
15
15
 
16
+ # Merge errors with the errors of another ContractFailure
17
+ #
18
+ # @param contract_failure [ContractFailure] other errors container which to
19
+ # merge with
20
+ # @return [ContractFailure]
21
+ #
22
+ def merge!(contract_failure)
23
+ @context[:errors] = @context[:errors].to_a + contract_failure.errors
24
+ self
25
+ end
26
+
16
27
  # List of errors per type after the data matching process
17
28
  #
18
29
  # @return [Array<Hash<BC::Refined, String>>]
@@ -41,7 +41,7 @@ module BloodContracts::Core
41
41
 
42
42
  # Override of case equality operator, to handle Tuple correctly
43
43
  def ===(object)
44
- return object.to_ary.any?(self) if object.is_a?(Tuple)
44
+ return object.attributes.values.any?(self) if object.is_a?(Tuple)
45
45
  super
46
46
  end
47
47
 
@@ -63,6 +63,15 @@ module BloodContracts::Core
63
63
  return super if name
64
64
  "Sum(#{sum_of.map(&:inspect).join(',')})"
65
65
  end
66
+
67
+ # Inheritance handler:
68
+ # - adds current sum_of to child
69
+ # - sets default value for failure_klass
70
+ def inherited(new_klass)
71
+ new_klass.instance_variable_set(:@sum_of, sum_of)
72
+ new_klass.failure_klass ||= SumContractFailure
73
+ super
74
+ end
66
75
  end
67
76
 
68
77
  # The type which is the result of data matching process
@@ -72,13 +81,13 @@ module BloodContracts::Core
72
81
  #
73
82
  def match
74
83
  @or_matches = self.class.sum_of.map do |type|
75
- type.match(@value, context: @context)
84
+ type.match(@value, context: @context.dup)
76
85
  end
77
86
 
78
87
  if (match = @or_matches.find(&:valid?))
79
88
  match
80
89
  else
81
- failure(:no_matches)
90
+ @or_matches.dup.unshift(failure).reduce(:merge!)
82
91
  end
83
92
  end
84
93
 
@@ -90,6 +99,16 @@ module BloodContracts::Core
90
99
  @context[:errors]
91
100
  end
92
101
 
102
+ # Helper to build a ContractFailure with shared context
103
+ #
104
+ # @return [ContractFailure]
105
+ #
106
+ private def failure(*)
107
+ sum_context = @or_matches.map(&:context)
108
+ @context[:sum_failure_contexts] = sum_context
109
+ self.class.failure_klass.new(context: @context)
110
+ end
111
+
93
112
  # @private
94
113
  private def inspect
95
114
  "#<sum #{self.class.name} is #{self.class.sum_of.to_a.join(' or ')}"\
@@ -143,10 +143,12 @@ module BloodContracts::Core
143
143
  def match
144
144
  @matches = attributes_enumerator.map do |(type, value), index|
145
145
  attribute_name = self.class.names[index]
146
- attributes.store(attribute_name, type.match(value, context: @context))
146
+ attributes.store(
147
+ attribute_name, type.match(value, context: @context.dup)
148
+ )
147
149
  end
148
- return if @matches.find(&:invalid?).nil?
149
- failure(:invalid_tuple)
150
+ return if (failures = @matches.select(&:invalid?)).empty?
151
+ failures.unshift(failure).reduce(:merge!)
150
152
  end
151
153
 
152
154
  # Turns match into array of unpacked values
@@ -190,6 +192,14 @@ module BloodContracts::Core
190
192
  {}
191
193
  end
192
194
 
195
+ # Helper to build a ContractFailure with shared context
196
+ #
197
+ # @return [ContractFailure]
198
+ #
199
+ private def failure(*)
200
+ self.class.failure_klass.new(context: @context)
201
+ end
202
+
193
203
  # @private
194
204
  private def parse_values_from_context(context)
195
205
  return if context.empty?
@@ -17,6 +17,14 @@ module BloodContracts::Core
17
17
  attributes.select { |_name, type| type.invalid? }
18
18
  end
19
19
 
20
+ # Contexts for subset of attributes which are invalid
21
+ #
22
+ # @return [Hash<String, ContractFailure>]
23
+ #
24
+ def attribute_contexts
25
+ attribute_errors.transform_values!(&:context)
26
+ end
27
+
20
28
  # Unpacked matching errors in form of a hash per attribute
21
29
  #
22
30
  # @return [Hash<String, ContractFailure>]
@@ -108,12 +108,8 @@ RSpec.describe BloodContracts::Core do
108
108
  context "when login is valid" do
109
109
  context "when login is email" do
110
110
  let(:login) { "admin@example.com" }
111
- let(:errors) { [{ Test::Phone => ["Not a phone"] }] }
112
111
  let(:validation_context) do
113
112
  hash_including(
114
- phone: login,
115
- clean_phone: login,
116
- errors: errors,
117
113
  email: login
118
114
  )
119
115
  end
@@ -129,13 +125,10 @@ RSpec.describe BloodContracts::Core do
129
125
  context "when login is phone" do
130
126
  let(:login) { "8(800) 200 - 11 - 00" }
131
127
  let(:cleaned_phone) { "88002001100" }
132
- let(:errors) { [{ Test::Email => ["Not an email"] }] }
133
128
  let(:validation_context) do
134
129
  hash_including(
135
130
  phone: login,
136
- clean_phone: cleaned_phone,
137
- errors: errors,
138
- email: login
131
+ clean_phone: cleaned_phone
139
132
  )
140
133
  end
141
134
 
@@ -153,15 +146,13 @@ RSpec.describe BloodContracts::Core do
153
146
  let(:errors) do
154
147
  [
155
148
  { Test::Email => ["Not an email"] },
156
- { Test::Phone => ["Not a phone"] },
157
- { Test::Login => [:no_matches] }
149
+ { Test::Phone => ["Not a phone"] }
158
150
  ]
159
151
  end
160
152
 
161
153
  it do
162
154
  is_expected.to be_invalid
163
155
  expect(subject.errors).to match_array(errors)
164
- expect(subject.unpack).to match({ Test::Login => [:no_matches] })
165
156
  end
166
157
  end
167
158
  end
@@ -254,15 +245,12 @@ RSpec.describe BloodContracts::Core do
254
245
  attribute_errors.merge(password: kind_of(dynamic_password_type))
255
246
  end
256
247
  let(:attribute_errors) { { email: kind_of(BC::ContractFailure) } }
257
- let(:tuple_invalid) do
258
- { Test::InlineRegistrationInput => [:invalid_tuple] }
259
- end
260
248
 
261
249
  it do
262
250
  expect(subject).to be_invalid
263
251
  expect(subject.attributes).to match(attributes)
264
252
  expect(subject.to_h).to match(email: email_error)
265
- expect(subject.errors).to match_array([email_error, tuple_invalid])
253
+ expect(subject.errors).to match_array([email_error])
266
254
  expect(subject.attribute_errors).to match(attribute_errors)
267
255
  end
268
256
  end
@@ -295,13 +283,12 @@ RSpec.describe BloodContracts::Core do
295
283
  attribute_errors.merge(password: kind_of(Test::Ascii))
296
284
  end
297
285
  let(:attribute_errors) { { email: kind_of(BC::ContractFailure) } }
298
- let(:tuple_invalid) { { Test::RegistrationInput => [:invalid_tuple] } }
299
286
 
300
287
  it do
301
288
  expect(subject).to be_invalid
302
289
  expect(subject.attributes).to match(attributes)
303
290
  expect(subject.to_h).to match(email: email_error)
304
- expect(subject.errors).to match_array([email_error, tuple_invalid])
291
+ expect(subject.errors).to match_array([email_error])
305
292
  expect(subject.attribute_errors).to match(attribute_errors)
306
293
  end
307
294
  end
@@ -347,19 +334,22 @@ RSpec.describe BloodContracts::Core do
347
334
  context "when value is valid JSON" do
348
335
  context "when value is invalid registration data" do
349
336
  let(:response) { '{"phone":"+78889992211"}' }
350
- let(:error) { { Test::RegistrationInput => [:invalid_tuple] } }
351
- let(:errors) do
337
+ let(:error) do
352
338
  [
353
339
  { Test::Email => ["Not an email"] },
354
340
  { Test::Phone => ["Not a phone"] },
355
- { Test::Ascii => ["Not ASCII"] },
356
- { Test::RegistrationInput => [:invalid_tuple] }
341
+ { Test::Ascii => ["Not ASCII"] }
357
342
  ]
358
343
  end
359
- let(:validation_context) do
344
+ let(:password_errors) do
345
+ [
346
+ { Test::Ascii => ["Not ASCII"] }
347
+ ]
348
+ end
349
+ let(:password_context) do
360
350
  hash_including(
361
351
  raw_value: response,
362
- errors: array_including(errors),
352
+ errors: array_including(password_errors),
363
353
  steps: ["Test::Json", "BloodContracts::Core::TupleContractFailure"],
364
354
  steps_values: {
365
355
  parse: response,
@@ -367,12 +357,35 @@ RSpec.describe BloodContracts::Core do
367
357
  }
368
358
  )
369
359
  end
360
+ let(:login_errors) do
361
+ [
362
+ { Test::Email => ["Not an email"] },
363
+ { Test::Phone => ["Not a phone"] }
364
+ ]
365
+ end
366
+ let(:login_context) do
367
+ hash_including(
368
+ raw_value: response,
369
+ errors: array_including(login_errors),
370
+ steps: ["Test::Json", "BloodContracts::Core::TupleContractFailure"],
371
+ steps_values: {
372
+ parse: response,
373
+ validate: { phone: "+78889992211" }
374
+ }
375
+ )
376
+ end
377
+ let(:validation_context) do
378
+ {
379
+ login: login_context,
380
+ password: password_context
381
+ }
382
+ end
370
383
 
371
384
  it do
372
385
  is_expected.to be_invalid
373
386
  is_expected.to be_kind_of(BC::TupleContractFailure)
374
- expect(subject.unpack).to match(error)
375
- expect(subject.context).to match(validation_context)
387
+ expect(subject.attribute_contexts).to match(validation_context)
388
+ expect(subject.errors).to match(error)
376
389
  end
377
390
  end
378
391
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blood_contracts-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Dolganov (sclinede)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-29 00:00:00.000000000 Z
11
+ date: 2019-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler