blood_contracts-core 0.4.2 → 0.4.3

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: 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