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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a6bfbf4821c7674fd0af6dbd4e88d274cbf14595eff278ef40ebdaf5bf4490a
4
- data.tar.gz: 9cc905974762089c3f3827ac40aa730040ad9e88176e8cae0275954bedd6e7a8
3
+ metadata.gz: b4eb0878d2e7d75bb4cfd55c0d112db0f14b4c43255d3e02d60ec60c0f9c2d6e
4
+ data.tar.gz: dd8ee0d7239a63b486de7efb7876c40db1942f2d54a383ed0c67bb86e20b0622
5
5
  SHA512:
6
- metadata.gz: 5446879d905a42e257b3dd5724b6dc53a455cf2ec8e3fd7a5f339f74216e8d87ce779912dd4124163a6af4a19052e558382e74e7b20a434420b8d2eae055ab37
7
- data.tar.gz: c91588b64905a6d86b84dc04db128e42fb0c570cf1e973ccd76563b0126e0e30d31764e918aa93aa71b06115a95c984299d4a57c249391d366d8738469aa8ecb
6
+ metadata.gz: '08f8ed0b78bcf298711a52b20be08908dc3000888a94b8bd3df289fa3682909bb87ba294c2ba42a482678af0f9dd5bff5a267169a256ca7cb0a23c40b961613c'
7
+ data.tar.gz: cc15020416823a965c29ece5947875817df0ccc6b7c21f4dc534ba24aa68e10134a4333d0e36bcb8afc08719f9e6aeea1741ea51be6b373cc8efc61e82b08a7f
data/.rubocop.yml CHANGED
@@ -18,9 +18,30 @@ Layout/ExtraSpacing:
18
18
  Style/ClassAndModuleChildren:
19
19
  Enabled: false
20
20
 
21
+ Style/MapToSet:
22
+ Exclude:
23
+ - lib/bcdd/result/expectations/contract/for_types.rb
24
+
25
+ Style/CaseEquality:
26
+ Exclude:
27
+ - lib/bcdd/result/expectations/contract/for_types_and_values.rb
28
+
29
+ Style/Lambda:
30
+ EnforcedStyle: literal
31
+
21
32
  Naming/MethodName:
22
33
  Exclude:
23
34
  - lib/bcdd/result/mixin.rb
35
+ - lib/bcdd/result/expectations.rb
24
36
 
25
37
  Minitest/MultipleAssertions:
26
38
  Enabled: false
39
+
40
+ Metrics/BlockLength:
41
+ Exclude:
42
+ - bcdd-result.gemspec
43
+ - test/**/*.rb
44
+
45
+ Metrics/ClassLength:
46
+ Exclude:
47
+ - test/**/*.rb
data/.rubocop_todo.yml CHANGED
@@ -1,22 +1,12 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2023-09-29 01:50:30 UTC using RuboCop version 1.56.3.
3
+ # on 2023-10-02 02:37:50 UTC using RuboCop version 1.56.3.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 14
9
+ # Offense count: 24
10
10
  # Configuration parameters: AllowedConstants.
11
11
  Style/Documentation:
