rest-client 1.1.0 → 1.6.3

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.

Potentially problematic release.


This version of rest-client might be problematic. Click here for more details.

@@ -1,146 +1,169 @@
1
1
  module RestClient
2
- # A class that can be instantiated for access to a RESTful resource,
3
- # including authentication.
4
- #
5
- # Example:
6
- #
7
- # resource = RestClient::Resource.new('http://some/resource')
8
- # jpg = resource.get(:accept => 'image/jpg')
9
- #
10
- # With HTTP basic authentication:
11
- #
12
- # resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
13
- # resource.delete
14
- #
15
- # With a timeout (seconds):
16
- #
17
- # RestClient::Resource.new('http://slow', :timeout => 10)
18
- #
19
- # With an open timeout (seconds):
20
- #
21
- # RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
22
- #
23
- # You can also use resources to share common headers. For headers keys,
24
- # symbols are converted to strings. Example:
25
- #
26
- # resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
27
- #
28
- # This header will be transported as X-Client-Version (notice the X prefix,
29
- # capitalization and hyphens)
30
- #
31
- # Use the [] syntax to allocate subresources:
32
- #
33
- # site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
34
- # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
35
- #
36
- class Resource
37
- attr_reader :url, :options
2
+ # A class that can be instantiated for access to a RESTful resource,
3
+ # including authentication.
4
+ #
5
+ # Example:
6
+ #
7
+ # resource = RestClient::Resource.new('http://some/resource')
8
+ # jpg = resource.get(:accept => 'image/jpg')
9
+ #
10
+ # With HTTP basic authentication:
11
+ #
12
+ # resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
13
+ # resource.delete
14
+ #
15
+ # With a timeout (seconds):
16
+ #
17
+ # RestClient::Resource.new('http://slow', :timeout => 10)
18
+ #
19
+ # With an open timeout (seconds):
20
+ #
21
+ # RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
22
+ #
23
+ # You can also use resources to share common headers. For headers keys,
24
+ # symbols are converted to strings. Example:
25
+ #
26
+ # resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
27
+ #
28
+ # This header will be transported as X-Client-Version (notice the X prefix,
29
+ # capitalization and hyphens)
30
+ #
31
+ # Use the [] syntax to allocate subresources:
32
+ #
33
+ # site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
34
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
35
+ #
36
+ class Resource
37
+ attr_reader :url, :options, :block
38
38
 
39
- def initialize(url, options={}, backwards_compatibility=nil)
40
- @url = url
41
- if options.class == Hash
42
- @options = options
43
- else # compatibility with previous versions
44
- @options = { :user => options, :password => backwards_compatibility }
45
- end
46
- end
39
+ def initialize(url, options={}, backwards_compatibility=nil, &block)
40
+ @url = url
41
+ @block = block
42
+ if options.class == Hash
43
+ @options = options
44
+ else # compatibility with previous versions
45
+ @options = { :user => options, :password => backwards_compatibility }
46
+ end
47
+ end
47
48
 
48
- def get(additional_headers={}, &b)
49
- headers = (options[:headers] || {}).merge(additional_headers)
50
- Request.execute(options.merge(
51
- :method => :get,
52
- :url => url,
53
- :headers => headers), &b)
54
- end
49
+ def get(additional_headers={}, &block)
50
+ headers = (options[:headers] || {}).merge(additional_headers)
51
+ Request.execute(options.merge(
52
+ :method => :get,
53
+ :url => url,
54
+ :headers => headers), &(block || @block))
55
+ end
55
56
 
56
- def post(payload, additional_headers={}, &b)
57
- headers = (options[:headers] || {}).merge(additional_headers)
58
- Request.execute(options.merge(
59
- :method => :post,
60
- :url => url,
61
- :payload => payload,
62
- :headers => headers), &b)
63
- end
57
+ def head(additional_headers={}, &block)
58
+ headers = (options[:headers] || {}).merge(additional_headers)
59
+ Request.execute(options.merge(
60
+ :method => :head,
61
+ :url => url,
62
+ :headers => headers), &(block || @block))
63
+ end
64
64
 
