rest-client 2.0.0.rc2 → 2.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
@@ -38,17 +38,15 @@ module RestClient
38
38
  "<RestClient::Response #{code.inspect} #{body_truncated(10).inspect}>"
39
39
  end
40
40
 
41
- def self.create(body, net_http_res, args, request)
41
+ def self.create(body, net_http_res, request)
42
42
  result = self.new(body || '')
43
43
 
44
- result.response_set_vars(net_http_res, args, request)
44
+ result.response_set_vars(net_http_res, request)
45
45
  fix_encoding(result)
46
46
 
47
47
  result
48
48
  end
49
49
 
50
- private
51
-
52
50
  def self.fix_encoding(response)
53
51
  charset = RestClient::Utils.get_encoding_from_headers(response.headers)
54
52
  encoding = nil
@@ -56,7 +54,9 @@ module RestClient
56
54
  begin
57
55
  encoding = Encoding.find(charset) if charset
58
56
  rescue ArgumentError
59
- RestClient.log << "No such encoding: #{charset.inspect}"
57
+ if RestClient.log
58
+ RestClient.log << "No such encoding: #{charset.inspect}"
59
+ end
60
60
  end
61
61
 
62
62
  return unless encoding
@@ -66,11 +66,14 @@ module RestClient
66
66
  response
67
67
  end
68
68
 
69
+ private
70
+
69
71
  def body_truncated(length)
70
- if body.length > length
71
- body[0..length] + '...'
72
+ b = body
73
+ if b.length > length
74
+ b[0..length] + '...'
72
75
  else
73
- body
76
+ b
74
77
  end
75
78
  end
76
79
  end
@@ -11,11 +11,15 @@ module RestClient
11
11
  # Strings will effectively end up using `Encoding.default_external` when
12
12
  # this method returns nil.
13
13
  #
14
- # @param headers [Hash]
14
+ # @param headers [Hash<Symbol,String>]
15
15
  #
16
16
  # @return [String, nil] encoding Return the string encoding or nil if no
17
17
  # header is found.
18
18
  #
19
+ # @example
20
+ # >> get_encoding_from_headers({:content_type => 'text/plain; charset=UTF-8'})
21
+ # => "UTF-8"
22
+ #
19
23
  def self.get_encoding_from_headers(headers)
20
24
  type_header = headers[:content_type]
21
25
  return nil unless type_header
@@ -54,7 +58,7 @@ module RestClient
54
58
  nil
55
59
  end
56
60
 
57
- # Parse a Content-type like header.
61
+ # Parse a Content-Type like header.
58
62
  #
59
63
  # Return the main content-type and a hash of options.
60
64
  #
@@ -89,5 +93,143 @@ module RestClient
89
93
 
90
94
  [key, pdict]
91
95
  end
