restfulness 0.1.0 → 0.2.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/README.md +98 -7
- data/example/README.md +1 -1
- data/example/app.rb +1 -1
- data/lib/restfulness.rb +3 -0
- data/lib/restfulness/dispatchers/rack.rb +6 -19
- data/lib/restfulness/exceptions.rb +5 -5
- data/lib/restfulness/request.rb +1 -1
- data/lib/restfulness/resource.rb +9 -14
- data/lib/restfulness/resources/events.rb +45 -0
- data/lib/restfulness/response.rb +36 -14
- data/lib/restfulness/version.rb +1 -1
- data/spec/unit/exceptions_spec.rb +2 -2
- data/spec/unit/resource_spec.rb +0 -18
- data/spec/unit/resources/events_spec.rb +68 -0
- data/spec/unit/response_spec.rb +71 -5
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 656fc9b27618b6b00f0cdc31dff51fc59081881b
|
|
4
|
+
data.tar.gz: eed7ea6153eb14c67d0888643287ed911bfb477a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d06a2ee69944bfd705f22516c246ec49293488e69bc7bc5f7357cbe3830ac1b283e5e5046063c4e22e427d5ddf2541811c2d49b0e285eb742c63c0f132b64564
|
|
7
|
+
data.tar.gz: b297d63290e7df33142641c2c8d072d97bb4494ece946b8ec6f2ddb4c44e10ac6cd997729689ede5fefb299f492c77b74e82b9646f04793ca49f77c473a99e70
|
data/README.md
CHANGED
|
@@ -27,7 +27,7 @@ module Twitter
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
desc "Return a personal timeline."
|
|
30
|
-
|
|
30
|
+
get :home_timeline do
|
|
31
31
|
authenticate!
|
|
32
32
|
current_user.statuses.limit(20)
|
|
33
33
|
end
|
|
@@ -128,8 +128,6 @@ run Rack::URLMap.new(
|
|
|
128
128
|
)
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
By default, Restfulness comes with a Rack compatible dispatcher, but in the future it might make sense to add others.
|
|
132
|
-
|
|
133
131
|
If you want to run Restfulness standalone, simply create a `config.ru` that will load up your application:
|
|
134
132
|
|
|
135
133
|
```ruby
|
|
@@ -143,7 +141,7 @@ You can then run this with rackup:
|
|
|
143
141
|
bundle exec rackup
|
|
144
142
|
```
|
|
145
143
|
|
|
146
|
-
For
|
|
144
|
+
For a very simple example project, checkout the `/example` directory in the source code.
|
|
147
145
|
|
|
148
146
|
|
|
149
147
|
### Routes
|
|
@@ -179,6 +177,7 @@ Resources are like Controllers in a Rails project. They handle the basic HTTP ac
|
|
|
179
177
|
* `get`
|
|
180
178
|
* `head`
|
|
181
179
|
* `post`
|
|
180
|
+
* `patch`
|
|
182
181
|
* `put`
|
|
183
182
|
* `delete`
|
|
184
183
|
* `options` - this is the only action provded by default
|
|
@@ -192,8 +191,8 @@ class ProjectResource < Restfulness::Resource
|
|
|
192
191
|
project
|
|
193
192
|
end
|
|
194
193
|
|
|
195
|
-
# Update the object
|
|
196
|
-
def
|
|
194
|
+
# Update the existing object with some new attributes
|
|
195
|
+
def patch
|
|
197
196
|
project.update(params)
|
|
198
197
|
end
|
|
199
198
|
|
|
@@ -290,11 +289,99 @@ request.body # "{'key':'value'}" - string payload
|
|
|
290
289
|
request.params # {'key' => 'value'} - usually a JSON deserialized object
|
|
291
290
|
```
|
|
292
291
|
|
|
292
|
+
## Error Handling
|
|
293
|
+
|
|
294
|
+
If you want your application to return anything other than a 200 (or 202) status, you have a couple of options that allow you to send codes back to the client.
|
|
295
|
+
|
|
296
|
+
The easiest method is probably just to update the `response` code. Take the following example where we set a 403 response and the model's errors object in the payload:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
class ProjectResource < Restfulness::Resource
|
|
300
|
+
def patch
|
|
301
|
+
if project.update_attributes(request.params)
|
|
302
|
+
project
|
|
303
|
+
else
|
|
304
|
+
response.status = 403
|
|
305
|
+
project.errors
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The favourite method in Restfulness however is to use the `HTTPException` class and helper methods that will raise the error for you. For example:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
class ProjectResource < Restfulness::Resource
|
|
315
|
+
def patch
|
|
316
|
+
unless project.update_attributes(request.params)
|
|
317
|
+
forbidden!(project.errors)
|
|
318
|
+
end
|
|
319
|
+
project
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The `forbidden!` bang method will call the `error!` method, which in turn will raise an `HTTPException` with the appropriate status code. Exceptions are permitted to include a payload also, so you could override the `error!` method if you wished with code that will automatically re-format the payload. Another example:
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
# Regular resource
|
|
328
|
+
class ProjectResource < ApplicationResource
|
|
329
|
+
def patch
|
|
330
|
+
unless project.update_attributes(request.params)
|
|
331
|
+
forbidden!(project) # only send the project object!
|
|
332
|
+
end
|
|
333
|
+
project
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Main Application Resource
|
|
338
|
+
class ApplicationResource < Restfulness::Resource
|
|
339
|
+
# Overwrite the regular error handler so we can provide
|
|
340
|
+
# our own format.
|
|
341
|
+
def error!(status, payload = "", opts = {})
|
|
342
|
+
case payload
|
|
343
|
+
when ActiveRecord::Base # or your favourite ORM
|
|
344
|
+
payload = {
|
|
345
|
+
:errors => payload.errors.full_messages
|
|
346
|
+
}
|
|
347
|
+
end
|
|
348
|
+
super(status, payload, opts)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
This can be a really nice way to mold your errors into a standard format. All HTTP exceptions generated inside resources will pass through `error!`, even those that a triggered by a callback. It gives a great way to provide your own more complete result, or even just resort to a simple string.
|
|
355
|
+
|
|
356
|
+
The currently built in error methods are:
|
|
357
|
+
|
|
358
|
+
* `not_modified!`
|
|
359
|
+
* `bad_request!`
|
|
360
|
+
* `unauthorized!`
|
|
361
|
+
* `payment_required!`
|
|
362
|
+
* `forbidden!`
|
|
363
|
+
* `resource_not_found!`
|
|
364
|
+
* `request_timeout!`
|
|
365
|
+
* `conflict!`
|
|
366
|
+
* `gone!`
|
|
367
|
+
* `unprocessable_entity!`
|
|
368
|
+
|
|
369
|
+
If you'd like to see me more, please send us a pull request! Failing that, you can create your own by writing something along the lines of:
|
|
370
|
+
|
|
371
|
+
```ruby
|
|
372
|
+
def im_a_teapot!(payload = "")
|
|
373
|
+
error!(418, payload)
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
|
|
293
378
|
## Caveats and TODOs
|
|
294
379
|
|
|
295
380
|
Restfulness is still very much a work in progress. Here is a list of things that we'd like to improve or fix:
|
|
296
381
|
|
|
297
|
-
* Support for more serializers, not just JSON.
|
|
382
|
+
* Support for more serializers and content types, not just JSON.
|
|
383
|
+
* Support path methods for automatic URL generation.
|
|
384
|
+
* Support redirect exceptions.
|
|
298
385
|
* Reloading is a PITA (see note below).
|
|
299
386
|
* Needs more functional testing.
|
|
300
387
|
* Support for before and after filters in resources, although I'm slightly aprehensive about this.
|
|
@@ -335,6 +422,10 @@ Restfulness was created by Sam Lown <me@samlown.com> as a solution for building
|
|
|
335
422
|
|
|
336
423
|
## History
|
|
337
424
|
|
|
425
|
+
### 0.2.0 - October 17, 2013
|
|
426
|
+
|
|
427
|
+
Refactoring error handling and reporting so that it is easier to use and simpler.
|
|
428
|
+
|
|
338
429
|
### 0.1.0 - October 16, 2013
|
|
339
430
|
|
|
340
431
|
First release!
|
data/example/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Curl is your friend!
|
|
|
33
33
|
curl -v http://localhost:9292/projects
|
|
34
34
|
|
|
35
35
|
# Try updating it
|
|
36
|
-
curl -v -X
|
|
36
|
+
curl -v -X PATCH http://localhost:9292/project/project1 -H "Content-Type: application/json" -d "{\"name\":\"First Updated Project\"}"
|
|
37
37
|
|
|
38
38
|
# Finally remove it and check the list is empty
|
|
39
39
|
curl -v -X DELETE http://localhost:9292/project/project1
|
data/example/app.rb
CHANGED
data/lib/restfulness.rb
CHANGED
|
@@ -11,27 +11,12 @@ module Restfulness
|
|
|
11
11
|
request = Request.new(app)
|
|
12
12
|
prepare_request(env, rack_req, request)
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
# Prepare a suitable response
|
|
16
15
|
response = Response.new(request)
|
|
17
16
|
response.run
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
log_response(response.code)
|
|
22
|
-
[response.code, response.headers, [response.payload || ""]]
|
|
23
|
-
|
|
24
|
-
rescue HTTPException => e
|
|
25
|
-
log_response(e.code)
|
|
26
|
-
[e.code, {}, [e.payload || ""]]
|
|
27
|
-
|
|
28
|
-
#rescue Exception => e
|
|
29
|
-
# log_response(500)
|
|
30
|
-
# puts
|
|
31
|
-
# puts e.message
|
|
32
|
-
# puts e.backtrace
|
|
33
|
-
# # Something unknown went wrong
|
|
34
|
-
# [500, {}, [STATUSES[500]]]
|
|
18
|
+
log_response(response.status)
|
|
19
|
+
[response.status, response.headers, [response.payload || ""]]
|
|
35
20
|
end
|
|
36
21
|
|
|
37
22
|
protected
|
|
@@ -60,6 +45,8 @@ module Restfulness
|
|
|
60
45
|
:post
|
|
61
46
|
when 'PUT'
|
|
62
47
|
:put
|
|
48
|
+
when 'PATCH'
|
|
49
|
+
:patch
|
|
63
50
|
when 'OPTIONS'
|
|
64
51
|
:options
|
|
65
52
|
else
|
|
@@ -67,8 +54,8 @@ module Restfulness
|
|
|
67
54
|
end
|
|
68
55
|
end
|
|
69
56
|
|
|
70
|
-
def log_response(
|
|
71
|
-
logger.info("Completed #{
|
|
57
|
+
def log_response(status)
|
|
58
|
+
logger.info("Completed #{status} #{STATUSES[status]}")
|
|
72
59
|
end
|
|
73
60
|
|
|
74
61
|
def prepare_headers(env)
|
|
@@ -3,13 +3,13 @@ module Restfulness
|
|
|
3
3
|
|
|
4
4
|
class HTTPException < ::StandardError
|
|
5
5
|
|
|
6
|
-
attr_accessor :
|
|
6
|
+
attr_accessor :status, :payload, :headers
|
|
7
7
|
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
8
|
+
def initialize(status, payload = "", opts = {})
|
|
9
|
+
@status = status
|
|
10
10
|
@payload = payload
|
|
11
|
-
@headers = opts[:headers]
|
|
12
|
-
super(opts[:message] || STATUSES[
|
|
11
|
+
@headers = opts[:headers] || {}
|
|
12
|
+
super(opts[:message] || STATUSES[status])
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
end
|
data/lib/restfulness/request.rb
CHANGED
data/lib/restfulness/resource.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Restfulness
|
|
2
2
|
|
|
3
3
|
class Resource
|
|
4
|
+
include Resources::Events
|
|
4
5
|
|
|
5
6
|
attr_reader :request, :response
|
|
6
7
|
|
|
@@ -18,6 +19,8 @@ module Restfulness
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def call
|
|
22
|
+
# At some point, we might add custom callbacks here. If you really need them though,
|
|
23
|
+
# you can wrap around the call method easily.
|
|
21
24
|
send(request.action)
|
|
22
25
|
end
|
|
23
26
|
|
|
@@ -49,14 +52,14 @@ module Restfulness
|
|
|
49
52
|
|
|
50
53
|
def check_callbacks
|
|
51
54
|
# Access control
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
method_not_allowed! unless method_allowed?
|
|
56
|
+
unauthorized! unless authorized?
|
|
57
|
+
forbidden! unless allowed?
|
|
55
58
|
|
|
56
59
|
# The following callbacks only make sense for certain methods
|
|
57
60
|
if [:head, :get, :put, :delete].include?(request.action)
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
resource_not_found! unless exists?
|
|
60
63
|
|
|
61
64
|
if [:get, :head].include?(request.action)
|
|
62
65
|
# Resource status
|
|
@@ -66,26 +69,18 @@ module Restfulness
|
|
|
66
69
|
end
|
|
67
70
|
end
|
|
68
71
|
|
|
69
|
-
##
|
|
70
|
-
|
|
71
|
-
|
|
72
72
|
protected
|
|
73
73
|
|
|
74
|
-
def error(code, payload = nil, opts = {})
|
|
75
|
-
raise HTTPException.new(code, payload, opts)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
74
|
def logger
|
|
79
75
|
Restfulness.logger
|
|
80
76
|
end
|
|
81
77
|
|
|
82
|
-
|
|
83
78
|
private
|
|
84
79
|
|
|
85
80
|
def check_if_modified
|
|
86
81
|
date = request.headers[:if_modified_since]
|
|
87
82
|
if date && date == last_modified.to_s
|
|
88
|
-
|
|
83
|
+
not_modified!
|
|
89
84
|
end
|
|
90
85
|
response.headers['Last-Modified'] = last_modified
|
|
91
86
|
end
|
|
@@ -93,7 +88,7 @@ module Restfulness
|
|
|
93
88
|
def check_etag
|
|
94
89
|
tag = request.headers[:if_none_match]
|
|
95
90
|
if tag && tag == etag.to_s
|
|
96
|
-
|
|
91
|
+
not_modified!
|
|
97
92
|
end
|
|
98
93
|
response.headers['ETag'] = etag
|
|
99
94
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Restfulness
|
|
2
|
+
module Resources
|
|
3
|
+
|
|
4
|
+
# Special events that can be used in replies. The idea here is to cover
|
|
5
|
+
# the basic messages that most applications will deal with in their
|
|
6
|
+
# resources.
|
|
7
|
+
module Events
|
|
8
|
+
|
|
9
|
+
# Event definitions go here. We only support a limited subset
|
|
10
|
+
# so that we don't end up with loads of methods that are not used.
|
|
11
|
+
# If you'd like to see another, please send us a pull request!
|
|
12
|
+
SUPPORTED_EVENTS = [
|
|
13
|
+
# 300 Events
|
|
14
|
+
[304, :not_modified],
|
|
15
|
+
|
|
16
|
+
# 400 Events
|
|
17
|
+
[400, :bad_request],
|
|
18
|
+
[401, :unauthorized],
|
|
19
|
+
[402, :payment_required],
|
|
20
|
+
[403, :forbidden],
|
|
21
|
+
[404, :resource_not_found],
|
|
22
|
+
[405, :method_not_allowed],
|
|
23
|
+
[408, :request_timeout],
|
|
24
|
+
[409, :conflict],
|
|
25
|
+
[410, :gone],
|
|
26
|
+
[422, :unprocessable_entity]
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# Main error event handler
|
|
30
|
+
def error!(code, payload = "", opts = {})
|
|
31
|
+
raise HTTPException.new(code, payload, opts)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
SUPPORTED_EVENTS.each do |row|
|
|
35
|
+
define_method("#{row[1]}!") do |*args|
|
|
36
|
+
payload = args.shift || ""
|
|
37
|
+
opts = args.shift || {}
|
|
38
|
+
error!(row[0], payload, opts)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/restfulness/response.rb
CHANGED
|
@@ -7,14 +7,11 @@ module Restfulness
|
|
|
7
7
|
attr_reader :request
|
|
8
8
|
|
|
9
9
|
# Outgoing data
|
|
10
|
-
attr_reader :
|
|
11
|
-
|
|
10
|
+
attr_reader :status, :headers, :payload
|
|
12
11
|
|
|
13
12
|
def initialize(request)
|
|
14
13
|
@request = request
|
|
15
|
-
|
|
16
|
-
# Default headers
|
|
17
|
-
@headers = {'Content-Type' => 'application/json; charset=utf-8'}
|
|
14
|
+
@headers = {}
|
|
18
15
|
end
|
|
19
16
|
|
|
20
17
|
def run
|
|
@@ -31,15 +28,15 @@ module Restfulness
|
|
|
31
28
|
# Perform the actual work
|
|
32
29
|
result = resource.call
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
@payload = MultiJson.encode(result)
|
|
31
|
+
update_status_and_payload(result.nil? ? 204 : 200, result)
|
|
36
32
|
else
|
|
37
|
-
|
|
38
|
-
# This is not something we can deal with, pass it on
|
|
39
|
-
@code = 404
|
|
40
|
-
@payload = ""
|
|
33
|
+
update_status_and_payload(404)
|
|
41
34
|
end
|
|
42
|
-
|
|
35
|
+
|
|
36
|
+
rescue HTTPException => e # Deal with HTTP exceptions
|
|
37
|
+
logger.error(e.message)
|
|
38
|
+
headers.update(e.headers)
|
|
39
|
+
update_status_and_payload(e.status, e.payload)
|
|
43
40
|
end
|
|
44
41
|
|
|
45
42
|
def logger
|
|
@@ -47,9 +44,34 @@ module Restfulness
|
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
protected
|
|
47
|
+
|
|
48
|
+
def update_status_and_payload(status, payload = "")
|
|
49
|
+
self.status = status
|
|
50
|
+
self.payload = payload
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def status=(code)
|
|
54
|
+
@status = code
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def payload=(body)
|
|
58
|
+
if body.nil? || body.is_a?(String)
|
|
59
|
+
@payload = body.to_s
|
|
60
|
+
update_content_headers(:text)
|
|
61
|
+
else
|
|
62
|
+
@payload = MultiJson.encode(body)
|
|
63
|
+
update_content_headers(:json)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
50
66
|
|
|
51
|
-
def
|
|
52
|
-
|
|
67
|
+
def update_content_headers(type = :json)
|
|
68
|
+
case type
|
|
69
|
+
when :json
|
|
70
|
+
headers['Content-Type'] = 'application/json; charset=utf-8'
|
|
71
|
+
else # Assume text
|
|
72
|
+
headers['Content-Type'] = 'text/plain; charset=utf-8'
|
|
73
|
+
end
|
|
74
|
+
headers['Content-Length'] = payload.to_s.bytesize.to_s
|
|
53
75
|
end
|
|
54
76
|
|
|
55
77
|
end
|
data/lib/restfulness/version.rb
CHANGED
|
@@ -5,13 +5,13 @@ describe Restfulness::HTTPException do
|
|
|
5
5
|
describe "#initialize" do
|
|
6
6
|
it "should assign variables" do
|
|
7
7
|
obj = Restfulness::HTTPException.new(200, "payload", :message => 'foo', :headers => {})
|
|
8
|
-
obj.
|
|
8
|
+
obj.status.should eql(200)
|
|
9
9
|
obj.payload.should eql("payload")
|
|
10
10
|
obj.message.should eql('foo')
|
|
11
11
|
obj.headers.should eql({})
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
it "should use status
|
|
14
|
+
it "should use status status for message if none provided" do
|
|
15
15
|
obj = Restfulness::HTTPException.new(200, "payload")
|
|
16
16
|
obj.message.should eql('OK')
|
|
17
17
|
end
|
data/spec/unit/resource_spec.rb
CHANGED
|
@@ -224,22 +224,4 @@ describe Restfulness::Resource do
|
|
|
224
224
|
end
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
-
describe "#error" do
|
|
228
|
-
|
|
229
|
-
class Get418Resource < Restfulness::Resource
|
|
230
|
-
def get
|
|
231
|
-
error(418, {})
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
it "should raise a new exception" do
|
|
236
|
-
klass = Get418Resource
|
|
237
|
-
obj = klass.new(request, response)
|
|
238
|
-
expect {
|
|
239
|
-
obj.get
|
|
240
|
-
}.to raise_error(Restfulness::HTTPException, "I'm A Teapot")
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
end
|
|
244
|
-
|
|
245
227
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Restfulness::Resources::Events do
|
|
5
|
+
|
|
6
|
+
let :app do
|
|
7
|
+
Class.new(Restfulness::Application) do
|
|
8
|
+
routes do
|
|
9
|
+
# empty
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
let :request do
|
|
14
|
+
Restfulness::Request.new(app)
|
|
15
|
+
end
|
|
16
|
+
let :response do
|
|
17
|
+
Restfulness::Response.new(request)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
describe "#error" do
|
|
22
|
+
|
|
23
|
+
class Get418Resource < Restfulness::Resource
|
|
24
|
+
def get
|
|
25
|
+
error!(418, {})
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should raise a new exception" do
|
|
30
|
+
klass = Get418Resource
|
|
31
|
+
obj = klass.new(request, response)
|
|
32
|
+
expect {
|
|
33
|
+
obj.get
|
|
34
|
+
}.to raise_error(Restfulness::HTTPException, "I'm A Teapot")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "generic bang error events" do
|
|
39
|
+
|
|
40
|
+
let :klass do
|
|
41
|
+
Class.new(Restfulness::Resource)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
let :obj do
|
|
45
|
+
klass.new(request, response)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "should support bad_request!" do
|
|
49
|
+
expect {
|
|
50
|
+
obj.instance_eval do
|
|
51
|
+
bad_request!
|
|
52
|
+
end
|
|
53
|
+
}.to raise_error(Restfulness::HTTPException, "Bad Request")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should support bad_request! with paramters" do
|
|
57
|
+
obj.should_receive(:error!).with(400, {:pay => 'load'}, {})
|
|
58
|
+
obj.instance_eval do
|
|
59
|
+
bad_request!({:pay => 'load'}, {})
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
end
|
data/spec/unit/response_spec.rb
CHANGED
|
@@ -26,8 +26,9 @@ describe Restfulness::Response do
|
|
|
26
26
|
describe "#initialize" do
|
|
27
27
|
it "should assign request and headers" do
|
|
28
28
|
obj.request.should eql(request)
|
|
29
|
-
obj.headers.should eql({
|
|
30
|
-
obj.
|
|
29
|
+
obj.headers.should eql({})
|
|
30
|
+
obj.status.should be_nil
|
|
31
|
+
obj.payload.should be_nil
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
|
|
@@ -36,8 +37,9 @@ describe Restfulness::Response do
|
|
|
36
37
|
it "should not do anything" do
|
|
37
38
|
request.stub(:route).and_return(nil)
|
|
38
39
|
obj.run
|
|
39
|
-
obj.
|
|
40
|
+
obj.status.should eql(404)
|
|
40
41
|
obj.payload.should be_empty
|
|
42
|
+
obj.headers['Content-Type'].should match(/text\/plain/)
|
|
41
43
|
obj.headers['Content-Length'].should eql(0.to_s)
|
|
42
44
|
end
|
|
43
45
|
end
|
|
@@ -54,12 +56,76 @@ describe Restfulness::Response do
|
|
|
54
56
|
resource.should_receive(:call).and_return({:foo => 'bar'})
|
|
55
57
|
route.stub(:build_resource).and_return(resource)
|
|
56
58
|
obj.run
|
|
57
|
-
obj.
|
|
59
|
+
obj.status.should eql(200)
|
|
58
60
|
str = "{\"foo\":\"bar\"}"
|
|
59
61
|
obj.payload.should eql(str)
|
|
62
|
+
obj.headers['Content-Type'].should match(/application\/json/)
|
|
60
63
|
obj.headers['Content-Length'].should eql(str.bytesize.to_s)
|
|
61
|
-
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "should call resource and set 204 result if no content" do
|
|
67
|
+
request.stub(:route).and_return(route)
|
|
68
|
+
request.action = :get
|
|
69
|
+
resource = double(:Resource)
|
|
70
|
+
resource.should_receive(:check_callbacks)
|
|
71
|
+
resource.should_receive(:call).and_return(nil)
|
|
72
|
+
route.stub(:build_resource).and_return(resource)
|
|
73
|
+
obj.run
|
|
74
|
+
obj.status.should eql(204)
|
|
75
|
+
obj.headers['Content-Type'].should match(/text\/plain/)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "should set string content type if payload is a string" do
|
|
79
|
+
request.stub(:route).and_return(route)
|
|
80
|
+
request.action = :get
|
|
81
|
+
resource = double(:Resource)
|
|
82
|
+
resource.should_receive(:check_callbacks)
|
|
83
|
+
resource.should_receive(:call).and_return("This is a text message")
|
|
84
|
+
route.stub(:build_resource).and_return(resource)
|
|
85
|
+
obj.run
|
|
86
|
+
obj.status.should eql(200)
|
|
87
|
+
obj.headers['Content-Type'].should match(/text\/plain/)
|
|
88
|
+
end
|
|
62
89
|
end
|
|
90
|
+
|
|
91
|
+
context "with exceptions" do
|
|
92
|
+
let :route do
|
|
93
|
+
app.router.routes.first
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "should update the status and payload" do
|
|
97
|
+
request.stub(:route).and_return(route)
|
|
98
|
+
request.action = :get
|
|
99
|
+
resource = double(:Resource)
|
|
100
|
+
txt = "This is a text error"
|
|
101
|
+
resource.stub(:check_callbacks) do
|
|
102
|
+
raise Restfulness::HTTPException.new(418, txt)
|
|
103
|
+
end
|
|
104
|
+
route.stub(:build_resource).and_return(resource)
|
|
105
|
+
obj.run
|
|
106
|
+
obj.status.should eql(418)
|
|
107
|
+
obj.headers['Content-Type'].should match(/text\/plain/)
|
|
108
|
+
obj.payload.should eql(txt)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "should update the status and provide JSON payload" do
|
|
112
|
+
request.stub(:route).and_return(route)
|
|
113
|
+
request.action = :get
|
|
114
|
+
resource = double(:Resource)
|
|
115
|
+
err = {:error => "This is a text error"}
|
|
116
|
+
resource.stub(:check_callbacks) do
|
|
117
|
+
raise Restfulness::HTTPException.new(418, err)
|
|
118
|
+
end
|
|
119
|
+
route.stub(:build_resource).and_return(resource)
|
|
120
|
+
obj.run
|
|
121
|
+
obj.status.should eql(418)
|
|
122
|
+
obj.headers['Content-Type'].should match(/application\/json/)
|
|
123
|
+
obj.payload.should eql(err.to_json)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
63
129
|
end
|
|
64
130
|
|
|
65
131
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: restfulness
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Lown
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2013-10-
|
|
11
|
+
date: 2013-10-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -134,6 +134,7 @@ files:
|
|
|
134
134
|
- lib/restfulness/path.rb
|
|
135
135
|
- lib/restfulness/request.rb
|
|
136
136
|
- lib/restfulness/resource.rb
|
|
137
|
+
- lib/restfulness/resources/events.rb
|
|
137
138
|
- lib/restfulness/response.rb
|
|
138
139
|
- lib/restfulness/route.rb
|
|
139
140
|
- lib/restfulness/router.rb
|
|
@@ -148,6 +149,7 @@ files:
|
|
|
148
149
|
- spec/unit/path_spec.rb
|
|
149
150
|
- spec/unit/request_spec.rb
|
|
150
151
|
- spec/unit/resource_spec.rb
|
|
152
|
+
- spec/unit/resources/events_spec.rb
|
|
151
153
|
- spec/unit/response_spec.rb
|
|
152
154
|
- spec/unit/route_spec.rb
|
|
153
155
|
- spec/unit/router_spec.rb
|
|
@@ -184,6 +186,7 @@ test_files:
|
|
|
184
186
|
- spec/unit/path_spec.rb
|
|
185
187
|
- spec/unit/request_spec.rb
|
|
186
188
|
- spec/unit/resource_spec.rb
|
|
189
|
+
- spec/unit/resources/events_spec.rb
|
|
187
190
|
- spec/unit/response_spec.rb
|
|
188
191
|
- spec/unit/route_spec.rb
|
|
189
192
|
- spec/unit/router_spec.rb
|