65
- def put(payload, additional_headers={}, &b)
66
- headers = (options[:headers] || {}).merge(additional_headers)
67
- Request.execute(options.merge(
68
- :method => :put,
69
- :url => url,
70
- :payload => payload,
71
- :headers => headers), &b)
72
- end
65
+ def post(payload, additional_headers={}, &block)
66
+ headers = (options[:headers] || {}).merge(additional_headers)
67
+ Request.execute(options.merge(
68
+ :method => :post,
69
+ :url => url,
70
+ :payload => payload,
71
+ :headers => headers), &(block || @block))
72
+ end
73
73
 
74
- def delete(additional_headers={}, &b)
75
- headers = (options[:headers] || {}).merge(additional_headers)
76
- Request.execute(options.merge(
77
- :method => :delete,
78
- :url => url,
79
- :headers => headers), &b)
80
- end
74
+ def put(payload, additional_headers={}, &block)
75
+ headers = (options[:headers] || {}).merge(additional_headers)
76
+ Request.execute(options.merge(
77
+ :method => :put,
78
+ :url => url,
79
+ :payload => payload,
80
+ :headers => headers), &(block || @block))
81
+ end
81
82
 
82
- def to_s
83
- url
84
- end
83
+ def patch(payload, additional_headers={}, &block)
84
+ headers = (options[:headers] || {}).merge(additional_headers)
85
+ Request.execute(options.merge(
86
+ :method => :patch,
87
+ :url => url,
88
+ :payload => payload,
89
+ :headers => headers), &(block || @block))
90
+ end
85
91
 
86
- def user
87
- options[:user]
88
- end
92
+ def delete(additional_headers={}, &block)
93
+ headers = (options[:headers] || {}).merge(additional_headers)
94
+ Request.execute(options.merge(
95
+ :method => :delete,
96
+ :url => url,
97
+ :headers => headers), &(block || @block))
98
+ end
89
99
 
90
- def password
91
- options[:password]
92
- end
100
+ def to_s
101
+ url
102
+ end
93
103
 
94
- def headers
95
- options[:headers] || {}
96
- end
104
+ def user
105
+ options[:user]
106
+ end
97
107
 
98
- def timeout
99
- options[:timeout]
100
- end
101
-
102
- def open_timeout
103
- options[:open_timeout]
104
- end
108
+ def password
109
+ options[:password]
110
+ end
105
111
 
106
- # Construct a subresource, preserving authentication.
107
- #
108
- # Example:
109
- #
110
- # site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
111
- # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
112
- #
113
- # This is especially useful if you wish to define your site in one place and
114
- # call it in multiple locations:
115
- #
116
- # def orders
117
- # RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
118
- # end
119
- #
120
- # orders.get # GET http://example.com/orders
121
- # orders['1'].get # GET http://example.com/orders/1
122
- # orders['1/items'].delete # DELETE http://example.com/orders/1/items
123
- #
124
- # Nest resources as far as you want:
125
- #
126
- # site = RestClient::Resource.new('http://example.com')
127
- # posts = site['posts']
128
- # first_post = posts['1']
129
- # comments = first_post['comments']
130
- # comments.post 'Hello', :content_type => 'text/plain'
131
- #
132
- def [](suburl)
133
- self.class.new(concat_urls(url, suburl), options)
134
- end
112
+ def headers
113
+ options[:headers] || {}
114
+ end
135
115
 