96
+
97
+ # Serialize a ruby object into HTTP query string parameters.
98
+ #
99
+ # There is no standard for doing this, so we choose our own slightly
100
+ # idiosyncratic format. The output closely matches the format understood by
101
+ # Rails, Rack, and PHP.
102
+ #
103
+ # If you don't want handling of complex objects and only want to handle
104
+ # simple flat hashes, you may want to use `URI.encode_www_form` instead,
105
+ # which implements HTML5-compliant URL encoded form data.
106
+ #
107
+ # @param [Hash,ParamsArray] object The object to serialize
108
+ #
109
+ # @return [String] A string appropriate for use as an HTTP query string
110
+ #
111
+ # @see {flatten_params}
112
+ #
113
+ # @see URI.encode_www_form
114
+ #
115
+ # @see See also Object#to_query in ActiveSupport
116
+ # @see http://php.net/manual/en/function.http-build-query.php
117
+ # http_build_query in PHP
118
+ # @see See also Rack::Utils.build_nested_query in Rack
119
+ #
120
+ # Notable differences from the ActiveSupport implementation:
121
+ #
122
+ # - Empty hash and empty array are treated the same as nil instead of being
123
+ # omitted entirely from the output. Rather than disappearing, they will
124
+ # appear to be nil instead.
125
+ #
126
+ # It's most common to pass a Hash as the object to serialize, but you can
127
+ # also use a ParamsArray if you want to be able to pass the same key with
128
+ # multiple values and not use the rack/rails array convention.
129
+ #
130
+ # @since 2.0.0
131
+ #
132
+ # @example Simple hashes
133
+ # >> encode_query_string({foo: 123, bar: 456})
134
+ # => 'foo=123&bar=456'
135
+ #
136
+ # @example Simple arrays
137
+ # >> encode_query_string({foo: [1,2,3]})
138
+ # => 'foo[]=1&foo[]=2&foo[]=3'
139
+ #
140
+ # @example Nested hashes
141
+ # >> encode_query_string({outer: {foo: 123, bar: 456}})
142
+ # => 'outer[foo]=123&outer[bar]=456'
143
+ #
144
+ # @example Deeply nesting
145
+ # >> encode_query_string({coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]})
146
+ # => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3'
147
+ #
148
+ # @example Null and empty values
149
+ # >> encode_query_string({string: '', empty: nil, list: [], hash: {}})
150
+ # => 'string=&empty&list&hash'
151
+ #
152
+ # @example Nested nulls
153
+ # >> encode_query_string({foo: {string: '', empty: nil}})
154
+ # => 'foo[string]=&foo[empty]'
155
+ #
156
+ # @example Multiple fields with the same name using ParamsArray
157
+ # >> encode_query_string(RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]]))
158
+ # => 'foo=1&foo=2&foo=3'
159
+ #
160
+ # @example Nested ParamsArray
161
+ # >> encode_query_string({foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])})
162
+ # => 'foo[a]=1&foo[a]=2'
163
+ #
164
+ # >> encode_query_string(RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]]))
165
+ # => 'foo[a]=1&foo[a]=2'
166
+ #
167
+ def self.encode_query_string(object)
168
+ flatten_params(object, true).map {|k, v| v.nil? ? k : "#{k}=#{v}" }.join('&')
169
+ end
170
+
171
+ # Transform deeply nested param containers into a flat array of [key,
172
+ # value] pairs.
173
+ #
174
+ # @example
175
+ # >> flatten_params({key1: {key2: 123}})
176
+ # => [["key1[key2]", 123]]
177
+ #
178
+ # @example
179
+ # >> flatten_params({key1: {key2: 123, arr: [1,2,3]}})
180
+ # => [["key1[key2]", 123], ["key1[arr][]", 1], ["key1[arr][]", 2], ["key1[arr][]", 3]]
181
+ #
182
+ # @param object [Hash, ParamsArray] The container to flatten
183
+ # @param uri_escape [Boolean] Whether to URI escape keys and values
184
+ # @param parent_key [String] Should not be passed (used for recursion)
185
+ #
186
+ def self.flatten_params(object, uri_escape=false, parent_key=nil)
187
+ unless object.is_a?(Hash) || object.is_a?(ParamsArray) ||
188
+ (parent_key && object.is_a?(Array))
189
+ raise ArgumentError.new('expected Hash or ParamsArray, got: ' + object.inspect)
190
+ end
191
+
192
+ # transform empty collections into nil, where possible
193
+ if object.empty? && parent_key
194
+ return [[parent_key, nil]]
195
+ end
196
+
197
+ # This is essentially .map(), but we need to do += for nested containers
198
+ object.reduce([]) { |result, item|
199
+ if object.is_a?(Array)
200
+ # item is already the value
201
+ k = nil
202
+ v = item
203
+ else
204
+ # item is a key, value pair
205
+ k, v = item
206
+ k = escape(k.to_s) if uri_escape
207
+ end
208
+
209
+ processed_key = parent_key ? "#{parent_key}[#{k}]" : k
210
+
211
+ case v
212
+ when Array, Hash, ParamsArray
213
+ result.concat flatten_params(v, uri_escape, processed_key)
214
+ else
215
+ v = escape(v.to_s) if uri_escape && v
216
+ result << [processed_key, v]
217
+ end
218
+ }
219
+ end
220
+
221
+ # Encode string for safe transport by URI or form encoding. This uses a CGI
222
+ # style escape, which transforms ` ` into `+` and various special
223
+ # characters into percent encoded forms.
224
+ #
225
+ # This calls URI.encode_www_form_component for the implementation. The only
226
+ # difference between this and CGI.escape is that it does not escape `*`.
227
+ # http://stackoverflow.com/questions/25085992/
228
+ #
229
+ # @see URI.encode_www_form_component
230
+ #
231
+ def self.escape(string)
232
+ URI.encode_www_form_component(string)
233
+ end
92
234
  end
