faraday 0.8.11 → 0.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.document +6 -0
  2. data/CONTRIBUTING.md +36 -0
  3. data/Gemfile +7 -6
  4. data/LICENSE.md +1 -1
  5. data/README.md +38 -51
  6. data/Rakefile +2 -18
  7. data/faraday.gemspec +34 -0
  8. data/lib/faraday.rb +181 -67
  9. data/lib/faraday/adapter.rb +19 -34
  10. data/lib/faraday/adapter/em_http.rb +24 -10
  11. data/lib/faraday/adapter/em_synchrony.rb +1 -15
  12. data/lib/faraday/adapter/excon.rb +6 -12
  13. data/lib/faraday/adapter/httpclient.rb +92 -0
  14. data/lib/faraday/adapter/net_http.rb +2 -3
  15. data/lib/faraday/adapter/net_http_persistent.rb +3 -15
  16. data/lib/faraday/adapter/patron.rb +13 -21
  17. data/lib/faraday/adapter/rack.rb +0 -2
  18. data/lib/faraday/adapter/test.rb +35 -36
  19. data/lib/faraday/adapter/typhoeus.rb +10 -12
  20. data/lib/faraday/autoload.rb +87 -0
  21. data/lib/faraday/connection.rb +196 -99
  22. data/lib/faraday/error.rb +33 -33
  23. data/lib/faraday/options.rb +215 -0
  24. data/lib/faraday/parameters.rb +193 -0
  25. data/lib/faraday/{builder.rb → rack_builder.rb} +78 -21
  26. data/lib/faraday/request.rb +12 -25
  27. data/lib/faraday/request/authorization.rb +3 -3
  28. data/lib/faraday/request/basic_authentication.rb +1 -1
  29. data/lib/faraday/request/instrumentation.rb +38 -0
  30. data/lib/faraday/request/multipart.rb +10 -9
  31. data/lib/faraday/request/retry.rb +70 -6
  32. data/lib/faraday/request/token_authentication.rb +2 -2
  33. data/lib/faraday/request/url_encoded.rb +7 -6
  34. data/lib/faraday/response.rb +17 -22
  35. data/lib/faraday/response/logger.rb +4 -4
  36. data/lib/faraday/response/raise_error.rb +4 -5
  37. data/lib/faraday/utils.rb +54 -67
  38. data/script/console +7 -0
  39. data/script/release +6 -3
  40. data/script/server +3 -1
  41. data/script/test +7 -33
  42. data/test/adapters/em_http_test.rb +6 -1
  43. data/test/adapters/em_synchrony_test.rb +7 -1
  44. data/test/adapters/excon_test.rb +0 -7
  45. data/test/adapters/httpclient_test.rb +16 -0
  46. data/test/adapters/integration.rb +8 -39
  47. data/test/adapters/logger_test.rb +1 -1
  48. data/test/adapters/net_http_test.rb +0 -31
  49. data/test/adapters/patron_test.rb +1 -1
  50. data/test/adapters/rack_test.rb +0 -5
  51. data/test/adapters/test_middleware_test.rb +19 -4
  52. data/test/adapters/typhoeus_test.rb +20 -3
  53. data/test/authentication_middleware_test.rb +7 -7
  54. data/test/connection_test.rb +52 -75
  55. data/test/env_test.rb +33 -24
  56. data/test/helper.rb +15 -13
  57. data/test/live_server.rb +10 -4
  58. data/test/middleware/instrumentation_test.rb +75 -0
  59. data/test/middleware/retry_test.rb +44 -38
  60. data/test/middleware_stack_test.rb +12 -11
  61. data/test/options_test.rb +126 -0
  62. data/test/request_middleware_test.rb +17 -7
  63. data/test/response_middleware_test.rb +2 -4
  64. data/test/strawberry.rb +2 -0
  65. metadata +82 -28
  66. checksums.yaml +0 -7
  67. data/script/proxy-server +0 -41
  68. data/test/multibyte.txt +0 -1
  69. data/test/parameters_test.rb +0 -24
  70. data/test/utils_test.rb +0 -30
@@ -1,45 +1,45 @@
1
1
  module Faraday
2
- module Error
3
- class ClientError < StandardError
4
- attr_reader :response
2
+ class Error < StandardError; end
3
+ class MissingDependency < Error; end
5
4
 
6
- def initialize(ex, response = nil)
7
- @wrapped_exception = nil
8
- @response = response
5
+ class ClientError < Error
6
+ attr_reader :response
9
7
 
10
- if ex.respond_to?(:backtrace)
11
- super(ex.message)
12
- @wrapped_exception = ex
13
- elsif ex.respond_to?(:each_key)
14
- super("the server responded with status #{ex[:status]}")
15
- @response = ex
16
- else
17
- super(ex.to_s)
18
- end
19
- end
8
+ def initialize(ex, response = nil)
9
+ @wrapped_exception = nil
10
+ @response = response
20
11
 