136
- def concat_urls(url, suburl) # :nodoc:
137
- url = url.to_s
138
- suburl = suburl.to_s
139
- if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
140
- url + suburl
141
- else
142
- "#{url}/#{suburl}"
143
- end
144
- end
145
- end
116
+ def timeout
117
+ options[:timeout]
118
+ end
119
+
120
+ def open_timeout
121
+ options[:open_timeout]
122
+ end
123
+
124
+ # Construct a subresource, preserving authentication.
125
+ #
126
+ # Example:
127
+ #
128
+ # site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
129
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
130
+ #
131
+ # This is especially useful if you wish to define your site in one place and
132
+ # call it in multiple locations:
133
+ #
134
+ # def orders
135
+ # RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
136
+ # end
137
+ #
138
+ # orders.get # GET http://example.com/orders
139
+ # orders['1'].get # GET http://example.com/orders/1
140
+ # orders['1/items'].delete # DELETE http://example.com/orders/1/items
141
+ #
142
+ # Nest resources as far as you want:
143
+ #
144
+ # site = RestClient::Resource.new('http://example.com')
145
+ # posts = site['posts']
146
+ # first_post = posts['1']
147
+ # comments = first_post['comments']
148
+ # comments.post 'Hello', :content_type => 'text/plain'
149
+ #
150
+ def [](suburl, &new_block)
151
+ case
152
+ when block_given? then self.class.new(concat_urls(url, suburl), options, &new_block)
153
+ when block then self.class.new(concat_urls(url, suburl), options, &block)
154
+ else
155
+ self.class.new(concat_urls(url, suburl), options)
156
+ end
157
+ end
158
+
159
+ def concat_urls(url, suburl) # :nodoc:
160
+ url = url.to_s
161
+ suburl = suburl.to_s
162
+ if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
163
+ url + suburl
164
+ else
165
+ "#{url}/#{suburl}"
166
+ end
167
+ end
168
+ end
146
169
  end
@@ -1,20 +1,24 @@
1
- require File.dirname(__FILE__) + '/mixin/response'
2
-
3
1
  module RestClient
4
- # The response from RestClient looks like a string, but is actually one of
5
- # these. 99% of the time you're making a rest call all you care about is
6
- # the body, but on the occassion you want to fetch the headers you can:
7
- #
8
- # RestClient.get('http://example.com').headers[:content_type]
9
- #
10
- class Response < String
11
2
 
12
- include RestClient::Mixin::Response
3
+ # A Response from RestClient, you can access the response body, the code or the headers.
4
+ #
5
+ module Response
6
+
7
+ include AbstractResponse
8
+
9
+ attr_accessor :args, :body, :net_http_res
10
+
11
+ def body
12
+ self
13
+ end
13
14
 
14
- def initialize(string, net_http_res)
15
- @net_http_res = net_http_res
16
- super(string || "")
17
- end
15
+ def Response.create body, net_http_res, args
16
+ result = body || ''
17
+ result.extend Response
18
+ result.net_http_res = net_http_res
19
+ result.args = args
20
+ result
21
+ end
18
22
 
19
- end
23
+ end
20
24
  end