93
235
  end
@@ -1,5 +1,5 @@
1
1
  module RestClient
2
- VERSION_INFO = [2, 0, 0, 'rc2'] unless defined?(self::VERSION_INFO)
2
+ VERSION_INFO = [2, 0, 0, 'rc3'] unless defined?(self::VERSION_INFO)
3
3
  VERSION = VERSION_INFO.map(&:to_s).join('.') unless defined?(self::VERSION)
4
4
 
5
5
  def self.version
@@ -10,22 +10,22 @@ Gem::Specification.new do |s|
10
10
  s.license = 'MIT'
11
11
  s.email = 'rest.client@librelist.com'
12
12
  s.executables = ['restclient']
13
- s.extra_rdoc_files = ['README.rdoc', 'history.md']
13
+ s.extra_rdoc_files = ['README.md', 'history.md']
14
14
  s.files = `git ls-files -z`.split("\0")
15
15
  s.test_files = `git ls-files -z spec/`.split("\0")
16
16
  s.homepage = 'https://github.com/rest-client/rest-client'
17
17
  s.summary = 'Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.'
18
18
 
19
- s.add_development_dependency('webmock', '~> 1.4')
20
- s.add_development_dependency('rspec', '~> 2.99')
19
+ s.add_development_dependency('webmock', '~> 2.0')
20
+ s.add_development_dependency('rspec', '~> 3.0')
21
21
  s.add_development_dependency('pry', '~> 0')
22
22
  s.add_development_dependency('pry-doc', '~> 0')
23
23
  s.add_development_dependency('rdoc', '>= 2.4.2', '< 5.0')
24
24
  s.add_development_dependency('rubocop', '~> 0')
25
25
 
26
26
  s.add_dependency('http-cookie', '>= 1.0.2', '< 2.0')
27
- s.add_dependency('mime-types', '>= 1.16', '< 3.0')
27
+ s.add_dependency('mime-types', '>= 1.16', '< 4.0')
28
28
  s.add_dependency('netrc', '~> 0.8')
29
29
 
30
- s.required_ruby_version = '>= 1.9.3'
30
+ s.required_ruby_version = '>= 2.0.0'
31
31
  end
@@ -1,3 +1,5 @@
1
+ require 'uri'
2
+
1
3
  module Helpers
2
4
  def response_double(opts={})
3
5
  double('response', {:to_hash => {}}.merge(opts))
@@ -11,4 +13,10 @@ module Helpers
11
13
  ensure
12
14
  $stderr = original_stderr
13
15
  end
16
+
17
+ def request_double(url: 'http://example.com', method: 'get')
18
+ double('request', url: url, uri: URI.parse(url), method: method,
19
+ user: nil, password: nil, cookie_jar: HTTP::CookieJar.new,
20
+ redirection_history: nil, args: {url: url, method: method})
21
+ end
14
22
  end
@@ -41,22 +41,22 @@ describe RestClient::Request do
41
41
  describe '.execute' do
42
42
  it 'sends a user agent' do
43
43
  data = execute_httpbin_json('user-agent', method: :get)
44
- data['user-agent'].should match(/rest-client/)
44
+ expect(data['user-agent']).to match(/rest-client/)
45
45
  end
46
46
 
47
47
  it 'receives cookies on 302' do
48
48
  expect {
49
49
  execute_httpbin('cookies/set?foo=bar', method: :get, max_redirects: 0)
50
50
  }.to raise_error(RestClient::Found) { |ex|
51
- ex.http_code.should eq 302
52
- ex.response.cookies['foo'].should eq 'bar'
51
+ expect(ex.http_code).to eq 302
52
+ expect(ex.response.cookies['foo']).to eq 'bar'
53
53
  }