12
- Exclude:
13
- - 'spec/**/*'
14
- - 'test/**/*'
15
- - 'lib/bcdd/result.rb'
16
- - 'lib/bcdd/result/data.rb'
17
- - 'lib/bcdd/result/error.rb'
18
- - 'lib/bcdd/result/failure.rb'
19
- - 'lib/bcdd/result/handler.rb'
20
- - 'lib/bcdd/result/mixin.rb'
21
- - 'lib/bcdd/result/success.rb'
22
- - 'lib/bcdd/result/type.rb'
12
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2023-10-09
4
+
5
+ - Add `BCDD::Result::Expectations` to define contracts for your results. There are two ways to use it: the standalone (`BCDD::Result::Expectations.new`) and the mixin (`BCDD::Result::Expectations.mixin`) mode.
6
+
7
+ The main difference is that the mixin mode will use the target object (who receives the include/extend) as the result's subject (like the `BCDD::Result::Mixin` does), while the standalone mode won't.
8
+
9
+ **Standalone mode:**
10
+
11
+ ```ruby
12
+ module Divide
13
+ Expected = BCDD::Result::Expectations.new(
14
+ success: {
15
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
16
+ division_completed: Numeric
17
+ },
18
+ failure: {
19
+ invalid_arg: String,
20
+ division_by_zero: String
21
+ }
22
+ )
23
+
24
+ def self.call(arg1, arg2)
25
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
26
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
27
+
28
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
29
+
30
+ Expected::Success(:division_completed, arg1 / arg2)
31
+ end
32
+ end
33
+ ```
34
+
35
+ **Mixin mode:**
36
+
37
+ ```ruby
38
+ class Divide
39
+ include BCDD::Result::Expectations.mixin(
40
+ success: {
41
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
42
+ division_completed: Numeric
43
+ },
44
+ failure: {
45
+ invalid_arg: String,
46
+ division_by_zero: String
47
+ }
48
+ )
49
+
50
+ def call(arg1, arg2)
51
+ validate_numbers(arg1, arg2)
52
+ .and_then(:validate_non_zero)
53
+ .and_then(:divide)
54
+ end
55
+
56
+ private
57
+
58
+ def validate_numbers(arg1, arg2)
59
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
60
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
61
+
62
+ Success(:numbers, [arg1, arg2])
63
+ end
64
+
65
+ def validate_non_zero(numbers)
66
+ return Success(:numbers, numbers) unless numbers.last.zero?
67
+
68
+ Failure(:division_by_zero, 'arg2 must not be zero')
69
+ end
70
+
71
+ def divide((number1, number2))
72
+ Success(:division_completed, number1 / number2)
73
+ end
74
+ end
75
+ ```
76
+
3
77
  ## [0.4.0] - 2023-09-28
4
78
 
5
79
  ### Added