@@ -0,0 +1,85 @@
1
+ require File.join( File.dirname(File.expand_path(__FILE__)), 'base')
2
+
3
+ describe RestClient::AbstractResponse do
4
+
5
+ class MyAbstractResponse
6
+
7
+ include RestClient::AbstractResponse
8
+
9
+ attr_accessor :size
10
+
11
+ def initialize net_http_res, args
12
+ @net_http_res = net_http_res
13
+ @args = args
14
+ end
15
+
16
+ end
17
+
18
+ before do
19
+ @net_http_res = mock('net http response')
20
+ @response = MyAbstractResponse.new(@net_http_res, {})
21
+ end
22
+
23
+ it "fetches the numeric response code" do
24
+ @net_http_res.should_receive(:code).and_return('200')
25
+ @response.code.should == 200
26
+ end
27
+
28
+ it "has a nice description" do
29
+ @net_http_res.should_receive(:to_hash).and_return({'Content-Type' => ['application/pdf']})
30
+ @net_http_res.should_receive(:code).and_return('200')
31
+ @response.description == '200 OK | application/pdf bytes\n'
32
+ end
33
+
34
+ it "beautifies the headers by turning the keys to symbols" do
35
+ h = RestClient::AbstractResponse.beautify_headers('content-type' => [ 'x' ])
36
+ h.keys.first.should == :content_type
37
+ end
38
+
39
+ it "beautifies the headers by turning the values to strings instead of one-element arrays" do
40
+ h = RestClient::AbstractResponse.beautify_headers('x' => [ 'text/html' ] )
41
+ h.values.first.should == 'text/html'
42
+ end
43
+
44
+ it "fetches the headers" do
45
+ @net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
46
+ @response.headers.should == { :content_type => 'text/html' }
47
+ end
48
+
49
+ it "extracts cookies from response headers" do
50
+ @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
51
+ @response.cookies.should == { 'session_id' => '1' }
52
+ end
53
+
54
+ it "extract strange cookies" do
55
+ @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/'])
56
+ @response.cookies.should == { 'session_id' => 'ZJ%2FHQVH6YE+rVkTpn0zvTQ%3D%3D' }
57
+ end
58
+
59
+ it "doesn't escape cookies" do
60
+ @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca; path=/'])
61
+ @response.cookies.should == { 'session_id' => 'BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca' }
62
+ end
63
+
64
+ it "can access the net http result directly" do
65
+ @response.net_http_res.should == @net_http_res
66
+ end
67
+
68
+ describe "#return!" do
69
+ it "should return the response itself on 200-codes" do
70
+ @net_http_res.should_receive(:code).and_return('200')
71
+ @response.return!.should be_equal(@response)
72
+ end
73
+
74
+ it "should raise RequestFailed on unknown codes" do
75
+ @net_http_res.should_receive(:code).and_return('1000')
76
+ lambda { @response.return! }.should raise_error RestClient::RequestFailed
77
+ end
78
+
79
+ it "should raise an error on a redirection after non-GET/HEAD requests" do
80
+ @net_http_res.should_receive(:code).and_return('301')
81
+ @response.args.merge(:method => :put)
82
+ lambda { @response.return! }.should raise_error RestClient::RequestFailed
83
+ end
84
+ end
85
+ end
@@ -1,3 +1,9 @@
1
+ def is_ruby_19?
2
+ RUBY_VERSION == '1.9.1' or RUBY_VERSION == '1.9.2'
3
+ end
4
+
5
+ Encoding.default_internal = Encoding.default_external = "ASCII-8BIT" if is_ruby_19?
6
+
1
7
  require 'rubygems'
2
8
  require 'spec'
3
9
 
@@ -1,65 +1,98 @@
1
- require File.dirname(__FILE__) + '/base'
1
+ require File.join( File.dirname(File.expand_path(__FILE__)), 'base')
2
+
3
+ require 'webmock/rspec'
4
+ include WebMock
2
5
 
3
6
  describe RestClient::Exception do
4
- it "sets the exception message to ErrorMessage" do
5
- RestClient::ResourceNotFound.new.message.should == 'Resource not found'
6
- end
7
+ it "returns a 'message' equal to the class name if the message is not set, because 'message' should not be nil" do
8
+ e = RestClient::Exception.new
9
+ e.message.should == "RestClient::Exception"
10
+ end
11
+
12
+ it "returns the 'message' that was set" do
13
+ e = RestClient::Exception.new
14
+ message = "An explicitly set message"
15
+ e.message = message
16
+ e.message.should == message
17
+ end
18
+
19
+ it "sets the exception message to ErrorMessage" do
20
+ RestClient::ResourceNotFound.new.message.should == 'Resource Not Found'
21
+ end
22
+
23
+ it "contains exceptions in RestClient" do
24
+ RestClient::Unauthorized.new.should be_a_kind_of(RestClient::Exception)
25
+ RestClient::ServerBrokeConnection.new.should be_a_kind_of(RestClient::Exception)
26
+ end
27
+ end
7
28
 
8
- it "contains exceptions in RestClient" do
9
- RestClient::Unauthorized.new.should be_a_kind_of(RestClient::Exception)
10
- RestClient::ServerBrokeConnection.new.should be_a_kind_of(RestClient::Exception)
11
- end
29
+ describe RestClient::ServerBrokeConnection do
30
+ it "should have a default message of 'Server broke connection'" do
31
+ e = RestClient::ServerBrokeConnection.new
32
+ e.message.should == 'Server broke connection'
33
+ end
12
34
  end
13
35
 
14
36
  describe RestClient::RequestFailed do