54
54
  end
55
55
 
56
56
  it 'passes along cookies through 302' do
57
57
  data = execute_httpbin_json('cookies/set?foo=bar', method: :get)
58
- data.should have_key('cookies')
59
- data['cookies']['foo'].should eq 'bar'
58
+ expect(data).to have_key('cookies')
59
+ expect(data['cookies']['foo']).to eq 'bar'
60
60
  end
61
61
 
62
62
  it 'handles quote wrapped cookies' do
@@ -64,8 +64,8 @@ describe RestClient::Request do
64
64
  execute_httpbin('cookies/set?foo=' + CGI.escape('"bar:baz"'),
65
65
  method: :get, max_redirects: 0)
66
66
  }.to raise_error(RestClient::Found) { |ex|
67
- ex.http_code.should eq 302
68
- ex.response.cookies['foo'].should eq '"bar:baz"'
67
+ expect(ex.http_code).to eq 302
68
+ expect(ex.response.cookies['foo']).to eq '"bar:baz"'
69
69
  }
70
70
  end
71
71
  end
@@ -8,15 +8,15 @@ describe RestClient do
8
8
  body = 'abc'
9
9
  stub_request(:get, "www.example.com").to_return(:body => body, :status => 200)
10
10
  response = RestClient.get "www.example.com"
11
- response.code.should eq 200
12
- response.body.should eq body
11
+ expect(response.code).to eq 200
12
+ expect(response.body).to eq body
13
13
  end
14
14
 
15
15
  it "a simple request with gzipped content" do
16
16
  stub_request(:get, "www.example.com").with(:headers => { 'Accept-Encoding' => 'gzip, deflate' }).to_return(:body => "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000", :status => 200, :headers => { 'Content-Encoding' => 'gzip' } )
17
17
  response = RestClient.get "www.example.com"
18
- response.code.should eq 200
19
- response.body.should eq "i'm gziped\n"
18
+ expect(response.code).to eq 200
19
+ expect(response.body).to eq "i'm gziped\n"
20
20
  end
21
21
 
22
22
  it "a 404" do
@@ -26,10 +26,10 @@ describe RestClient do
26
26
  RestClient.get "www.example.com"
27
27
  raise
28
28
  rescue RestClient::ResourceNotFound => e
29
- e.http_code.should eq 404
30
- e.response.code.should eq 404
31
- e.response.body.should eq body
32
- e.http_body.should eq body
29
+ expect(e.http_code).to eq 404
30
+ expect(e.response.code).to eq 404
31
+ expect(e.response.body).to eq body
32
+ expect(e.http_body).to eq body
33
33
  end
34
34
  end
35
35
 
@@ -41,8 +41,8 @@ describe RestClient do
41
41
  'Content-Type' => 'text/plain; charset=UTF-8'
42
42
  })
43
43
  response = RestClient.get "www.example.com"
44
- response.encoding.should eq Encoding::UTF_8
45
- response.valid_encoding?.should eq true
44
+ expect(response.encoding).to eq Encoding::UTF_8
45
+ expect(response.valid_encoding?).to eq true
46
46
  end
47
47
 
48
48
  it 'handles windows-1252' do
@@ -52,9 +52,9 @@ describe RestClient do
52
52
  'Content-Type' => 'text/plain; charset=windows-1252'
53
53
  })
54
54
  response = RestClient.get "www.example.com"
55
- response.encoding.should eq Encoding::WINDOWS_1252
56
- response.encode('utf-8').should eq "ÿ"
57
- response.valid_encoding?.should eq true
55
+ expect(response.encoding).to eq Encoding::WINDOWS_1252
56
+ expect(response.encode('utf-8')).to eq "ÿ"
57
+ expect(response.valid_encoding?).to eq true
58
58
  end
59
59
 
60
60
  it 'handles binary' do
@@ -64,28 +64,28 @@ describe RestClient do
64
64
  'Content-Type' => 'application/octet-stream; charset=binary'
65
65
  })
66
66
  response = RestClient.get "www.example.com"
