functional-light-service 0.4.4 → 6.0.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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/project-build.yml +43 -11
  3. data/.rubocop.yml +101 -160
  4. data/AUDIT-functional-light-service.md +352 -0
  5. data/Appraisals +4 -0
  6. data/CHANGELOG.md +118 -0
  7. data/Gemfile +0 -2
  8. data/README.md +1544 -1426
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/audit/bench.rb +99 -0
  12. data/audit/verify_findings.rb +172 -0
  13. data/functional-light-service.gemspec +15 -16
  14. data/lib/functional-light-service/action.rb +97 -101
  15. data/lib/functional-light-service/configuration.rb +26 -24
  16. data/lib/functional-light-service/context/key_verifier.rb +124 -118
  17. data/lib/functional-light-service/context.rb +63 -20
  18. data/lib/functional-light-service/deprecations.rb +26 -0
  19. data/lib/functional-light-service/errors.rb +8 -6
  20. data/lib/functional-light-service/functional/enum.rb +286 -250
  21. data/lib/functional-light-service/functional/maybe.rb +21 -15
  22. data/lib/functional-light-service/functional/monad.rb +77 -66
  23. data/lib/functional-light-service/functional/null.rb +88 -74
  24. data/lib/functional-light-service/functional/option.rb +100 -97
  25. data/lib/functional-light-service/functional/result.rb +129 -116
  26. data/lib/functional-light-service/localization_adapter.rb +48 -47
  27. data/lib/functional-light-service/organizer/execute.rb +16 -14
  28. data/lib/functional-light-service/organizer/iterate.rb +30 -25
  29. data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
  30. data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
  31. data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
  32. data/lib/functional-light-service/organizer/with_callback.rb +28 -26
  33. data/lib/functional-light-service/organizer/with_reducer.rb +81 -71
  34. data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
  35. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -105
  36. data/lib/functional-light-service/organizer.rb +114 -104
  37. data/lib/functional-light-service/testing/context_factory.rb +48 -42
  38. data/lib/functional-light-service/testing.rb +3 -1
  39. data/lib/functional-light-service/version.rb +5 -3
  40. data/lib/functional-light-service.rb +30 -28
  41. data/spec/acceptance/after_actions_spec.rb +87 -71
  42. data/spec/acceptance/before_actions_spec.rb +115 -98
  43. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
  44. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  45. data/spec/acceptance/fail_spec.rb +52 -50
  46. data/spec/acceptance/message_localization_spec.rb +119 -118
  47. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  48. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  49. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
  50. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  51. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -83
  52. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  53. data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
  54. data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
  55. data/spec/acceptance/rollback_spec.rb +183 -132
  56. data/spec/action_expects_and_promises_spec.rb +97 -93
  57. data/spec/action_promised_keys_spec.rb +126 -122
  58. data/spec/action_spec.rb +8 -0
  59. data/spec/context_spec.rb +289 -197
  60. data/spec/examples/controller_spec.rb +63 -63
  61. data/spec/examples/validate_address_spec.rb +38 -37
  62. data/spec/lib/deterministic/currify_spec.rb +90 -88
  63. data/spec/lib/deterministic/null_spec.rb +6 -1
  64. data/spec/lib/deterministic/option_spec.rb +140 -133
  65. data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
  66. data/spec/lib/deterministic/result/result_shared.rb +3 -2
  67. data/spec/lib/deterministic/result_spec.rb +2 -2
  68. data/spec/lib/edge_cases_spec.rb +156 -0
  69. data/spec/lib/enum_spec.rb +1 -1
  70. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  71. data/spec/organizer_spec.rb +115 -93
  72. data/spec/readme_spec.rb +45 -47
  73. data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
  74. data/spec/sample/calculates_tax_spec.rb +1 -1
  75. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
  76. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  77. data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
  78. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
  79. data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
  80. data/spec/spec_helper.rb +21 -13
  81. data/spec/test_doubles.rb +628 -564
  82. data/spec/testing/context_factory_spec.rb +21 -0
  83. metadata +49 -117
  84. data/.travis.yml +0 -24
  85. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
  86. data/spec/acceptance/include_warning_spec.rb +0 -29
