blood_contracts-core 0.2.1 → 0.3.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 +4 -4
- data/.gitignore +1 -0
- data/examples/tariff_contract.rb +357 -0
- data/lib/blood_contracts/core/contract.rb +19 -8
- data/lib/blood_contracts/core/pipe.rb +16 -8
- data/lib/blood_contracts/core/refined.rb +32 -9
- data/lib/blood_contracts/core/sum.rb +14 -4
- data/lib/blood_contracts/core/tuple.rb +4 -0
- data/lib/blood_contracts/core/version.rb +1 -1
- data/lib/blood_contracts/core.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85f39b273547e389e3938ab33f8182857ef7c0d8f573e5664cb877c40c4d991a
|
4
|
+
data.tar.gz: a8e6d07a8311096fdb4a83d7c30a510807b6713a29ad620b4e0e174c09448346
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 726747a300fa76dab9a86ac8efe2f79aae54bfadee6c12989a377833754cf5f912d585b214c23370e4b6bf75eecba05684b5f816ac6eb66a3b208505935ccf9d
|
7
|
+
data.tar.gz: dd2d045b398fc3d8a82da25226a2b9f8cddc92e9b451111aa20b8c5ca4e44706d197d0b2eef665e776ef8d4d004ac57409d2c3c260bd650b624bb4da2ce9884d
|
data/.gitignore
CHANGED
@@ -0,0 +1,357 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "json"
|
3
|
+
require 'blood_contracts/core'
|
4
|
+
require "pry"
|
5
|
+
|
6
|
+
module Types
|
7
|
+
class JSON < BC::Refined
|
8
|
+
def match
|
9
|
+
super do
|
10
|
+
begin
|
11
|
+
context[:parsed] = ::JSON.parse(unpack_refined(@value))
|
12
|
+
self
|
13
|
+
rescue StandardError => error
|
14
|
+
failure(error)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def unpack
|
20
|
+
super { |match| match.context[:parsed] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module RussianPost
|
26
|
+
class DomesticTariffMapper
|
27
|
+
def self.call(parcel)
|
28
|
+
{
|
29
|
+
"mass": parcel.weight,
|
30
|
+
"mail-from": parcel.origin_postal_code,
|
31
|
+
"mail-to": parcel.destination_postal_code,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class InputValidationFailure < BC::ContractFailure; end
|
37
|
+
class ExceptionCaught < BC::ContractFailure; end
|
38
|
+
|
39
|
+
class BaseType < BC::Refined
|
40
|
+
def exception(ex, context: @context)
|
41
|
+
ExceptionCaught.new({ exception: ex }, context: context)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class DomesticParcel < BaseType
|
46
|
+
self.failure_klass = InputValidationFailure
|
47
|
+
|
48
|
+
alias :parcel :value
|
49
|
+
def _match
|
50
|
+
return failure(key: :undef_weight, field: :weight) unless parcel.weight
|
51
|
+
return self if domestic?
|
52
|
+
failure(non_domestic_error)
|
53
|
+
rescue StandardError => error
|
54
|
+
exception(error)
|
55
|
+
end
|
56
|
+
|
57
|
+
def _unpack(match)
|
58
|
+
DomesticTariffMapper.call(match.parcel)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def domestic?
|
64
|
+
[parcel.origin_country, parcel.destination_country].all?("RU")
|
65
|
+
end
|
66
|
+
|
67
|
+
def non_domestic_error
|
68
|
+
{
|
69
|
+
key: :non_domestic_parcel,
|
70
|
+
context: {
|
71
|
+
origin: parcel.origin_country,
|
72
|
+
destination: parcel.destination_country,
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class InternationalTariffMapper
|
79
|
+
def self.call(parcel)
|
80
|
+
{
|
81
|
+
"mass": parcel.weight,
|
82
|
+
"mail-direct": parcel.destination_country,
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class InternationalParcel < BaseType
|
88
|
+
self.failure_klass = InputValidationFailure
|
89
|
+
|
90
|
+
alias :parcel :value
|
91
|
+
def _match
|
92
|
+
return failure(key: :undef_weight, field: :weight) unless parcel.weight
|
93
|
+
return failure(not_from_ru_error) if parcel_outside_ru?
|
94
|
+
return failure(non_international_error) if non_international_parcel?
|
95
|
+
self
|
96
|
+
rescue StandardError => error
|
97
|
+
exception(error)
|
98
|
+
end
|
99
|
+
|
100
|
+
def _unpack(match)
|
101
|
+
InternationalTariffMapper.call(match.parcel)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def parcel_outside_ru?
|
107
|
+
parcel.origin_country != "RU"
|
108
|
+
end
|
109
|
+
|
110
|
+
def non_international_parcel?
|
111
|
+
parcel.destination_country == "RU"
|
112
|
+
end
|
113
|
+
|
114
|
+
def not_from_ru_error
|
115
|
+
{
|
116
|
+
key: :parcel_is_not_from_ru,
|
117
|
+
context: {
|
118
|
+
origin: parcel.origin_country,
|
119
|
+
}
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def non_international_error
|
124
|
+
{ key: :parcel_is_not_international }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class RecoverableInputError < BC::Refined
|
129
|
+
alias :parsed_response :value
|
130
|
+
def match
|
131
|
+
super do
|
132
|
+
begin
|
133
|
+
next self if [error_code, error_message].all?
|
134
|
+
failure(not_a_recoverable_error)
|
135
|
+
rescue StandardError => error
|
136
|
+
failure(error)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def error_message
|
142
|
+
@error_message ||= parsed_response["desc"]
|
143
|
+
@error_message ||= parsed_response["error-details"]&.join("; ")
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def not_a_recoverable_error
|
149
|
+
{ key: :not_a_recoverable_error }
|
150
|
+
end
|
151
|
+
|
152
|
+
def error_code
|
153
|
+
parsed_response.values_at("code", "error-code").compact.first
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class OtherError < BC::Refined
|
158
|
+
alias :parsed_response :value
|
159
|
+
def match
|
160
|
+
super do
|
161
|
+
begin
|
162
|
+
next failure({key: :not_a_known_error}) if error_code.nil?
|
163
|
+
self
|
164
|
+
rescue StandardError => error
|
165
|
+
failure(error)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def error_code
|
173
|
+
parsed_response.values_at("code", "error-code", "status").compact.first
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class DomesticTariff < BC::Refined
|
178
|
+
alias :parsed_response :value
|
179
|
+
def match
|
180
|
+
super do
|
181
|
+
begin
|
182
|
+
next self if is_a_domestic_tariff?
|
183
|
+
context[:raw_response] = parsed_response
|
184
|
+
failure({key: :not_a_domestic_tariff})
|
185
|
+
rescue StandardError => error
|
186
|
+
failure(error)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def cost
|
192
|
+
@cost ||= delivery_cost / 100.0
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def is_a_domestic_tariff?
|
198
|
+
[delivery_cost, delivery_date, cost].all?
|
199
|
+
end
|
200
|
+
|
201
|
+
def delivery_cost
|
202
|
+
parsed_response["total-cost"]
|
203
|
+
end
|
204
|
+
|
205
|
+
def delivery_date
|
206
|
+
@delivery_date ||= parsed_response["delivery-till"]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class InternationalTariff < BC::Refined
|
211
|
+
alias :parsed_response :value
|
212
|
+
def match
|
213
|
+
super do
|
214
|
+
begin
|
215
|
+
next self if is_an_international_tariff?
|
216
|
+
context[:raw_response] = parsed_response
|
217
|
+
failure({key: :not_an_international_tariff})
|
218
|
+
rescue StandardError => error
|
219
|
+
failure(error)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def cost
|
225
|
+
@cost ||= (delivery_rate + delivery_vat) / 100.0
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def is_an_international_tariff?
|
231
|
+
[delivery_rate, delivery_vat, cost].all?
|
232
|
+
end
|
233
|
+
|
234
|
+
def delivery_rate
|
235
|
+
parsed_response["total-rate"]
|
236
|
+
end
|
237
|
+
|
238
|
+
def delivery_vat
|
239
|
+
parsed_response["total-vat"]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
module RussianPost
|
245
|
+
KnownError = RecoverableInputError | OtherError
|
246
|
+
|
247
|
+
DomesticResponse =
|
248
|
+
(Types::JSON > (DomesticTariff | KnownError)).set(names: %i(parsed mapped))
|
249
|
+
InternationalResponse =
|
250
|
+
(Types::JSON > (InternationalTariff | KnownError)).set(names: %i(parsed mapped))
|
251
|
+
|
252
|
+
TariffRequestContract = ::BC::Contract.new(
|
253
|
+
DomesticParcel => DomesticResponse,
|
254
|
+
InternationalParcel => InternationalResponse,
|
255
|
+
)
|
256
|
+
end
|
257
|
+
|
258
|
+
def contractable_request_tariff(input)
|
259
|
+
RussianPost::TariffRequestContract.match(input) do |refined_parcel|
|
260
|
+
request_tariff(refined_parcel.unpack)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def match_response(response)
|
265
|
+
case response
|
266
|
+
when RussianPost::ExceptionCaught
|
267
|
+
puts "Honeybadger.notify #{response.errors_h[:exception]}"
|
268
|
+
when RussianPost::InputValidationFailure
|
269
|
+
# работаем с тарифом
|
270
|
+
puts "render json: { errors: 'Parcel is invalid for request (#{response.to_h})' }"
|
271
|
+
when RussianPost::DomesticTariff
|
272
|
+
# работаем с тарифом
|
273
|
+
puts "render json: { context: 'inside Russia only!', cost: #{response.cost} }"
|
274
|
+
when RussianPost::InternationalTariff
|
275
|
+
# работаем с тарифом
|
276
|
+
puts "render json: { context: 'outside Russia only!', cost_inc_vat: #{response.cost} }"
|
277
|
+
when RussianPost::RecoverableInputError
|
278
|
+
# работаем с ошибкой, e.g. адрес слишком длинный
|
279
|
+
puts "render json: { errors: [#{response.error_message}] } }"
|
280
|
+
when RussianPost::OtherError
|
281
|
+
# работаем с ошибкой, e.g. адрес слишком длинный
|
282
|
+
puts "Honeybadger.notify 'Non-recoverable error from Russian Post API', context: #{pp(response.context)}"
|
283
|
+
puts "render json: { errors: ['Sorry, API could not process your request, we've been notified. Try again later'] } }"
|
284
|
+
when BC::ContractFailure
|
285
|
+
puts "Honeybadger.notify 'Unexpected behavior in Russian Post API Client', context:"
|
286
|
+
puts " 'Unexpected behavior in Russian Post API Client'"
|
287
|
+
puts " context:"
|
288
|
+
pp(response.context)
|
289
|
+
puts "render json: { errors: 'Ooops! Not working, we've been notified. Please, try again later' }"
|
290
|
+
else
|
291
|
+
require'pry';binding.pry
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# DEMO STUFF
|
296
|
+
|
297
|
+
Stuff = Struct.new(:daaamn, keyword_init: true)
|
298
|
+
Parcel = Struct.new(
|
299
|
+
:weight, :origin_country, :origin_postal_code, :destination_country,
|
300
|
+
:destination_postal_code,
|
301
|
+
keyword_init: true
|
302
|
+
)
|
303
|
+
|
304
|
+
PARCELS = [
|
305
|
+
# domestic without weight
|
306
|
+
Parcel.new(weight: nil, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123" ),
|
307
|
+
|
308
|
+
# not from RU
|
309
|
+
Parcel.new(weight: 123, origin_country: "US", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123" ),
|
310
|
+
|
311
|
+
# domestic
|
312
|
+
Parcel.new(weight: 123, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123" ),
|
313
|
+
|
314
|
+
# international
|
315
|
+
Parcel.new(weight: 123, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123" ),
|
316
|
+
|
317
|
+
# not a parcel
|
318
|
+
Stuff.new(daaamn: "WTF?!")
|
319
|
+
]
|
320
|
+
|
321
|
+
RESPONSES = [
|
322
|
+
'{"total-cost": 10000, "delivery-till": "2019-12-12"}',
|
323
|
+
'{"total-rate": 100000, "total-vat": 1800}',
|
324
|
+
'{"code": 1010, "desc": "Too long address"}',
|
325
|
+
'{"error-code": 2020, "error-details": ["Too heavy parcel"]}',
|
326
|
+
]
|
327
|
+
|
328
|
+
def run_tests(runs: ENV["RUNS"] || 10)
|
329
|
+
runs.to_i.times do
|
330
|
+
input = PARCELS.sample
|
331
|
+
puts "#{'=' * 20}================================#{'=' * 20}"
|
332
|
+
puts "\n\n\n"
|
333
|
+
puts "#{'=' * 20}================================#{'=' * 20}"
|
334
|
+
puts "#{'=' * 20} WHEN INPUT: #{'=' * 20}"
|
335
|
+
pp(input)
|
336
|
+
match = contractable_request_tariff(input)
|
337
|
+
puts "#{'=' * 20}================================#{'=' * 20}"
|
338
|
+
puts "#{'=' * 20} ACTION: #{'=' * 20}"
|
339
|
+
match_response(match)
|
340
|
+
puts "#{'=' * 20}================================#{'=' * 20}"
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def request_tariff(request)
|
345
|
+
puts "#{'=' * 20}================================#{'=' * 20}"
|
346
|
+
puts "#{'=' * 20} AND THEN REQUEST: #{'=' * 20}"
|
347
|
+
pp(request)
|
348
|
+
|
349
|
+
puts "#{'=' * 20}================================#{'=' * 20}"
|
350
|
+
puts "#{'=' * 20} AND THEN RESPONSE: #{'=' * 20}"
|
351
|
+
response = RESPONSES.sample
|
352
|
+
puts response
|
353
|
+
|
354
|
+
response
|
355
|
+
end
|
356
|
+
|
357
|
+
run_tests
|
@@ -2,15 +2,26 @@ module BloodContracts
|
|
2
2
|
module Core
|
3
3
|
class Contract
|
4
4
|
class << self
|
5
|
-
|
6
|
-
|
5
|
+
def new(*args)
|
6
|
+
input, output =
|
7
|
+
if (opts = args.last).is_a?(Hash)
|
8
|
+
accumulate_contract = opts.reduce({}) do |acc, (input, output)|
|
9
|
+
prev_input, prev_output = acc.first
|
10
|
+
{ (input | prev_input) => (output | prev_output) }
|
11
|
+
end
|
12
|
+
accumulate_contract.first
|
13
|
+
else
|
14
|
+
_validate_args!(args)
|
15
|
+
args
|
16
|
+
end
|
17
|
+
BC::Pipe.new(input, output, names: %i(input output))
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
input_match
|
20
|
+
def _validate_args!(args)
|
21
|
+
return if args.size == 2
|
22
|
+
raise ArgumentError, <<~MESSAGE
|
23
|
+
wrong number of arguments (given #{args.size}, expected 2)
|
24
|
+
MESSAGE
|
14
25
|
end
|
15
26
|
end
|
16
27
|
end
|
@@ -28,6 +28,14 @@ module BloodContracts
|
|
28
28
|
pipe
|
29
29
|
end
|
30
30
|
alias :> :and_then
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
self.name || "Pipe(#{self.steps.to_a.join(',')})"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_writer :names
|
31
39
|
end
|
32
40
|
|
33
41
|
def match
|
@@ -43,27 +51,27 @@ module BloodContracts
|
|
43
51
|
end
|
44
52
|
|
45
53
|
break match if match.invalid?
|
46
|
-
|
54
|
+
if block_given? && index < self.class.steps.size
|
55
|
+
next refine_value(yield(match))
|
56
|
+
end
|
47
57
|
match
|
48
58
|
end
|
49
59
|
end
|
50
60
|
end
|
51
61
|
|
52
|
-
private
|
53
|
-
|
54
|
-
def step_name(index)
|
62
|
+
private def step_name(index)
|
55
63
|
self.class.names[index] || index
|
56
64
|
end
|
57
65
|
|
58
|
-
def steps_with_names
|
66
|
+
private def steps_with_names
|
59
67
|
steps = if self.class.names.empty?
|
60
|
-
self.class.steps.map(&:
|
68
|
+
self.class.steps.map(&:inspect)
|
61
69
|
else
|
62
|
-
self.class.steps.zip(self.class.names).map { |k, n| "#{k}(#{n})" }
|
70
|
+
self.class.steps.zip(self.class.names).map { |k, n| "#{k.inspect}(#{n})" }
|
63
71
|
end
|
64
72
|
end
|
65
73
|
|
66
|
-
def inspect
|
74
|
+
private def inspect
|
67
75
|
"#<pipe #{self.class.name} = #{steps_with_names.join(' > ')} (value=#{@value})>"
|
68
76
|
end
|
69
77
|
end
|
@@ -14,7 +14,11 @@ module BloodContracts
|
|
14
14
|
alias :> :and_then
|
15
15
|
|
16
16
|
def match(*args)
|
17
|
-
|
17
|
+
if block_given?
|
18
|
+
new(*args).match { |*subargs| yield(*subargs) }
|
19
|
+
else
|
20
|
+
new(*args).match
|
21
|
+
end
|
18
22
|
end
|
19
23
|
alias :call :match
|
20
24
|
|
@@ -22,6 +26,18 @@ module BloodContracts
|
|
22
26
|
return object.to_ary.any?(self) if object.is_a?(Tuple)
|
23
27
|
super
|
24
28
|
end
|
29
|
+
|
30
|
+
def set(**kwargs)
|
31
|
+
kwargs.each do |setting, value|
|
32
|
+
send(:"#{setting}=", value)
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_accessor :failure_klass
|
38
|
+
def inherited(new_klass)
|
39
|
+
new_klass.failure_klass ||= ContractFailure
|
40
|
+
end
|
25
41
|
end
|
26
42
|
|
27
43
|
attr_accessor :context
|
@@ -36,6 +52,7 @@ module BloodContracts
|
|
36
52
|
def match
|
37
53
|
return @match if defined? @match
|
38
54
|
return @match = yield if block_given?
|
55
|
+
return @match = _match if respond_to?(:_match)
|
39
56
|
self
|
40
57
|
end
|
41
58
|
alias :call :match
|
@@ -48,13 +65,15 @@ module BloodContracts
|
|
48
65
|
def unpack
|
49
66
|
raise "This is not what you're looking for" if match.invalid?
|
50
67
|
return yield(match) if block_given?
|
68
|
+
return @match = _unpack(match) if respond_to?(:_unpack)
|
51
69
|
|
52
70
|
unpack_refined @value
|
53
71
|
end
|
54
72
|
|
55
|
-
def failure(error = nil, errors: @errors, context: @context)
|
73
|
+
def failure(error = nil, errors: @errors, context: @context, **kwargs)
|
74
|
+
error ||= kwargs unless kwargs.empty?
|
56
75
|
errors << error if error
|
57
|
-
|
76
|
+
self.class.failure_klass.new(
|
58
77
|
{ self.class => errors }, context: context
|
59
78
|
)
|
60
79
|
end
|
@@ -79,21 +98,25 @@ module BloodContracts
|
|
79
98
|
end
|
80
99
|
|
81
100
|
def errors_by_type(matches)
|
82
|
-
|
83
|
-
matches.map(&:class).zip(matches.map(&:errors))
|
84
|
-
].delete_if { |_, errors| errors.empty? }
|
101
|
+
matches.map(&:errors).reduce(:+).delete_if(&:empty?)
|
85
102
|
end
|
86
103
|
end
|
87
104
|
|
88
105
|
class ContractFailure < Refined
|
89
|
-
def initialize(
|
106
|
+
def initialize(value = nil, **)
|
90
107
|
super
|
91
|
-
|
108
|
+
return unless @value
|
109
|
+
@context[:errors] = (@context[:errors].to_a << @value.to_h)
|
92
110
|
end
|
93
111
|
|
94
112
|
def errors
|
95
|
-
context[:errors]
|
113
|
+
@context[:errors].to_a
|
114
|
+
end
|
115
|
+
|
116
|
+
def errors_h
|
117
|
+
errors.reduce(:merge)
|
96
118
|
end
|
119
|
+
alias :to_h :errors_h
|
97
120
|
|
98
121
|
def match
|
99
122
|
self
|
@@ -9,20 +9,29 @@ module BloodContracts
|
|
9
9
|
def new(*args)
|
10
10
|
return super if finalized
|
11
11
|
|
12
|
+
new_sum = args.reduce([]) { |acc, type| type.respond_to?(:sum_of) ? acc + type.sum_of.to_a : acc << type }
|
13
|
+
|
12
14
|
sum = Class.new(Sum) { def inspect; super; end }
|
13
|
-
sum.instance_variable_set(:@sum_of, ::Set.new(
|
15
|
+
sum.instance_variable_set(:@sum_of, ::Set.new(new_sum.compact))
|
14
16
|
sum.instance_variable_set(:@finalized, true)
|
15
17
|
sum
|
16
18
|
end
|
17
19
|
|
18
20
|
def or_a(other_type)
|
19
21
|
sum = Class.new(Sum) { def inspect; super; end }
|
20
|
-
|
22
|
+
new_sum = self.sum_of.to_a
|
23
|
+
new_sum += other_type.sum_of.to_a if other_type.respond_to?(:sum_of)
|
24
|
+
sum.instance_variable_set(:@sum_of, ::Set.new(new_sum.compact))
|
21
25
|
sum.instance_variable_set(:@finalized, true)
|
22
26
|
sum
|
23
27
|
end
|
24
28
|
alias :or_an :or_a
|
25
29
|
alias :| :or_a
|
30
|
+
|
31
|
+
def inspect
|
32
|
+
return super if self.name
|
33
|
+
"Sum(#{self.sum_of.map(&:inspect).join(',')})"
|
34
|
+
end
|
26
35
|
end
|
27
36
|
|
28
37
|
def match
|
@@ -32,10 +41,11 @@ module BloodContracts
|
|
32
41
|
end
|
33
42
|
|
34
43
|
if (match = or_matches.find(&:valid?))
|
35
|
-
match.context[:errors].merge(errors_by_type(or_matches))
|
36
44
|
match
|
37
45
|
else
|
38
|
-
|
46
|
+
or_matches.first
|
47
|
+
# just use the context
|
48
|
+
# ContractFailure.new(context: context)
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
data/lib/blood_contracts/core.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blood_contracts-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Dolganov
|
@@ -85,6 +85,7 @@ files:
|
|
85
85
|
- bin/setup
|
86
86
|
- blood_contracts-core.gemspec
|
87
87
|
- examples/json_response.rb
|
88
|
+
- examples/tariff_contract.rb
|
88
89
|
- examples/tuple.rb
|
89
90
|
- lib/blood_contracts-core.rb
|
90
91
|
- lib/blood_contracts/core.rb
|