67
- response.encoding.should eq Encoding::BINARY
68
- lambda {
67
+ expect(response.encoding).to eq Encoding::BINARY
68
+ expect {
69
69
  response.encode('utf-8')
70
- }.should raise_error(Encoding::UndefinedConversionError)
71
- response.valid_encoding?.should eq true
70
+ }.to raise_error(Encoding::UndefinedConversionError)
71
+ expect(response.valid_encoding?).to eq true
72
72
  end
73
73
 
74
74
  it 'handles euc-jp' do
75
75
  body = "\xA4\xA2\xA4\xA4\xA4\xA6\xA4\xA8\xA4\xAA".
76
76
  force_encoding(Encoding::BINARY)
77
77
  body_utf8 = 'あいうえお'
78
- body_utf8.encoding.should eq Encoding::UTF_8
78
+ expect(body_utf8.encoding).to eq Encoding::UTF_8
79
79
 
80
80
  stub_request(:get, 'www.example.com').to_return(
81
81
  :body => body, :status => 200, :headers => {
82
82
  'Content-Type' => 'text/plain; charset=EUC-JP'
83
83
  })
84
84
  response = RestClient.get 'www.example.com'
85
- response.encoding.should eq Encoding::EUC_JP
86
- response.valid_encoding?.should eq true
87
- response.length.should eq 5
88
- response.encode('utf-8').should eq body_utf8
85
+ expect(response.encoding).to eq Encoding::EUC_JP
86
+ expect(response.valid_encoding?).to eq true
87
+ expect(response.length).to eq 5
88
+ expect(response.encode('utf-8')).to eq body_utf8
89
89
  end
90
90
 
91
91
  it 'defaults to Encoding.default_external' do
@@ -95,7 +95,17 @@ describe RestClient do
95
95
  })
96
96
 
97
97
  response = RestClient.get 'www.example.com'
98
- response.encoding.should eq Encoding.default_external
98
+ expect(response.encoding).to eq Encoding.default_external
99
+ end
100
+
101
+ it 'handles invalid encoding' do
102
+ stub_request(:get, 'www.example.com').to_return(
103
+ body: 'abc', status: 200, headers: {
104
+ 'Content-Type' => 'text; charset=plain'
105
+ })
106
+
107
+ response = RestClient.get 'www.example.com'
108
+ expect(response.encoding).to eq Encoding.default_external
99
109
  end
100
110
 
101
111
  it 'leaves images as binary' do
@@ -107,7 +117,7 @@ describe RestClient do
107
117
  })
108
118
 
109
119
  response = RestClient.get 'www.example.com'
110
- response.encoding.should eq Encoding::BINARY
120
+ expect(response.encoding).to eq Encoding::BINARY
111
121
  end
112
122
  end
113
123
  end
@@ -75,7 +75,7 @@ describe RestClient::Request do
75
75
  },
76
76
  )
77
77
  expect {request.execute }.to_not raise_error
78
- ran_callback.should eq(true)
78
+ expect(ran_callback).to eq(true)
79
79
  end
80
80
 
81
81
  it "fails verification when the callback returns false",
@@ -5,7 +5,7 @@ require_relative './helpers'
5
5
 
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
7
  RSpec.configure do |config|
8
- config.treat_symbols_as_metadata_keys_with_true_values = true
8
+ config.raise_errors_for_deprecations!
9
9
 
10
10
  # Run specs in random order to surface order dependencies. If you find an
11
11
  # order dependency and want to debug it, you can fix the order by providing
@@ -13,6 +13,10 @@ RSpec.configure do |config|
13
13
  # --seed 1234
14
14
  config.order = 'random'
15
15
 
16
+ # always run with ruby warnings enabled
17
+ # TODO: figure out why this is so obscenely noisy (rspec bug?)
18
+ # config.warnings = true
19
+
16
20
  # add helpers
17
21
  config.include Helpers, :include_helpers
18
22
 
@@ -20,3 +24,6 @@ RSpec.configure do |config|
20
24
  mocks.yield_receiver_to_any_instance_implementation_blocks = true
21
25
  end
22
26
  end
27
+
28
+ # always run with ruby warnings enabled (see above)
29
+ $VERBOSE = true