21
- def backtrace
22
- if @wrapped_exception
23
- @wrapped_exception.backtrace
24
- else
25
- super
26
- end
12
+ if ex.respond_to?(:backtrace)
13
+ super(ex.message)
14
+ @wrapped_exception = ex
15
+ elsif ex.respond_to?(:each_key)
16
+ super("the server responded with status #{ex[:status]}")
17
+ @response = ex
18
+ else
19
+ super(ex.to_s)
27
20
  end
21
+ end
28
22
 
29
- def inspect
30
- %(#<#{self.class}>)
23
+ def backtrace
24
+ if @wrapped_exception
25
+ @wrapped_exception.backtrace
26
+ else
27
+ super
31
28
  end
32
29
  end
33
30
 
34
- class ConnectionFailed < ClientError; end
35
- class ResourceNotFound < ClientError; end
36
- class ParsingError < ClientError; end
37
- class MissingDependency < StandardError; end
38
-
39
- class TimeoutError < ClientError
40
- def initialize(ex = nil)
41
- super(ex || "timeout")
42
- end
31
+ def inspect
32
+ %(#<#{self.class}>)
43
33
  end
44
34
  end
35
+
36
+ class ConnectionFailed < ClientError; end
37
+ class ResourceNotFound < ClientError; end
38
+ class ParsingError < ClientError; end
39
+ class TimeoutError < ClientError; end
40
+
41
+ [:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
42
+ :ParsingError, :TimeoutError].each do |const|
43
+ Error.const_set(const, Faraday.const_get(const))
44
+ end
45
45
  end
@@ -0,0 +1,215 @@
1
+ module Faraday
2
+ # Subclasses Struct with some special helpers for converting from a Hash to
3
+ # a Struct.
4
+ class Options < Struct
5
+ # Public
6
+ def self.from(value)
7
+ value ? new.update(value) : new
8
+ end
9
+
10
+ # Public
11
+ def each(&block)
12
+ members.each do |key|
13
+ block.call key.to_sym, send(key)
14
+ end
15
+ end
16
+
17
+ # Public
18
+ def update(obj)
19
+ obj.each do |key, value|
20
+ next unless value
21
+ sub_options = self.class.options_for(key)
22
+ if sub_options && value
23
+ value = sub_options.from(value)
24
+ elsif Hash === value
25
+ hash = {}
26
+ value.each do |hash_key, hash_value|
27
+ hash[hash_key] = hash_value
28
+ end
29
+ value = hash
30
+ end
31
+
32
+ self.send("#{key}=", value)
33
+ end
34
+ self
35
+ end
36
+
37
+ # Public
38
+ def delete(key)
39
+ value = send(key)
40
+ send("#{key}=", nil)
41
+ value
42
+ end
43
+
44
+ # Public
45
+ def merge(value)
46
+ dup.update(value)
47
+ end
48
+
49
+ # Public
50
+ def fetch(key, default = nil)
51
+ send(key) || send("#{key}=", default ||
52
+ (block_given? ? Proc.new.call : nil))
53
+ end
54
+
55
+ # Public
56
+ def values_at(*keys)
57
+ keys.map { |key| send(key) }
58
+ end
59
+
60
+ # Public
61
+ def keys
62
+ members.reject { |m| send(m).nil? }
63
+ end
64
+
65
+ # Public
66
+ def to_hash
67
+ hash = {}
68
+ members.each do |key|
69
+ value = send(key)
70
+ hash[key.to_sym] = value if value
71
+ end
72
+ hash
73
+ end
74
+
75
+ # Internal
76
+ def inspect
77
+ values = []
78
+ members.each do |m|
79
+ value = send(m)
80
+ values << "#{m}=#{value.inspect}" if value
81
+ end
82
+ values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
83
+
84
+ %(#<#{self.class}#{values}>)
85
+ end
86
+
87
+ # Internal
88
+ def self.options(mapping)
89
+ attribute_options.update(mapping)
90
+ end
91
+
92
+ # Internal
93
+ def self.options_for(key)
94
+ attribute_options[key]
95
+ end
96
+
97
+ # Internal
98
+ def self.attribute_options
99
+ @attribute_options ||= {}
100
+ end
101
+ end
102
+
103
+ class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
104
+ :timeout, :open_timeout, :boundary,
105
+ :oauth)
106
+
107
+ def []=(key, value)
108
+ if key && key.to_sym == :proxy
109
+ super(key, value ? ProxyOptions.from(value) : nil)
110
+ else
111
+ super(key, value)
112
+ end
113
+ end
114
+ end
115
+
116
+ class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
117
+ :cert_store, :client_cert, :client_key, :verify_depth, :version)
118
+
119
+ def verify?
120
+ verify != false
121
+ end
122
+
123
+ def disable?
124
+ !verify?
125
+ end
126
+ end
127
+
128
+ class ProxyOptions < Options.new(:uri, :user, :password)
129
+ extend Forwardable
130
+ def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
131
+
132
+ def self.from(value)
133
+ case value
134
+ when String then value = {:uri => Utils.URI(value)}
135
+ when URI then value = {:uri => value}
136
+ when Hash, Options
137
+ if uri = value.delete(:uri)
138
+ value[:uri] = Utils.URI(uri)
139
+ end
140
+ end
141
+ super(value)
142
+ end
143
+
144
+ def user
145
+ self[:user] ||= Utils.unescape(uri.user)
146
+ end
147
+
148
+ def password
149
+ self[:password] ||= Utils.unescape(uri.password)
150
+ end
151
+ end
152
+
153
+ class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
154
+ :parallel_manager, :params, :headers, :builder_class)
155
+
156
+ options :request => RequestOptions, :ssl => SSLOptions
157
+
158
+ def request
159
+ self[:request] ||= self.class.options_for(:request).new
160
+ end
161
+
162
+ def ssl
163
+ self[:ssl] ||= self.class.options_for(:ssl).new
164
+ end
165
+
166
+ def builder_class
167
+ self[:builder_class] ||= RackBuilder
168
+ end
169
+
170
+ def new_builder(block)
171
+ builder_class.new(&block)
172
+ end
173
+ end
174
+
175
+ class Env < Options.new(:method, :body, :url, :request, :request_headers,
176
+ :ssl, :parallel_manager, :params, :response, :response_headers, :status)
177
+
178
+ ContentLength = 'Content-Length'.freeze
179
+ StatusesWithoutBody = Set.new [204, 304]
180
+ SuccessfulStatuses = 200..299
181
+
182
+ # A Set of HTTP verbs that typically send a body. If no body is set for
183
+ # these requests, the Content-Length header is set to 0.
184
+ MethodsWithBodies = Set.new [:post, :put, :patch, :options]
185
+
186
+ options :request => RequestOptions,
187
+ :request_headers => Utils::Headers, :response_headers => Utils::Headers
188
+
189
+ extend Forwardable
190
+
191
+ def_delegators :request, :params_encoder
192
+
193
+ def success?
194
+ SuccessfulStatuses.include?(status)
195
+ end
196
+
197
+ def needs_body?
198
+ !body && MethodsWithBodies.include?(method)
199
+ end
200
+
201
+ def clear_body
202
+ request_headers[ContentLength] = '0'
203
+ self.body = ''
204
+ end
205
+
206
+ def parse_body?
207
+ !StatusesWithoutBody.include?(status)
208
+ end
209
+
210
+ def parallel?
211
+ !!parallel_manager
212
+ end
213
+ end
214
+ end
215
+
@@ -0,0 +1,193 @@
1
+ module Faraday
2
+ module NestedParamsEncoder
3
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
4
+
5
+ def self.escape(s)
6
+ return s.to_s.gsub(ESCAPE_RE) {
7
+ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
8
+ }.tr(' ', '+')
9
+ end
10
+
11
+ def self.unescape(s)
12
+ CGI.unescape(s.to_s)
13
+ end
14
+
15
+ def self.encode(params)
16
+ return nil if params == nil
17
+
18
+ if !params.is_a?(Array)
19
+ if !params.respond_to?(:to_hash)
20
+ raise TypeError,
21
+ "Can't convert #{params.class} into Hash."
22
+ end
23
+ params = params.to_hash
24
+ params = params.map do |key, value|
25
+ key = key.to_s if key.kind_of?(Symbol)
26
+ [key, value]
27
+ end
28
+ # Useful default for OAuth and caching.
29
+ # Only to be used for non-Array inputs. Arrays should preserve order.
30
+ params.sort!
31
+ end
32
+
33
+ # Helper lambda
34
+ to_query = lambda do |parent, value|
35
+ if value.is_a?(Hash)
36
+ value = value.map do |key, val|
37
+ key = escape(key)
38
+ [key, val]
39
+ end
40
+ value.sort!
41
+ buffer = ""
42
+ value.each do |key, val|
43
+ new_parent = "#{parent}%5B#{key}%5D"
44
+ buffer << "#{to_query.call(new_parent, val)}&"
45
+ end
46
+ return buffer.chop
47
+ elsif value.is_a?(Array)
48
+ buffer = ""
49
+ value.each_with_index do |val, i|
50
+ new_parent = "#{parent}%5B%5D"
51
+ buffer << "#{to_query.call(new_parent, val)}&"
52
+ end
53
+ return buffer.chop
54
+ else
55
+ encoded_value = escape(value)
56
+ return "#{parent}=#{encoded_value}"
57
+ end
58
+ end
59
+
60
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
61
+ buffer = ''
62
+ params.each do |parent, value|
63
+ encoded_parent = escape(parent)
64
+ buffer << "#{to_query.call(encoded_parent, value)}&"
65
+ end
66
+ return buffer.chop
67
+ end
68
+
69
+ def self.decode(query)
70
+ return nil if query == nil
71
+ # Recursive helper lambda
72
+ dehash = lambda do |hash|
73
+ hash.each do |(key, value)|
74
+ if value.kind_of?(Hash)
75
+ hash[key] = dehash.call(value)
76
+ end
77
+ end
78
+ # Numeric keys implies an array
79
+ if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
80
+ hash.sort.inject([]) do |accu, (_, value)|
81
+ accu << value; accu
82
+ end
83
+ else
84
+ hash
85
+ end
86
+ end
87
+
88
+ empty_accumulator = {}
89
+ return ((query.split('&').map do |pair|
90
+ pair.split('=', 2) if pair && !pair.empty?
91
+ end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
92
+ key = unescape(key)
93
+ if value.kind_of?(String)
94
+ value = unescape(value.gsub(/\+/, ' '))
95
+ end
96
+
97
+ array_notation = !!(key =~ /\[\]$/)
98
+ subkeys = key.split(/[\[\]]+/)
99
+ current_hash = accu
100
+ for i in 0...(subkeys.size - 1)
101
+ subkey = subkeys[i]
102
+ current_hash[subkey] = {} unless current_hash[subkey]
103
+ current_hash = current_hash[subkey]
104
+ end
105
+ if array_notation
106
+ current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
107
+ current_hash[subkeys.last] << value
108
+ else
109
+ current_hash[subkeys.last] = value
110
+ end
111
+ accu
112
+ end).inject(empty_accumulator.dup) do |accu, (key, value)|
113
+ accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
114
+ accu
115
+ end
116
+ end
117
+ end
118
+
119
+ module FlatParamsEncoder
120
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
121
+
122
+ def self.escape(s)
123
+ return s.to_s.gsub(ESCAPE_RE) {
124
+ '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
125
+ }.tr(' ', '+')
126
+ end
127
+
128
+ def self.unescape(s)
129
+ CGI.unescape(s.to_s)
130
+ end
131
+
132
+ def self.encode(params)
133
+ return nil if params == nil
134
+
135
+ if !params.is_a?(Array)
136
+ if !params.respond_to?(:to_hash)
137
+ raise TypeError,
138
+ "Can't convert #{params.class} into Hash."
139
+ end
140
+ params = params.to_hash
141
+ params = params.map do |key, value|
142
+ key = key.to_s if key.kind_of?(Symbol)
143
+ [key, value]
144
+ end
145
+ # Useful default for OAuth and caching.
146
+ # Only to be used for non-Array inputs. Arrays should preserve order.
147
+ params.sort!
148
+ end
149
+
150
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
151
+ buffer = ''
152
+ params.each do |key, value|
153
+ encoded_key = escape(key)
154
+ value = value.to_s if value == true || value == false
155
+ if value == nil
156
+ buffer << "#{encoded_key}&"
157
+ elsif value.kind_of?(Array)
158
+ value.each do |sub_value|
159
+ encoded_value = escape(sub_value)
160
+ buffer << "#{encoded_key}=#{encoded_value}&"
161
+ end
162
+ else
163
+ encoded_value = escape(value)
164
+ buffer << "#{encoded_key}=#{encoded_value}&"
165
+ end
166
+ end
167
+ return buffer.chop
168
+ end
169
+
170
+ def self.decode(query)
171
+ empty_accumulator = {}
172
+ return nil if query == nil
173
+ split_query = (query.split('&').map do |pair|
174
+ pair.split('=', 2) if pair && !pair.empty?
175
+ end).compact
176
+ return split_query.inject(empty_accumulator.dup) do |accu, pair|
177
+ pair[0] = unescape(pair[0])
178
+ pair[1] = true if pair[1].nil?
179
+ if pair[1].respond_to?(:to_str)
180
+ pair[1] = unescape(pair[1].to_str.gsub(/\+/, " "))
181
+ end
182
+ if accu[pair[0]].kind_of?(Array)
183
+ accu[pair[0]] << pair[1]
184
+ elsif accu[pair[0]]
185
+ accu[pair[0]] = [accu[pair[0]], pair[1]]
186
+ else
187
+ accu[pair[0]] = pair[1]
188
+ end
189
+ accu
190
+ end
191
+ end
192
+ end
193
+ end