blood_contracts-core 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|