@@ -1,66 +1,77 @@
1
- module FunctionalLightService
2
- module Monad
3
- class NotMonadError < StandardError; end
4
-
5
- # Basicly the `pure` function
6
- def initialize(init)
7
- @value = join(init)
8
- end
9
-
10
- # If the passed value is monad already, get the value to avoid nesting
11
- # M[M[A]] is equivalent to M[A]
12
- def join(other)
13
- if other.is_a? self.class
14
- other.value
15
- else
16
- other
17
- end
18
- end
19
-
20
- # The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
21
- # boxes it back to the same monad (Mb)
22
- # fmap :: (a -> b) -> M a -> M b
23
- def fmap(proc = nil, &block)
24
- result = (proc || block).call(value)
25
- self.class.new(result)
26
- end
27
-
28
- # The monad: takes a function which returns a monad (of the same type), applies the function
29
- # bind :: (a -> Mb) -> M a -> M b
30
- # the self.class, i.e. the containing monad is passed as a second (optional) arg to the function
31
- def bind(proc = nil, &block)
32
- (proc || block).call(value).tap do |result|
33
- # rubocop:disable Style/CaseEquality
34
- parent = self.class.superclass === Object ? self.class : self.class.superclass
35
- # rubocop:enable Style/CaseEquality
36
- unless result.is_a? parent
37
- raise NotMonadError, "Expected #{result.inspect} to be an #{parent}"
38
- end
39
- end
40
- end
41
- alias :'>>=' :bind
42
-
43
- # Get the underlying value, return in Haskell
44
- # return :: M a -> a
45
- def value
46
- @value
47
- end
48
-
49
- def to_s
50
- value.to_s
51
- end
52
-
53
- # Two monads are equivalent if they are of the same type and when their values are equal
54
- def ==(other)
55
- return false unless other.is_a? self.class
56
-
57
- @value == other.instance_variable_get(:@value)
58
- end
59
-
60
- # Return the string representation of the Monad
61
- def inspect
62
- pretty_class_name = self.class.name.split('::')[-1]
63
- "#{pretty_class_name}(#{value.inspect})"
64
- end
65
- end
66
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FunctionalLightService
4
+ module Monad
5
+ class NotMonadError < StandardError; end
6
+
7
+ # Basicly the `pure` function
8
+ def initialize(init)
9
+ @value = join(init)
10
+ end
11
+
12
+ # If the passed value is monad already, get the value to avoid nesting
13
+ # M[M[A]] is equivalent to M[A]
14
+ def join(other)
15
+ if other.is_a? self.class
16
+ other.value
17
+ else
18
+ other
19
+ end
20
+ end
21
+
22
+ # The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
23
+ # boxes it back to the same monad (Mb)
24
+ # fmap :: (a -> b) -> M a -> M b
25
+ def fmap(proc = nil, &block)
26
+ result = (proc || block).call(value)
27
+ self.class.new(result)
28
+ end
29
+
30
+ # The monad: takes a function which returns a monad (of the same type), applies the function
31
+ # bind :: (a -> Mb) -> M a -> M b
32
+ # the self.class, i.e. the containing monad is passed as a second (optional) arg to the function
33
+ def bind(proc = nil, &block)
34
+ (proc || block).call(value).tap do |result|
35
+ # rubocop:disable Style/CaseEquality
36
+ parent = self.class.superclass === Object ? self.class : self.class.superclass
37
+ # rubocop:enable Style/CaseEquality
38
+ unless result.is_a? parent
39
+ raise NotMonadError, "Expected #{result.inspect} to be an #{parent}"
40
+ end
41
+ end
42
+ end
43
+ # rubocop:disable Naming/MethodName
44
+ alias :'>>=' :bind
45
+ # rubocop:enable Naming/MethodName
46
+
47
+ # Get the underlying value, return in Haskell
48
+ # return :: M a -> a
49
+ def value
50
+ @value
51
+ end
52
+
53
+ def to_s
54
+ value.to_s
55
+ end
56
+
57
+ # Two monads are equivalent if they are of the same type and when their values are equal
58
+ def ==(other)
59
+ return false unless other.is_a? self.class
60
+
61
+ @value == other.monad_value
62
+ end
63
+
64
+ # Reader protetto: #value e' privato nelle varianti Nullary (es. None),
65
+ # ma il confronto tra monadi dello stesso tipo deve poter leggere il valore
66
+ def monad_value
67
+ @value
68
+ end
69
+ protected :monad_value
70
+
71
+ # Return the string representation of the Monad
72
+ def inspect
73
+ pretty_class_name = self.class.name.split('::')[-1]
74
+ "#{pretty_class_name}(#{value.inspect})"
75
+ end
76
+ end
77
+ end
@@ -1,74 +1,88 @@
1
- # The simplest NullObject there can be
2
- class Null
3
- class << self
4
- def method_missing(m, *args)
5
- if m == :new
6
- super
7
- else
8
- Null.instance
9
- end
10
- end
11
-
12
- def instance
13
- @instance ||= new([])
14
- end
15
-
16
- def null?
17
- true
18
- end
19
-
20
- def some?
21
- false
22
- end
23
-
24
- def mimic(klas)
25
- new(klas.instance_methods(false))
26
- end
27
-
28
- def ==(other)
29
- other.respond_to?(:null?) && other.null?
30
- end
31
- end
32
- private_class_method :new
33
-
34
- def initialize(methods)
35
- @methods = methods
36
- end
37
-
38
- # implicit conversions
39
- def to_str
40
- ''
41
- end
42
-
43
- def to_ary
44
- []
45
- end
46
-
47
- def method_missing(m, *args)
48
- return self if respond_to?(m)
49
-
50
- super
51
- end
52
-
53
- def null?
54
- true
55
- end
56
-
57
- def some?
58
- false
59
- end
60
-
61
- def respond_to?(m)
62
- return true if @methods.empty? || @methods.include?(m)
63
-
64
- super
65
- end
66
-
67
- def inspect
68
- 'Null'
69
- end
70
-
71
- def ==(other)
72
- other.respond_to?(:null?) && other.null?
73
- end
74
- end
1
+ # frozen_string_literal: true
2
+
3
+ # The simplest NullObject there can be
4
+ class Null
5
+ class << self
6
+ def method_missing(m, *args)
7
+ if m == :new
8
+ super
9
+ else
10
+ Null.instance
11
+ end
12
+ end
13
+
14
+ def respond_to_missing?(m, _include_all = false)
15
+ m != :new || super
16
+ end
17
+
18
+ def instance
19
+ FunctionalLightService::Deprecations.warn(
20
+ "Maybe()/Null are deprecated and will be removed in a future release; " \
21
+ "use FunctionalLightService::Option (Some/None) instead"
22
+ )
23
+ @instance ||= new([])
24
+ end
25
+
26
+ def null?
27
+ true
28
+ end
29
+
30
+ def some?
31
+ false
32
+ end
33
+
34
+ def mimic(klas)
35
+ FunctionalLightService::Deprecations.warn(
36
+ "Maybe()/Null are deprecated and will be removed in a future release; " \
37
+ "use FunctionalLightService::Option (Some/None) instead"
38
+ )
39
+ new(klas.instance_methods(false))
40
+ end
41
+
42
+ def ==(other)
43
+ other.respond_to?(:null?) && other.null?
44
+ end
45
+ end
46
+ private_class_method :new
47
+
48
+ def initialize(methods)
49
+ @methods = methods
50
+ end
51
+
52
+ # implicit conversions
53
+ def to_str
54
+ ''
55
+ end
56
+
57
+ def to_ary
58
+ []
59
+ end
60
+
61
+ def method_missing(m, *args)
62
+ return self if respond_to_missing?(m)
63
+
64
+ super
65
+ end
66
+
67
+ def null?
68
+ true
69
+ end
70
+
71
+ def some?
72
+ false
73
+ end
74
+
75
+ # Convenzione Ruby: si estende respond_to_missing?, mai respond_to?
76
+ # (il vecchio override aveva anche la firma sbagliata: mancava include_all)
77
+ def respond_to_missing?(m, _include_all = false)
78
+ @methods.empty? || @methods.include?(m) || super
79
+ end
80
+
81
+ def inspect
82
+ 'Null'
83
+ end
84
+
85
+ def ==(other)
86
+ other.respond_to?(:null?) && other.null?
87
+ end
88
+ end
@@ -1,97 +1,100 @@
1
- module FunctionalLightService
2
- Option = FunctionalLightService.enum do
3
- Some(:s)
4
- None()
5
- end
6
-
7
- class Option
8
- class << self
9
- def some?(expr)
10
- to_option(expr) { expr.nil? }
11
- end
12
-
13
- def any?(expr)
14
- to_option(expr) { expr.nil? || (expr.respond_to?(:empty?) && expr.empty?) }
15
- end
16
-
17
- def to_option(expr)
18
- yield(expr) ? None.new : Some.new(expr)
19
- end
20
-
21
- def try!
22
- yield
23
- rescue StandardError
24
- None.new
25
- end
26
- end
27
- end
28
-
29
- # rubocop:disable Metrics/BlockLength
30
- impl(Option) do
31
- def fmap
32
- match do
33
- Some() { |s| self.class.new(yield(s)) }
34
- None() { self }
35
- end
36
- end
37
-
38
- def map(&fn)
39
- match do
40
- Some() { |_s| bind(&fn) }
41
- None() { self }
42
- end
43
- end
44
-
45
- def some?
46
- is_a? Option::Some
47
- end
48
-
49
- def none?
50
- is_a? Option::None
51
- end
52
-
53
- alias :empty? :none?
54
-
55
- def value_or(n)
56
- match do
57
- Some() { |s| s }
58
- None() { n }
59
- end
60
- end
61
-
62
- def value_to_a
63
- @value
64
- end
65
-
66
- def +(other)
67
- match do
68
- None() { other }
69
- Some(where { !other.is_a?(Option) }) { |_| raise TypeError, "Other must be an #{Option}" }
70
- Some(where { other.some? }) { |s| Option::Some.new(s + other.value) }
71
- Some() { |_| self }
72
- end
73
- end
74
- end
75
- # rubocop:enable Metrics/BlockLength
76
-
77
- module Prelude
78
- module Option
79
- None = FunctionalLightService::Option::None.new
80
- Option = FunctionalLightService::Option
81
- # rubocop:disable Naming/MethodName
82
- def Some(s)
83
- FunctionalLightService::Option::Some.new(s)
84
- end
85
-
86
- def None
87
- FunctionalLightService::Prelude::Option::None
88
- end
89
-
90
- def Option
91
- FunctionalLightService::Option
92
- end
93
- # rubocop:enable Naming/MethodName
94
- # include Option
95
- end
96
- end
97
- end
1
+ # frozen_string_literal: true
2
+
3
+ module FunctionalLightService
4
+ Option = FunctionalLightService.enum do
5
+ Some(:s)
6
+ None()
7
+ end
8
+
9
+ class Option
10
+ class Some
11
+ def initialize(init)
12
+ raise ArgumentError, "Some cannot wrap nil: use None instead" if init.nil?
13
+
14
+ super
15
+ end
16
+ end
17
+
18
+ class << self
19
+ def some?(expr)
20
+ to_option(expr) { expr.nil? }
21
+ end
22
+
23
+ def any?(expr)
24
+ to_option(expr) { expr.nil? || (expr.respond_to?(:empty?) && expr.empty?) }
25
+ end
26
+
27
+ def to_option(expr)
28
+ yield(expr) ? None.new : Some.new(expr)
29
+ end
30
+
31
+ def try!
32
+ yield
33
+ rescue StandardError
34
+ None.new
35
+ end
36
+ end
37
+ end
38
+
39
+ # Le operazioni usano il dispatch diretto invece del motore match:
40
+ # stessa semantica, ~2 ordini di grandezza piu veloce (audit, finding 3.1)
41
+ impl(Option) do
42
+ def fmap
43
+ some? ? self.class.new(yield(@value)) : self
44
+ end
45
+
46
+ def map(&fn)
47
+ some? ? bind(&fn) : self
48
+ end
49
+
50
+ def some?
51
+ is_a? Option::Some
52
+ end
53
+
54
+ def none?
55
+ is_a? Option::None
56
+ end
57
+
58
+ alias :empty? :none?
59
+
60
+ def value_or(n)
61
+ some? ? @value : n
62
+ end
63
+
64
+ def value_to_a
65
+ @value
66
+ end
67
+
68
+ def +(other)
69
+ FunctionalLightService::Deprecations.warn(
70
+ "Option#+ is deprecated and will be removed in a future release; " \
71
+ "combine the two options explicitly"
72
+ )
73
+ return other if none?
74
+ raise TypeError, "Other must be an #{Option}" unless other.is_a?(Option)
75
+
76
+ other.some? ? Option::Some.new(@value + other.value) : self
77
+ end
78
+ end
79
+
80
+ module Prelude
81
+ module Option
82
+ None = FunctionalLightService::Option::None.new
83
+ Option = FunctionalLightService::Option
84
+ # rubocop:disable Naming/MethodName
85
+ def Some(s)
86
+ FunctionalLightService::Option::Some.new(s)
87
+ end
88
+
89
+ def None
90
+ FunctionalLightService::Prelude::Option::None
91
+ end
92
+
93
+ def Option
94
+ FunctionalLightService::Option
95
+ end
96
+ # rubocop:enable Naming/MethodName
97
+ # include Option
98
+ end
99
+ end
100
+ end