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.
@@ -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
@@ -7,7 +7,7 @@ class BCDD::Result
7
7
  end
8
8
 
9
9
  def failure?(type = nil)
10
- type.nil? || type == self.type
10
+ type.nil? || type_checker.allow_failure?([type])
11
11
  end
12
12
 
13
13
  def value_or
@@ -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
@@ -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 _type.in?(types, allow_empty: false)
20
+ self.outcome = block if allowed_types.allow?(types)
18
21
  end
19
22
 
20
- def failure(*types, &block)
21
- self.outcome = block if result.failure? && _type.in?(types, allow_empty: true)
23
+ def success(*types, &block)
24
+ self.outcome = block if allowed_types.allow_success?(types) && result.success?
22
25
  end
23
26
 
24
- def success(*types, &block)
25
- self.outcome = block if result.success? && _type.in?(types, allow_empty: true)
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 :_type, :result
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
@@ -3,7 +3,7 @@
3
3
  class BCDD::Result
4
4
  class Success < self
5
5
  def success?(type = nil)
6
- type.nil? || type == self.type
6
+ type.nil? || type_checker.allow_success?([type])
7
7
  end
8
8
 
9
9
  def failure?(_type = nil)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
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 :_type, :value, :subject
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
- @_type = Type.new(type)
23
- @value = value
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
- _type.to_sym
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 _type.in?(types, allow_empty: false) }
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 success? && _type.in?(types, allow_empty: true) }
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 failure? && _type.in?(types, allow_empty: true) }
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
- raise Error::NotImplemented
113
+ :unknown
112
114
  end
113
115
 
114
116
  def known(block)