bcdd-result 0.4.0 → 0.5.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 +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)
|