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.
@@ -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)