bcdd-result 0.4.0 → 0.5.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 +21 -0
- data/.rubocop_todo.yml +3 -13
- data/CHANGELOG.md +74 -0
- data/README.md +407 -10
- data/lib/bcdd/result/data.rb +12 -7
- data/lib/bcdd/result/error.rb +8 -0
- data/lib/bcdd/result/expectations/contract/disabled.rb +25 -0
- data/lib/bcdd/result/expectations/contract/evaluator.rb +45 -0
- data/lib/bcdd/result/expectations/contract/for_types.rb +29 -0
- data/lib/bcdd/result/expectations/contract/for_types_and_values.rb +37 -0
- data/lib/bcdd/result/expectations/contract/interface.rb +21 -0
- data/lib/bcdd/result/expectations/contract.rb +25 -0
- data/lib/bcdd/result/expectations/error.rb +15 -0
- data/lib/bcdd/result/expectations/type_checker.rb +33 -0
- data/lib/bcdd/result/expectations.rb +62 -0
- data/lib/bcdd/result/failure.rb +1 -1
- data/lib/bcdd/result/handler/allowed_types.rb +45 -0
- data/lib/bcdd/result/handler.rb +13 -8
- data/lib/bcdd/result/success.rb +1 -1
- data/lib/bcdd/result/version.rb +1 -1
- data/lib/bcdd/result.rb +18 -16
- data/sig/bcdd/result.rbs +200 -41
- metadata +12 -3
- data/lib/bcdd/result/type.rb +0 -17
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations
|
4
|
+
class Contract::Evaluator
|
5
|
+
include Contract::Interface
|
6
|
+
|
7
|
+
attr_reader :allowed_types, :success, :failure
|
8
|
+
|
9
|
+
def initialize(success, failure)
|
10
|
+
@success = success
|
11
|
+
@failure = failure
|
12
|
+
|
13
|
+
@allowed_types = (success.allowed_types | failure.allowed_types).freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def type?(type)
|
17
|
+
success_disabled = success == Contract::Disabled
|
18
|
+
failure_disabled = failure == Contract::Disabled
|
19
|
+
|
20
|
+
return Contract::Disabled.type?(type) if success_disabled && failure_disabled
|
21
|
+
|
22
|
+
(!success_disabled && success.type?(type)) || (!failure_disabled && failure.type?(type))
|
23
|
+
end
|
24
|
+
|
25
|
+
def type!(type)
|
26
|
+
return type if type?(type)
|
27
|
+
|
28
|
+
raise Error::UnexpectedType.build(type: type, allowed_types: allowed_types)
|
29
|
+
end
|
30
|
+
|
31
|
+
def type_and_value!(data)
|
32
|
+
self.for(data).type_and_value!(data)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def for(data)
|
38
|
+
case data.name
|
39
|
+
when :unknown then Contract::Disabled
|
40
|
+
when :success then success
|
41
|
+
else failure
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations
|
4
|
+
class Contract::ForTypes
|
5
|
+
include Contract::Interface
|
6
|
+
|
7
|
+
attr_reader :allowed_types
|
8
|
+
|
9
|
+
def initialize(types)
|
10
|
+
@allowed_types = Array(types).map(&:to_sym).to_set.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def type?(type)
|
14
|
+
allowed_types.member?(type)
|
15
|
+
end
|
16
|
+
|
17
|
+
def type!(type)
|
18
|
+
return type if type?(type)
|
19
|
+
|
20
|
+
raise Error::UnexpectedType.build(type: type, allowed_types: allowed_types)
|
21
|
+
end
|
22
|
+
|
23
|
+
def type_and_value!(data)
|
24
|
+
type!(data.type)
|
25
|
+
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations
|
4
|
+
class Contract::ForTypesAndValues
|
5
|
+
include Contract::Interface
|
6
|
+
|
7
|
+
def initialize(types_and_values)
|
8
|
+
@types_and_values = types_and_values.transform_keys(&:to_sym)
|
9
|
+
|
10
|
+
@types_contract = Contract::ForTypes.new(@types_and_values.keys)
|
11
|
+
end
|
12
|
+
|
13
|
+
def allowed_types
|
14
|
+
@types_contract.allowed_types
|
15
|
+
end
|
16
|
+
|
17
|
+
def type?(type)
|
18
|
+
@types_contract.type?(type)
|
19
|
+
end
|
20
|
+
|
21
|
+
def type!(type)
|
22
|
+
@types_contract.type!(type)
|
23
|
+
end
|
24
|
+
|
25
|
+
def type_and_value!(data)
|
26
|
+
type = data.type
|
27
|
+
value = data.value
|
28
|
+
allowed_value = @types_and_values[type!(type)]
|
29
|
+
|
30
|
+
return value if allowed_value === value
|
31
|
+
|
32
|
+
raise Error::UnexpectedValue.build(type: type, value: value)
|
33
|
+
rescue NoMatchingPatternError
|
34
|
+
raise Error::UnexpectedValue.build(type: data.type, value: data.value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations
|
4
|
+
module Contract::Interface
|
5
|
+
def allowed_types
|
6
|
+
raise ::BCDD::Result::Error::NotImplemented
|
7
|
+
end
|
8
|
+
|
9
|
+
def type?(_type)
|
10
|
+
raise ::BCDD::Result::Error::NotImplemented
|
11
|
+
end
|
12
|
+
|
13
|
+
def type!(_type)
|
14
|
+
raise ::BCDD::Result::Error::NotImplemented
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_and_value!(_data)
|
18
|
+
raise ::BCDD::Result::Error::NotImplemented
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations
|
4
|
+
module Contract
|
5
|
+
require_relative 'contract/interface'
|
6
|
+
require_relative 'contract/evaluator'
|
7
|
+
require_relative 'contract/disabled'
|
8
|
+
require_relative 'contract/for_types'
|
9
|
+
require_relative 'contract/for_types_and_values'
|
10
|
+
|
11
|
+
NONE = Contract::Evaluator.new(Contract::Disabled, Contract::Disabled).freeze
|
12
|
+
|
13
|
+
ToEnsure = ->(spec) do
|
14
|
+
return Contract::Disabled if spec.nil?
|
15
|
+
|
16
|
+
spec.is_a?(Hash) ? Contract::ForTypesAndValues.new(spec) : Contract::ForTypes.new(Array(spec))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.new(success:, failure:)
|
20
|
+
Contract::Evaluator.new(ToEnsure[success], ToEnsure[failure])
|
21
|
+
end
|
22
|
+
|
23
|
+
private_constant :ToEnsure
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations::Error < BCDD::Result::Error
|
4
|
+
class UnexpectedType < self
|
5
|
+
def self.build(type:, allowed_types:)
|
6
|
+
new("type :#{type} is not allowed. Allowed types: #{allowed_types.map(&:inspect).join(', ')}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class UnexpectedValue < self
|
11
|
+
def self.build(type:, value:)
|
12
|
+
new("value #{value.inspect} is not allowed for :#{type} type")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations
|
4
|
+
class TypeChecker
|
5
|
+
attr_reader :result_type, :expectations
|
6
|
+
|
7
|
+
def initialize(result_type, expectations:)
|
8
|
+
@result_type = result_type
|
9
|
+
|
10
|
+
@expectations = expectations
|
11
|
+
end
|
12
|
+
|
13
|
+
def allow?(types)
|
14
|
+
validate(types, expected: expectations, allow_empty: false)
|
15
|
+
end
|
16
|
+
|
17
|
+
def allow_success?(types)
|
18
|
+
validate(types, expected: expectations.success, allow_empty: true)
|
19
|
+
end
|
20
|
+
|
21
|
+
def allow_failure?(types)
|
22
|
+
validate(types, expected: expectations.failure, allow_empty: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def validate(types, expected:, allow_empty:)
|
28
|
+
(allow_empty && types.empty?) || types.any? { |type| expected.type!(type) == result_type }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private_constant :TypeChecker
|
33
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result::Expectations
|
4
|
+
require_relative 'expectations/error'
|
5
|
+
require_relative 'expectations/contract'
|
6
|
+
require_relative 'expectations/type_checker'
|
7
|
+
|
8
|
+
MIXIN_METHODS = <<~RUBY
|
9
|
+
def Success(...)
|
10
|
+
_expected_result::Success(...)
|
11
|
+
end
|
12
|
+
|
13
|
+
def Failure(...)
|
14
|
+
_expected_result::Failure(...)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def _expected_result
|
20
|
+
@_expected_result ||= Expected.with(subject: self)
|
21
|
+
end
|
22
|
+
RUBY
|
23
|
+
|
24
|
+
def self.mixin(success: nil, failure: nil)
|
25
|
+
mod = Module.new
|
26
|
+
mod.const_set(:Expected, new(success: success, failure: failure).freeze)
|
27
|
+
mod.module_eval(MIXIN_METHODS)
|
28
|
+
mod
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.evaluate(data, expectations)
|
32
|
+
expectations ||= Contract::NONE
|
33
|
+
|
34
|
+
expectations.type_and_value!(data)
|
35
|
+
|
36
|
+
TypeChecker.new(data.type, expectations: expectations)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(subject: nil, success: nil, failure: nil, contract: nil)
|
40
|
+
@subject = subject
|
41
|
+
|
42
|
+
@contract = contract if contract.is_a?(Contract::Evaluator)
|
43
|
+
|
44
|
+
@contract ||= Contract.new(success: success, failure: failure).freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
def Success(type, value = nil)
|
48
|
+
::BCDD::Result::Success.new(type: type, value: value, subject: subject, expectations: contract)
|
49
|
+
end
|
50
|
+
|
51
|
+
def Failure(type, value = nil)
|
52
|
+
::BCDD::Result::Failure.new(type: type, value: value, subject: subject, expectations: contract)
|
53
|
+
end
|
54
|
+
|
55
|
+
def with(subject:)
|
56
|
+
self.class.new(subject: subject, contract: contract)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
attr_reader :subject, :contract
|
62
|
+
end
|
data/lib/bcdd/result/failure.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BCDD::Result
|
4
|
+
class Handler
|
5
|
+
class AllowedTypes
|
6
|
+
attr_reader :unchecked, :type_checker
|
7
|
+
|
8
|
+
def initialize(type_checker)
|
9
|
+
@type_checker = type_checker
|
10
|
+
|
11
|
+
@expectations = type_checker.expectations
|
12
|
+
|
13
|
+
@unchecked = @expectations.allowed_types.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
def allow?(types)
|
17
|
+
check!(types, type_checker.allow?(types))
|
18
|
+
end
|
19
|
+
|
20
|
+
def allow_success?(types)
|
21
|
+
unchecked.subtract(@expectations.success.allowed_types) if types.empty?
|
22
|
+
|
23
|
+
check!(types, type_checker.allow_success?(types))
|
24
|
+
end
|
25
|
+
|
26
|
+
def allow_failure?(types)
|
27
|
+
unchecked.subtract(@expectations.failure.allowed_types) if types.empty?
|
28
|
+
|
29
|
+
check!(types, type_checker.allow_failure?(types))
|
30
|
+
end
|
31
|
+
|
32
|
+
def all_checked?
|
33
|
+
unchecked.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def check!(types, checked)
|
39
|
+
unchecked.subtract(types) unless all_checked?
|
40
|
+
|
41
|
+
checked
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/bcdd/result/handler.rb
CHANGED
@@ -2,27 +2,30 @@
|
|
2
2
|
|
3
3
|
class BCDD::Result
|
4
4
|
class Handler
|
5
|
+
require_relative 'handler/allowed_types'
|
6
|
+
|
5
7
|
UNDEFINED = ::Object.new
|
6
8
|
|
7
|
-
def initialize(result)
|
9
|
+
def initialize(result, type_checker:)
|
10
|
+
@allowed_types = AllowedTypes.new(type_checker)
|
11
|
+
|
8
12
|
@outcome = UNDEFINED
|
9
13
|
|
10
|
-
@_type = result._type
|
11
14
|
@result = result
|
12
15
|
end
|
13
16
|
|
14
17
|
def [](*types, &block)
|
15
18
|
raise Error::MissingTypeArgument if types.empty?
|
16
19
|
|
17
|
-
self.outcome = block if
|
20
|
+
self.outcome = block if allowed_types.allow?(types)
|
18
21
|
end
|
19
22
|
|
20
|
-
def
|
21
|
-
self.outcome = block if
|
23
|
+
def success(*types, &block)
|
24
|
+
self.outcome = block if allowed_types.allow_success?(types) && result.success?
|
22
25
|
end
|
23
26
|
|
24
|
-
def
|
25
|
-
self.outcome = block if
|
27
|
+
def failure(*types, &block)
|
28
|
+
self.outcome = block if allowed_types.allow_failure?(types) && result.failure?
|
26
29
|
end
|
27
30
|
|
28
31
|
def unknown(&block)
|
@@ -33,7 +36,7 @@ class BCDD::Result
|
|
33
36
|
|
34
37
|
private
|
35
38
|
|
36
|
-
attr_reader :
|
39
|
+
attr_reader :result, :allowed_types
|
37
40
|
|
38
41
|
def outcome?
|
39
42
|
@outcome != UNDEFINED
|
@@ -44,6 +47,8 @@ class BCDD::Result
|
|
44
47
|
end
|
45
48
|
|
46
49
|
def outcome
|
50
|
+
allowed_types.all_checked? or raise Error::UnhandledTypes.build(types: allowed_types.unchecked)
|
51
|
+
|
47
52
|
@outcome if outcome?
|
48
53
|
end
|
49
54
|
end
|
data/lib/bcdd/result/success.rb
CHANGED
data/lib/bcdd/result/version.rb
CHANGED
data/lib/bcdd/result.rb
CHANGED
@@ -2,32 +2,38 @@
|
|
2
2
|
|
3
3
|
require_relative 'result/version'
|
4
4
|
require_relative 'result/error'
|
5
|
-
require_relative 'result/type'
|
6
5
|
require_relative 'result/data'
|
7
6
|
require_relative 'result/handler'
|
8
7
|
require_relative 'result/failure'
|
9
8
|
require_relative 'result/success'
|
10
9
|
require_relative 'result/mixin'
|
10
|
+
require_relative 'result/expectations'
|
11
11
|
|
12
12
|
class BCDD::Result
|
13
13
|
attr_accessor :unknown
|
14
14
|
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :subject, :data, :type_checker
|
16
16
|
|
17
17
|
protected :subject
|
18
18
|
|
19
|
-
private :unknown, :unknown
|
19
|
+
private :unknown, :unknown=, :type_checker
|
20
20
|
|
21
|
-
def initialize(type:, value:, subject: nil)
|
22
|
-
|
23
|
-
|
21
|
+
def initialize(type:, value:, subject: nil, expectations: nil)
|
22
|
+
data = Data.new(name, type, value)
|
23
|
+
|
24
|
+
@type_checker = Expectations.evaluate(data, expectations)
|
24
25
|
@subject = subject
|
26
|
+
@data = data
|
25
27
|
|
26
28
|
self.unknown = true
|
27
29
|
end
|
28
30
|
|
29
31
|
def type
|
30
|
-
|
32
|
+
data.type
|
33
|
+
end
|
34
|
+
|
35
|
+
def value
|
36
|
+
data.value
|
31
37
|
end
|
32
38
|
|
33
39
|
def success?(_type = nil)
|
@@ -45,15 +51,15 @@ class BCDD::Result
|
|
45
51
|
def on(*types, &block)
|
46
52
|
raise Error::MissingTypeArgument if types.empty?
|
47
53
|
|
48
|
-
tap { known(block) if
|
54
|
+
tap { known(block) if type_checker.allow?(types) }
|
49
55
|
end
|
50
56
|
|
51
57
|
def on_success(*types, &block)
|
52
|
-
tap { known(block) if
|
58
|
+
tap { known(block) if type_checker.allow_success?(types) && success? }
|
53
59
|
end
|
54
60
|
|
55
61
|
def on_failure(*types, &block)
|
56
|
-
tap { known(block) if
|
62
|
+
tap { known(block) if type_checker.allow_failure?(types) && failure? }
|
57
63
|
end
|
58
64
|
|
59
65
|
def on_unknown
|
@@ -71,17 +77,13 @@ class BCDD::Result
|
|
71
77
|
end
|
72
78
|
|
73
79
|
def handle
|
74
|
-
handler = Handler.new(self)
|
80
|
+
handler = Handler.new(self, type_checker: type_checker)
|
75
81
|
|
76
82
|
yield handler
|
77
83
|
|
78
84
|
handler.send(:outcome)
|
79
85
|
end
|
80
86
|
|
81
|
-
def data
|
82
|
-
Data.new(self)
|
83
|
-
end
|
84
|
-
|
85
87
|
def ==(other)
|
86
88
|
self.class == other.class && type == other.type && value == other.value
|
87
89
|
end
|
@@ -108,7 +110,7 @@ class BCDD::Result
|
|
108
110
|
private
|
109
111
|
|
110
112
|
def name
|
111
|
-
|
113
|
+
:unknown
|
112
114
|
end
|
113
115
|
|
114
116
|
def known(block)
|