15
- before do
16
- @response = mock('HTTP Response', :code => '502')
17
- end
37
+ before do
38
+ @response = mock('HTTP Response', :code => '502')
39
+ end
18
40
 
19
- it "stores the http response on the exception" do
20
- begin
21
- raise RestClient::RequestFailed, :response
22
- rescue RestClient::RequestFailed => e
23
- e.response.should == :response
24
- end
25
- end
41
+ it "stores the http response on the exception" do
42
+ response = "response"
43
+ begin
44
+ raise RestClient::RequestFailed, response
45
+ rescue RestClient::RequestFailed => e
46
+ e.response.should == response
47
+ end
48
+ end
26
49
 
27
- it "http_code convenience method for fetching the code as an integer" do
28
- RestClient::RequestFailed.new(@response).http_code.should == 502
29
- end
50
+ it "http_code convenience method for fetching the code as an integer" do
51
+ RestClient::RequestFailed.new(@response).http_code.should == 502
52
+ end
30
53
 
31
- it "http_body convenience method for fetching the body (decoding when necessary)" do
32
- @response.stub!(:[]).with('content-encoding').and_return('gzip')
33
- @response.stub!(:body).and_return('compressed body')
34
- RestClient::Request.should_receive(:decode).with('gzip', 'compressed body').and_return('plain body')
35
- RestClient::RequestFailed.new(@response).http_body.should == 'plain body'
36
- end
54
+ it "http_body convenience method for fetching the body (decoding when necessary)" do
55
+ RestClient::RequestFailed.new(@response).http_code.should == 502
56
+ RestClient::RequestFailed.new(@response).message.should == 'HTTP status code 502'
57
+ end
37
58
 
38
- it "shows the status code in the message" do
39
- RestClient::RequestFailed.new(@response).to_s.should match(/502/)
40
- end
59
+ it "shows the status code in the message" do
60
+ RestClient::RequestFailed.new(@response).to_s.should match(/502/)
61
+ end
41
62
  end
42
63
 
43
64
  describe RestClient::ResourceNotFound do
44
- it "also has the http response attached" do
45
- begin
46
- raise RestClient::ResourceNotFound, :response
47
- rescue RestClient::ResourceNotFound => e
48
- e.response.should == :response
49
- end
50
- end
65
+ it "also has the http response attached" do
66
+ response = "response"
67
+ begin
68
+ raise RestClient::ResourceNotFound, response
69
+ rescue RestClient::ResourceNotFound => e
70
+ e.response.should == response
71
+ end
72
+ end
51
73
  end
52
74
 
53
75
  describe "backwards compatibility" do
54
- it "alias RestClient::Request::Redirect to RestClient::Redirect" do
55
- RestClient::Request::Redirect.should == RestClient::Redirect
56
- end
76
+ it "alias RestClient::Request::Redirect to RestClient::Redirect" do
77
+ RestClient::Request::Redirect.should == RestClient::Redirect
78
+ end
79
+
80
+ it "alias RestClient::Request::Unauthorized to RestClient::Unauthorized" do
81
+ RestClient::Request::Unauthorized.should == RestClient::Unauthorized
82
+ end
57
83
 
58
- it "alias RestClient::Request::Unauthorized to RestClient::Unauthorized" do
59
- RestClient::Request::Unauthorized.should == RestClient::Unauthorized
60
- end
84
+ it "alias RestClient::Request::RequestFailed to RestClient::RequestFailed" do
85
+ RestClient::Request::RequestFailed.should == RestClient::RequestFailed
86
+ end
61
87
 
62
- it "alias RestClient::Request::RequestFailed to RestClient::RequestFailed" do
63
- RestClient::Request::RequestFailed.should == RestClient::RequestFailed
64
- end
88
+ it "make the exception's response act like an Net::HTTPResponse" do
89
+ body = "body"
90
+ stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
91
+ begin
92
+ RestClient.get "www.example.com"
93
+ raise
94
+ rescue RestClient::ResourceNotFound => e
95
+ e.response.body.should == body
96
+ end
97
+ end
65
98
  end