restfolia 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/.travis.yml +3 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +11 -0
- data/Readme.md +101 -0
- data/ReadmeDeveloper.md +30 -0
- data/lib/restfolia/entry_point.rb +156 -0
- data/lib/restfolia/exceptions.rb +109 -0
- data/lib/restfolia/http/behaviour.rb +164 -0
- data/lib/restfolia/http/configuration.rb +74 -0
- data/lib/restfolia/http/request.rb +54 -0
- data/lib/restfolia/http.rb +135 -0
- data/lib/restfolia/resource.rb +109 -0
- data/lib/restfolia/resource_creator.rb +97 -0
- data/lib/restfolia/version.rb +3 -0
- data/lib/restfolia.rb +97 -0
- data/restfolia.gemspec +28 -0
- data/samples/changing_behaviour.rb +32 -0
- data/samples/changing_links_parse.rb +38 -0
- data/samples/cookies_options.rb +23 -0
- data/samples/headers_options.rb +27 -0
- data/samples/http_behaviour.rb +52 -0
- data/samples/using_custom_factory.rb +25 -0
- data/samples/using_custom_resource.rb +25 -0
- data/test/restfolia/entry_point_test.rb +123 -0
- data/test/restfolia/http_behaviour_test.rb +86 -0
- data/test/restfolia/http_configuration_test.rb +45 -0
- data/test/restfolia/resource_creator_test.rb +54 -0
- data/test/restfolia/resource_test.rb +89 -0
- data/test/restfolia_test.rb +10 -0
- data/test/support/json_samples.rb +41 -0
- data/test/support/stub_helpers.rb +36 -0
- data/test/test_helper.rb +13 -0
- metadata +182 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--plugin yard-tomdoc -r Readme.md - MIT-LICENSE ReadmeDeveloper.md
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Roger Leite http://1up4dev.org
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Restfolia [![Build Status][travis_status]][travis]
|
2
|
+
|
3
|
+
[travis_status]: https://secure.travis-ci.org/rogerleite/restfolia.png
|
4
|
+
[travis]: http://travis-ci.org/rogerleite/restfolia
|
5
|
+
|
6
|
+
REST client to consume and interact with Hypermedia API, using JSON as Media Type.
|
7
|
+
|
8
|
+
## Description
|
9
|
+
|
10
|
+
Restfolia is a REST client and it's main goal is help you **consume and interact** with Hypermedia APIs.
|
11
|
+
|
12
|
+
Against the grain, Restfolia is very opinionated about some REST's concepts:
|
13
|
+
|
14
|
+
* Aims only **JSON Media Type**.
|
15
|
+
* All responses are parsed and returned as Restfolia::Resource.
|
16
|
+
* Less is more. Restfolia is very proud to be small, easy to maintain and evolve. You can compare Restfolia's code with "Similar Projects" at page's bottom.
|
17
|
+
* Restfolia::Resource is Ruby object with attributes from JSON and can optionally contains **hypermedia links** which have to be a specific format. See the examples below.
|
18
|
+
* All code is very well documented, using [TomDoc](http://tomdoc.org style).
|
19
|
+
|
20
|
+
Obs: This is a draft version. Not ready for production (yet!).
|
21
|
+
|
22
|
+
## References
|
23
|
+
|
24
|
+
You can find more information about arquitecture REST below:
|
25
|
+
|
26
|
+
* [Roy Fielding's](http://roy.gbiv.com/untangled) see this post for example: [REST APIs must be hypertext-driven](http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven).
|
27
|
+
* [Rest in Practice](http://restinpractice.com), especially the chapter titled "Hypermedia Formats".
|
28
|
+
* [Mike Amundsen's Blog](http://amundsen.com/blog)
|
29
|
+
* ROAR - [Resource Oriented Arquitectures in Ruby](https://github.com/apotonick/roar) it seems really good to build a hypermedia API, of course you can go with Sinatra+rabl solutions too.
|
30
|
+
|
31
|
+
## Examples of use
|
32
|
+
|
33
|
+
```js
|
34
|
+
// GET http://localhost:9292/recursos/busca
|
35
|
+
{ "itens_por_pagina" : 10,
|
36
|
+
"paginal_atual" : 1,
|
37
|
+
"paginas_totais" : 1,
|
38
|
+
"query" : "",
|
39
|
+
"total_resultado" : 100,
|
40
|
+
"resultado" : [ { "id" : 1,
|
41
|
+
"name" : "Test1",
|
42
|
+
"links" : [ { "href" : "http://localhost:9292/recursos/id/1",
|
43
|
+
"rel" : "recurso",
|
44
|
+
"type" : "application/json"
|
45
|
+
} ]
|
46
|
+
},
|
47
|
+
{ "id" : 2,
|
48
|
+
"name" : "Test2",
|
49
|
+
"links" : [ { "href" : "http://localhost:9292/recursos/id/2",
|
50
|
+
"rel" : "recurso",
|
51
|
+
"type" : "application/json"
|
52
|
+
} ]
|
53
|
+
}
|
54
|
+
],
|
55
|
+
"links" : { "href" : "http://localhost:9292/recursos/busca",
|
56
|
+
"rel" : "self",
|
57
|
+
"type" : "application/json"
|
58
|
+
},
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
```js
|
63
|
+
// GET http://localhost:9292/recursos/id/1
|
64
|
+
{ "id" : 1,
|
65
|
+
"name" : "Test1",
|
66
|
+
"links" : { "href" : "http://localhost:9292/recursos/id/1",
|
67
|
+
"rel" : "self",
|
68
|
+
"type" : "application/json"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
```
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# getting a resource
|
75
|
+
resource = Restfolia.at('http://localhost:9292/recursos/busca').get
|
76
|
+
resource.pagina_atual # => 1
|
77
|
+
resource.resultado # => [#<Resource ...>, #<Resource ...>]
|
78
|
+
|
79
|
+
# example of hypermedia navigation
|
80
|
+
r1 = resource.resultado.first
|
81
|
+
r1 = r1.links("recurso").get # => #<Resource ...>
|
82
|
+
r1.name # => "Test1"
|
83
|
+
```
|
84
|
+
|
85
|
+
## Similar Projects
|
86
|
+
|
87
|
+
* [ROAR](https://github.com/apotonick/roar)
|
88
|
+
* [Leadlight](https://github.com/avdi/leadlight)
|
89
|
+
* [Frenetic](https://github.com/dlindahl/frenetic)
|
90
|
+
* [Restfulie](https://github.com/caelum/restfulie)
|
91
|
+
|
92
|
+
## What is "folia"?
|
93
|
+
|
94
|
+
Folia is a portuguese word and a simple translation in English can be:
|
95
|
+
|
96
|
+
> sf merry-making, merriment, revelry. que folia! what a fun!
|
97
|
+
|
98
|
+
## License
|
99
|
+
|
100
|
+
Restfolia is copyright 2012 Roger Leite and contributors. It is licensed under the MIT license. See the include MIT-LICENSE file for details.
|
101
|
+
|
data/ReadmeDeveloper.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
## Steps to generate doc
|
2
|
+
|
3
|
+
```
|
4
|
+
# dependencies
|
5
|
+
$ gem install yard
|
6
|
+
$ gem install yard-tomdoc
|
7
|
+
$ gem install redcarpet
|
8
|
+
|
9
|
+
# use params from .yardopts file
|
10
|
+
$ yard
|
11
|
+
$ open doc/index.html
|
12
|
+
```
|
13
|
+
**Obs:** ignore errors :X, until my [pull request](https://github.com/rubyworks/yard-tomdoc/pull/5) be accepted.
|
14
|
+
|
15
|
+
## TODO:
|
16
|
+
|
17
|
+
Improvements:
|
18
|
+
|
19
|
+
* Add "events" before and after request. This could be
|
20
|
+
cool to make projects that extends Restfolia features.
|
21
|
+
|
22
|
+
Resource & EntryPoint
|
23
|
+
|
24
|
+
* facilitate cache (Cache Control, ETag, Last-Modified).
|
25
|
+
This could be another project like restfolia-cazuza.
|
26
|
+
|
27
|
+
Future Ideas
|
28
|
+
|
29
|
+
* Support to JSON HAL
|
30
|
+
* Another project, to Restfolia be a "Restfulie like". Main mission: migrate old projects
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Restfolia
|
2
|
+
|
3
|
+
|
4
|
+
# Public: Responsible for request and validate a Resource.
|
5
|
+
# Abstract details of how to deal with HTTP, like headers,
|
6
|
+
# cookies, auth etc.
|
7
|
+
# The correct form to create an EntryPoint, is using Restfolia.at
|
8
|
+
# or links from some instance of Restfolia::Resource. See the
|
9
|
+
# examples for more details.
|
10
|
+
#
|
11
|
+
# Examples
|
12
|
+
#
|
13
|
+
# ep = Restfolia.at("http://fakeurl.com/some/service")
|
14
|
+
# # => #<EntryPoint ...>
|
15
|
+
#
|
16
|
+
# resource = Restfolia.at("http://fakeurl.com/service/id/1").get
|
17
|
+
# resource.links("contacts")
|
18
|
+
# # => #<EntryPoint ...> to "contacts" from this resource
|
19
|
+
class EntryPoint
|
20
|
+
|
21
|
+
include Restfolia::HTTP::Configuration
|
22
|
+
|
23
|
+
# Public: Returns the String url of EntryPoint.
|
24
|
+
attr_reader :url
|
25
|
+
|
26
|
+
# Public: Returns String that represents the relation of EntryPoint.
|
27
|
+
attr_reader :rel
|
28
|
+
|
29
|
+
# Public: Creates an EntryPoint.
|
30
|
+
#
|
31
|
+
# url - A String address of some API service.
|
32
|
+
# rel - An optional String that represents the relation of EntryPoint.
|
33
|
+
def initialize(url, rel = nil)
|
34
|
+
@url = url
|
35
|
+
@rel = rel
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Get the Resource from this EntryPoint's url.
|
39
|
+
#
|
40
|
+
# params - an optional query String or Hash object. String parameter
|
41
|
+
# is passed direct as query. Hash object, before mounting query,
|
42
|
+
# URI.encode is used on values.
|
43
|
+
#
|
44
|
+
# Examples
|
45
|
+
#
|
46
|
+
# # GET on http://service.com/search
|
47
|
+
# resource = Restfolia.at("http://service.com/search").get
|
48
|
+
#
|
49
|
+
# # GET on http://service.com/search?q=test
|
50
|
+
# resource = Restfolia.at("http://service.com/search").get(:q => "test")
|
51
|
+
# # or if you want to control your query, you can send a String
|
52
|
+
# resource = Restfolia.at("http://service.com/search").get("q=test")
|
53
|
+
#
|
54
|
+
# Returns depends on http code from response. For each "range"
|
55
|
+
# (ex. 2xx, 3xx ... etc) you can have a different return value.
|
56
|
+
# For 2xx range, you can expect an instance of Restfolia::Resource.
|
57
|
+
# You can see Restfolia::HttpBehaviour for more details.
|
58
|
+
#
|
59
|
+
# Raises Restfolia::ResponseError for unexpected conditions. See
|
60
|
+
# Restfolia::HTTP::Behaviour methods for more details.
|
61
|
+
# Raises URI::InvalidURIError if url attribute is invalid.
|
62
|
+
def get(params = nil)
|
63
|
+
query = if params && params.is_a?(String)
|
64
|
+
params
|
65
|
+
elsif params && params.is_a?(Hash)
|
66
|
+
params.map { |k, v| "#{k}=#{URI.encode(v)}" }.join
|
67
|
+
end
|
68
|
+
|
69
|
+
args = self.configuration.merge(:query => query)
|
70
|
+
http_resp = Restfolia::HTTP::Request.do_request(:get, self.url, args)
|
71
|
+
Restfolia::HTTP.response_by_status_code(http_resp)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Public: Post data to EntryPoint's url.
|
75
|
+
#
|
76
|
+
# params - Hash object with data to be encoded as JSON and send as
|
77
|
+
# request body.
|
78
|
+
#
|
79
|
+
# Examples
|
80
|
+
#
|
81
|
+
# # Expecting API respond with 201 and Location header
|
82
|
+
# data = {:name => "Test"}
|
83
|
+
# resource = Restfolia.at("http://service.com/resource/1").post(data)
|
84
|
+
#
|
85
|
+
# Returns depends on http code from response. For each "range"
|
86
|
+
# (ex. 2xx, 3xx ... etc) you can have a different return value.
|
87
|
+
# For 2xx range, you can expect an instance of Restfolia::Resource.
|
88
|
+
# You can see Restfolia::HttpBehaviour for more details.
|
89
|
+
#
|
90
|
+
# Raises Restfolia::ResponseError for unexpected conditions. See
|
91
|
+
# Restfolia::HTTP::Behaviour methods for more details.
|
92
|
+
# Raises URI::InvalidURIError if url attribute is invalid.
|
93
|
+
def post(params)
|
94
|
+
body = MultiJson.dump(params)
|
95
|
+
|
96
|
+
args = self.configuration.merge(:body => body)
|
97
|
+
http_resp = Restfolia::HTTP::Request.do_request(:post, self.url, args)
|
98
|
+
Restfolia::HTTP.response_by_status_code(http_resp)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: Put data to EntryPoint's url.
|
102
|
+
#
|
103
|
+
# params - Hash object with data to be encoded as JSON and send as
|
104
|
+
# request body.
|
105
|
+
#
|
106
|
+
# Examples
|
107
|
+
#
|
108
|
+
# # Expecting API respond with 201 and Location header
|
109
|
+
# data = {:name => "Test"}
|
110
|
+
# resource = Restfolia.at("http://service.com/resource/1").put(data)
|
111
|
+
#
|
112
|
+
# Returns depends on http code from response. For each "range"
|
113
|
+
# (ex. 2xx, 3xx ... etc) you can have a different return value.
|
114
|
+
# For 2xx range, you can expect an instance of Restfolia::Resource.
|
115
|
+
# You can see Restfolia::HttpBehaviour for more details.
|
116
|
+
#
|
117
|
+
# Raises Restfolia::ResponseError for unexpected conditions. See
|
118
|
+
# Restfolia::HTTP::Behaviour methods for more details.
|
119
|
+
# Raises URI::InvalidURIError if url attribute is invalid.
|
120
|
+
def put(params)
|
121
|
+
body = MultiJson.dump(params)
|
122
|
+
|
123
|
+
args = self.configuration.merge(:body => body)
|
124
|
+
http_resp = Restfolia::HTTP::Request.do_request(:put, self.url, args)
|
125
|
+
Restfolia::HTTP.response_by_status_code(http_resp)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Public: Send Delete verb to EntryPoint's url.
|
129
|
+
#
|
130
|
+
# Examples
|
131
|
+
#
|
132
|
+
# # Expecting API respond with 204 and empty body
|
133
|
+
# resource = Restfolia.at("http://service.com/resource/1").delete
|
134
|
+
#
|
135
|
+
# Returns depends on http code from response. For each "range"
|
136
|
+
# (ex. 2xx, 3xx ... etc) you can have a different return value.
|
137
|
+
# For 2xx range, you can expect an instance of Restfolia::Resource.
|
138
|
+
# You can see Restfolia::HttpBehaviour for more details.
|
139
|
+
#
|
140
|
+
# Raises Restfolia::ResponseError for unexpected conditions. See
|
141
|
+
# Restfolia::HTTP::Behaviour methods for more details.
|
142
|
+
# Raises URI::InvalidURIError if url attribute is invalid.
|
143
|
+
def delete
|
144
|
+
http_resp = Restfolia::HTTP::Request.do_request(:delete, self.url, self.configuration)
|
145
|
+
Restfolia::HTTP.response_by_status_code(http_resp)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns url and rel for inspecting.
|
149
|
+
def inspect
|
150
|
+
"#<#{self.class} @url=\"#{@url}\" @rel=\"#{@rel}\">"
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Restfolia
|
2
|
+
|
3
|
+
# Public: Exception to represent an invalid HTTP response.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# begin
|
8
|
+
# # assuming http_response is 404 response
|
9
|
+
# raise ResponseError.new("message", caller, http_response)
|
10
|
+
# rescue Restfolia::ResponseError => ex
|
11
|
+
# ex.http_code # => 404
|
12
|
+
# ex.http_message # => "Not Found"
|
13
|
+
# ex.http_object # => http_response object
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
class ResponseError < StandardError
|
17
|
+
|
18
|
+
# Returns nil or #code from http_response instance
|
19
|
+
attr_reader :http_code
|
20
|
+
|
21
|
+
# Returns nil or status code definition from #http_code
|
22
|
+
attr_reader :http_message
|
23
|
+
|
24
|
+
# List of HTTP Status code definitions.
|
25
|
+
# Source http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
26
|
+
HTTP_CODE_MSG = {
|
27
|
+
# 2xx
|
28
|
+
200 => 'OK',
|
29
|
+
201 => 'Created',
|
30
|
+
202 => 'Accepted',
|
31
|
+
203 => 'Non-Authoritative Information',
|
32
|
+
204 => 'No Content',
|
33
|
+
205 => 'Reset Content',
|
34
|
+
206 => 'Partial Content',
|
35
|
+
# 3xx
|
36
|
+
300 => 'Multiple Choices',
|
37
|
+
301 => 'MovedPermanently',
|
38
|
+
302 => 'Found',
|
39
|
+
303 => 'SeeOther',
|
40
|
+
304 => 'NotModified',
|
41
|
+
305 => 'UseProxy',
|
42
|
+
306 => '(Unused)',
|
43
|
+
307 => 'TemporaryRedirect',
|
44
|
+
# 4xx
|
45
|
+
400 => 'Bad Request',
|
46
|
+
401 => 'Unauthorized',
|
47
|
+
402 => 'Payment Required',
|
48
|
+
403 => 'Forbidden',
|
49
|
+
404 => 'Not Found',
|
50
|
+
405 => 'Method Not Allowed',
|
51
|
+
406 => 'Not Acceptable',
|
52
|
+
407 => 'Proxy Authentication Required',
|
53
|
+
408 => 'Request Timeout',
|
54
|
+
409 => 'Conflict',
|
55
|
+
410 => 'Gone',
|
56
|
+
411 => 'Length Required',
|
57
|
+
412 => 'Precondition Failed',
|
58
|
+
413 => 'Request Entity Too Large',
|
59
|
+
414 => 'Request-URI Too Long',
|
60
|
+
415 => 'Unsupported Media Type',
|
61
|
+
416 => 'Requested Range Not Satisfiable',
|
62
|
+
417 => 'Expectation Failed',
|
63
|
+
# 5xx
|
64
|
+
500 => 'Internal Server Error',
|
65
|
+
501 => 'Not Implemented',
|
66
|
+
502 => 'Bad Gateway',
|
67
|
+
503 => 'Service Unavailable',
|
68
|
+
504 => 'Gateway Timeout',
|
69
|
+
505 => 'HTTP Version Not Supported'
|
70
|
+
}
|
71
|
+
|
72
|
+
# Public: Creates a ResponseError.
|
73
|
+
#
|
74
|
+
# message - String to describe the error.
|
75
|
+
# backtrace - Array, usually mounted by Kernel#caller.
|
76
|
+
# http_response - Net::HTTPResponse with error.
|
77
|
+
#
|
78
|
+
# Examples
|
79
|
+
#
|
80
|
+
# begin
|
81
|
+
# # assuming http_response is 404 response
|
82
|
+
# raise ResponseError.new("message", caller, http_response)
|
83
|
+
# rescue Restfolia::ResponseError => ex
|
84
|
+
# ex.http_code # => 404
|
85
|
+
# ex.http_message # => "Not Found"
|
86
|
+
# ex.http_object # => http_response object
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
def initialize(message, backtrace, http_response)
|
90
|
+
super(message)
|
91
|
+
self.set_backtrace(backtrace)
|
92
|
+
|
93
|
+
@http_response = http_response
|
94
|
+
@http_code, @http_message = nil
|
95
|
+
if http_response.respond_to?(:code)
|
96
|
+
@http_code = http_response.code.to_i
|
97
|
+
@http_message = HTTP_CODE_MSG[@http_code] \
|
98
|
+
|| "Unknown HTTP code (#{@http_code})"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns nil or Net::HTTPResponse instance.
|
103
|
+
def http_object
|
104
|
+
@http_response
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Restfolia::HTTP
|
2
|
+
|
3
|
+
# Public: Separated methods to handle HTTP response by status code.
|
4
|
+
module Behaviour
|
5
|
+
|
6
|
+
# Returns Restfolia::HTTP::Behaviour::Store instance.
|
7
|
+
def self.store
|
8
|
+
@store ||= Store.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# Internal: Helpers Available to behaviours blocks
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# Restfolia::HTTP.behaviours do
|
16
|
+
# on(200) do |http_response|
|
17
|
+
# helpers.parse_json(http_response.body)
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
class Helpers
|
21
|
+
|
22
|
+
# Internal: Parse response body, checking for errors.
|
23
|
+
#
|
24
|
+
# http_response - HTTP Response with body. Expected to be a JSON.
|
25
|
+
#
|
26
|
+
# Returns Hash who represents JSON parsed.
|
27
|
+
# Raises Restfolia::ResponseError if body seens invalid somehow.
|
28
|
+
def parse_json(http_response)
|
29
|
+
body = http_response.body
|
30
|
+
begin
|
31
|
+
MultiJson.load(body, :symbolize_keys => true)
|
32
|
+
rescue MultiJson::DecodeError => ex
|
33
|
+
msg = "Body should be a valid json. #{ex.message}"
|
34
|
+
raise Restfolia::ResponseError.new(msg, caller, http_response)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Request url with GET and forwards to Restfolia::HTTP.
|
39
|
+
#
|
40
|
+
# url - String. Ex: http://service.com/resources
|
41
|
+
#
|
42
|
+
# Returns what Restfolia::HTTP.response_by_status_code returns.
|
43
|
+
def follow_url(url)
|
44
|
+
http_resp = Request.do_request(:get, url)
|
45
|
+
Restfolia::HTTP.response_by_status_code(http_resp)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: Responsible to store behaviours. See #behaviours for details.
|
51
|
+
class Store
|
52
|
+
|
53
|
+
# Returns Restfolia::HTTP::Behaviour::Helpers instance.
|
54
|
+
attr_reader :helpers
|
55
|
+
|
56
|
+
# Public: Creates a Store.
|
57
|
+
def initialize
|
58
|
+
self.clear
|
59
|
+
@helpers = Helpers.new
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: clear all defined behaviours.
|
63
|
+
# Returns nothing.
|
64
|
+
def clear
|
65
|
+
@behaviours = {}
|
66
|
+
@behaviours_range = {}
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: It's a nice way to define configurations for
|
71
|
+
# your behaves using a block.
|
72
|
+
#
|
73
|
+
# block - Required block to customize your behaves. Below are
|
74
|
+
# the methods available inside block:
|
75
|
+
# #on - See #on
|
76
|
+
#
|
77
|
+
# Examples
|
78
|
+
#
|
79
|
+
# store = Restfolia::HTTP::Behaviour::Store.new
|
80
|
+
# store.behaviours do
|
81
|
+
# on(200) { '200 behaviour' }
|
82
|
+
# on([201, 204]) { 'behaviour for 201 and 204 codes' }
|
83
|
+
# on(300...400) { '3xx range' }
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# Returns nothing.
|
87
|
+
def behaviours(&block)
|
88
|
+
self.instance_eval(&block)
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Public: Add behaviour on this store. See #behaviours for
|
93
|
+
# examples.
|
94
|
+
#
|
95
|
+
# code - Integer or any object that respond to #include?
|
96
|
+
# block - Required block with behaviour for this code.
|
97
|
+
#
|
98
|
+
# Returns nothing.
|
99
|
+
def on(code, &block)
|
100
|
+
if code.is_a?(Integer)
|
101
|
+
@behaviours[code] = block
|
102
|
+
elsif code.respond_to?(:include?)
|
103
|
+
@behaviours_range[code] = block
|
104
|
+
end
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
# Public: Method called by #execute in case of 'not found' http code.
|
109
|
+
#
|
110
|
+
# http_response - Net::HTTPResponse object.
|
111
|
+
#
|
112
|
+
# Examples
|
113
|
+
#
|
114
|
+
# store = Restfolia::HTTP::Behaviour::Store.new
|
115
|
+
# store.behaviours do
|
116
|
+
# on(200) { '200 ok' }
|
117
|
+
# end
|
118
|
+
# http_resp = OpenStruct.new(:code => 201) #simulate response 201
|
119
|
+
# store.execute(http_resp)
|
120
|
+
# # => #<Restfolia::ResponseError "Undefined behaviour for 201" ...>
|
121
|
+
#
|
122
|
+
# Returns nothing.
|
123
|
+
# Raises Restfolia::ResponseError
|
124
|
+
def default_behaviour(http_response)
|
125
|
+
msg = "Undefined behaviour for #{http_response.code}"
|
126
|
+
raise Restfolia::ResponseError.new(msg, caller, http_response)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Public: Look for defined behaviour, based on HTTP code.
|
130
|
+
# If behaviour not found, call #default_behaviour.
|
131
|
+
#
|
132
|
+
# http_response - Net::HTTPResponse.
|
133
|
+
# Returns Result from Proc behaviour or default_behaviour method.
|
134
|
+
def execute(http_response)
|
135
|
+
code = http_response.code.to_i
|
136
|
+
if (behaviour = find(code))
|
137
|
+
behaviour.call(http_response)
|
138
|
+
else
|
139
|
+
default_behaviour(http_response)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
# Internal: Search for defined behaviour.
|
146
|
+
#
|
147
|
+
# code - Integer or object that respond to #include?
|
148
|
+
#
|
149
|
+
# Returns nil or Proc
|
150
|
+
def find(code)
|
151
|
+
behaviour = @behaviours[code]
|
152
|
+
if behaviour.nil?
|
153
|
+
_, behaviour = @behaviours_range.detect do |range, proc|
|
154
|
+
range.include?(code)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
behaviour
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Restfolia::HTTP
|
2
|
+
|
3
|
+
# Internal: Simply a bag of HTTP options like headers, cookies, auth ... etc.
|
4
|
+
module Configuration
|
5
|
+
|
6
|
+
# Public: Sets/Returns cookies values as String.
|
7
|
+
attr_accessor :cookies
|
8
|
+
|
9
|
+
# Public: Returns Hash to be used as Headers on request.
|
10
|
+
def headers
|
11
|
+
@headers ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: A fluent way to add Cookies to Request.
|
15
|
+
#
|
16
|
+
# cookies - String in cookie format.
|
17
|
+
#
|
18
|
+
# Examples
|
19
|
+
#
|
20
|
+
# # setting cookie from Google Translate
|
21
|
+
# cookies = "PREF=ID=07eb...; expires=Sat, 26-Apr-2014 19:19:36 GMT; path=/; domain=.google.com, NID=59...; expires=Fri, 26-Oct-2012 19:19:36 GMT; path=/; domain=.google.com; HttpOnly"
|
22
|
+
# resource = Restfolia.at("http://fake.com").
|
23
|
+
# set_cookies(cookies).get
|
24
|
+
#
|
25
|
+
# Returns self, always!
|
26
|
+
def set_cookies(cookies)
|
27
|
+
self.cookies = cookies
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: A fluent way to add HTTP headers.
|
32
|
+
# Headers informed here are merged with headers attribute.
|
33
|
+
#
|
34
|
+
# new_headers - Hash with headers.
|
35
|
+
#
|
36
|
+
# Examples
|
37
|
+
#
|
38
|
+
# entry_point = Restfolia.at("http://fake.com")
|
39
|
+
# entry_point.with_headers("X-Custom1" => "value",
|
40
|
+
# "X-Custom2" => "value2").get
|
41
|
+
#
|
42
|
+
# Returns self, always!
|
43
|
+
# Raises ArgumentError unless new_headers is a Hash.
|
44
|
+
def with_headers(new_headers)
|
45
|
+
unless new_headers.is_a?(Hash)
|
46
|
+
raise ArgumentError, "New Headers should Hash object."
|
47
|
+
end
|
48
|
+
|
49
|
+
headers.merge!(new_headers)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: A fluent way to add Content-Type and Accept headers.
|
54
|
+
#
|
55
|
+
# content_type - String value. Ex: "application/json"
|
56
|
+
#
|
57
|
+
# Returns self, always!
|
58
|
+
def as(content_type)
|
59
|
+
headers["Content-Type"] = content_type
|
60
|
+
headers["Accept"] = content_type
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
# Returns Hash with headers, cookies, auth ... etc.
|
67
|
+
def configuration
|
68
|
+
{:headers => headers,
|
69
|
+
:cookies => cookies}
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|