acfs 1.4.0 → 1.7.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/CHANGELOG.md +26 -0
- data/README.md +22 -39
- data/acfs.gemspec +8 -14
- data/lib/acfs/adapter/base.rb +2 -0
- data/lib/acfs/adapter/typhoeus.rb +16 -11
- data/lib/acfs/collections/paginatable.rb +1 -1
- data/lib/acfs/configuration.rb +13 -3
- data/lib/acfs/errors.rb +41 -21
- data/lib/acfs/global.rb +2 -2
- data/lib/acfs/location.rb +26 -32
- data/lib/acfs/middleware/base.rb +2 -2
- data/lib/acfs/middleware/json.rb +4 -2
- data/lib/acfs/middleware/logger.rb +4 -6
- data/lib/acfs/middleware/serializer.rb +1 -1
- data/lib/acfs/operation.rb +21 -8
- data/lib/acfs/request/callbacks.rb +4 -4
- data/lib/acfs/request.rb +4 -11
- data/lib/acfs/resource/attributes/date_time.rb +1 -1
- data/lib/acfs/resource/attributes/uuid.rb +1 -1
- data/lib/acfs/resource/attributes.rb +16 -15
- data/lib/acfs/resource/dirty.rb +2 -2
- data/lib/acfs/resource/initialization.rb +5 -5
- data/lib/acfs/resource/locatable.rb +11 -8
- data/lib/acfs/resource/operational.rb +6 -3
- data/lib/acfs/resource/persistence.rb +13 -15
- data/lib/acfs/resource/query_methods.rb +10 -10
- data/lib/acfs/resource/service.rb +2 -2
- data/lib/acfs/resource/validation.rb +17 -7
- data/lib/acfs/response.rb +5 -5
- data/lib/acfs/runner.rb +15 -15
- data/lib/acfs/service.rb +16 -19
- data/lib/acfs/singleton_resource.rb +2 -2
- data/lib/acfs/stub.rb +41 -31
- data/lib/acfs/version.rb +2 -2
- data/spec/acfs/adapter/typhoeus_spec.rb +2 -2
- data/spec/acfs/collection_spec.rb +66 -41
- data/spec/acfs/configuration_spec.rb +22 -12
- data/spec/acfs/global_spec.rb +11 -9
- data/spec/acfs/location_spec.rb +2 -2
- data/spec/acfs/middleware/json_spec.rb +22 -8
- data/spec/acfs/middleware/{msgpack_spec.rb → message_pack_spec.rb} +6 -6
- data/spec/acfs/operation_spec.rb +3 -2
- data/spec/acfs/request/callbacks_spec.rb +19 -10
- data/spec/acfs/request_spec.rb +16 -20
- data/spec/acfs/resource/attributes/boolean_spec.rb +32 -32
- data/spec/acfs/resource/attributes/date_time_spec.rb +16 -8
- data/spec/acfs/resource/attributes/dict_spec.rb +15 -9
- data/spec/acfs/resource/attributes/float_spec.rb +20 -10
- data/spec/acfs/resource/attributes/integer_spec.rb +10 -5
- data/spec/acfs/resource/attributes/list_spec.rb +13 -8
- data/spec/acfs/resource/attributes/uuid_spec.rb +12 -6
- data/spec/acfs/resource/attributes_spec.rb +37 -38
- data/spec/acfs/resource/dirty_spec.rb +6 -3
- data/spec/acfs/resource/initialization_spec.rb +4 -5
- data/spec/acfs/resource/loadable_spec.rb +3 -1
- data/spec/acfs/resource/locatable_spec.rb +24 -18
- data/spec/acfs/resource/{persistance_spec.rb → persistence_spec.rb} +122 -90
- data/spec/acfs/resource/query_methods_spec.rb +143 -110
- data/spec/acfs/resource/validation_spec.rb +34 -27
- data/spec/acfs/response/formats_spec.rb +8 -8
- data/spec/acfs/response/status_spec.rb +16 -9
- data/spec/acfs/runner_spec.rb +10 -8
- data/spec/acfs/service/middleware_spec.rb +3 -3
- data/spec/acfs/service_spec.rb +6 -5
- data/spec/acfs/singleton_resource_spec.rb +2 -1
- data/spec/acfs/stub_spec.rb +57 -53
- data/spec/acfs_spec.rb +111 -93
- data/spec/spec_helper.rb +1 -2
- data/spec/support/response.rb +2 -2
- data/spec/support/service.rb +1 -1
- data/spec/support/shared/find_callbacks.rb +14 -10
- metadata +30 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3cbc57dd595fa4242fa029498d90ca5a497216d2003674296209b679b84a6df2
|
4
|
+
data.tar.gz: 31a009d88f2a028e2fc11513b68dd011f5cfa0b7f11a83238f167e807c365b35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba37a7c96e65b8774879835d93ccf43a5c91bcd77ce9cf604937544f0897ebf307ffbbf914d5e7304eb90e6f62139e37eb52b2e6406db4e86d454ef45bb4423b
|
7
|
+
data.tar.gz: 976193afda7d9e6d5bbdc3eef3ec7235a90abcaf23e79f055735cb663400c02448a683e734b9f3212b2a27c101fabad4055f0a0c5dff23a517885a7bda4cea43
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
---
|
7
7
|
|
8
8
|
### New
|
9
|
+
* Support for Ruby 3.1 and Rails 7.0
|
9
10
|
|
10
11
|
### Changes
|
11
12
|
|
@@ -14,6 +15,31 @@
|
|
14
15
|
### Breaks
|
15
16
|
|
16
17
|
|
18
|
+
## 1.6.0 - (2021-01-07)
|
19
|
+
---
|
20
|
+
|
21
|
+
### New
|
22
|
+
* Support Ruby 3.0
|
23
|
+
* Use keyword arguments in parameters and when calling methods
|
24
|
+
|
25
|
+
|
26
|
+
## 1.5.1 - (2020-12-30)
|
27
|
+
---
|
28
|
+
|
29
|
+
### Changes
|
30
|
+
* Revert back to using `::MultiJson`
|
31
|
+
|
32
|
+
|
33
|
+
## 1.5.0 - (2020-06-19)
|
34
|
+
---
|
35
|
+
|
36
|
+
### New
|
37
|
+
* Error classes for more HTTP error responses: `400`, `401`, `403`, `500`, `502`, `503`, `504`.
|
38
|
+
|
39
|
+
### Changes
|
40
|
+
* Replace deprecated MultiJson with core JSON module
|
41
|
+
|
42
|
+
|
17
43
|
## 1.4.0 - (2020-06-12)
|
18
44
|
---
|
19
45
|
|
data/README.md
CHANGED
@@ -8,13 +8,16 @@
|
|
8
8
|
|
9
9
|
Acfs is a library to develop API client libraries for single services within a larger service oriented application.
|
10
10
|
|
11
|
-
Acfs covers model and service abstraction, convenient query and filter methods, full middleware stack for pre-processing requests and responses
|
11
|
+
Acfs covers model and service abstraction, convenient query and filter methods, full middleware stack for pre-processing requests and responses, as well as automatic request queuing and parallel processing.
|
12
|
+
|
12
13
|
|
13
14
|
## Installation
|
14
15
|
|
15
16
|
Add this line to your application's Gemfile:
|
16
17
|
|
17
|
-
|
18
|
+
```ruby
|
19
|
+
gem 'acfs', '~> 1.7'
|
20
|
+
```
|
18
21
|
|
19
22
|
And then execute:
|
20
23
|
|
@@ -24,6 +27,7 @@ Or install it yourself as:
|
|
24
27
|
|
25
28
|
> gem install acfs
|
26
29
|
|
30
|
+
|
27
31
|
## Usage
|
28
32
|
|
29
33
|
First you need to define your service(s):
|
@@ -119,7 +123,7 @@ Acfs.run # This call will fire all request as parallel as possible.
|
|
119
123
|
@friends[0].name # => "Miraculix"
|
120
124
|
```
|
121
125
|
|
122
|
-
Use `.find_by` to get first element only. `.find_by` will call the `index`-Action and return the first resource. Optionally passed
|
126
|
+
Use `.find_by` to get first element only. `.find_by` will call the `index`-Action and return the first resource. Optionally passed parameters will be sent as `GET` parameters and can be used for filtering in the service's controller.
|
123
127
|
```ruby
|
124
128
|
@user = User.find_by age: 24
|
125
129
|
|
@@ -145,6 +149,7 @@ Acfs has basic update support using `PUT` requests:
|
|
145
149
|
@user.persisted? # => true
|
146
150
|
```
|
147
151
|
|
152
|
+
|
148
153
|
## Singleton resources
|
149
154
|
|
150
155
|
Singletons can be used in Acfs by creating a new resource which inherits from `SingletonResource`:
|
@@ -176,18 +181,17 @@ my_single.save # sends PUT request to /single
|
|
176
181
|
my_single.delete # sends DELETE request to /single
|
177
182
|
```
|
178
183
|
|
179
|
-
You also can pass parameters to the find call
|
184
|
+
You also can pass parameters to the find call. They will be sent as query parameters to the index action:
|
180
185
|
|
181
186
|
```ruby
|
182
187
|
my_single = Single.find name: 'Max'
|
183
188
|
Acfs.run # sends GET request with param to /single?name=Max
|
184
189
|
```
|
185
190
|
|
191
|
+
|
186
192
|
## Resource Inheritance
|
187
193
|
|
188
|
-
Acfs provides a resource inheritance similar to ActiveRecord Single Table Inheritance. If a
|
189
|
-
`type` attribute exists and is a valid subclass of your resource they will be converted
|
190
|
-
to you subclassed resources:
|
194
|
+
Acfs provides a resource inheritance similar to ActiveRecord Single Table Inheritance. If a `type` attribute exists and is a valid subclass of your resource they will be converted to you subclassed resources:
|
191
195
|
|
192
196
|
```ruby
|
193
197
|
class Computer < Acfs::Resource
|
@@ -198,8 +202,7 @@ class Pc < Computer end
|
|
198
202
|
class Mac < Computer end
|
199
203
|
```
|
200
204
|
|
201
|
-
With the following response on `GET /computers` the collection will contain the appropriate
|
202
|
-
subclass resources:
|
205
|
+
With the following response on `GET /computers` the collection will contain the appropriate subclass resources:
|
203
206
|
|
204
207
|
```json
|
205
208
|
[
|
@@ -219,6 +222,7 @@ Acfs.run
|
|
219
222
|
@computer[2].class # => Pc
|
220
223
|
```
|
221
224
|
|
225
|
+
|
222
226
|
## Stubbing
|
223
227
|
|
224
228
|
You can stub resources in applications using an Acfs service client:
|
@@ -243,9 +247,9 @@ it 'should find user number one' do
|
|
243
247
|
user = MyUser.find 1
|
244
248
|
Acfs.run
|
245
249
|
|
246
|
-
expect(user.id).to
|
247
|
-
expect(user.name).to
|
248
|
-
expect(user.age).to
|
250
|
+
expect(user.id).to eq 1
|
251
|
+
expect(user.name).to eq 'John Smith'
|
252
|
+
expect(user.age).to eq 32
|
249
253
|
|
250
254
|
expect(@stub).to be_called
|
251
255
|
expect(@stub).to_not be_called 5.times
|
@@ -260,8 +264,8 @@ end
|
|
260
264
|
it 'should allow stub resource creation' do
|
261
265
|
session = Session.create! ident: 'john@exmaple.org', password: 's3cr3t'
|
262
266
|
|
263
|
-
expect(session.id).to
|
264
|
-
expect(session.user).to
|
267
|
+
expect(session.id).to eq 'longhash'
|
268
|
+
expect(session.user).to eq 1
|
265
269
|
end
|
266
270
|
```
|
267
271
|
|
@@ -279,11 +283,10 @@ it 'should find user number one' do
|
|
279
283
|
end
|
280
284
|
```
|
281
285
|
|
282
|
-
## Instrumentation
|
283
286
|
|
284
|
-
|
287
|
+
## Instrumentation
|
285
288
|
|
286
|
-
Acfs
|
289
|
+
Acfs supports [instrumentation via active support][1] and exposes the following events:
|
287
290
|
|
288
291
|
* `acfs.operation.complete(operation, response)`: Acfs operation completed
|
289
292
|
* `acfs.runner.sync_run(operation)`: Run operation right now skipping queue.
|
@@ -291,26 +294,11 @@ Acfs expose to following events
|
|
291
294
|
* `acfs.before_run`: directly before `acfs.run`
|
292
295
|
* `acfs.run`: Run all queued operations.
|
293
296
|
|
294
|
-
Read [official guide][2]
|
297
|
+
Read the [official guide][2] on how to subscribe to these events.
|
295
298
|
|
296
299
|
[1]: http://guides.rubyonrails.org/active_support_instrumentation.html
|
297
300
|
[2]: http://guides.rubyonrails.org/active_support_instrumentation.html#subscribing-to-an-event
|
298
301
|
|
299
|
-
## Roadmap
|
300
|
-
|
301
|
-
* Update
|
302
|
-
* Better new? detection eg. storing ETag from request resources.
|
303
|
-
* Use PATCH for with only changed attributes and `If-Unmodifed-Since`
|
304
|
-
and `If-Match` header fields if resource was surly loaded from service
|
305
|
-
and not created with an id (e.g `User.new id: 5, name: "john"`).
|
306
|
-
* Conflict detection (ETag / If-Unmodified-Since)
|
307
|
-
* High level features
|
308
|
-
* Support for custom mime types on client and server side. (`application/vnd.myservice.user.v2+msgpack`)
|
309
|
-
* Server side components
|
310
|
-
* Reusing model definitions for generating responses?
|
311
|
-
* Rails responders providing REST operations with integrated ETag,
|
312
|
-
Modified Headers, conflict detection, ...
|
313
|
-
* Documentation
|
314
302
|
|
315
303
|
## Contributing
|
316
304
|
|
@@ -322,14 +310,9 @@ Read [official guide][2] to see to to subscribe.
|
|
322
310
|
7. Push to the branch (`git push origin my-new-feature`)
|
323
311
|
8. Create new Pull Request
|
324
312
|
|
325
|
-
## Contributors
|
326
|
-
|
327
|
-
* [Nicolas Fricke](https://github.com/nicolas-fricke)
|
328
|
-
* [Tino Junge](https://github.com/tino-junge)
|
329
|
-
* [Malte Swart](https://github.com/mswart)
|
330
313
|
|
331
314
|
## License
|
332
315
|
|
333
316
|
MIT License
|
334
317
|
|
335
|
-
Copyright (c) 2013 Jan Graichen. MIT license, see LICENSE for more details.
|
318
|
+
Copyright (c) 2013-2022 Jan Graichen. MIT license, see LICENSE for more details.
|
data/acfs.gemspec
CHANGED
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
|
|
16
16
|
An abstract API base client for service oriented application.
|
17
17
|
SUMMARY
|
18
18
|
|
19
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
20
|
+
|
19
21
|
spec.files = Dir['**/*'].grep(%r{
|
20
22
|
^((bin|lib|test|spec|features)/|
|
21
23
|
.*\.gemspec|.*LICENSE.*|.*README.*|.*CHANGELOG.*)
|
@@ -25,22 +27,14 @@ Gem::Specification.new do |spec|
|
|
25
27
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
28
|
spec.require_paths = %w[lib]
|
27
29
|
|
28
|
-
spec.
|
29
|
-
spec.add_runtime_dependency 'activemodel', '>= 4.2'
|
30
|
-
spec.add_runtime_dependency 'activesupport', '>= 4.2'
|
31
|
-
spec.add_runtime_dependency 'multi_json'
|
32
|
-
|
33
|
-
# Bundle update w/o version resolves to 0.3.3 ...
|
34
|
-
spec.add_runtime_dependency 'typhoeus', '~> 1.0'
|
30
|
+
spec.required_ruby_version = '>= 2.5.0'
|
35
31
|
|
32
|
+
spec.add_runtime_dependency 'actionpack', '>= 5.2'
|
33
|
+
spec.add_runtime_dependency 'activemodel', '>= 5.2'
|
34
|
+
spec.add_runtime_dependency 'activesupport', '>= 5.2'
|
35
|
+
spec.add_runtime_dependency 'multi_json', '~> 1.0'
|
36
36
|
spec.add_runtime_dependency 'rack'
|
37
|
+
spec.add_runtime_dependency 'typhoeus', '~> 1.0'
|
37
38
|
|
38
39
|
spec.add_development_dependency 'bundler'
|
39
|
-
|
40
|
-
if ENV['TRAVIS_BUILD_NUMBER'] && !ENV['TRAVIS_TAG']
|
41
|
-
# Append travis build number for auto-releases
|
42
|
-
# rubocop:disable Gemspec/DuplicatedAssignment
|
43
|
-
spec.version = "#{spec.version}.1.b#{ENV['TRAVIS_BUILD_NUMBER']}"
|
44
|
-
# rubocop:enable Gemspec/DuplicatedAssignment
|
45
|
-
end
|
46
40
|
end
|
data/lib/acfs/adapter/base.rb
CHANGED
@@ -7,14 +7,19 @@ module Acfs
|
|
7
7
|
DEFAULT_OPTIONS = {
|
8
8
|
tcp_keepalive: true,
|
9
9
|
tcp_keepidle: 5,
|
10
|
-
tcp_keepintvl: 5
|
10
|
+
tcp_keepintvl: 5,
|
11
11
|
}.freeze
|
12
12
|
|
13
13
|
# Adapter for Typhoeus.
|
14
14
|
#
|
15
15
|
class Typhoeus < Base
|
16
|
-
def initialize(
|
17
|
-
|
16
|
+
def initialize(**kwargs)
|
17
|
+
super
|
18
|
+
|
19
|
+
extra_opts = kwargs.delete(:opts)
|
20
|
+
|
21
|
+
@opts = DEFAULT_OPTIONS
|
22
|
+
@opts = @opts.merge(extra_opts) if extra_opts
|
18
23
|
@kwargs = kwargs
|
19
24
|
end
|
20
25
|
|
@@ -47,22 +52,22 @@ module Acfs
|
|
47
52
|
params: req.params,
|
48
53
|
headers: req.headers.merge(
|
49
54
|
'Expect' => '',
|
50
|
-
'Transfer-Encoding' => ''
|
55
|
+
'Transfer-Encoding' => '',
|
51
56
|
),
|
52
|
-
body: req.body
|
57
|
+
body: req.body,
|
53
58
|
}
|
54
59
|
|
55
|
-
request = ::Typhoeus::Request.new(req.url, **@opts
|
60
|
+
request = ::Typhoeus::Request.new(req.url, **@opts, **opts)
|
56
61
|
|
57
62
|
request.on_complete do |response|
|
58
|
-
if response.timed_out?
|
59
|
-
|
60
|
-
|
63
|
+
raise ::Acfs::TimeoutError.new(req) if response.timed_out?
|
64
|
+
|
65
|
+
if response.code.zero?
|
61
66
|
# Failed to get HTTP response
|
62
67
|
raise ::Acfs::RequestError.new(req, response.return_message)
|
63
|
-
else
|
64
|
-
req.complete! convert_response(req, response)
|
65
68
|
end
|
69
|
+
|
70
|
+
req.complete! convert_response(req, response)
|
66
71
|
end
|
67
72
|
|
68
73
|
request
|
data/lib/acfs/configuration.rb
CHANGED
@@ -28,7 +28,7 @@ module Acfs
|
|
28
28
|
#
|
29
29
|
def configure(&block)
|
30
30
|
if block.arity.positive?
|
31
|
-
|
31
|
+
yield self
|
32
32
|
else
|
33
33
|
instance_eval(&block)
|
34
34
|
end
|
@@ -70,8 +70,8 @@ module Acfs
|
|
70
70
|
# @return [undefined]
|
71
71
|
#
|
72
72
|
def load(filename)
|
73
|
-
config =
|
74
|
-
env
|
73
|
+
config = load_yaml_file(filename)
|
74
|
+
env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
75
75
|
|
76
76
|
config = config[env] if config.key? env
|
77
77
|
config.each do |key, value|
|
@@ -116,5 +116,15 @@ module Acfs
|
|
116
116
|
@current = configuration if configuration.is_a? Configuration
|
117
117
|
end
|
118
118
|
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def load_yaml_file(path)
|
123
|
+
if YAML.respond_to?(:safe_load_file)
|
124
|
+
YAML.safe_load_file(path, aliases: true)
|
125
|
+
else
|
126
|
+
YAML.safe_load(File.read(path), [], [], true)
|
127
|
+
end
|
128
|
+
end
|
119
129
|
end
|
120
130
|
end
|
data/lib/acfs/errors.rb
CHANGED
@@ -5,7 +5,7 @@ module Acfs
|
|
5
5
|
#
|
6
6
|
class Error < StandardError
|
7
7
|
def initialize(opts = {}, message = nil)
|
8
|
-
opts
|
8
|
+
opts[:message] = message if message
|
9
9
|
super opts[:message]
|
10
10
|
end
|
11
11
|
end
|
@@ -26,7 +26,7 @@ module Acfs
|
|
26
26
|
|
27
27
|
class TimeoutError < RequestError
|
28
28
|
def initialize(request)
|
29
|
-
super(request,
|
29
|
+
super(request, 'Timeout reached')
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -38,13 +38,13 @@ module Acfs
|
|
38
38
|
def initialize(opts = {})
|
39
39
|
@response = opts[:response]
|
40
40
|
|
41
|
-
if response
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
message = if response
|
42
|
+
(opts[:message] ? "#{opts[:message]}:" : 'Received') +
|
43
|
+
" #{response.code} for #{response.request.method.upcase}" \
|
44
|
+
" #{response.request.url} #{response.request.format}"
|
45
|
+
else
|
46
|
+
opts[:message] || 'Received erroneous response'
|
47
|
+
end
|
48
48
|
|
49
49
|
super opts, message
|
50
50
|
end
|
@@ -67,11 +67,19 @@ module Acfs
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
# 400
|
71
|
+
class BadRequest < ErroneousResponse; end
|
72
|
+
|
73
|
+
# 401
|
74
|
+
class Unauthorized < ErroneousResponse; end
|
75
|
+
|
76
|
+
# 403
|
77
|
+
class Forbidden < ErroneousResponse; end
|
74
78
|
|
79
|
+
# 404
|
80
|
+
class ResourceNotFound < ErroneousResponse; end
|
81
|
+
|
82
|
+
# 422
|
75
83
|
class InvalidResource < ErroneousResponse
|
76
84
|
attr_reader :errors, :resource
|
77
85
|
|
@@ -79,18 +87,31 @@ module Acfs
|
|
79
87
|
@errors = opts.delete :errors
|
80
88
|
@resource = opts.delete :resource
|
81
89
|
|
82
|
-
|
83
|
-
|
84
|
-
@errors.
|
85
|
-
|
86
|
-
|
87
|
-
|
90
|
+
case @errors
|
91
|
+
when Hash
|
92
|
+
opts[:message] ||= @errors.each_pair.map do |k, v|
|
93
|
+
@errors.is_a?(Array) ? "#{k}: #{v.join(', ')}" : "#{k}: #{v}"
|
94
|
+
end.join ', '
|
95
|
+
when Array
|
96
|
+
opts[:message] ||= @errors.join ', '
|
88
97
|
end
|
89
98
|
|
90
99
|
super
|
91
100
|
end
|
92
101
|
end
|
93
102
|
|
103
|
+
# 500
|
104
|
+
class ServerError < ErroneousResponse; end
|
105
|
+
|
106
|
+
# 502
|
107
|
+
class BadGateway < ErroneousResponse; end
|
108
|
+
|
109
|
+
# 503
|
110
|
+
class ServiceUnavailable < ErroneousResponse; end
|
111
|
+
|
112
|
+
# 504
|
113
|
+
class GatewayTimeout < ErroneousResponse; end
|
114
|
+
|
94
115
|
# A ResourceNotLoaded error will be thrown when calling some
|
95
116
|
# modifing methods on not loaded resources as it is usally
|
96
117
|
# unwanted to call e.g. `update_attributes` on a not loaded
|
@@ -111,8 +132,7 @@ module Acfs
|
|
111
132
|
# parent resource. Check if the type is set to the correct
|
112
133
|
# Acfs::Resource Name
|
113
134
|
class ResourceTypeError < Error
|
114
|
-
attr_reader :base_class
|
115
|
-
attr_reader :type_name
|
135
|
+
attr_reader :base_class, :type_name
|
116
136
|
|
117
137
|
def initialize(opts = {})
|
118
138
|
@base_class = opts.delete :base_class
|
data/lib/acfs/global.rb
CHANGED
@@ -71,12 +71,12 @@ module Acfs
|
|
71
71
|
def add_callback(resource, &block)
|
72
72
|
unless resource.respond_to?(:__callbacks__)
|
73
73
|
raise ArgumentError.new 'Given resource is not an Acfs resource ' \
|
74
|
-
|
74
|
+
"delegator but a: #{resource.class.name}"
|
75
75
|
end
|
76
76
|
return false if block.nil?
|
77
77
|
|
78
78
|
if resource.nil? || resource.loaded?
|
79
|
-
|
79
|
+
yield resource
|
80
80
|
else
|
81
81
|
resource.__callbacks__ << block
|
82
82
|
end
|
data/lib/acfs/location.rb
CHANGED
@@ -6,36 +6,31 @@ module Acfs
|
|
6
6
|
# Describes a URL with placeholders.
|
7
7
|
#
|
8
8
|
class Location
|
9
|
-
attr_reader :arguments, :raw, :struct, :
|
9
|
+
attr_reader :arguments, :raw, :struct, :vars
|
10
10
|
|
11
11
|
REGEXP = /^:([A-z][A-z0-9_]*)$/.freeze
|
12
12
|
|
13
|
-
def initialize(uri,
|
13
|
+
def initialize(uri, vars = {})
|
14
14
|
@raw = URI.parse uri
|
15
|
-
@
|
15
|
+
@vars = vars
|
16
16
|
@struct = raw.path.split('/').reject(&:empty?).map {|s| s =~ REGEXP ? Regexp.last_match[1].to_sym : s }
|
17
17
|
@arguments = struct.select {|s| s.is_a?(Symbol) }
|
18
18
|
end
|
19
19
|
|
20
|
-
def build(
|
21
|
-
|
22
|
-
raise ArgumentError.new "URI path arguments must be a hash, `#{args.inspect}' given."
|
23
|
-
end
|
24
|
-
|
25
|
-
self.class.new raw.to_s, args.merge(self.args)
|
20
|
+
def build(vars)
|
21
|
+
self.class.new raw.to_s, vars.stringify_keys.merge(self.vars)
|
26
22
|
end
|
27
23
|
|
28
24
|
def extract_from(*args)
|
29
|
-
|
30
|
-
|
31
|
-
end
|
25
|
+
vars = {}
|
26
|
+
arguments.each {|key| vars[key.to_s] = extract_arg(key, args) }
|
32
27
|
|
33
|
-
build
|
28
|
+
build(vars)
|
34
29
|
end
|
35
30
|
|
36
31
|
def str
|
37
32
|
uri = raw.dup
|
38
|
-
uri.path =
|
33
|
+
uri.path = "/#{struct.map {|s| lookup_variable(s) }.join('/')}"
|
39
34
|
uri.to_s
|
40
35
|
end
|
41
36
|
|
@@ -49,34 +44,33 @@ module Acfs
|
|
49
44
|
def extract_arg(key, hashes)
|
50
45
|
hashes.each_with_index do |hash, index|
|
51
46
|
if hash.key?(key)
|
52
|
-
return (index
|
47
|
+
return (index.zero? ? hash.delete(key) : hash.fetch(key))
|
53
48
|
end
|
54
49
|
end
|
55
50
|
|
56
51
|
nil
|
57
52
|
end
|
58
53
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
54
|
+
def lookup_variable(name)
|
55
|
+
return name unless name.is_a?(Symbol)
|
56
|
+
|
57
|
+
value = vars.fetch(name.to_s) do
|
58
|
+
if @raise.nil? || @raise
|
59
|
+
raise ArgumentError.new <<~ERROR.strip
|
60
|
+
URI path argument `#{name}' missing on `#{self}'. Given: `#{vars}.inspect'
|
61
|
+
ERROR
|
62
|
+
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
return ::URI.encode_www_form_component(value) unless value.empty?
|
64
|
+
":#{name}"
|
65
|
+
end
|
66
66
|
|
67
|
-
|
68
|
-
end
|
67
|
+
value = value.to_s.strip
|
69
68
|
|
70
|
-
|
71
|
-
|
72
|
-
args.fetch(sym) do
|
73
|
-
if args[:raise].nil? || args[:raise]
|
74
|
-
raise ArgumentError.new "URI path argument `#{sym}' missing on `#{self}'. Given: `#{args}.inspect'"
|
75
|
-
else
|
76
|
-
":#{sym}"
|
77
|
-
end
|
78
|
-
end
|
69
|
+
if value.empty?
|
70
|
+
raise ArgumentError.new "Cannot replace path argument `#{name}' with empty string."
|
79
71
|
end
|
72
|
+
|
73
|
+
::URI.encode_www_form_component(value)
|
80
74
|
end
|
81
75
|
end
|
82
76
|
end
|
data/lib/acfs/middleware/base.rb
CHANGED
data/lib/acfs/middleware/json.rb
CHANGED
@@ -12,11 +12,13 @@ module Acfs
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def encode(data)
|
15
|
-
::MultiJson.dump
|
15
|
+
::MultiJson.dump(data)
|
16
16
|
end
|
17
17
|
|
18
18
|
def decode(body)
|
19
|
-
::MultiJson.load
|
19
|
+
::MultiJson.load(body)
|
20
|
+
rescue ::MultiJson::ParseError => e
|
21
|
+
raise ::JSON::ParserError.new(e)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
@@ -7,19 +7,17 @@ module Acfs
|
|
7
7
|
# Log requests and responses.
|
8
8
|
#
|
9
9
|
class Logger < Base
|
10
|
-
|
10
|
+
attr_reader :logger
|
11
|
+
|
12
|
+
def initialize(app, **opts)
|
11
13
|
super
|
12
|
-
@logger = options[:logger]
|
14
|
+
@logger = options[:logger] || ::Logger.new($stdout)
|
13
15
|
end
|
14
16
|
|
15
17
|
def response(res, nxt)
|
16
18
|
logger.info "[ACFS] #{res.request.method.to_s.upcase} #{res.request.url} -> #{res.status}"
|
17
19
|
nxt.call res
|
18
20
|
end
|
19
|
-
|
20
|
-
def logger
|
21
|
-
@logger ||= ::Logger.new STDOUT
|
22
|
-
end
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
@@ -29,7 +29,7 @@ module Acfs
|
|
29
29
|
request.headers['Accept'] = accept.join(',')
|
30
30
|
|
31
31
|
request.on_complete do |response, nxt|
|
32
|
-
response.data = decode
|
32
|
+
response.data = decode(response.body) if mime == response.content_type
|
33
33
|
|
34
34
|
nxt.call response
|
35
35
|
end
|