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.
- checksums.yaml +4 -4
- data/.github/workflows/project-build.yml +43 -11
- data/.rubocop.yml +101 -160
- data/AUDIT-functional-light-service.md +352 -0
- data/Appraisals +4 -0
- data/CHANGELOG.md +118 -0
- data/Gemfile +0 -2
- data/README.md +1544 -1426
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/audit/bench.rb +99 -0
- data/audit/verify_findings.rb +172 -0
- data/functional-light-service.gemspec +15 -16
- data/lib/functional-light-service/action.rb +97 -101
- data/lib/functional-light-service/configuration.rb +26 -24
- data/lib/functional-light-service/context/key_verifier.rb +124 -118
- data/lib/functional-light-service/context.rb +63 -20
- data/lib/functional-light-service/deprecations.rb +26 -0
- data/lib/functional-light-service/errors.rb +8 -6
- data/lib/functional-light-service/functional/enum.rb +286 -250
- data/lib/functional-light-service/functional/maybe.rb +21 -15
- data/lib/functional-light-service/functional/monad.rb +77 -66
- data/lib/functional-light-service/functional/null.rb +88 -74
- data/lib/functional-light-service/functional/option.rb +100 -97
- data/lib/functional-light-service/functional/result.rb +129 -116
- data/lib/functional-light-service/localization_adapter.rb +48 -47
- data/lib/functional-light-service/organizer/execute.rb +16 -14
- data/lib/functional-light-service/organizer/iterate.rb +30 -25
- data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
- data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
- data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
- data/lib/functional-light-service/organizer/with_callback.rb +28 -26
- data/lib/functional-light-service/organizer/with_reducer.rb +81 -71
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -105
- data/lib/functional-light-service/organizer.rb +114 -104
- data/lib/functional-light-service/testing/context_factory.rb +48 -42
- data/lib/functional-light-service/testing.rb +3 -1
- data/lib/functional-light-service/version.rb +5 -3
- data/lib/functional-light-service.rb +30 -28
- data/spec/acceptance/after_actions_spec.rb +87 -71
- data/spec/acceptance/before_actions_spec.rb +115 -98
- data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
- data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
- data/spec/acceptance/fail_spec.rb +52 -50
- data/spec/acceptance/message_localization_spec.rb +119 -118
- data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
- data/spec/acceptance/organizer/iterate_spec.rb +7 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +89 -83
- data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
- data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
- data/spec/acceptance/rollback_spec.rb +183 -132
- data/spec/action_expects_and_promises_spec.rb +97 -93
- data/spec/action_promised_keys_spec.rb +126 -122
- data/spec/action_spec.rb +8 -0
- data/spec/context_spec.rb +289 -197
- data/spec/examples/controller_spec.rb +63 -63
- data/spec/examples/validate_address_spec.rb +38 -37
- data/spec/lib/deterministic/currify_spec.rb +90 -88
- data/spec/lib/deterministic/null_spec.rb +6 -1
- data/spec/lib/deterministic/option_spec.rb +140 -133
- data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
- data/spec/lib/deterministic/result/result_shared.rb +3 -2
- data/spec/lib/deterministic/result_spec.rb +2 -2
- data/spec/lib/edge_cases_spec.rb +156 -0
- data/spec/lib/enum_spec.rb +1 -1
- data/spec/lib/native_pattern_matching_spec.rb +74 -0
- data/spec/organizer_spec.rb +115 -93
- data/spec/readme_spec.rb +45 -47
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
- data/spec/sample/calculates_tax_spec.rb +1 -1
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
- data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
- data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
- data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
- data/spec/spec_helper.rb +21 -13
- data/spec/test_doubles.rb +628 -564
- data/spec/testing/context_factory_spec.rb +21 -0
- metadata +49 -117
- data/.travis.yml +0 -24
- data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
- data/spec/acceptance/include_warning_spec.rb +0 -29
|
@@ -1,66 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
other
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# rubocop:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def
|
|
50
|
-
value
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
super
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
None
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|