nestful 1.0.7 → 1.1.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/Gemfile +1 -0
- data/lib/nestful/connection.rb +50 -30
- data/lib/nestful/exceptions.rb +38 -16
- data/lib/nestful/helpers.rb +26 -2
- data/lib/nestful/mash.rb +19 -25
- data/lib/nestful/request.rb +71 -42
- data/lib/nestful/response.rb +8 -3
- data/lib/nestful/version.rb +1 -1
- data/test/nestful/test_request.rb +6 -0
- metadata +7 -7
- data/lib/nestful/.DS_Store +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd97aa5fb848801d10cc36bfcc8a1f1d3997393e
|
4
|
+
data.tar.gz: a7f428dd9f1d1f363e8834f5180511d2eeec7036
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28b12558ec65700945a6a594a42710174114e3f87c56350aca183265d3be2a1e41ffb24db9468a81dbe17618982733d401395e65c0ce65710c7578c1182d6244
|
7
|
+
data.tar.gz: 2e10eec7723fbfb4cb5f291b7a904c1ce0e1f81927a5705a8332e41d7f5d171bfe6af8007f23b7fcf6574059679b5fb79f76fa8be43117d3824f60a25e26bb21
|
data/Gemfile
CHANGED
data/lib/nestful/connection.rb
CHANGED
@@ -53,45 +53,65 @@ module Nestful
|
|
53
53
|
# Makes a request to the remote service.
|
54
54
|
def request(method, path, *arguments)
|
55
55
|
response = http.send(method, path, *arguments)
|
56
|
+
response.uri = URI.join(endpoint, path)
|
57
|
+
|
56
58
|
handle_response(response)
|
57
59
|
|
58
|
-
rescue Timeout::Error => e
|
60
|
+
rescue Timeout::Error, Net::OpenTimeout => e
|
59
61
|
raise TimeoutError.new(e.message)
|
60
62
|
rescue OpenSSL::SSL::SSLError => e
|
61
63
|
raise SSLError.new(e.message)
|
64
|
+
rescue SocketError,
|
65
|
+
EOFError,
|
66
|
+
Net::HTTPBadResponse,
|
67
|
+
Net::HTTPHeaderSyntaxError,
|
68
|
+
Net::HTTPServerException,
|
69
|
+
Net::ProtocolError,
|
70
|
+
Errno::ECONNABORTED,
|
71
|
+
Errno::ECONNREFUSED,
|
72
|
+
Errno::ECONNRESET,
|
73
|
+
Errno::ETIMEDOUT,
|
74
|
+
Errno::ENETUNREACH,
|
75
|
+
Errno::EHOSTUNREACH,
|
76
|
+
Errno::EINVAL,
|
77
|
+
Errno::ENOPROTOOPT => e
|
78
|
+
raise ErrnoError.new(e.message)
|
79
|
+
rescue Zlib::DataError,
|
80
|
+
Zlib::BufError => e
|
81
|
+
raise ZlibError.new(e.message)
|
62
82
|
end
|
63
83
|
|
64
84
|
# Handles response and error codes from the remote service.
|
65
85
|
def handle_response(response)
|
66
86
|
case response.code.to_i
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
87
|
+
when 200...299
|
88
|
+
response
|
89
|
+
when 300..399
|
90
|
+
raise Redirection.new(response)
|
91
|
+
when 400
|
92
|
+
raise BadRequest.new(response)
|
93
|
+
when 401
|
94
|
+
raise UnauthorizedAccess.new(response)
|
95
|
+
when 403
|
96
|
+
raise ForbiddenAccess.new(response)
|
97
|
+
when 404
|
98
|
+
raise ResourceNotFound.new(response)
|
99
|
+
when 405
|
100
|
+
raise MethodNotAllowed.new(response)
|
101
|
+
when 409
|
102
|
+
raise ResourceConflict.new(response)
|
103
|
+
when 410
|
104
|
+
raise ResourceGone.new(response)
|
105
|
+
when 422
|
106
|
+
raise ResourceInvalid.new(response)
|
107
|
+
when 401...500
|
108
|
+
raise ClientError.new(response)
|
109
|
+
when 500...600
|
110
|
+
raise ServerError.new(response)
|
111
|
+
else
|
112
|
+
raise ResponseError.new(
|
113
|
+
response, "Unknown response code: #{response.code}"
|
114
|
+
)
|
95
115
|
end
|
96
116
|
end
|
97
117
|
|
@@ -137,4 +157,4 @@ module Nestful
|
|
137
157
|
http
|
138
158
|
end
|
139
159
|
end
|
140
|
-
end
|
160
|
+
end
|
data/lib/nestful/exceptions.rb
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
module Nestful
|
2
|
-
class
|
2
|
+
class Error < StandardError; end
|
3
|
+
ConnectionError = Error
|
4
|
+
|
5
|
+
class RequestError < Error
|
6
|
+
def initialize(message)
|
7
|
+
@message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
@message
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ResponseError < Error
|
3
16
|
attr_reader :response
|
4
17
|
|
5
18
|
def initialize(response, message = nil)
|
@@ -11,34 +24,43 @@ module Nestful
|
|
11
24
|
message = "Failed."
|
12
25
|
message << " Response code = #{response.code}." if response.respond_to?(:code)
|
13
26
|
message << " Response message = #{response.message}." if response.respond_to?(:message)
|
14
|
-
|
27
|
+
|
28
|
+
if response.respond_to?(:body)
|
29
|
+
# Error messages need to be in UTF-8
|
30
|
+
body = response.body.dup.to_s
|
31
|
+
body = body.encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?')
|
32
|
+
message << " Response Body = #{body}."
|
33
|
+
end
|
34
|
+
|
15
35
|
message
|
16
36
|
end
|
17
37
|
end
|
18
38
|
|
19
39
|
# Raised when a Timeout::Error occurs.
|
20
|
-
class TimeoutError <
|
21
|
-
def initialize(message)
|
22
|
-
@message = message
|
23
|
-
end
|
24
|
-
def to_s; @message ;end
|
40
|
+
class TimeoutError < RequestError
|
25
41
|
end
|
26
42
|
|
27
43
|
# Raised when a OpenSSL::SSL::SSLError occurs.
|
28
|
-
class SSLError <
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
44
|
+
class SSLError < RequestError
|
45
|
+
end
|
46
|
+
|
47
|
+
class ErrnoError < RequestError
|
48
|
+
end
|
49
|
+
|
50
|
+
class ZlibError < RequestError
|
33
51
|
end
|
34
52
|
|
35
53
|
# 3xx Redirection
|
36
|
-
class Redirection <
|
54
|
+
class Redirection < ResponseError # :nodoc:
|
55
|
+
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RedirectionLoop < ResponseError # :nodoc:
|
37
59
|
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
|
38
60
|
end
|
39
61
|
|
40
62
|
# 4xx Client Error
|
41
|
-
class ClientError <
|
63
|
+
class ClientError < ResponseError; end # :nodoc:
|
42
64
|
|
43
65
|
# 400 Bad Request
|
44
66
|
class BadRequest < ClientError; end # :nodoc
|
@@ -62,7 +84,7 @@ module Nestful
|
|
62
84
|
class ResourceInvalid < ClientError; end # :nodoc:
|
63
85
|
|
64
86
|
# 5xx Server Error
|
65
|
-
class ServerError <
|
87
|
+
class ServerError < ResponseError; end # :nodoc:
|
66
88
|
|
67
89
|
# 405 Method Not Allowed
|
68
90
|
class MethodNotAllowed < ClientError # :nodoc:
|
@@ -70,4 +92,4 @@ module Nestful
|
|
70
92
|
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
71
93
|
end
|
72
94
|
end
|
73
|
-
end
|
95
|
+
end
|
data/lib/nestful/helpers.rb
CHANGED
@@ -38,6 +38,22 @@ module Nestful
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
def to_url_param(value, prefix = nil)
|
42
|
+
case value
|
43
|
+
when Array
|
44
|
+
value.map { |v|
|
45
|
+
to_url_param(v, "#{prefix}[]")
|
46
|
+
}.join("&")
|
47
|
+
when Hash
|
48
|
+
value.map { |k, v|
|
49
|
+
to_url_param(v, prefix ? "#{prefix}[#{uri_escape(k)}]" : uri_escape(k))
|
50
|
+
}.join("&")
|
51
|
+
else
|
52
|
+
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
53
|
+
"#{prefix}=#{uri_escape(value)}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
41
57
|
def from_param(param)
|
42
58
|
Rack::Utils.parse_nested_query(param)
|
43
59
|
(value || '').split('&').each do |res|
|
@@ -59,7 +75,15 @@ module Nestful
|
|
59
75
|
end
|
60
76
|
|
61
77
|
def escape(s)
|
62
|
-
URI.encode_www_form_component(s)
|
78
|
+
URI.encode_www_form_component(s.to_s)
|
79
|
+
end
|
80
|
+
|
81
|
+
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
|
82
|
+
|
83
|
+
def uri_escape(s)
|
84
|
+
s.to_s.gsub(ESCAPE_RE) {|match|
|
85
|
+
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
|
86
|
+
}.tr(' ', '+')
|
63
87
|
end
|
64
88
|
|
65
89
|
if defined?(::Encoding)
|
@@ -105,4 +129,4 @@ module Nestful
|
|
105
129
|
return params
|
106
130
|
end
|
107
131
|
end
|
108
|
-
end
|
132
|
+
end
|
data/lib/nestful/mash.rb
CHANGED
@@ -1,42 +1,38 @@
|
|
1
1
|
module Nestful
|
2
2
|
class Mash < Hash
|
3
|
-
def self.get(
|
4
|
-
|
3
|
+
def self.get(action = '', params = {}, options = {})
|
4
|
+
request(action, options.merge(:method => :get, :params => params))
|
5
5
|
end
|
6
6
|
|
7
|
-
def self.
|
8
|
-
|
7
|
+
def self.put(action = '', params = {}, options = {})
|
8
|
+
request(action, options.merge(:method => :put, :params => params))
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.
|
12
|
-
|
11
|
+
def self.post(action = '', params = {}, options = {})
|
12
|
+
request(action, options.merge(:method => :post, :params => params))
|
13
13
|
end
|
14
14
|
|
15
|
-
def self.delete(
|
16
|
-
|
15
|
+
def self.delete(action = '', params = {}, options = {})
|
16
|
+
request(action, options.merge(:method => :delete, :params => params))
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.request(
|
20
|
-
|
19
|
+
def self.request(url, options = {})
|
20
|
+
self.new Request.new(url, options).execute
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
when Array
|
28
|
-
response.decoded.map {|v| self.new(v) }
|
23
|
+
def self.new(value = nil, *args)
|
24
|
+
if value.respond_to?(:each) &&
|
25
|
+
!value.respond_to?(:each_pair)
|
26
|
+
value.map {|v| super(v) }
|
29
27
|
else
|
30
|
-
|
28
|
+
super
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
34
32
|
alias_method :to_s, :inspect
|
35
33
|
|
36
|
-
attr_reader :response
|
37
|
-
|
38
34
|
def initialize(source_hash = nil, default = nil, &blk)
|
39
|
-
deep_update(source_hash) if source_hash
|
35
|
+
deep_update(source_hash.to_hash) if source_hash
|
40
36
|
default ? super(default) : super(&blk)
|
41
37
|
end
|
42
38
|
|
@@ -191,13 +187,11 @@ module Nestful
|
|
191
187
|
case val
|
192
188
|
when self.class
|
193
189
|
val.dup
|
194
|
-
when Hash
|
195
|
-
duping ? val.dup : val
|
196
190
|
when ::Hash
|
197
191
|
val = val.dup if duping
|
198
|
-
|
199
|
-
when Array
|
200
|
-
val.
|
192
|
+
Mash.new(val)
|
193
|
+
when ::Array
|
194
|
+
val.map {|e| convert_value(e) }
|
201
195
|
else
|
202
196
|
val
|
203
197
|
end
|
data/lib/nestful/request.rb
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
module Nestful
|
2
2
|
class Request
|
3
3
|
attr_reader :options, :format, :url
|
4
|
-
attr_accessor :params, :body, :method, :headers
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
attr_accessor :params, :body, :method, :headers,
|
6
|
+
:proxy, :user, :password,
|
7
|
+
:auth_type, :timeout, :ssl_options,
|
8
|
+
:max_attempts, :follow_redirection
|
8
9
|
|
9
10
|
def initialize(url, options = {})
|
10
11
|
@url = url.to_s
|
11
12
|
|
12
|
-
@options =
|
13
|
+
@options = {
|
14
|
+
:method => :get,
|
15
|
+
:params => {},
|
16
|
+
:headers => {},
|
17
|
+
:format => :form,
|
18
|
+
:max_attempts => 5,
|
19
|
+
:follow_redirection => true
|
20
|
+
}.merge(options)
|
21
|
+
|
13
22
|
@options.each do |key, val|
|
14
23
|
method = "#{key}="
|
15
24
|
send(method, val) if respond_to?(method)
|
16
25
|
end
|
17
|
-
|
18
|
-
self.method ||= :get
|
19
|
-
self.params ||= {}
|
20
|
-
self.headers ||= {}
|
21
|
-
self.format ||= :form
|
22
26
|
end
|
23
27
|
|
24
28
|
def format=(mime_or_format)
|
@@ -40,34 +44,77 @@ module Nestful
|
|
40
44
|
@uri = URI.parse(url)
|
41
45
|
@uri.path = '/' if @uri.path.empty?
|
42
46
|
|
43
|
-
if @uri.query
|
44
|
-
@params.merge!(Helpers.from_param(@uri.query))
|
45
|
-
@uri.query = nil
|
46
|
-
end
|
47
|
-
|
48
47
|
@uri
|
49
48
|
end
|
50
49
|
|
50
|
+
def uri_params
|
51
|
+
uri.query ? Helpers.from_param(uri.query) : {}
|
52
|
+
end
|
53
|
+
|
51
54
|
def path
|
52
55
|
uri.path
|
53
56
|
end
|
54
57
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
def query_path
|
59
|
+
query_path = path.dup
|
60
|
+
query_params = uri_params.dup
|
61
|
+
query_params.merge!(params) unless encoded?
|
62
|
+
|
63
|
+
if query_params.any?
|
64
|
+
query_path += '?' + Helpers.to_url_param(query_params)
|
60
65
|
end
|
61
66
|
|
62
|
-
|
67
|
+
query_path
|
68
|
+
end
|
69
|
+
|
70
|
+
def encoded?
|
71
|
+
[:post, :put].include?(method)
|
72
|
+
end
|
73
|
+
|
74
|
+
def encoded
|
75
|
+
params.any? ? format.encode(params) : body
|
76
|
+
end
|
63
77
|
|
64
|
-
|
65
|
-
|
66
|
-
|
78
|
+
def execute
|
79
|
+
with_redirection do
|
80
|
+
if encoded?
|
81
|
+
result = connection.send(method, query_path, encoded, build_headers)
|
82
|
+
else
|
83
|
+
result = connection.send(method, query_path, build_headers)
|
84
|
+
end
|
85
|
+
|
86
|
+
Response.new(result, uri)
|
87
|
+
end
|
67
88
|
end
|
68
89
|
|
69
90
|
protected
|
70
91
|
|
92
|
+
def with_redirection(&block)
|
93
|
+
attempts = 1
|
94
|
+
|
95
|
+
begin
|
96
|
+
yield
|
97
|
+
rescue Redirection => error
|
98
|
+
raise error unless follow_redirection
|
99
|
+
|
100
|
+
attempts += 1
|
101
|
+
|
102
|
+
raise error unless error.response['Location']
|
103
|
+
raise RedirectionLoop.new(error.response) if attempts > max_attempts
|
104
|
+
|
105
|
+
location = error.response['Location'].scrub
|
106
|
+
location = URI.parse(location)
|
107
|
+
|
108
|
+
# Path is relative
|
109
|
+
unless location.host
|
110
|
+
location = URI.join(uri, location)
|
111
|
+
end
|
112
|
+
|
113
|
+
self.url = location.to_s
|
114
|
+
retry
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
71
118
|
def connection
|
72
119
|
Connection.new(uri,
|
73
120
|
:proxy => proxy,
|
@@ -99,23 +146,5 @@ module Nestful
|
|
99
146
|
.merge(content_type_headers)
|
100
147
|
.merge(headers)
|
101
148
|
end
|
102
|
-
|
103
|
-
def query_path
|
104
|
-
query_path = path
|
105
|
-
|
106
|
-
if params.any?
|
107
|
-
query_path += '?' + Helpers.to_param(params)
|
108
|
-
end
|
109
|
-
|
110
|
-
query_path
|
111
|
-
end
|
112
|
-
|
113
|
-
def encoded?
|
114
|
-
[:post, :put].include?(method)
|
115
|
-
end
|
116
|
-
|
117
|
-
def encoded
|
118
|
-
params.any? ? format.encode(params) : body
|
119
|
-
end
|
120
149
|
end
|
121
|
-
end
|
150
|
+
end
|
data/lib/nestful/response.rb
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
module Nestful
|
2
2
|
class Response
|
3
|
-
attr_reader :response, :body, :headers, :parser
|
3
|
+
attr_reader :response, :location, :body, :headers, :parser
|
4
4
|
|
5
|
-
def initialize(response,
|
5
|
+
def initialize(response, location = nil)
|
6
6
|
@response = response
|
7
7
|
@body = response.body
|
8
|
+
@location = location
|
8
9
|
@headers = Headers.new(response.to_hash)
|
9
10
|
@format = Formats.for(headers.content_type)
|
10
11
|
@parser ||= @format && @format.new
|
11
12
|
end
|
12
13
|
|
14
|
+
def to_s
|
15
|
+
body
|
16
|
+
end
|
17
|
+
|
13
18
|
def as_json
|
14
19
|
decoded
|
15
20
|
end
|
@@ -46,4 +51,4 @@ module Nestful
|
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
49
|
-
require 'nestful/response/headers'
|
54
|
+
require 'nestful/response/headers'
|
data/lib/nestful/version.rb
CHANGED
@@ -59,6 +59,12 @@ class TestRequest < MiniTest::Unit::TestCase
|
|
59
59
|
assert_requested(:post, 'http://example.com/v1/tokens', :body => '{"card":{"number":4242,"exp_month":12}}', :headers => {'Content-Type' => 'application/json'})
|
60
60
|
end
|
61
61
|
|
62
|
+
def test_form_and_query_params
|
63
|
+
stub_request(:any, 'http://example.com/v1/tokens?query=123')
|
64
|
+
Nestful::Request.new('http://example.com/v1/tokens?query=123', :method => :post, :params => {:number => 4242, :exp_month => 12}).execute
|
65
|
+
assert_requested(:post, 'http://example.com/v1/tokens?query=123', :body => 'number=4242&exp_month=12')
|
66
|
+
end
|
67
|
+
|
62
68
|
def test_timeout
|
63
69
|
skip # TODO
|
64
70
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nestful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex MacCaw
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -17,7 +17,7 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
- .gitignore
|
20
|
+
- ".gitignore"
|
21
21
|
- Gemfile
|
22
22
|
- MIT-LICENSE
|
23
23
|
- README.markdown
|
@@ -26,7 +26,6 @@ files:
|
|
26
26
|
- examples/pagespeed.rb
|
27
27
|
- examples/resource.rb
|
28
28
|
- lib/nestful.rb
|
29
|
-
- lib/nestful/.DS_Store
|
30
29
|
- lib/nestful/connection.rb
|
31
30
|
- lib/nestful/endpoint.rb
|
32
31
|
- lib/nestful/exceptions.rb
|
@@ -54,17 +53,17 @@ require_paths:
|
|
54
53
|
- lib
|
55
54
|
required_ruby_version: !ruby/object:Gem::Requirement
|
56
55
|
requirements:
|
57
|
-
- -
|
56
|
+
- - ">="
|
58
57
|
- !ruby/object:Gem::Version
|
59
58
|
version: '0'
|
60
59
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
60
|
requirements:
|
62
|
-
- -
|
61
|
+
- - ">="
|
63
62
|
- !ruby/object:Gem::Version
|
64
63
|
version: '0'
|
65
64
|
requirements: []
|
66
65
|
rubyforge_project:
|
67
|
-
rubygems_version: 2.
|
66
|
+
rubygems_version: 2.2.2
|
68
67
|
signing_key:
|
69
68
|
specification_version: 4
|
70
69
|
summary: Simple Ruby HTTP/REST client with a sane API
|
@@ -73,3 +72,4 @@ test_files:
|
|
73
72
|
- test/nestful/test_request.rb
|
74
73
|
- test/nestful/test_resource.rb
|
75
74
|
- test/nestful/test_response.rb
|
75
|
+
has_rdoc:
|
data/lib/nestful/.DS_Store
DELETED
Binary file
|