data/README.md CHANGED
@@ -38,12 +38,26 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi
38
38
  - [Railway Oriented Programming](#railway-oriented-programming)
39
39
  - [`result.and_then`](#resultand_then)
40
40
  - [`BCDD::Result::Mixin`](#bcddresultmixin)
41
- - [Class example (instance methods)](#class-example-instance-methods)
42
- - [Module example (singleton methods)](#module-example-singleton-methods)
41
+ - [Class example (Instance Methods)](#class-example-instance-methods)
42
+ - [Module example (Singleton Methods)](#module-example-singleton-methods)
43
43
  - [Restrictions](#restrictions)
44
44
  - [Pattern Matching](#pattern-matching)
45
45
  - [`Array`/`Find` patterns](#arrayfind-patterns)
46
46
  - [`Hash` patterns](#hash-patterns)
47
+ - [BCDD::Result::Expectations](#bcddresultexpectations)
48
+ - [`BCDD::Result::Expectations`](#bcddresultexpectations-1)
49
+ - [Standalone *versus* Mixin mode](#standalone-versus-mixin-mode)
50
+ - [Type checking - Result Hooks](#type-checking---result-hooks)
51
+ - [`#success?` and `#failure?`](#success-and-failure)
52
+ - [`#on` and `#on_type`](#on-and-on_type)
53
+ - [`#on_success` and `#on_failure`](#on_success-and-on_failure)
54
+ - [`#handle`](#handle)
55
+ - [Type checking - Result Creation](#type-checking---result-creation)
56
+ - [Mixin mode](#mixin-mode)
57
+ - [Standalone mode](#standalone-mode)
58
+ - [Value checking - Result Creation](#value-checking---result-creation)
59
+ - [Success()](#success)
60
+ - [Failure()](#failure)
47
61
  - [About](#about)
48
62
  - [Development](#development)
49
63
  - [Contributing](#contributing)
@@ -371,9 +385,9 @@ end
371
385
 
372
386
  ### Result Value
373
387
 
374
- The most simple way to get the result value is by calling `BCDD::Result#value` or `BCDD::Result#data`.
388
+ The most simple way to get the result value is by calling `BCDD::Result#value`.
375
389
 
376
- But sometimes you need to get the value of a successful result or a default value if it is a failure. In this case, you can use `BCDD::Result#value_or` or `BCDD::Result#data_or`.
390
+ But sometimes you need to get the value of a successful result or a default value if it is a failure. In this case, you can use `BCDD::Result#value_or`.
377
391
 
378
392
  #### `result.value_or`
379
393
 
@@ -509,13 +523,11 @@ Divide.call(2, 2)
509
523
 
510
524
  #### `BCDD::Result::Mixin`
511
525
 
512
- It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`.
526
+ It is a module that can be included/extended by any object. It adds two methods to the target object: `Success()` and `Failure()`. The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the first ones will use the target object (who received the include/extend) as the result's subject.
513
527
 
514
- The main difference between these methods and `BCDD::Result::Success()`/`BCDD::Result::Failure()` is that the first ones will use the target object as the result's subject.
528
+ And because of this, you can use the `#and_then` method to call methods from the result's subject.
515
529
 
516
- And because of this, you can use the `#and_then` method to call methods from the target object (result's subject).
517
-
518
- ##### Class example (instance methods)
530
+ ##### Class example (Instance Methods)
519
531
 
520
532
  ```ruby
521
533
  class Divide
@@ -563,7 +575,7 @@ Divide.new('4', 2).call #<BCDD::Result::Failure type=:invalid_arg value="arg1 mu
563
575
  Divide.new(4, '2').call #<BCDD::Result::Failure type=:invalid_arg value="arg2 must be numeric">
564
576
  ```
565
577
 
566
- ##### Module example (singleton methods)
578
+ ##### Module example (Singleton Methods)
567
579
 
568
580
  ```ruby
569
581
  module Divide
@@ -674,6 +686,391 @@ end
674
686
 
675
687
  <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
676
688
 
689
+ #### BCDD::Result::Expectations
690
+
691
+ I'd like you to please read the following section to understand how to use this feature.
692
+
693
+ But if you are using Ruby >= 3.0, you can use the `in` operator to use the pattern matching to validate the result's value.
694
+
695
+ ```ruby
696
+ module Divide
697
+ extend BCDD::Result::Expectations.mixin(
698
+ success: {
699
+ numbers: ->(value) { value in [Numeric, Numeric] },
700
+ division_completed: ->(value) { value in (Integer | Float) }
701
+ },
702
+ failure: { invalid_arg: String, division_by_zero: String }
703
+ )
704
+
705
+ def self.call(arg1, arg2)
706
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
707
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
708
+
709
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
710
+
711
+ Success(:division_completed, arg1 / arg2)
712
+ end
713
+ end
714
+ ```
715
+
716
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
717
+
718
+ ### `BCDD::Result::Expectations`
719
+
720
+ This feature lets you define contracts for your results' types and values. There are two ways to use it: the standalone (`BCDD::Result::Expectations.new`) and the mixin (`BCDD::Result::Expectations.mixin`) mode.
721
+
722
+ It was designed to ensure all the aspects of the result's type and value. So, an error will be raised if you try to create or handle a result with an unexpected type or value.
723
+
724
+ #### Standalone *versus* Mixin mode
725
+
726
+ The _**standalone mode**_ creates an object that knows how to produce and validate results based on the defined expectations. Look at the example below:
727
+
728
+ ```ruby
729
+ module Divide
730
+ Expected = BCDD::Result::Expectations.new(
731
+ success: %i[numbers division_completed],
732
+ failure: %i[invalid_arg division_by_zero]
733
+ )
734
+
735
+ def self.call(arg1, arg2)
736
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
737
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
738
+
739
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
740
+
741
+ Expected::Success(:division_completed, arg1 / arg2)
742
+ end
743
+ end
744
+ ```
745
+
746
+ In the code above, we define a constant `Divide::Expected`. And because of this (it is a constant), we can use it inside and outside the module.
747
+
748
+ Look what happens if you try to create a result without one of the expected types.
749
+
750
+ ```ruby
751
+ Divide::Expected::Success(:ok)
752
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
753
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
754
+
755
+ Divide::Expected::Failure(:err)
756
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
757
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
758
+ ```
759
+
760
+ The _**mixin mode**_ is similar to `BCDD::Result::Mixin`, but it also defines the expectations for the result's types and values.
761
+
762
+ ```ruby
763
+ class Divide
764
+ include BCDD::Result::Expectations.mixin(
765
+ success: %i[numbers division_completed],
766
+ failure: %i[invalid_arg division_by_zero]
767
+ )
768
+
769
+ def call(arg1, arg2)
770
+ validate_numbers(arg1, arg2)
771
+ .and_then(:validate_non_zero)
772
+ .and_then(:divide)
773
+ end
774
+
775
+ private
776
+
777
+ def validate_numbers(arg1, arg2)
778
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
779
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
780
+
781
+ Success(:numbers, [arg1, arg2])
782
+ end
783
+
784
+ def validate_non_zero(numbers)
785
+ return Success(:numbers, numbers) unless numbers.last.zero?
786
+
787
+ Failure(:division_by_zero, 'arg2 must not be zero')
788
+ end
789
+
790
+ def divide((number1, number2))
791
+ Success(:division_completed, number1 / number2)
792
+ end
793
+ end
794
+ ```
795
+
796
+ This mode also defines an `Expected` constant to be used inside and outside the module.
797
+
798
+ > **PROTIP:**
799
+ > You can use the `Expected` constant to mock the result's type and value in your tests. As they will have the exact expectations, your tests will check if the result clients are handling the result correctly.
800
+
801
+ Now that you know the two modes, let's understand how expectations can be beneficial and powerful for defining contracts.
802
+
803
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
804
+
805
+ #### Type checking - Result Hooks
806
+
807
+ The `BCDD::Result::Expectations` will check if the result's type is valid. This checking will be performed in all the methods that rely on the result's type, like `#success?`, `#failure?`, `#on`, `#on_type`, `#on_success`, `#on_failure`, `#handle`.
808
+
809
+ ##### `#success?` and `#failure?`
810
+
811
+ When you check whether a result is a success or failure, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
812
+
813
+ **Success example:**
814
+
815
+ ```ruby
816
+ result = Divide.new.call(10, 2)
817
+
818
+ result.success? # true
819
+ result.success?(:numbers) # false
820
+ result.success?(:division_completed) # true
821
+
822
+ result.success?(:ok)
823
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
824
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
825
+ ```
826
+
827
+ **Failure example:**
828
+
829
+ ```ruby
830
+ result = Divide.new.call(10, '2')
831
+
832
+ result.failure? # true
833
+ result.failure?(:invalid_arg) # true
834
+ result.failure?(:division_by_zero) # false
835
+
836
+ result.failure?(:err)
837
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
838
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
839
+ ```
840
+
841
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
842
+
843
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
844
+
845
+ ##### `#on` and `#on_type`
846
+
847
+ If you use `#on` or `#on_type` to perform a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
848
+
849
+ ```ruby
850
+ result = Divide.new.call(10, 2)
851
+
852
+ result
853
+ .on(:invalid_arg, :division_by_zero) { |msg| puts msg }
854
+ .on(:division_completed) { |number| puts "The result is #{number}" }
855
+
856
+ # The code above will print 'The result is 5'
857
+
858
+ result.on(:number) { |_| :this_type_does_not_exist }
859
+ # type :number is not allowed. Allowed types: :numbers, :division_completed, :invalid_arg, :division_by_zero
860
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
861
+ ```
862
+
863
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
864
+
865
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
866
+
867
+ ##### `#on_success` and `#on_failure`
868
+
869
+ If you use `#on_success` or `#on_failure` to perform a block, `BCDD::Result::Expectations` will check whether the result type is valid/expected. Otherwise, an error will be raised.
870
+
871
+ ```ruby
872
+ result = Divide.new.call(10, '2')
873
+
874
+ result
875
+ .on_failure(:invalid_arg, :division_by_zero) { |msg| puts msg }
876
+ .on_success(:division_completed) { |number| puts "The result is #{number}" }
877
+
878
+ result
879
+ .on_success { |number| puts "The result is #{number}" }
880
+ .on_failure { |msg| puts msg }
881
+
882
+ # Both codes above will print 'arg2 must be numeric'
883
+
884
+ result.on_success(:ok) { |_| :this_type_does_not_exist }
885
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed
886
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
887
+
888
+ result.on_failure(:err) { |_| :this_type_does_not_exist }
889
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero
890
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
891
+ ```
892
+
893
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
894
+
895
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
896
+
897
+ ##### `#handle`
898
+
899
+ The `BCDD::Result::Expectations` will also be applied on all the handlers defined by the `#handle` method/block.
900
+
901
+ ```ruby
902
+ result = Divide.call(10, 2)
903
+
904
+ result.handle do |on|
905
+ on.type(:ok) { |_| :this_type_does_not_exist }
906
+ end
907
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed, :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
908
+
909
+ result.handle do |on|
910
+ on.success(:ok) { |_| :this_type_does_not_exist }
911
+ end
912
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
913
+
914
+ result.handle do |on|
915
+ on.failure(:err) { |_| :this_type_does_not_exist }
916
+ end
917
+ # type :err is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
918
+ ```
919
+
920
+ *PS: The `Divide` implementation is [here](#standalone-versus-mixin-mode).*
921
+
922
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
923
+
924
+ #### Type checking - Result Creation
925
+
926
+ The `BCDD::Result::Expectations` will be used on the result creation `Success()` and `Failure()` methods. So, when the result type is valid/expected, the result will be created. Otherwise, an error will be raised.
927
+
928
+ This works for both modes (standalone and mixin).
929
+
930
+ ##### Mixin mode
931
+
932
+ ```ruby
933
+ module Divide
934
+ extend BCDD::Result::Expectations.mixin(success: :ok, failure: :err)
935
+
936
+ def self.call(arg1, arg2)
937
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
938
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
939
+
940
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
941
+
942
+ Success(:division_completed, arg1 / arg2)
943
+ end
944
+ end
945
+
946
+ Divide.call('4', 2)
947
+ # type :invalid_arg is not allowed. Allowed types: :err
948
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
949
+
950
+ Divide.call(4, 2)
951
+ # type :division_completed is not allowed. Allowed types: :ok
952
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
953
+ ```
954
+
955
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
956
+
957
+ ##### Standalone mode
958
+
959
+ ```ruby
960
+ module Divide
961
+ Expected = BCDD::Result::Expectations.new(success: :ok, failure: :err)
962
+
963
+ def self.call(arg1, arg2)
964
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
965
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
966
+
967
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
968
+
969
+ Expected::Success(:division_completed, arg1 / arg2)
970
+ end
971
+ end
972
+
973
+ Divide.call('4', 2)
974
+ # type :invalid_arg is not allowed. Allowed types: :err
975
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
976
+
977
+ Divide.call(4, 2)
978
+ # type :division_completed is not allowed. Allowed types: :ok
979
+ # (BCDD::Result::Expectations::Error::UnexpectedType)
980
+ ```
981
+
982
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
983
+
984
+ #### Value checking - Result Creation
985
+
986
+ The `Result::Expectations` supports types of validations. The first is the type checking only, and the second is the type and value checking.
987
+
988
+ To define expectations for your result's values, you must declare a Hash with the type as the key and the value as the value. A value validator is any object that responds to `#===` (case equality operator).
989
+
990
+ **Mixin mode:**
991
+
992
+ ```ruby
993
+ module Divide
994
+ extend BCDD::Result::Expectations.mixin(
995
+ success: {
996
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
997
+ division_completed: Numeric
998
+ },
999
+ failure: {
1000
+ invalid_arg: String,
1001
+ division_by_zero: String
1002
+ }
1003
+ )
1004
+
1005
+ def self.call(arg1, arg2)
1006
+ arg1.is_a?(Numeric) or return Failure(:invalid_arg, 'arg1 must be numeric')
1007
+ arg2.is_a?(Numeric) or return Failure(:invalid_arg, 'arg2 must be numeric')
1008
+
1009
+ arg2.zero? and return Failure(:division_by_zero, 'arg2 must not be zero')
1010
+
1011
+ Success(:division_completed, arg1 / arg2)
1012
+ end
1013
+ end
1014
+ ```
1015
+
1016
+ **Standalone mode:**
1017
+
1018
+ ```ruby
1019
+ module Divide
1020
+ Expected = BCDD::Result::Expectations.new(
1021
+ success: {
1022
+ numbers: ->(value) { value.is_a?(Array) && value.size == 2 && value.all?(Numeric) },
1023
+ division_completed: Numeric
1024
+ },
1025
+ failure: {
1026
+ invalid_arg: String,
1027
+ division_by_zero: String
1028
+ }
1029
+ )
1030
+
1031
+ def self.call(arg1, arg2)
1032
+ arg1.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg1 must be numeric')
1033
+ arg2.is_a?(Numeric) or return Expected::Failure(:invalid_arg, 'arg2 must be numeric')
1034
+
1035
+ arg2.zero? and return Expected::Failure(:division_by_zero, 'arg2 must not be zero')
1036
+
1037
+ Expected::Success(:division_completed, arg1 / arg2)
1038
+ end
1039
+ end
1040
+ ```
1041
+
1042
+ The value validation will only be performed through the methods `Success()` and `Failure()`.
1043
+
1044
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1045
+
1046
+ ##### Success()
1047
+
1048
+ ```ruby
1049
+ Divide::Expected::Success(:ok)
1050
+ # type :ok is not allowed. Allowed types: :numbers, :division_completed (BCDD::Result::Expectations::Error::UnexpectedType)
1051
+
1052
+ Divide::Expected::Success(:numbers, [1])
1053
+ # value [1] is not allowed for :numbers type (BCDD::Result::Expectations::Error::UnexpectedValue)
1054
+
1055
+ Divide::Expected::Success(:division_completed, '2')
1056
+ # value "2" is not allowed for :division_completed type (BCDD::Result::Expectations::Error::UnexpectedValue)
1057
+ ```
1058
+
1059
+ ##### Failure()
1060
+
1061
+ ```ruby
1062
+ Divide::Expected::Failure(:err)
1063
+ # type :err is not allowed. Allowed types: :invalid_arg, :division_by_zero (BCDD::Result::Expectations::Error::UnexpectedType)
1064
+
1065
+ Divide::Expected::Failure(:invalid_arg, :arg1_must_be_numeric)
1066
+ # value :arg1_must_be_numeric is not allowed for :invalid_arg type (BCDD::Result::Expectations::Error::UnexpectedValue)
1067
+
1068
+ Divide::Expected::Failure(:division_by_zero, msg: 'arg2 must not be zero')
1069
+ # value {:msg=>"arg2 must not be zero"} is not allowed for :division_by_zero type (BCDD::Result::Expectations::Error::UnexpectedValue)
1070
+ ```
1071
+
1072
+ <p align="right"><a href="#-bcddresult">⬆️ &nbsp;back to top</a></p>
1073
+
677
1074
  ## About
678
1075
 
679
1076
  [Rodrigo Serradura](https://github.com/serradura) created this project. He is the B/CDD process/method creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem is a general-purpose abstraction/monad, but it also contains key features that serve as facilitators for adopting B/CDD in the code.
@@ -2,15 +2,20 @@
2
2
 
3
3
  class BCDD::Result
4
4
  class Data
5
- attr_reader :name, :type, :value, :to_h, :to_a
5
+ attr_reader :name, :type, :value
6
6
 
7
- def initialize(result)
8
- @name = result.send(:name)
9
- @type = result.type
10
- @value = result.value
7
+ def initialize(name, type, value)
8
+ @name = name
9
+ @type = type.to_sym
10
+ @value = value
11
+ end
12
+
13
+ def to_h
14
+ { name: name, type: type, value: value }
15
+ end
11
16
 
12
- @to_h = { name: name, type: type, value: value }
13
- @to_a = [name, type, value]
17
+ def to_a
18
+ [name, type, value]
14
19
  end
15
20
 
16
21
  def inspect
@@ -41,4 +41,12 @@ class BCDD::Result::Error < StandardError
41
41
  new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0 or 1.")
42
42
  end
43
43
  end
44
+
45
+ class UnhandledTypes < self
46
+ def self.build(types:)
47
+ subject = types.size == 1 ? 'This was' : 'These were'
48
+
49
+ new("You must handle all cases. #{subject} not handled: #{types.map(&:inspect).join(', ')}")
50
+ end
51
+ end
44
52
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result::Expectations
4
+ module Contract::Disabled
5
+ extend Contract::Interface
6
+
7
+ EMPTY_SET = Set.new.freeze
8
+
9
+ def self.allowed_types
10
+ EMPTY_SET
11
+ end
12
+
13
+ def self.type?(_type)
14
+ true
15
+ end
16
+
17
+ def self.type!(type)
18
+ type
19
+ end
20
+
21
+ def self.type_and_value!(_data); end
22
+
23
+ private_constant :EMPTY_SET
24
+ end
25
+ end