f_service 0.2.0 → 0.3.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/.dependabot.yml +8 -0
- data/.github/workflows/tests-and-linter.yml +1 -1
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +13 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +16 -7
- data/README.md +86 -8
- data/f_service.gemspec +2 -2
- data/lib/f_service/base.rb +37 -34
- data/lib/f_service/result/base.rb +70 -38
- data/lib/f_service/result/failure.rb +11 -7
- data/lib/f_service/result/success.rb +13 -8
- data/lib/f_service/rspec/support/helpers/result.rb +32 -0
- data/lib/f_service/rspec/support/helpers.rb +3 -0
- data/lib/f_service/rspec/support/matchers/result.rb +61 -0
- data/lib/f_service/rspec/support/matchers.rb +3 -0
- data/lib/f_service/rspec/support.rb +4 -0
- data/lib/f_service/rspec.rb +3 -0
- data/lib/f_service/version.rb +1 -1
- data/lib/f_service.rb +19 -4
- metadata +12 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6cb29d87cf5c970b2b185ed2c3da41020f40122e2d0857af068302fb32e55400
|
|
4
|
+
data.tar.gz: 27a89660bb6d1e65d537f5d6617cc8175f600ab71efca79f65875a1b6adf0c97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b444953aee676163a2b076d814b3cc801b0808a30660c4edd2f19172754d58c41a7fcb3129a52cf95c1a0fdb8546419a55a8101b6882459673cd647d520f660
|
|
7
|
+
data.tar.gz: 0e2f10e1ad6cf4ba7ba2375b2302661429721b7bf4023afcb1559909205eb7e8f013efbb3da07c4b7926b2e967592c31a6c930ca6d6e69b195bba97342407b69
|
data/.rubocop.yml
CHANGED
|
@@ -17,3 +17,17 @@ Style/DocumentationMethod:
|
|
|
17
17
|
Naming/MethodName:
|
|
18
18
|
Exclude:
|
|
19
19
|
- lib/f_service/base.rb
|
|
20
|
+
|
|
21
|
+
RSpec/ContextWording:
|
|
22
|
+
Prefixes:
|
|
23
|
+
- and
|
|
24
|
+
- but
|
|
25
|
+
- when
|
|
26
|
+
- with
|
|
27
|
+
- without
|
|
28
|
+
|
|
29
|
+
RSpec/ExampleLength:
|
|
30
|
+
Max: 20
|
|
31
|
+
|
|
32
|
+
RSpec/NestedGroups:
|
|
33
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -10,9 +10,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
<!-- ### Removed -->
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## 0.3.0
|
|
14
|
+
### Added
|
|
15
|
+
- Added Rspec Helper `#mock_service` #41;
|
|
16
|
+
- Added Rspec Matcher `#have_succeed_with` and `#have_failed_with` #41;
|
|
17
|
+
- Added `Success()`, `Failure()`, `Check()`, `Try()` now can be multipe types #41;
|
|
18
|
+
- Changed Depreacate `Result#type` method #41;
|
|
19
|
+
- Changed Deprecate method `#then` for Success and Failure classes #40
|
|
20
|
+
- Added RSpec support for mock and match results #35
|
|
21
|
+
- Deprecate method `#then` for Success and Failure classes #40;
|
|
22
|
+
- Removed deprecated method `#on` #33
|
|
23
|
+
- Changed Capture just one callback per result #30;
|
|
24
|
+
|
|
13
25
|
## 0.2.0
|
|
14
26
|
### Added
|
|
15
|
-
- Add `
|
|
27
|
+
- Add `and_then` as alias for `then` (partially fix [#23](https://github.com/Fretadao/f_service/issues/23)).
|
|
16
28
|
- Add `catch` to `Failure` and `Success`. It acts as an inverted `then` and has a `or_else` alias.
|
|
17
29
|
- Add support to custom `data` property to be passed when calling `Base#Check`.
|
|
18
30
|
- Add support to multiple type checks on `Result#on_success` and `Result#on_failure` hooks.
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
f_service (0.
|
|
4
|
+
f_service (0.3.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
@@ -9,24 +9,31 @@ GEM
|
|
|
9
9
|
ast (2.4.0)
|
|
10
10
|
backport (1.1.2)
|
|
11
11
|
benchmark (0.1.0)
|
|
12
|
+
coderay (1.1.3)
|
|
12
13
|
diff-lcs (1.3)
|
|
13
14
|
docile (1.3.5)
|
|
14
15
|
e2mmap (0.1.0)
|
|
15
16
|
jaro_winkler (1.5.4)
|
|
16
17
|
maruku (0.7.3)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
method_source (1.0.0)
|
|
19
|
+
mini_portile2 (2.8.1)
|
|
20
|
+
nokogiri (1.14.3)
|
|
21
|
+
mini_portile2 (~> 2.8.0)
|
|
20
22
|
racc (~> 1.4)
|
|
21
23
|
parallel (1.19.1)
|
|
22
24
|
parser (2.7.1.1)
|
|
23
25
|
ast (~> 2.4.0)
|
|
24
|
-
|
|
26
|
+
pry (0.14.1)
|
|
27
|
+
coderay (~> 1.1)
|
|
28
|
+
method_source (~> 1.0)
|
|
29
|
+
pry-nav (1.0.0)
|
|
30
|
+
pry (>= 0.9.10, < 0.15)
|
|
31
|
+
racc (1.6.2)
|
|
25
32
|
rainbow (3.0.0)
|
|
26
33
|
rake (13.0.1)
|
|
27
34
|
reverse_markdown (1.4.0)
|
|
28
35
|
nokogiri
|
|
29
|
-
rexml (3.2.
|
|
36
|
+
rexml (3.2.5)
|
|
30
37
|
rspec (3.9.0)
|
|
31
38
|
rspec-core (~> 3.9.0)
|
|
32
39
|
rspec-expectations (~> 3.9.0)
|
|
@@ -82,6 +89,8 @@ PLATFORMS
|
|
|
82
89
|
DEPENDENCIES
|
|
83
90
|
bundler (~> 2.0)
|
|
84
91
|
f_service!
|
|
92
|
+
pry
|
|
93
|
+
pry-nav
|
|
85
94
|
rake (~> 13.0.0)
|
|
86
95
|
rspec (~> 3.0)
|
|
87
96
|
rubocop (~> 0.82.0)
|
|
@@ -91,4 +100,4 @@ DEPENDENCIES
|
|
|
91
100
|
yard
|
|
92
101
|
|
|
93
102
|
BUNDLED WITH
|
|
94
|
-
2.
|
|
103
|
+
2.2.32
|
data/README.md
CHANGED
|
@@ -58,17 +58,17 @@ end
|
|
|
58
58
|
|
|
59
59
|
The next step is writing the `#run` method, which is where the work should be done.
|
|
60
60
|
Use the methods `#Success` and `#Failure` to handle your return values.
|
|
61
|
-
You can optionally specify a
|
|
61
|
+
You can optionally specify a list of types which represents that result and a value for your result.
|
|
62
62
|
|
|
63
63
|
```ruby
|
|
64
64
|
class User::Create < FService::Base
|
|
65
65
|
# ...
|
|
66
66
|
def run
|
|
67
|
-
return Failure(:no_name) if @name.nil?
|
|
67
|
+
return Failure(:no_name, :invalid_attribute) if @name.nil?
|
|
68
68
|
|
|
69
69
|
user = UserRepository.create(name: @name)
|
|
70
|
-
if user.
|
|
71
|
-
Success(:created, data: user)
|
|
70
|
+
if user.save
|
|
71
|
+
Success(:success, :created, data: user)
|
|
72
72
|
else
|
|
73
73
|
Failure(:creation_failed, data: user.errors)
|
|
74
74
|
end
|
|
@@ -128,6 +128,29 @@ class UsersController < BaseController
|
|
|
128
128
|
end
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
Or else it is possible to specify an unhandled option to ensure that the callback will process that message anyway the
|
|
132
|
+
error.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
class UsersController < BaseController
|
|
136
|
+
def create
|
|
137
|
+
User::Create.(user_params)
|
|
138
|
+
.on_success(unhandled: true) { |value| return json_success(value) }
|
|
139
|
+
.on_failure(unhandled: true) { |error| return json_error(error) }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
class UsersController < BaseController
|
|
146
|
+
def create
|
|
147
|
+
User::Create.(user_params)
|
|
148
|
+
.on_success { |value| return json_success(value) }
|
|
149
|
+
.on_failure { |error| return json_error(error) }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
131
154
|
> You can ignore any of the callbacks, if you want to.
|
|
132
155
|
|
|
133
156
|
Going further, you can match the Result type, in case you want to handle them differently:
|
|
@@ -166,6 +189,12 @@ class UsersController < BaseController
|
|
|
166
189
|
end
|
|
167
190
|
```
|
|
168
191
|
|
|
192
|
+
### Types precedence
|
|
193
|
+
|
|
194
|
+
FService matches types from left to right, from more specific to more generic.
|
|
195
|
+
Example (:unprocessable_entity, :client_error, :http_response)
|
|
196
|
+
Then, result will match first :unprocessable_entity, after :client_error, after :http_response, then not matched.
|
|
197
|
+
|
|
169
198
|
### Chaining services
|
|
170
199
|
|
|
171
200
|
Since all services return Results, you can chain service calls making a data pipeline.
|
|
@@ -206,10 +235,10 @@ You can use `Check` to converts a boolean to a Result, truthy values map to `Suc
|
|
|
206
235
|
|
|
207
236
|
```ruby
|
|
208
237
|
Check(:math_works) { 1 < 2 }
|
|
209
|
-
# => #<Success @value=true, @
|
|
238
|
+
# => #<Success @value=true, @types=[:math_works]>
|
|
210
239
|
|
|
211
240
|
Check(:math_works) { 1 > 2 }
|
|
212
|
-
# => #<Failure @error=false, @
|
|
241
|
+
# => #<Failure @error=false, @types=[:math_works]>
|
|
213
242
|
```
|
|
214
243
|
|
|
215
244
|
`Try` transforms an exception into a `Failure` if some exception is raised for the given block. You can specify which exception class to watch for
|
|
@@ -228,10 +257,59 @@ class IHateEvenNumbers < FService::Base
|
|
|
228
257
|
end
|
|
229
258
|
|
|
230
259
|
IHateEvenNumbers.call
|
|
231
|
-
# => #<Success @value=9, @
|
|
260
|
+
# => #<Success @value=9, @types=[:rand_int]>
|
|
232
261
|
|
|
233
262
|
IHateEvenNumbers.call
|
|
234
|
-
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @
|
|
263
|
+
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:rand_int]>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Testing
|
|
267
|
+
|
|
268
|
+
We provide some helpers and matchers to make ease to test code envolving Fservice services.
|
|
269
|
+
|
|
270
|
+
To make available in the system, in the file 'spec/spec_helper.rb' or 'spec/rails_helper.rb'
|
|
271
|
+
|
|
272
|
+
add the folowing require:
|
|
273
|
+
|
|
274
|
+
```rb
|
|
275
|
+
require 'f_service/rspec'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Mocking a result
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
mock_service(Uer::Create)
|
|
282
|
+
# => Mocks a successful result with all values nil
|
|
283
|
+
|
|
284
|
+
mock_service(Uer::Create, result: :success)
|
|
285
|
+
# => Mocks a successful result with all values nil
|
|
286
|
+
|
|
287
|
+
mock_service(Uer::Create, result: :success, types: [:created, :success])
|
|
288
|
+
# => Mocks a successful result with type created
|
|
289
|
+
|
|
290
|
+
mock_service(Uer::Create, result: :success, types: :created, value: instance_spy(User))
|
|
291
|
+
# => Mocks a successful result with type created and a value
|
|
292
|
+
|
|
293
|
+
mock_service(Uer::Create, result: :failure)
|
|
294
|
+
# => Mocs a failure with all nil values
|
|
295
|
+
|
|
296
|
+
mock_service(User::Create, result: :failure, types: [:unprocessable_entity, :client_error])
|
|
297
|
+
# => Mocs a failure with a failure type
|
|
298
|
+
|
|
299
|
+
mock_service(User::Create, result: :failure, types: [:unprocessable_entity, :client_error], value: { name: ["can't be blank"] })
|
|
300
|
+
# => Mocs a failure with a failure type and an error value
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Matching a result
|
|
304
|
+
|
|
305
|
+
```rb
|
|
306
|
+
expect(User::Create.(name: 'Joe')).to have_succeed_with(:created)
|
|
307
|
+
|
|
308
|
+
expect(User::Create.(name: 'Joe')).to have_succeed_with(:created).and_value(an_instance_of(User))
|
|
309
|
+
|
|
310
|
+
expect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes)
|
|
311
|
+
|
|
312
|
+
expect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes).and_error({ name: ["can't be blank"] })
|
|
235
313
|
```
|
|
236
314
|
|
|
237
315
|
## API Docs
|
data/f_service.gemspec
CHANGED
|
@@ -7,8 +7,8 @@ require 'f_service/version'
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'f_service'
|
|
9
9
|
spec.version = FService::VERSION
|
|
10
|
-
spec.authors = ['
|
|
11
|
-
spec.email
|
|
10
|
+
spec.authors = ['Fretadao Tech Team']
|
|
11
|
+
spec.email = ['tech@fretadao.com.br']
|
|
12
12
|
|
|
13
13
|
spec.summary = 'A small, monad-based service class'
|
|
14
14
|
spec.description = <<-DESCRIPTION
|
data/lib/f_service/base.rb
CHANGED
|
@@ -101,69 +101,70 @@ module FService
|
|
|
101
101
|
def success(data = nil)
|
|
102
102
|
FService.deprecate!(
|
|
103
103
|
name: "#{self.class}##{__method__}",
|
|
104
|
-
alternative: '#Success'
|
|
104
|
+
alternative: '#Success',
|
|
105
|
+
from: caller[0]
|
|
105
106
|
)
|
|
106
107
|
|
|
107
108
|
Result::Success.new(data)
|
|
108
109
|
end
|
|
109
110
|
|
|
110
111
|
# Returns a successful result.
|
|
111
|
-
# You can optionally specify a
|
|
112
|
+
# You can optionally specify a list of types and a value for your result.
|
|
112
113
|
# You'll probably want to return this inside {#run}.
|
|
113
114
|
#
|
|
114
115
|
#
|
|
115
116
|
# @example
|
|
116
117
|
# def run
|
|
117
118
|
# Success()
|
|
118
|
-
# # => #<Success @value=nil, @
|
|
119
|
+
# # => #<Success @value=nil, @types=[]>
|
|
119
120
|
#
|
|
120
121
|
# Success(:ok)
|
|
121
|
-
# # => #<Success @value=nil, @
|
|
122
|
+
# # => #<Success @value=nil, @types=[:ok]>
|
|
122
123
|
#
|
|
123
124
|
# Success(data: 10)
|
|
124
|
-
# # => #<Success @value=10, @
|
|
125
|
+
# # => #<Success @value=10, @types=[]>
|
|
125
126
|
#
|
|
126
127
|
# Success(:ok, data: 10)
|
|
127
|
-
# # => #<Success @value=10, @
|
|
128
|
+
# # => #<Success @value=10, @types=[:ok]>
|
|
128
129
|
# end
|
|
129
130
|
#
|
|
130
|
-
# @param
|
|
131
|
+
# @param types the Result types
|
|
131
132
|
# @param data the result value
|
|
132
133
|
# @return [Result::Success] a successful result
|
|
133
|
-
def Success(
|
|
134
|
-
Result::Success.new(data,
|
|
134
|
+
def Success(*types, data: nil)
|
|
135
|
+
Result::Success.new(data, types)
|
|
135
136
|
end
|
|
136
137
|
|
|
137
138
|
# Returns a failed result.
|
|
138
|
-
# You can optionally specify
|
|
139
|
+
# You can optionally specify types and a value for your result.
|
|
139
140
|
# You'll probably want to return this inside {#run}.
|
|
140
141
|
#
|
|
141
142
|
#
|
|
142
143
|
# @example
|
|
143
144
|
# def run
|
|
144
145
|
# Failure()
|
|
145
|
-
# # => #<Failure @error=nil, @
|
|
146
|
+
# # => #<Failure @error=nil, @types=[]>
|
|
146
147
|
#
|
|
147
148
|
# Failure(:not_a_number)
|
|
148
|
-
# # => #<Failure @error=nil, @
|
|
149
|
+
# # => #<Failure @error=nil, @types=[:not_a_number]>
|
|
149
150
|
#
|
|
150
151
|
# Failure(data: "10")
|
|
151
|
-
# # => #<Failure @error="10", @
|
|
152
|
+
# # => #<Failure @error="10", @types=[]>
|
|
152
153
|
#
|
|
153
154
|
# Failure(:not_a_number, data: "10")
|
|
154
|
-
# # => #<Failure @error="10", @
|
|
155
|
+
# # => #<Failure @error="10", @types=[:not_a_number]>
|
|
155
156
|
# end
|
|
156
157
|
#
|
|
157
|
-
# @param
|
|
158
|
+
# @param types the Result types
|
|
158
159
|
# @param data the result value
|
|
159
160
|
# @return [Result::Failure] a failed result
|
|
160
|
-
def Failure(
|
|
161
|
-
Result::Failure.new(data,
|
|
161
|
+
def Failure(*types, data: nil)
|
|
162
|
+
Result::Failure.new(data, types)
|
|
162
163
|
end
|
|
163
164
|
|
|
164
165
|
# Converts a boolean to a Result.
|
|
165
166
|
# Truthy values map to Success, and falsey values map to Failures.
|
|
166
|
-
# You can optionally provide a
|
|
167
|
+
# You can optionally provide a types for the result.
|
|
167
168
|
# The result value defaults as the evaluated value of the given block.
|
|
168
169
|
# If you want another value you can pass it through the `data:` argument.
|
|
169
170
|
#
|
|
@@ -171,34 +172,34 @@ module FService
|
|
|
171
172
|
# class CheckMathWorks < FService::Base
|
|
172
173
|
# def run
|
|
173
174
|
# Check(:math_works) { 1 < 2 }
|
|
174
|
-
# # => #<Success @value=true, @
|
|
175
|
+
# # => #<Success @value=true, @types=[:math_works]>
|
|
175
176
|
#
|
|
176
177
|
# Check(:math_works) { 1 > 2 }
|
|
177
|
-
# # => #<Failure @error=false, @
|
|
178
|
+
# # => #<Failure @error=false, @types=[:math_works]>
|
|
178
179
|
#
|
|
179
180
|
# Check(:math_works, data: 1 + 2) { 1 > 2 }
|
|
180
|
-
# # => #<Failure @
|
|
181
|
+
# # => #<Failure @types=:math_works, @error=3>
|
|
181
182
|
# end
|
|
182
183
|
#
|
|
183
184
|
# Check(:math_works, data: 1 + 2) { 1 < 2 }
|
|
184
|
-
# # => #<Success @
|
|
185
|
+
# # => #<Success @types=[:math_works], @value=3>
|
|
185
186
|
# end
|
|
186
187
|
# end
|
|
187
188
|
#
|
|
188
|
-
# @param
|
|
189
|
+
# @param types the Result types
|
|
189
190
|
# @return [Result::Success, Result::Failure] a Result from the boolean expression
|
|
190
|
-
def Check(
|
|
191
|
+
def Check(*types, data: nil)
|
|
191
192
|
res = yield
|
|
192
193
|
|
|
193
194
|
final_data = data || res
|
|
194
195
|
|
|
195
|
-
res ? Success(
|
|
196
|
+
res ? Success(*types, data: final_data) : Failure(*types, data: final_data)
|
|
196
197
|
end
|
|
197
198
|
|
|
198
199
|
# If the given block raises an exception, it wraps it in a Failure.
|
|
199
200
|
# Otherwise, maps the block value in a Success object.
|
|
200
201
|
# You can specify which exceptions to watch for.
|
|
201
|
-
# It's possible to provide a
|
|
202
|
+
# It's possible to provide a types for the result too.
|
|
202
203
|
#
|
|
203
204
|
# @example
|
|
204
205
|
# class IHateEvenNumbers < FService::Base
|
|
@@ -213,20 +214,20 @@ module FService
|
|
|
213
214
|
# end
|
|
214
215
|
#
|
|
215
216
|
# IHateEvenNumbers.call
|
|
216
|
-
# # => #<Success @value=9, @
|
|
217
|
+
# # => #<Success @value=9, @types=[:rand_int]>
|
|
217
218
|
#
|
|
218
219
|
# IHateEvenNumbers.call
|
|
219
|
-
# # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @
|
|
220
|
+
# # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:rand_int]>
|
|
220
221
|
#
|
|
221
|
-
# @param
|
|
222
|
+
# @param types the Result types
|
|
222
223
|
# @param catch the exception list to catch
|
|
223
224
|
# @return [Result::Success, Result::Failure] a result from the boolean expression
|
|
224
|
-
def Try(
|
|
225
|
+
def Try(*types, catch: StandardError)
|
|
225
226
|
res = yield
|
|
226
227
|
|
|
227
|
-
Success(
|
|
228
|
+
Success(*types, data: res)
|
|
228
229
|
rescue *catch => e
|
|
229
|
-
Failure(
|
|
230
|
+
Failure(*types, data: e)
|
|
230
231
|
end
|
|
231
232
|
|
|
232
233
|
# Returns a failed operation.
|
|
@@ -250,7 +251,8 @@ module FService
|
|
|
250
251
|
def failure(data = nil)
|
|
251
252
|
FService.deprecate!(
|
|
252
253
|
name: "#{self.class}##{__method__}",
|
|
253
|
-
alternative: '#Failure'
|
|
254
|
+
alternative: '#Failure',
|
|
255
|
+
from: caller[0]
|
|
254
256
|
)
|
|
255
257
|
|
|
256
258
|
Result::Failure.new(data)
|
|
@@ -283,7 +285,8 @@ module FService
|
|
|
283
285
|
def result(condition, data = nil)
|
|
284
286
|
FService.deprecate!(
|
|
285
287
|
name: "#{self.class}##{__method__}",
|
|
286
|
-
alternative: '#Check'
|
|
288
|
+
alternative: '#Check',
|
|
289
|
+
from: caller[0]
|
|
287
290
|
)
|
|
288
291
|
|
|
289
292
|
condition ? success(data) : failure(data)
|
|
@@ -7,24 +7,38 @@ module FService
|
|
|
7
7
|
#
|
|
8
8
|
# @abstract
|
|
9
9
|
class Base
|
|
10
|
-
|
|
10
|
+
attr_reader :types
|
|
11
|
+
|
|
12
|
+
%i[and_then successful? failed? value value! error].each do |method_name|
|
|
11
13
|
define_method(method_name) do |*_args|
|
|
12
14
|
raise NotImplementedError, "called #{method_name} on class Result::Base"
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
# You usually shouldn't call this directly. See {FService::Base#Failure} and {FService::Base#Success}.
|
|
19
|
+
def initialize(types = [])
|
|
20
|
+
@handled = false
|
|
21
|
+
@types = types
|
|
22
|
+
@matching_types = []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Implements old attribute type. Its deprecated in favor of using types.
|
|
26
|
+
def type
|
|
27
|
+
FService.deprecate!(name: "#{self.class}##{__method__}", alternative: '#types', from: caller[0])
|
|
28
|
+
|
|
29
|
+
types.size == 1 ? types.first : Array(@matching_types).first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# This hook runs if the result is successful.
|
|
33
|
+
# Can receive one or more types to be checked before running the given block.
|
|
20
34
|
#
|
|
21
35
|
# @example
|
|
22
36
|
# class UsersController < BaseController
|
|
23
37
|
# def update
|
|
24
|
-
# User::Update.(user: user)
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
38
|
+
# User::Update.(user: user)
|
|
39
|
+
# .on_success(:type, :type2) { return json_success({ status: :ok }) } # run only if type matches
|
|
40
|
+
# .on_success { |value| return json_success(value) }
|
|
41
|
+
# .on_failure { |error| return json_error(error) } # this won't run
|
|
28
42
|
# end
|
|
29
43
|
#
|
|
30
44
|
# private
|
|
@@ -34,33 +48,13 @@ module FService
|
|
|
34
48
|
# end
|
|
35
49
|
# end
|
|
36
50
|
#
|
|
37
|
-
# @param success [#call] a lambda (or anything that responds to #call) to run on success
|
|
38
|
-
# @param failure [#call] a lambda (or anything that responds to #call) to run on failure
|
|
39
|
-
# @deprecated Use {#on_success} and/or {#on_failure} instead.
|
|
40
|
-
# @api public
|
|
41
|
-
def on(success:, failure:)
|
|
42
|
-
FService.deprecate!(
|
|
43
|
-
name: "#{self.class}##{__method__}",
|
|
44
|
-
alternative: '#on_success and/or #on_failure'
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if successful?
|
|
48
|
-
success.call(value)
|
|
49
|
-
else
|
|
50
|
-
failure.call(error)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# This hook runs if the result is successful.
|
|
55
|
-
# Can receive one or more types to be checked before running the given block.
|
|
56
|
-
#
|
|
57
51
|
# @example
|
|
58
52
|
# class UsersController < BaseController
|
|
59
53
|
# def update
|
|
60
54
|
# User::Update.(user: user)
|
|
61
55
|
# .on_success(:type, :type2) { return json_success({ status: :ok }) } # run only if type matches
|
|
62
|
-
# .on_success { |value| return json_success(value) }
|
|
63
|
-
# .on_failure { |error| return json_error(error) } # this won't run
|
|
56
|
+
# .on_success(unhandled: true) { |value| return json_success(value) }
|
|
57
|
+
# .on_failure(unhandled: true) { |error| return json_error(error) } # this won't run
|
|
64
58
|
# end
|
|
65
59
|
#
|
|
66
60
|
# private
|
|
@@ -74,8 +68,13 @@ module FService
|
|
|
74
68
|
# @yieldparam type type of the failure object
|
|
75
69
|
# @return [Success, Failure] the original Result object
|
|
76
70
|
# @api public
|
|
77
|
-
def on_success(*target_types)
|
|
78
|
-
|
|
71
|
+
def on_success(*target_types, unhandled: false)
|
|
72
|
+
if successful? && unhandled? && expected_type?(target_types, unhandled: unhandled)
|
|
73
|
+
match_types(target_types)
|
|
74
|
+
yield(*to_ary)
|
|
75
|
+
@handled = true
|
|
76
|
+
freeze
|
|
77
|
+
end
|
|
79
78
|
|
|
80
79
|
self
|
|
81
80
|
end
|
|
@@ -99,12 +98,33 @@ module FService
|
|
|
99
98
|
# end
|
|
100
99
|
# end
|
|
101
100
|
#
|
|
101
|
+
# @example
|
|
102
|
+
# class UsersController < BaseController
|
|
103
|
+
# def update
|
|
104
|
+
# User::Update.(user: user)
|
|
105
|
+
# .on_success(:unhandled: true) { |value| return json_success(value) } # this won't run
|
|
106
|
+
# .on_failure(:type, :type2) { |error| return json_error(error) } # runs only if type matches
|
|
107
|
+
# .on_failure(:unhandled: true) { |error| return json_error(error) }
|
|
108
|
+
# end
|
|
109
|
+
#
|
|
110
|
+
# private
|
|
111
|
+
#
|
|
112
|
+
# def user
|
|
113
|
+
# @user ||= User.find_by!(slug: params[:slug])
|
|
114
|
+
# end
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
102
117
|
# @yieldparam value value of the failure object
|
|
103
118
|
# @yieldparam type type of the failure object
|
|
104
119
|
# @return [Success, Failure] the original Result object
|
|
105
120
|
# @api public
|
|
106
|
-
def on_failure(*target_types)
|
|
107
|
-
|
|
121
|
+
def on_failure(*target_types, unhandled: false)
|
|
122
|
+
if failed? && unhandled? && expected_type?(target_types, unhandled: unhandled)
|
|
123
|
+
match_types(target_types)
|
|
124
|
+
yield(*to_ary)
|
|
125
|
+
@handled = true
|
|
126
|
+
freeze
|
|
127
|
+
end
|
|
108
128
|
|
|
109
129
|
self
|
|
110
130
|
end
|
|
@@ -115,13 +135,25 @@ module FService
|
|
|
115
135
|
def to_ary
|
|
116
136
|
data = successful? ? value : error
|
|
117
137
|
|
|
118
|
-
[data,
|
|
138
|
+
[data, @matching_types.first]
|
|
119
139
|
end
|
|
120
140
|
|
|
121
141
|
private
|
|
122
142
|
|
|
123
|
-
def
|
|
124
|
-
|
|
143
|
+
def handled?
|
|
144
|
+
@handled
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def unhandled?
|
|
148
|
+
!handled?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def expected_type?(target_types, unhandled:)
|
|
152
|
+
target_types.empty? || unhandled || target_types.any? { |target_type| types.include?(target_type) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def match_types(target_types)
|
|
156
|
+
@matching_types = target_types.empty? ? types : target_types & types
|
|
125
157
|
end
|
|
126
158
|
end
|
|
127
159
|
end
|
|
@@ -10,20 +10,19 @@ module FService
|
|
|
10
10
|
#
|
|
11
11
|
# @!attribute [r] error
|
|
12
12
|
# @return [Object] the provided error for the result
|
|
13
|
-
# @!attribute [r]
|
|
14
|
-
# @return [Object] the provided
|
|
13
|
+
# @!attribute [r] types
|
|
14
|
+
# @return [Object] the provided types for the result. Defaults to nil.
|
|
15
15
|
# @api public
|
|
16
16
|
class Failure < Result::Base
|
|
17
|
-
attr_reader :error
|
|
17
|
+
attr_reader :error
|
|
18
18
|
|
|
19
19
|
# Creates a failed operation.
|
|
20
20
|
# You usually shouldn't call this directly. See {FService::Base#Failure}.
|
|
21
21
|
#
|
|
22
22
|
# @param error [Object] failure value.
|
|
23
|
-
def initialize(error,
|
|
23
|
+
def initialize(error, types = [])
|
|
24
|
+
super(types)
|
|
24
25
|
@error = error
|
|
25
|
-
@type = type
|
|
26
|
-
freeze
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
# Returns false.
|
|
@@ -110,7 +109,12 @@ module FService
|
|
|
110
109
|
def and_then
|
|
111
110
|
self
|
|
112
111
|
end
|
|
113
|
-
|
|
112
|
+
|
|
113
|
+
# See #and_then
|
|
114
|
+
def then
|
|
115
|
+
FService.deprecate!(name: "#{self.class}##{__method__}", alternative: '#and_then', from: caller[0])
|
|
116
|
+
and_then
|
|
117
|
+
end
|
|
114
118
|
|
|
115
119
|
# Outputs a string representation of the object
|
|
116
120
|
#
|
|
@@ -9,20 +9,19 @@ module FService
|
|
|
9
9
|
#
|
|
10
10
|
# @!attribute [r] value
|
|
11
11
|
# @return [Object] the provided value for the result
|
|
12
|
-
# @!attribute [r]
|
|
13
|
-
# @return [Object] the provided
|
|
12
|
+
# @!attribute [r] types
|
|
13
|
+
# @return [Object] the provided types for the result. Defaults to nil.
|
|
14
14
|
# @api public
|
|
15
15
|
class Success < Result::Base
|
|
16
|
-
attr_reader :value
|
|
16
|
+
attr_reader :value
|
|
17
17
|
|
|
18
18
|
# Creates a successful operation.
|
|
19
19
|
# You usually shouldn't call this directly. See {FService::Base#Success}.
|
|
20
20
|
#
|
|
21
21
|
# @param value [Object] success value.
|
|
22
|
-
def initialize(value,
|
|
22
|
+
def initialize(value, types = [])
|
|
23
|
+
super(types)
|
|
23
24
|
@value = value
|
|
24
|
-
@type = type
|
|
25
|
-
freeze
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
# Returns true.
|
|
@@ -79,11 +78,17 @@ module FService
|
|
|
79
78
|
# end
|
|
80
79
|
#
|
|
81
80
|
# @yieldparam value pass {#value} to a block
|
|
82
|
-
# @yieldparam
|
|
81
|
+
# @yieldparam types pass {#types} to a block
|
|
83
82
|
def and_then
|
|
84
83
|
yield(*to_ary)
|
|
85
84
|
end
|
|
86
|
-
|
|
85
|
+
|
|
86
|
+
# See #and_then
|
|
87
|
+
def then(&block)
|
|
88
|
+
FService.deprecate!(name: "#{self.class}##{__method__}", alternative: '#and_then', from: caller[0])
|
|
89
|
+
|
|
90
|
+
and_then(&block)
|
|
91
|
+
end
|
|
87
92
|
|
|
88
93
|
# Returns itself to the given block.
|
|
89
94
|
# Use this to chain multiple actions or service calls (only valid when they return a Result).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Methods to mock a FService result from a service call.
|
|
4
|
+
module FServiceResultHelpers
|
|
5
|
+
# Create an Fservice result Success or Failure.
|
|
6
|
+
def f_service_result(result, value = nil, types = [])
|
|
7
|
+
if result == :success
|
|
8
|
+
FService::Result::Success.new(value, Array(types))
|
|
9
|
+
else
|
|
10
|
+
FService::Result::Failure.new(value, Array(types))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Mock a Fservice service call returning a result.
|
|
15
|
+
def mock_service(service, result: :success, value: nil, type: :not_passed, types: [])
|
|
16
|
+
result_types = Array(types)
|
|
17
|
+
|
|
18
|
+
if type != :not_passed
|
|
19
|
+
alternative = "mock_service(..., types: [#{type.inspect}])"
|
|
20
|
+
name = 'mock_service'
|
|
21
|
+
FService.deprecate_argument_name(name: name, argument_name: :type, alternative: alternative, from: caller[0])
|
|
22
|
+
result_types = Array(type)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
service_result = f_service_result(result, value, result_types)
|
|
26
|
+
allow(service).to receive(:call).and_return(service_result)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
RSpec.configure do |config|
|
|
31
|
+
config.include FServiceResultHelpers
|
|
32
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec/expectations'
|
|
4
|
+
|
|
5
|
+
RSpec::Matchers.define :have_failed_with do |*expected_types|
|
|
6
|
+
match do |actual|
|
|
7
|
+
matched = actual.is_a?(FService::Result::Failure) && actual.types == expected_types
|
|
8
|
+
|
|
9
|
+
matched &&= actual.error == @expected_error if defined?(@expected_error)
|
|
10
|
+
|
|
11
|
+
matched
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
chain :and_error do |expected_error|
|
|
15
|
+
@expected_error = expected_error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
failure_message do |actual|
|
|
19
|
+
if actual.is_a?(FService::Result::Failure)
|
|
20
|
+
message = "expected failure's types '#{actual.types.inspect}' to be equal '#{expected_types.inspect}'"
|
|
21
|
+
if defined?(@expected_error)
|
|
22
|
+
has_description = @expected_error.respond_to?(:description)
|
|
23
|
+
message += " and error '#{actual.error.inspect}' to be "
|
|
24
|
+
message += has_description ? @expected_error.description : "equal '#{@expected_error.inspect}'"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
message
|
|
28
|
+
else
|
|
29
|
+
"result '#{actual.inspect}' is not a Failure object"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
RSpec::Matchers.define :have_succeed_with do |*expected_types|
|
|
35
|
+
match do |actual|
|
|
36
|
+
matched = actual.is_a?(FService::Result::Success) && actual.types == expected_types
|
|
37
|
+
|
|
38
|
+
matched &&= values_match?(@expected_value, actual.value) if defined?(@expected_value)
|
|
39
|
+
|
|
40
|
+
matched
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
chain :and_value do |expected_value|
|
|
44
|
+
@expected_value = expected_value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
failure_message do |actual|
|
|
48
|
+
if actual.is_a?(FService::Result::Success)
|
|
49
|
+
message = "expected success's types '#{actual.types.inspect}' to be equal '#{expected_types.inspect}'"
|
|
50
|
+
if defined?(@expected_value)
|
|
51
|
+
has_description = @expected_value.respond_to?(:description)
|
|
52
|
+
message += " and value '#{actual.value.inspect}' to be "
|
|
53
|
+
message += has_description ? @expected_value.description : "equal '#{@expected_value.inspect}'"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
message
|
|
57
|
+
else
|
|
58
|
+
"result '#{actual.inspect}' is not a Success object"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/f_service/version.rb
CHANGED
data/lib/f_service.rb
CHANGED
|
@@ -9,9 +9,24 @@ module FService
|
|
|
9
9
|
# Marks a method as deprecated
|
|
10
10
|
#
|
|
11
11
|
# @api private
|
|
12
|
-
def self.deprecate!(name:, alternative:)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
def self.deprecate!(name:, alternative:, from: nil)
|
|
13
|
+
warn_message = ["\n[DEPRECATED] #{name} is deprecated; "]
|
|
14
|
+
warn_message << ["called from #{from}; "] unless from.nil?
|
|
15
|
+
warn_message << "use #{alternative} instead. "
|
|
16
|
+
warn_message << 'It will be removed on the next release.'
|
|
17
|
+
|
|
18
|
+
warn warn_message.join("\n")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Marks an argument as deprecated
|
|
22
|
+
#
|
|
23
|
+
# @api private
|
|
24
|
+
def self.deprecate_argument_name(name:, argument_name:, alternative:, from: nil)
|
|
25
|
+
warn_message = ["\n[DEPRECATED] #{name} passing #{argument_name.inspect} is deprecated; "]
|
|
26
|
+
warn_message << ["called from #{from}; "] unless from.nil?
|
|
27
|
+
warn_message << "use #{alternative} instead. "
|
|
28
|
+
warn_message << 'It will be removed on the next release.'
|
|
29
|
+
|
|
30
|
+
warn warn_message.join("\n")
|
|
16
31
|
end
|
|
17
32
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: f_service
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Fretadao Tech Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-05-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -29,11 +29,12 @@ description: |2
|
|
|
29
29
|
The goal is to make services simpler, safer and more composable.
|
|
30
30
|
It uses the Result monad for handling operations.
|
|
31
31
|
email:
|
|
32
|
-
-
|
|
32
|
+
- tech@fretadao.com.br
|
|
33
33
|
executables: []
|
|
34
34
|
extensions: []
|
|
35
35
|
extra_rdoc_files: []
|
|
36
36
|
files:
|
|
37
|
+
- ".github/.dependabot.yml"
|
|
37
38
|
- ".github/workflows/tests-and-linter.yml"
|
|
38
39
|
- ".gitignore"
|
|
39
40
|
- ".rubocop.yml"
|
|
@@ -52,6 +53,12 @@ files:
|
|
|
52
53
|
- lib/f_service/result/base.rb
|
|
53
54
|
- lib/f_service/result/failure.rb
|
|
54
55
|
- lib/f_service/result/success.rb
|
|
56
|
+
- lib/f_service/rspec.rb
|
|
57
|
+
- lib/f_service/rspec/support.rb
|
|
58
|
+
- lib/f_service/rspec/support/helpers.rb
|
|
59
|
+
- lib/f_service/rspec/support/helpers/result.rb
|
|
60
|
+
- lib/f_service/rspec/support/matchers.rb
|
|
61
|
+
- lib/f_service/rspec/support/matchers/result.rb
|
|
55
62
|
- lib/f_service/version.rb
|
|
56
63
|
- logo.png
|
|
57
64
|
homepage: https://github.com/Fretadao/f_service
|
|
@@ -77,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
77
84
|
- !ruby/object:Gem::Version
|
|
78
85
|
version: '0'
|
|
79
86
|
requirements: []
|
|
80
|
-
rubygems_version: 3.
|
|
87
|
+
rubygems_version: 3.3.5
|
|
81
88
|
signing_key:
|
|
82
89
|
specification_version: 4
|
|
83
90
|
summary: A small, monad-based service class
|