blood_contracts-core 0.3.5 → 0.4.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/.rubocop.yml +31 -0
- data/.travis.yml +16 -4
- data/CHANGELOG.md +14 -0
- data/README.md +363 -5
- data/Rakefile +1 -1
- data/blood_contracts-core.gemspec +18 -25
- data/examples/json_response.rb +33 -41
- data/examples/tariff_contract.rb +35 -32
- data/examples/tuple.rb +11 -12
- data/lib/blood_contracts/core/anything.rb +23 -0
- data/lib/blood_contracts/core/contract.rb +37 -23
- data/lib/blood_contracts/core/contract_failure.rb +50 -0
- data/lib/blood_contracts/core/pipe.rb +143 -77
- data/lib/blood_contracts/core/refined.rb +148 -125
- data/lib/blood_contracts/core/sum.rb +81 -49
- data/lib/blood_contracts/core/tuple.rb +151 -66
- data/lib/blood_contracts/core/tuple_contract_failure.rb +31 -0
- data/lib/blood_contracts/core.rb +16 -10
- data/spec/blood_contracts/core_spec.rb +314 -0
- data/spec/spec_helper.rb +26 -0
- metadata +36 -10
- data/lib/blood_contracts/core/version.rb +0 -5
data/examples/tariff_contract.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "bundler/setup"
|
2
2
|
require "json"
|
3
|
-
require
|
3
|
+
require "blood_contracts/core"
|
4
4
|
require "pry"
|
5
5
|
|
6
6
|
module Types
|
@@ -14,7 +14,7 @@ module Types
|
|
14
14
|
class JSON < Base
|
15
15
|
def _match
|
16
16
|
context[:parsed] = ::JSON.parse(unpack_refined(@value))
|
17
|
-
|
17
|
+
nil
|
18
18
|
rescue StandardError => error
|
19
19
|
exception(error)
|
20
20
|
end
|
@@ -41,17 +41,18 @@ module RussianPost
|
|
41
41
|
class DomesticParcel < Types::Base
|
42
42
|
self.failure_klass = InputValidationFailure
|
43
43
|
|
44
|
-
alias
|
45
|
-
def
|
44
|
+
alias parcel value
|
45
|
+
def match
|
46
46
|
return failure(key: :undef_weight, field: :weight) unless parcel.weight
|
47
47
|
return if domestic?
|
48
|
+
|
48
49
|
failure(non_domestic_error)
|
49
50
|
rescue StandardError => error
|
50
51
|
exception(error)
|
51
52
|
end
|
52
53
|
|
53
|
-
def
|
54
|
-
DomesticTariffMapper.call(
|
54
|
+
def mapped
|
55
|
+
DomesticTariffMapper.call(parcel)
|
55
56
|
end
|
56
57
|
|
57
58
|
private
|
@@ -83,18 +84,18 @@ module RussianPost
|
|
83
84
|
class InternationalParcel < Types::Base
|
84
85
|
self.failure_klass = InputValidationFailure
|
85
86
|
|
86
|
-
alias
|
87
|
-
def
|
87
|
+
alias parcel value
|
88
|
+
def match
|
88
89
|
return failure(key: :undef_weight, field: :weight) unless parcel.weight
|
89
90
|
return failure(not_from_ru_error) if parcel_outside_ru?
|
90
91
|
return failure(non_international_error) if non_international_parcel?
|
91
|
-
|
92
|
+
nil
|
92
93
|
rescue StandardError => error
|
93
94
|
exception(error)
|
94
95
|
end
|
95
96
|
|
96
|
-
def
|
97
|
-
InternationalTariffMapper.call(
|
97
|
+
def mapped
|
98
|
+
InternationalTariffMapper.call(parcel)
|
98
99
|
end
|
99
100
|
|
100
101
|
private
|
@@ -122,9 +123,10 @@ module RussianPost
|
|
122
123
|
end
|
123
124
|
|
124
125
|
class RecoverableInputError < Types::Base
|
125
|
-
alias
|
126
|
-
def
|
126
|
+
alias parsed_response value
|
127
|
+
def match
|
127
128
|
return if [error_code, error_message].all?
|
129
|
+
|
128
130
|
failure(key: :not_a_recoverable_error)
|
129
131
|
rescue StandardError => error
|
130
132
|
exception(error)
|
@@ -143,10 +145,11 @@ module RussianPost
|
|
143
145
|
end
|
144
146
|
|
145
147
|
class OtherError < Types::Base
|
146
|
-
alias
|
147
|
-
def
|
148
|
-
return
|
149
|
-
|
148
|
+
alias parsed_response value
|
149
|
+
def match
|
150
|
+
return unless error_code.nil?
|
151
|
+
|
152
|
+
failure(key: :not_a_known_error)
|
150
153
|
rescue StandardError => error
|
151
154
|
exception(error)
|
152
155
|
end
|
@@ -159,8 +162,8 @@ module RussianPost
|
|
159
162
|
end
|
160
163
|
|
161
164
|
class DomesticTariff < Types::Base
|
162
|
-
alias
|
163
|
-
def
|
165
|
+
alias parsed_response value
|
166
|
+
def match
|
164
167
|
return if is_a_domestic_tariff?
|
165
168
|
context[:raw_response] = parsed_response
|
166
169
|
failure(key: :not_a_domestic_tariff)
|
@@ -188,8 +191,8 @@ module RussianPost
|
|
188
191
|
end
|
189
192
|
|
190
193
|
class InternationalTariff < Types::Base
|
191
|
-
alias
|
192
|
-
def
|
194
|
+
alias parsed_response value
|
195
|
+
def match
|
193
196
|
return if is_an_international_tariff?
|
194
197
|
context[:raw_response] = parsed_response
|
195
198
|
failure(key: :not_an_international_tariff)
|
@@ -221,13 +224,13 @@ module RussianPost
|
|
221
224
|
KnownError = RecoverableInputError | OtherError
|
222
225
|
|
223
226
|
DomesticResponse =
|
224
|
-
(Types::JSON.and_then(DomesticTariff | KnownError)).set(names: %i
|
227
|
+
(Types::JSON.and_then(DomesticTariff | KnownError)).set(names: %i[parsed mapped])
|
225
228
|
InternationalResponse =
|
226
|
-
(Types::JSON.and_then(InternationalTariff | KnownError)).set(names: %i
|
229
|
+
(Types::JSON.and_then(InternationalTariff | KnownError)).set(names: %i[parsed mapped])
|
227
230
|
|
228
231
|
TariffRequestContract = ::BC::Contract.new(
|
229
232
|
DomesticParcel => DomesticResponse,
|
230
|
-
InternationalParcel => InternationalResponse
|
233
|
+
InternationalParcel => InternationalResponse
|
231
234
|
)
|
232
235
|
end
|
233
236
|
|
@@ -239,8 +242,6 @@ end
|
|
239
242
|
|
240
243
|
def match_response(response)
|
241
244
|
case response
|
242
|
-
when Types::ExceptionCaught
|
243
|
-
puts "Honeybadger.notify #{response.errors_h[:exception]}"
|
244
245
|
when RussianPost::InputValidationFailure
|
245
246
|
# работаем с тарифом
|
246
247
|
puts "render json: { errors: 'Parcel is invalid for request (#{response.to_h})' }"
|
@@ -257,6 +258,8 @@ def match_response(response)
|
|
257
258
|
# работаем с ошибкой, e.g. адрес слишком длинный
|
258
259
|
puts "Honeybadger.notify 'Non-recoverable error from Russian Post API', context: #{pp(response.context)}"
|
259
260
|
puts "render json: { errors: ['Sorry, API could not process your request, we've been notified. Try again later'] } }"
|
261
|
+
when Types::ExceptionCaught
|
262
|
+
puts "Honeybadger.notify #{response.errors_h[:exception]}"
|
260
263
|
when BC::ContractFailure
|
261
264
|
puts "Honeybadger.notify 'Unexpected behavior in Russian Post API Client', context:"
|
262
265
|
puts " 'Unexpected behavior in Russian Post API Client'"
|
@@ -264,7 +267,7 @@ def match_response(response)
|
|
264
267
|
pp(response.context)
|
265
268
|
puts "render json: { errors: 'Ooops! Not working, we've been notified. Please, try again later' }"
|
266
269
|
else
|
267
|
-
require
|
270
|
+
require"pry"; binding.pry
|
268
271
|
end
|
269
272
|
end
|
270
273
|
|
@@ -279,16 +282,16 @@ Parcel = Struct.new(
|
|
279
282
|
|
280
283
|
PARCELS = [
|
281
284
|
# domestic without weight
|
282
|
-
Parcel.new(weight: nil, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"
|
285
|
+
Parcel.new(weight: nil, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"),
|
283
286
|
|
284
287
|
# not from RU
|
285
|
-
Parcel.new(weight: 123, origin_country: "US", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"
|
288
|
+
Parcel.new(weight: 123, origin_country: "US", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"),
|
286
289
|
|
287
290
|
# domestic
|
288
|
-
Parcel.new(weight: 123, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"
|
291
|
+
Parcel.new(weight: 123, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"),
|
289
292
|
|
290
293
|
# international
|
291
|
-
Parcel.new(weight: 123, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"
|
294
|
+
Parcel.new(weight: 123, origin_country: "RU", origin_postal_code: "123", destination_country: "RU", destination_postal_code: "123"),
|
292
295
|
|
293
296
|
# not a parcel
|
294
297
|
Stuff.new(daaamn: "WTF?!")
|
@@ -299,7 +302,7 @@ RESPONSES = [
|
|
299
302
|
'{"total-rate": 100000, "total-vat": 1800}',
|
300
303
|
'{"total-rate": "some", "total-vat": "text"}',
|
301
304
|
'{"code": 1010, "desc": "Too long address"}',
|
302
|
-
'{"error-code": 2020, "error-details": ["Too heavy parcel"]}'
|
305
|
+
'{"error-code": 2020, "error-details": ["Too heavy parcel"]}'
|
303
306
|
]
|
304
307
|
|
305
308
|
def run_tests(runs: ENV["RUNS"] || 10)
|
data/examples/tuple.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "bundler/setup"
|
2
2
|
require "json"
|
3
|
-
require
|
3
|
+
require "blood_contracts/core"
|
4
4
|
require "pry"
|
5
5
|
|
6
6
|
module Types
|
@@ -8,7 +8,7 @@ module Types
|
|
8
8
|
def match
|
9
9
|
super do
|
10
10
|
begin
|
11
|
-
context[:parsed] = ::JSON.parse(
|
11
|
+
context[:parsed] = ::JSON.parse(value)
|
12
12
|
self
|
13
13
|
rescue StandardError => error
|
14
14
|
failure(error)
|
@@ -23,14 +23,10 @@ module Types
|
|
23
23
|
|
24
24
|
class Symbol < BC::Refined
|
25
25
|
def match
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
rescue StandardError => error
|
31
|
-
failure(error)
|
32
|
-
end
|
33
|
-
end
|
26
|
+
context[:as_symbol] = value.to_sym
|
27
|
+
self
|
28
|
+
rescue StandardError => error
|
29
|
+
failure(error)
|
34
30
|
end
|
35
31
|
|
36
32
|
def unpack
|
@@ -39,7 +35,10 @@ module Types
|
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
42
|
-
Config = BC::Tuple.new
|
38
|
+
Config = BC::Tuple.new do
|
39
|
+
attribute :name, Types::Symbol
|
40
|
+
attribute :config, Types::JSON
|
41
|
+
end
|
42
|
+
|
43
43
|
c = Config.new("test", '{"some": "value"}')
|
44
44
|
binding.pry
|
45
|
-
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module BloodContracts::Core
|
2
|
+
# Refinement type which represents data which is always correct
|
3
|
+
class Anything < Refined
|
4
|
+
|
5
|
+
# The type which is the result of data matching process
|
6
|
+
# (for Anything is always self)
|
7
|
+
#
|
8
|
+
# @return [BC::Refined]
|
9
|
+
#
|
10
|
+
def match
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# Checks whether the data matches the expectations or not
|
15
|
+
# (for Anything is always true)
|
16
|
+
#
|
17
|
+
# @return [Boolean]
|
18
|
+
#
|
19
|
+
def valid?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,28 +1,42 @@
|
|
1
|
-
module BloodContracts
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
module BloodContracts::Core
|
2
|
+
# Meta refinement type, represents contract built upon input and output
|
3
|
+
# types
|
4
|
+
class Contract
|
5
|
+
class << self
|
6
|
+
# Metaprogramming around constructor
|
7
|
+
# Turns input types into a contract
|
8
|
+
#
|
9
|
+
# @param [Hash<BC::Refined, BC::Refined>] expectations about possible
|
10
|
+
# data input expressed in form of BC::Refined types
|
11
|
+
# @return [BC::Refined] contract for data validation in form of
|
12
|
+
# BC::Refined class
|
13
|
+
#
|
14
|
+
def new(*args)
|
15
|
+
input, output =
|
16
|
+
if (opts = args.last).is_a?(Hash)
|
17
|
+
accumulate_contract(opts)
|
18
|
+
else
|
19
|
+
_validate_args!(args)
|
20
|
+
args
|
21
|
+
end
|
22
|
+
BC::Pipe.new(input, output, names: %i[input output])
|
23
|
+
end
|
24
|
+
|
25
|
+
# @private
|
26
|
+
private def _validate_args!(args)
|
27
|
+
return if args.size == 2
|
28
|
+
raise ArgumentError, <<~MESSAGE
|
29
|
+
wrong number of arguments (given #{args.size}, expected 2)
|
30
|
+
MESSAGE
|
31
|
+
end
|
19
32
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
33
|
+
# @private
|
34
|
+
private def accumulate_contract(options)
|
35
|
+
accumulate_contract = options.reduce({}) do |acc, (input, output)|
|
36
|
+
prev_input, prev_output = acc.first
|
37
|
+
{ (input | prev_input) => (output | prev_output) }
|
25
38
|
end
|
39
|
+
accumulate_contract.first
|
26
40
|
end
|
27
41
|
end
|
28
42
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module BloodContracts::Core
|
2
|
+
# Refinement type which represents invalid data
|
3
|
+
class ContractFailure < Refined
|
4
|
+
# Constructs a ContractsFailure using the given value
|
5
|
+
# (for ContractFailure value is an error)
|
6
|
+
#
|
7
|
+
# @return [ContractFailure]
|
8
|
+
#
|
9
|
+
def initialize(value = nil, **)
|
10
|
+
super
|
11
|
+
@match = self
|
12
|
+
return unless @value
|
13
|
+
@context[:errors] = (@context[:errors].to_a << @value.to_h)
|
14
|
+
end
|
15
|
+
|
16
|
+
# List of errors per type after the data matching process
|
17
|
+
#
|
18
|
+
# @return [Array<Hash<BC::Refined, String>>]
|
19
|
+
#
|
20
|
+
def errors
|
21
|
+
@context[:errors].to_a
|
22
|
+
end
|
23
|
+
|
24
|
+
# Flatten list of error messages
|
25
|
+
#
|
26
|
+
# @return [Array<String>]
|
27
|
+
#
|
28
|
+
def messages
|
29
|
+
errors.reduce(:merge).values.flatten!
|
30
|
+
end
|
31
|
+
|
32
|
+
# Merged map of errors per type after the data matching process
|
33
|
+
#
|
34
|
+
# @return [Hash<BC::Refined, String>]
|
35
|
+
#
|
36
|
+
def errors_h
|
37
|
+
errors.reduce(:merge)
|
38
|
+
end
|
39
|
+
alias to_h errors_h
|
40
|
+
|
41
|
+
# The type which is the result of validation
|
42
|
+
# (for ContractFailure is always self)
|
43
|
+
#
|
44
|
+
# @return [BC::Refined]
|
45
|
+
#
|
46
|
+
def match
|
47
|
+
self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,95 +1,161 @@
|
|
1
|
-
module BloodContracts
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
pipe
|
29
|
-
end
|
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
|
1
|
+
module BloodContracts::Core
|
2
|
+
# Meta refinement type, represents pipe of several refinement types
|
3
|
+
class Pipe < Refined
|
4
|
+
class << self
|
5
|
+
# List of data transformation step
|
6
|
+
#
|
7
|
+
# @return [Array<Refined>]
|
8
|
+
#
|
9
|
+
attr_reader :steps
|
10
|
+
|
11
|
+
# List of data transformation step names
|
12
|
+
#
|
13
|
+
# @return [Array<Symbol>]
|
14
|
+
#
|
15
|
+
attr_reader :names
|
16
|
+
|
17
|
+
# rubocop:disable Style/SingleLineMethods
|
18
|
+
def new(*args, **kwargs, &block)
|
19
|
+
return super(*args, **kwargs) if @finalized
|
20
|
+
names = kwargs.delete(:names) unless kwargs.empty?
|
21
|
+
names ||= []
|
22
|
+
|
23
|
+
raise ArgumentError unless args.all? { |type| type < Refined }
|
24
|
+
pipe = Class.new(Pipe) { def inspect; super; end }
|
25
|
+
finalize!(pipe, args, names)
|
26
|
+
pipe.class_eval(&block) if block_given?
|
27
|
+
pipe
|
39
28
|
end
|
40
29
|
|
41
|
-
|
42
|
-
|
43
|
-
|
30
|
+
# Compose types in a Pipe check
|
31
|
+
# Pipe passes data from type to type sequentially
|
32
|
+
#
|
33
|
+
# @return [BC::Pipe]
|
34
|
+
#
|
35
|
+
# rubocop:disable Style/CaseEquality
|
36
|
+
def and_then(other_type, **kwargs)
|
37
|
+
raise ArgumentError unless Class === other_type
|
38
|
+
pipe = Class.new(Pipe) { def inspect; super; end }
|
39
|
+
finalize!(pipe, [self, other_type], kwargs[:names].to_a)
|
40
|
+
pipe
|
44
41
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
next refine_value(yield(match))
|
60
|
-
end
|
61
|
-
match
|
62
|
-
end
|
42
|
+
# rubocop:enable Style/CaseEquality Style/SingleLineMethods
|
43
|
+
alias > and_then
|
44
|
+
|
45
|
+
# Helper which registers step in validation pipe, also defines a reader
|
46
|
+
#
|
47
|
+
# @param [Symbol] name of the matching step
|
48
|
+
# @param [Refined] type of the matching step
|
49
|
+
#
|
50
|
+
def step(name, type)
|
51
|
+
raise ArgumentError unless type < Refined
|
52
|
+
@steps << type
|
53
|
+
@names << name
|
54
|
+
define_method(name) do
|
55
|
+
match.context.dig(:steps_values, name)
|
63
56
|
end
|
64
57
|
end
|
65
58
|
|
66
|
-
|
67
|
-
|
59
|
+
# Returns text representation of Pipe meta-class
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
#
|
63
|
+
def inspect
|
64
|
+
return super if name
|
65
|
+
"Pipe(#{steps.to_a.join(',')})"
|
68
66
|
end
|
69
67
|
|
70
|
-
private def
|
71
|
-
|
68
|
+
private def finalize!(new_class, steps, names)
|
69
|
+
new_class.instance_variable_set(:@steps, steps)
|
70
|
+
new_class.instance_variable_set(:@names, names)
|
71
|
+
new_class.instance_variable_set(:@finalized, true)
|
72
72
|
end
|
73
|
+
end
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
# Constructs a Pipe using the given value
|
76
|
+
# (for Pipe steps are also stored in the context)
|
77
|
+
#
|
78
|
+
# @return [Pipe]
|
79
|
+
#
|
80
|
+
def initialize(*)
|
81
|
+
super
|
82
|
+
@context[:steps] = @context[:steps].to_a
|
83
|
+
end
|
77
84
|
|
78
|
-
|
79
|
-
|
85
|
+
# The type which is the result of data matching process
|
86
|
+
# For PIpe it verifies that data is valid through all data transformation
|
87
|
+
# steps
|
88
|
+
#
|
89
|
+
# @return [BC::Refined]
|
90
|
+
#
|
91
|
+
def match
|
92
|
+
steps_enumerator.reduce(value) do |next_value, (step, index)|
|
93
|
+
match = next_step_value_match!(step, next_value, index)
|
94
|
+
|
95
|
+
break match if match.invalid?
|
96
|
+
next match unless block_given?
|
97
|
+
next refine_value(yield(match)) if index < self.class.steps.size - 1
|
98
|
+
|
99
|
+
match
|
80
100
|
end
|
101
|
+
end
|
81
102
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
103
|
+
# List of errors per type during the matching
|
104
|
+
#
|
105
|
+
# @return [Array<Hash<Refined, String>>]
|
106
|
+
#
|
107
|
+
def errors
|
108
|
+
@context[:errors]
|
109
|
+
end
|
89
110
|
|
90
|
-
|
91
|
-
|
111
|
+
# @private
|
112
|
+
private def next_step_value_match!(step, value, index)
|
113
|
+
unpacked_value = unpack_refined(value)
|
114
|
+
match = step.match(unpacked_value, context: @context)
|
115
|
+
track_steps!(index, unpacked_value, match.class.name)
|
116
|
+
match
|
117
|
+
end
|
118
|
+
|
119
|
+
# @private
|
120
|
+
private def steps_enumerator
|
121
|
+
self.class.steps.each_with_index
|
122
|
+
end
|
123
|
+
|
124
|
+
# @private
|
125
|
+
private def track_steps!(index, unpacked_value, match_class_name)
|
126
|
+
@context[:steps_values][step_name(index)] = unpacked_value
|
127
|
+
steps << match_class_name unless current_step.eql?(match_class_name)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @private
|
131
|
+
private def steps
|
132
|
+
@context[:steps]
|
133
|
+
end
|
134
|
+
|
135
|
+
# @private
|
136
|
+
private def current_step
|
137
|
+
@context[:steps].last
|
138
|
+
end
|
139
|
+
|
140
|
+
# @private
|
141
|
+
private def step_name(index)
|
142
|
+
self.class.names[index] || index
|
143
|
+
end
|
144
|
+
|
145
|
+
# @private
|
146
|
+
private def steps_with_names
|
147
|
+
steps = self.class.steps
|
148
|
+
if self.class.names.empty?
|
149
|
+
steps.map(&:inspect)
|
150
|
+
else
|
151
|
+
steps.zip(self.class.names).map { |k, n| "#{k.inspect}(#{n})" }
|
92
152
|
end
|
93
153
|
end
|
154
|
+
|
155
|
+
# @private
|
156
|
+
private def inspect
|
157
|
+
"#<pipe #{self.class.name} = #{steps_with_names.join(' > ')}"\
|
158
|
+
" (value=#{@value})>"
|
159
|
+
end
|
94
160
|
end
|
95
161
|
end
|