functional-light-service 0.3.4 → 0.4.4
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 +39 -0
- data/.rubocop.yml +103 -15
- data/.solargraph.yml +11 -0
- data/.travis.yml +5 -5
- data/Appraisals +2 -2
- data/CHANGELOG.md +48 -0
- data/Gemfile +2 -2
- data/README.md +3 -1
- data/VERSION +1 -1
- data/functional-light-service.gemspec +14 -8
- data/gemfiles/dry_inflector_0_2_1.gemfile +5 -0
- data/gemfiles/i18n_1_8_11.gemfile +5 -0
- data/lib/functional-light-service/context/key_verifier.rb +2 -2
- data/lib/functional-light-service/context.rb +152 -154
- data/lib/functional-light-service/functional/enum.rb +2 -6
- data/lib/functional-light-service/functional/maybe.rb +1 -0
- data/lib/functional-light-service/functional/null.rb +1 -1
- data/lib/functional-light-service/functional/option.rb +0 -2
- data/lib/functional-light-service/functional/result.rb +2 -2
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +2 -2
- data/lib/functional-light-service/testing/context_factory.rb +2 -0
- data/lib/functional-light-service/version.rb +1 -1
- data/spec/acceptance/fail_spec.rb +42 -16
- data/spec/acceptance/organizer/reduce_if_spec.rb +32 -0
- data/spec/context/inspect_spec.rb +6 -21
- data/spec/context_spec.rb +1 -1
- data/spec/lib/deterministic/monad_axioms.rb +2 -0
- data/spec/lib/deterministic/monad_spec.rb +2 -0
- data/spec/lib/deterministic/null_spec.rb +2 -0
- data/spec/lib/enum_spec.rb +3 -1
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +3 -1
- data/spec/spec_helper.rb +9 -8
- data/spec/test_doubles.rb +21 -9
- metadata +107 -25
- data/gemfiles/dry_inflector_0_2.gemfile +0 -8
- data/gemfiles/i18n_1_8.gemfile +0 -8
@@ -1,154 +1,152 @@
|
|
1
|
-
module FunctionalLightService
|
2
|
-
# rubocop:disable ClassLength
|
3
|
-
class Context < Hash
|
4
|
-
include FunctionalLightService::Prelude::Option
|
5
|
-
include FunctionalLightService::Prelude::Result
|
6
|
-
attr_accessor :outcome, :current_action
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
context
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
error_code = options_or_error_code
|
70
|
-
options =
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
# rubocop:enable ClassLength
|
154
|
-
end
|
1
|
+
module FunctionalLightService
|
2
|
+
# rubocop:disable Metrics/ClassLength
|
3
|
+
class Context < Hash
|
4
|
+
include FunctionalLightService::Prelude::Option
|
5
|
+
include FunctionalLightService::Prelude::Result
|
6
|
+
attr_accessor :outcome, :current_action
|
7
|
+
|
8
|
+
# rubocop:disable Lint/MissingSuper
|
9
|
+
def initialize(context = {},
|
10
|
+
outcome = Success(:message => '', :error => nil))
|
11
|
+
@outcome = outcome
|
12
|
+
@skip_remaining = false
|
13
|
+
context.to_hash.each { |k, v| self[k] = v }
|
14
|
+
end
|
15
|
+
# rubocop:enable Lint/MissingSuper
|
16
|
+
|
17
|
+
def self.make(context = {})
|
18
|
+
unless context.is_a?(Hash) || context.is_a?(FunctionalLightService::Context)
|
19
|
+
msg = 'Argument must be Hash or FunctionalLightService::Context'
|
20
|
+
raise ArgumentError, msg
|
21
|
+
end
|
22
|
+
|
23
|
+
context = new(context) unless context.is_a?(Context)
|
24
|
+
|
25
|
+
context.assign_aliases(context.delete(:_aliases)) if context[:_aliases]
|
26
|
+
context
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_to_context(values)
|
30
|
+
merge! values
|
31
|
+
end
|
32
|
+
|
33
|
+
def success?
|
34
|
+
@outcome.success?
|
35
|
+
end
|
36
|
+
|
37
|
+
def failure?
|
38
|
+
@outcome.failure?
|
39
|
+
end
|
40
|
+
|
41
|
+
def skip_remaining?
|
42
|
+
@skip_remaining
|
43
|
+
end
|
44
|
+
|
45
|
+
def reset_skip_remaining!
|
46
|
+
@outcome = Success(:message => '', :error => nil)
|
47
|
+
@skip_remaining = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def message
|
51
|
+
@outcome.value[:message]
|
52
|
+
end
|
53
|
+
|
54
|
+
def error_code
|
55
|
+
@outcome.value[:error]
|
56
|
+
end
|
57
|
+
|
58
|
+
def succeed!(message = nil, options = {})
|
59
|
+
message = Configuration.localization_adapter.success(message,
|
60
|
+
current_action,
|
61
|
+
options)
|
62
|
+
@outcome = Success(:message => message)
|
63
|
+
end
|
64
|
+
|
65
|
+
def fail!(message = nil, options_or_error_code = {})
|
66
|
+
options_or_error_code ||= {}
|
67
|
+
|
68
|
+
if options_or_error_code.is_a?(Hash)
|
69
|
+
error_code = options_or_error_code.delete(:error_code)
|
70
|
+
options = options_or_error_code
|
71
|
+
else
|
72
|
+
error_code = options_or_error_code
|
73
|
+
options = {}
|
74
|
+
end
|
75
|
+
|
76
|
+
message = Configuration.localization_adapter.failure(message,
|
77
|
+
current_action,
|
78
|
+
options)
|
79
|
+
|
80
|
+
@outcome = Failure(:message => message, :error => error_code)
|
81
|
+
end
|
82
|
+
|
83
|
+
def fail_and_return!(*args)
|
84
|
+
fail!(*args)
|
85
|
+
throw(:jump_when_failed)
|
86
|
+
end
|
87
|
+
|
88
|
+
def fail_with_rollback!(message = nil, error_code = nil)
|
89
|
+
fail!(message, error_code)
|
90
|
+
raise FailWithRollbackError
|
91
|
+
end
|
92
|
+
|
93
|
+
def skip_remaining!(message = nil)
|
94
|
+
@outcome = Success(:message => message)
|
95
|
+
@skip_remaining = true
|
96
|
+
end
|
97
|
+
|
98
|
+
def stop_processing?
|
99
|
+
failure? || skip_remaining?
|
100
|
+
end
|
101
|
+
|
102
|
+
def define_accessor_methods_for_keys(keys)
|
103
|
+
return if keys.nil?
|
104
|
+
|
105
|
+
keys.each do |key|
|
106
|
+
next if respond_to?(key.to_sym)
|
107
|
+
|
108
|
+
define_singleton_method(key.to_s) { fetch(key) }
|
109
|
+
define_singleton_method("#{key}=") { |value| self[key] = value }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def assign_aliases(aliases)
|
114
|
+
@aliases = aliases
|
115
|
+
|
116
|
+
aliases.each_pair do |key, key_alias|
|
117
|
+
self[key_alias] = self[key]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def aliases
|
122
|
+
@aliases ||= {}
|
123
|
+
end
|
124
|
+
|
125
|
+
def [](key)
|
126
|
+
key = aliases.key(key) || key
|
127
|
+
return super(key)
|
128
|
+
end
|
129
|
+
|
130
|
+
def fetch(key, default = nil, &blk)
|
131
|
+
self[key] ||= if block_given?
|
132
|
+
super(key, &blk)
|
133
|
+
else
|
134
|
+
super
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def inspect
|
139
|
+
"#{self.class}(#{self}, success: #{success?}, message: #{check_nil(message)}, error_code: " \
|
140
|
+
"#{check_nil(error_code)}, skip_remaining: #{@skip_remaining}, aliases: #{@aliases})"
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def check_nil(value)
|
146
|
+
return 'nil' unless value
|
147
|
+
|
148
|
+
"'#{value}'"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
# rubocop:enable Metrics/ClassLength
|
152
|
+
end
|
@@ -53,9 +53,9 @@ module FunctionalLightService
|
|
53
53
|
end
|
54
54
|
|
55
55
|
@value = if init.count == 1 && init[0].is_a?(Hash)
|
56
|
-
|
56
|
+
args.zip(init[0].values).to_h
|
57
57
|
else
|
58
|
-
|
58
|
+
args.zip(init).to_h
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -67,7 +67,6 @@ module FunctionalLightService
|
|
67
67
|
|
68
68
|
# rubocop:disable Metrics/MethodLength
|
69
69
|
def self.create(parent, args)
|
70
|
-
# rubocop:disable Style/AccessModifierDeclarations
|
71
70
|
if args.include? :value
|
72
71
|
raise ArgumentError, "#{args} may not contain the reserved name :value"
|
73
72
|
end
|
@@ -106,14 +105,11 @@ module FunctionalLightService
|
|
106
105
|
end
|
107
106
|
|
108
107
|
dt
|
109
|
-
# rubocop:enable Style/AccessModifierDeclarations
|
110
108
|
end
|
111
109
|
# rubocop:enable Metrics/MethodLength
|
112
110
|
|
113
111
|
class << self
|
114
|
-
# rubocop:disable Style/AccessModifierDeclarations
|
115
112
|
public :new
|
116
|
-
# rubocop:enable Style/AccessModifierDeclarations
|
117
113
|
end
|
118
114
|
end
|
119
115
|
|
@@ -5,7 +5,7 @@ module FunctionalLightService
|
|
5
5
|
|
6
6
|
alias logged? logged
|
7
7
|
|
8
|
-
def initialize(organizer, decorated: WithReducer.new
|
8
|
+
def initialize(organizer, logger:, decorated: WithReducer.new)
|
9
9
|
@decorated = decorated
|
10
10
|
@organizer = organizer
|
11
11
|
@logger = logger
|
@@ -19,7 +19,7 @@ module FunctionalLightService
|
|
19
19
|
|
20
20
|
logger.info do
|
21
21
|
"[FunctionalLightService] - keys in context: " \
|
22
|
-
|
22
|
+
"#{extract_keys(decorated.context.keys)}"
|
23
23
|
end
|
24
24
|
self
|
25
25
|
end
|
@@ -26,11 +26,13 @@ module FunctionalLightService
|
|
26
26
|
|
27
27
|
# More than one arguments can be passed to the
|
28
28
|
# Organizer's #call method
|
29
|
+
# rubocop:disable Style/ArgumentsForwarding
|
29
30
|
def with(*args, &block)
|
30
31
|
catch(:return_ctx_from_execution) do
|
31
32
|
@organizer.call(*args, &block)
|
32
33
|
end
|
33
34
|
end
|
35
|
+
# rubocop:enable Style/ArgumentsForwarding
|
34
36
|
|
35
37
|
def initialize(organizer)
|
36
38
|
@organizer = organizer
|
@@ -1,24 +1,50 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
RSpec.describe "
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
RSpec.describe "fail_and_return!" do
|
4
|
+
describe "returns immediately from executed block" do
|
5
|
+
class FailAndReturnAction
|
6
|
+
extend FunctionalLightService::Action
|
7
|
+
promises :one, :two
|
8
|
+
|
9
|
+
executed do |ctx|
|
10
|
+
ctx.one = 1
|
11
|
+
# Have to set it in Context
|
12
|
+
ctx.two = nil
|
13
|
+
|
14
|
+
ctx.fail_and_return!('Something went wrong')
|
15
|
+
ctx.two = 2
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns immediately from executed block" do
|
20
|
+
result = FailAndReturnAction.execute
|
21
|
+
|
22
|
+
expect(result).to be_failure
|
23
|
+
expect(result.two).to be_nil
|
15
24
|
end
|
16
25
|
end
|
17
26
|
|
18
|
-
|
19
|
-
|
27
|
+
describe "accepts error_code option" do
|
28
|
+
class FailAndReturnWithErrorCodeAction
|
29
|
+
extend FunctionalLightService::Action
|
30
|
+
promises :one, :two
|
20
31
|
|
21
|
-
|
22
|
-
|
32
|
+
executed do |ctx|
|
33
|
+
ctx.one = 1
|
34
|
+
# Have to set it in Context
|
35
|
+
ctx.two = nil
|
36
|
+
|
37
|
+
ctx.fail_and_return!('Something went wrong', :error_code => 401)
|
38
|
+
ctx.two = 2
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returned context contains the error_code" do
|
43
|
+
result = FailAndReturnWithErrorCodeAction.execute
|
44
|
+
|
45
|
+
expect(result).to be_failure
|
46
|
+
expect(result.error_code).to eq 401
|
47
|
+
expect(result.two).to be_nil
|
48
|
+
end
|
23
49
|
end
|
24
50
|
end
|
@@ -48,4 +48,36 @@ RSpec.describe FunctionalLightService::Organizer do
|
|
48
48
|
result = TestReduceIf.call(empty_context)
|
49
49
|
expect(result).to be_success
|
50
50
|
end
|
51
|
+
|
52
|
+
it 'skips actions within in its own scope' do
|
53
|
+
org = Class.new do
|
54
|
+
extend FunctionalLightService::Organizer
|
55
|
+
|
56
|
+
def self.call
|
57
|
+
reduce(actions)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.actions
|
61
|
+
[
|
62
|
+
reduce_if(
|
63
|
+
->(c) { !c.nil? },
|
64
|
+
[
|
65
|
+
execute(->(c) { c[:first_reduce_if] = true }),
|
66
|
+
execute(->(c) { c.skip_remaining! }),
|
67
|
+
execute(->(c) { c[:second_reduce_if] = true })
|
68
|
+
]
|
69
|
+
),
|
70
|
+
execute(->(c) { c[:last_outside] = true })
|
71
|
+
]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
result = org.call
|
76
|
+
|
77
|
+
aggregate_failures do
|
78
|
+
expect(result[:first_reduce_if]).to be true
|
79
|
+
expect(result[:second_reduce_if]).to be_nil
|
80
|
+
expect(result[:last_outside]).to be true
|
81
|
+
end
|
82
|
+
end
|
51
83
|
end
|
@@ -13,13 +13,8 @@ RSpec.describe FunctionalLightService::Context do
|
|
13
13
|
describe '#inspect' do
|
14
14
|
it 'inspects the hash with all the fields' do
|
15
15
|
inspected_context =
|
16
|
-
|
17
|
-
|
18
|
-
+ 'message: \'\', ' \
|
19
|
-
+ 'error_code: nil, ' \
|
20
|
-
+ 'skip_remaining: false, ' \
|
21
|
-
+ 'aliases: {}' \
|
22
|
-
+ ')'
|
16
|
+
"FunctionalLightService::Context({}, success: true, message: '', error_code: nil, " \
|
17
|
+
"skip_remaining: false, aliases: {})"
|
23
18
|
|
24
19
|
expect(context.inspect).to eq(inspected_context)
|
25
20
|
end
|
@@ -28,13 +23,8 @@ RSpec.describe FunctionalLightService::Context do
|
|
28
23
|
context.fail!('There was an error')
|
29
24
|
|
30
25
|
inspected_context =
|
31
|
-
|
32
|
-
|
33
|
-
+ 'message: \'There was an error\', ' \
|
34
|
-
+ 'error_code: nil, ' \
|
35
|
-
+ 'skip_remaining: false, ' \
|
36
|
-
+ 'aliases: {}' \
|
37
|
-
+ ')'
|
26
|
+
"FunctionalLightService::Context({}, success: false, message: 'There was an error', " \
|
27
|
+
"error_code: nil, skip_remaining: false, aliases: {})"
|
38
28
|
|
39
29
|
expect(context.inspect).to eq(inspected_context)
|
40
30
|
end
|
@@ -43,13 +33,8 @@ RSpec.describe FunctionalLightService::Context do
|
|
43
33
|
context.skip_remaining!('No need to process')
|
44
34
|
|
45
35
|
inspected_context =
|
46
|
-
|
47
|
-
|
48
|
-
+ 'message: \'No need to process\', ' \
|
49
|
-
+ 'error_code: nil, ' \
|
50
|
-
+ 'skip_remaining: true, ' \
|
51
|
-
+ 'aliases: {}' \
|
52
|
-
+ ')'
|
36
|
+
"FunctionalLightService::Context({}, success: true, message: 'No need to process', " \
|
37
|
+
"error_code: nil, skip_remaining: true, aliases: {})"
|
53
38
|
|
54
39
|
expect(context.inspect).to eq(inspected_context)
|
55
40
|
end
|
data/spec/context_spec.rb
CHANGED
@@ -167,7 +167,7 @@ RSpec.describe FunctionalLightService::Context do
|
|
167
167
|
end
|
168
168
|
|
169
169
|
it "allows a default block value for #fetch" do
|
170
|
-
expect(context.fetch(:madeup
|
170
|
+
expect(context.fetch(:madeup, :default)).to eq(:default)
|
171
171
|
end
|
172
172
|
|
173
173
|
context "when aliases are included via .make" do
|
@@ -34,7 +34,9 @@ shared_examples 'a Monad' do
|
|
34
34
|
|
35
35
|
it '#bind must return a monad' do
|
36
36
|
expect(monad.new(1).bind { |v| monad.new(v) }).to eq monad.new(1)
|
37
|
+
# rubocop:disable Lint/EmptyBlock
|
37
38
|
expect { monad.new(1).bind {} }.to raise_error(FunctionalLightService::Monad::NotMonadError)
|
39
|
+
# rubocop:enable Lint/EmptyBlock
|
38
40
|
end
|
39
41
|
|
40
42
|
it '#new must return a monad' do
|
@@ -21,8 +21,10 @@ describe FunctionalLightService::Monad do
|
|
21
21
|
|
22
22
|
context '#bind' do
|
23
23
|
it "raises an error if the passed function does not return a monad of the same class" do
|
24
|
+
# rubocop:disable Lint/EmptyBlock
|
24
25
|
expect { Identity.new(1).bind {} }.to \
|
25
26
|
raise_error(FunctionalLightService::Monad::NotMonadError)
|
27
|
+
# rubocop:enable Lint/EmptyBlock
|
26
28
|
end
|
27
29
|
specify { expect(Identity.new(1).bind { |value| Identity.new(value) }).to eq Identity.new(1) }
|
28
30
|
|
@@ -38,7 +38,9 @@ describe Null do
|
|
38
38
|
null = Null.instance
|
39
39
|
expect(null.to_str).to eq ""
|
40
40
|
expect(null.to_ary).to eq []
|
41
|
+
# rubocop:disable Style/StringConcatenation
|
41
42
|
expect("" + null).to eq ""
|
43
|
+
# rubocop:enable Style/StringConcatenation
|
42
44
|
|
43
45
|
a, b, c = null
|
44
46
|
expect(a).to be_nil
|
data/spec/lib/enum_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe FunctionalLightService::Enum do
|
|
8
8
|
InvalidEnum = FunctionalLightService.enum do
|
9
9
|
Unary(:value)
|
10
10
|
end
|
11
|
-
end
|
11
|
+
end.to raise_error ArgumentError
|
12
12
|
end
|
13
13
|
|
14
14
|
context "Nullary, Unary, Binary" do
|
@@ -32,7 +32,9 @@ describe FunctionalLightService::Enum do
|
|
32
32
|
expect { n.value }.to raise_error NoMethodError
|
33
33
|
expect(n.inspect).to eq "Nullary"
|
34
34
|
expect(n.to_s).to eq ""
|
35
|
+
# rubocop:disable Lint/EmptyBlock
|
35
36
|
expect(n.fmap {}).to eq n
|
37
|
+
# rubocop:enable Lint/EmptyBlock
|
36
38
|
end
|
37
39
|
|
38
40
|
it "Unary" do
|