faraday 0.13.0 → 2.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.
Files changed (92) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +496 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +28 -328
  5. data/Rakefile +7 -0
  6. data/examples/client_spec.rb +97 -0
  7. data/examples/client_test.rb +118 -0
  8. data/lib/faraday/adapter/test.rb +127 -68
  9. data/lib/faraday/adapter.rb +71 -22
  10. data/lib/faraday/adapter_registry.rb +30 -0
  11. data/lib/faraday/connection.rb +314 -226
  12. data/lib/faraday/encoders/flat_params_encoder.rb +105 -0
  13. data/lib/faraday/encoders/nested_params_encoder.rb +176 -0
  14. data/lib/faraday/error.rb +121 -37
  15. data/lib/faraday/logging/formatter.rb +106 -0
  16. data/lib/faraday/methods.rb +6 -0
  17. data/lib/faraday/middleware.rb +18 -25
  18. data/lib/faraday/middleware_registry.rb +65 -0
  19. data/lib/faraday/options/connection_options.rb +22 -0
  20. data/lib/faraday/options/env.rb +181 -0
  21. data/lib/faraday/options/proxy_options.rb +32 -0
  22. data/lib/faraday/options/request_options.rb +22 -0
  23. data/lib/faraday/options/ssl_options.rb +59 -0
  24. data/lib/faraday/options.rb +41 -195
  25. data/lib/faraday/parameters.rb +4 -196
  26. data/lib/faraday/rack_builder.rb +91 -74
  27. data/lib/faraday/request/authorization.rb +37 -29
  28. data/lib/faraday/request/instrumentation.rb +47 -27
  29. data/lib/faraday/request/json.rb +55 -0
  30. data/lib/faraday/request/url_encoded.rb +45 -23
  31. data/lib/faraday/request.rb +74 -32
  32. data/lib/faraday/response/json.rb +54 -0
  33. data/lib/faraday/response/logger.rb +22 -69
  34. data/lib/faraday/response/raise_error.rb +57 -14
  35. data/lib/faraday/response.rb +26 -33
  36. data/lib/faraday/utils/headers.rb +139 -0
  37. data/lib/faraday/utils/params_hash.rb +61 -0
  38. data/lib/faraday/utils.rb +47 -251
  39. data/lib/faraday/version.rb +5 -0
  40. data/lib/faraday.rb +104 -197
  41. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  42. data/spec/faraday/adapter/test_spec.rb +377 -0
  43. data/spec/faraday/adapter_registry_spec.rb +28 -0
  44. data/spec/faraday/adapter_spec.rb +55 -0
  45. data/spec/faraday/connection_spec.rb +787 -0
  46. data/spec/faraday/error_spec.rb +60 -0
  47. data/spec/faraday/middleware_spec.rb +52 -0
  48. data/spec/faraday/options/env_spec.rb +70 -0
  49. data/spec/faraday/options/options_spec.rb +297 -0
  50. data/spec/faraday/options/proxy_options_spec.rb +44 -0
  51. data/spec/faraday/options/request_options_spec.rb +19 -0
  52. data/spec/faraday/params_encoders/flat_spec.rb +42 -0
  53. data/spec/faraday/params_encoders/nested_spec.rb +142 -0
  54. data/spec/faraday/rack_builder_spec.rb +302 -0
  55. data/spec/faraday/request/authorization_spec.rb +83 -0
  56. data/spec/faraday/request/instrumentation_spec.rb +74 -0
  57. data/spec/faraday/request/json_spec.rb +111 -0
  58. data/spec/faraday/request/url_encoded_spec.rb +82 -0
  59. data/spec/faraday/request_spec.rb +109 -0
  60. data/spec/faraday/response/json_spec.rb +117 -0
  61. data/spec/faraday/response/logger_spec.rb +220 -0
  62. data/spec/faraday/response/raise_error_spec.rb +172 -0
  63. data/spec/faraday/response_spec.rb +75 -0
  64. data/spec/faraday/utils/headers_spec.rb +82 -0
  65. data/spec/faraday/utils_spec.rb +117 -0
  66. data/spec/faraday_spec.rb +37 -0
  67. data/spec/spec_helper.rb +132 -0
  68. data/spec/support/disabling_stub.rb +14 -0
  69. data/spec/support/fake_safe_buffer.rb +15 -0
  70. data/spec/support/helper_methods.rb +96 -0
  71. data/spec/support/shared_examples/adapter.rb +104 -0
  72. data/spec/support/shared_examples/params_encoder.rb +18 -0
  73. data/spec/support/shared_examples/request_method.rb +249 -0
  74. data/spec/support/streaming_response_checker.rb +35 -0
  75. metadata +71 -34
  76. data/lib/faraday/adapter/em_http.rb +0 -243
  77. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -56
  78. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -66
  79. data/lib/faraday/adapter/em_synchrony.rb +0 -106
  80. data/lib/faraday/adapter/excon.rb +0 -80
  81. data/lib/faraday/adapter/httpclient.rb +0 -128
  82. data/lib/faraday/adapter/net_http.rb +0 -135
  83. data/lib/faraday/adapter/net_http_persistent.rb +0 -54
  84. data/lib/faraday/adapter/patron.rb +0 -83
  85. data/lib/faraday/adapter/rack.rb +0 -58
  86. data/lib/faraday/adapter/typhoeus.rb +0 -123
  87. data/lib/faraday/autoload.rb +0 -84
  88. data/lib/faraday/request/basic_authentication.rb +0 -13
  89. data/lib/faraday/request/multipart.rb +0 -68
  90. data/lib/faraday/request/retry.rb +0 -164
  91. data/lib/faraday/request/token_authentication.rb +0 -15
  92. data/lib/faraday/upload_io.rb +0 -67
@@ -1,51 +1,60 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
4
  class Adapter
3
- # Examples
4
- #
5
+ # @example
5
6
  # test = Faraday::Connection.new do
6
7
  # use Faraday::Adapter::Test do |stub|
7
- # # simply define matcher to match the request
8
+ # # Define matcher to match the request
8
9
  # stub.get '/resource.json' do
9
10
  # # return static content
10
11
  # [200, {'Content-Type' => 'application/json'}, 'hi world']
11
12
  # end
12
- #
13
+ #
13
14
  # # response with content generated based on request
14
15
  # stub.get '/showget' do |env|
15
16
  # [200, {'Content-Type' => 'text/plain'}, env[:method].to_s]
16
17
  # end
17
- #
18
- # # regular expression can be used as matching filter
18
+ #
19
+ # # A regular expression can be used as matching filter
19
20
  # stub.get /\A\/items\/(\d+)\z/ do |env, meta|
20
- # # in case regular expression is used an instance of MatchData can be received
21
- # [200, {'Content-Type' => 'text/plain'}, "showing item: #{meta[:match_data][1]}"]
21
+ # # in case regular expression is used, an instance of MatchData
22
+ # # can be received
23
+ # [200,
24
+ # {'Content-Type' => 'text/plain'},
25
+ # "showing item: #{meta[:match_data][1]}"
26
+ # ]
22
27
  # end
28
+ #
29
+ # # You can set strict_mode to exactly match the stubbed requests.
30
+ # stub.strict_mode = true
23
31
  # end
24
32
  # end
25
- #
33
+ #
26
34
  # resp = test.get '/resource.json'
27
35
  # resp.body # => 'hi world'
28
- #
36
+ #
29
37
  # resp = test.get '/showget'
30
38
  # resp.body # => 'get'
31
- #
39
+ #
32
40
  # resp = test.get '/items/1'
33
41
  # resp.body # => 'showing item: 1'
34
- #
42
+ #
35
43
  # resp = test.get '/items/2'
36
44
  # resp.body # => 'showing item: 2'
37
- #
38
-
39
45
  class Test < Faraday::Adapter
40
46
  attr_accessor :stubs
41
47
 
48
+ # A stack of Stubs
42
49
  class Stubs
43
50
  class NotFound < StandardError
44
51
  end
45
52
 
46
- def initialize
47
- # {:get => [Stub, Stub]}
48
- @stack, @consumed = {}, {}
53
+ def initialize(strict_mode: false)
54
+ # { get: [Stub, Stub] }
55
+ @stack = {}
56
+ @consumed = {}
57
+ @strict_mode = strict_mode
49
58
  yield(self) if block_given?
50
59
  end
51
60
 
@@ -53,17 +62,20 @@ module Faraday
53
62
  @stack.empty?
54
63
  end
55
64
 
56
- def match(request_method, path, headers, body)
57
- return false if !@stack.key?(request_method)
65
+ # @param env [Faraday::Env]
66
+ def match(env)
67
+ request_method = env[:method]
68
+ return false unless @stack.key?(request_method)
69
+
58
70
  stack = @stack[request_method]
59
71
  consumed = (@consumed[request_method] ||= [])
60
72
 
61
- stub, meta = matches?(stack, path, headers, body)
73
+ stub, meta = matches?(stack, env)
62
74
  if stub
63
75
  consumed << stack.delete(stub)
64
76
  return stub, meta
65
77
  end
66
- matches?(consumed, path, headers, body)
78
+ matches?(consumed, env)
67
79
  end
68
80
 
69
81
  def get(path, headers = {}, &block)
@@ -74,15 +86,15 @@ module Faraday
74
86
  new_stub(:head, path, headers, &block)
75
87
  end
76
88
 
77
- def post(path, body=nil, headers = {}, &block)
89
+ def post(path, body = nil, headers = {}, &block)
78
90
  new_stub(:post, path, headers, body, &block)
79
91
  end
80
92
 
81
- def put(path, body=nil, headers = {}, &block)
93
+ def put(path, body = nil, headers = {}, &block)
82
94
  new_stub(:put, path, headers, body, &block)
83
95
  end
84
96
 
85
- def patch(path, body=nil, headers = {}, &block)
97
+ def patch(path, body = nil, headers = {}, &block)
86
98
  new_stub(:patch, path, headers, body, &block)
87
99
  end
88
100
 
@@ -98,69 +110,109 @@ module Faraday
98
110
  def verify_stubbed_calls
99
111
  failed_stubs = []
100
112
  @stack.each do |method, stubs|
101
- unless stubs.size == 0
102
- failed_stubs.concat(stubs.map {|stub|
113
+ next if stubs.empty?
114
+
115
+ failed_stubs.concat(
116
+ stubs.map do |stub|
103
117
  "Expected #{method} #{stub}."
104
- })
118
+ end
119
+ )
120
+ end
121
+ raise failed_stubs.join(' ') unless failed_stubs.empty?
122
+ end
123
+
124
+ # Set strict_mode. If the value is true, this adapter tries to find matched requests strictly,
125
+ # which means that all of a path, parameters, and headers must be the same as an actual request.
126
+ def strict_mode=(value)
127
+ @strict_mode = value
128
+ @stack.each do |_method, stubs|
129
+ stubs.each do |stub|
130
+ stub.strict_mode = value
105
131
  end
106
132
  end
107
- raise failed_stubs.join(" ") unless failed_stubs.size == 0
108
133
  end
109
134
 
110
135
  protected
111
136
 
112
- def new_stub(request_method, path, headers = {}, body=nil, &block)
113
- normalized_path = path.is_a?(Regexp) ? path : Faraday::Utils.normalize_path(path)
114
- (@stack[request_method] ||= []) << Stub.new(normalized_path, headers, body, block)
137
+ def new_stub(request_method, path, headers = {}, body = nil, &block)
138
+ normalized_path, host =
139
+ if path.is_a?(Regexp)
140
+ path
141
+ else
142
+ [
143
+ Faraday::Utils.normalize_path(path),
144
+ Faraday::Utils.URI(path).host
145
+ ]
146
+ end
147
+ path, query = normalized_path.respond_to?(:split) ? normalized_path.split('?') : normalized_path
148
+ headers = Utils::Headers.new(headers)
149
+
150
+ stub = Stub.new(host, path, query, headers, body, @strict_mode, block)
151
+ (@stack[request_method] ||= []) << stub
115
152
  end
116
153
 
117
- def matches?(stack, path, headers, body)
154
+ # @param stack [Hash]
155
+ # @param env [Faraday::Env]
156
+ def matches?(stack, env)
118
157
  stack.each do |stub|
119
- match_result, meta = stub.matches?(path, headers, body)
158
+ match_result, meta = stub.matches?(env)
120
159
  return stub, meta if match_result
121
160
  end
122
161
  nil
123
162
  end
124
163
  end
125
164
 
126
- class Stub < Struct.new(:path, :params, :headers, :body, :block)
127
- def initialize(full, headers, body, block)
128
- path, query = full.respond_to?(:split) ? full.split("?") : full
129
- params = query ?
130
- Faraday::Utils.parse_nested_query(query) :
131
- {}
132
- super(path, params, headers, body, block)
133
- end
134
-
135
- def matches?(request_uri, request_headers, request_body)
136
- request_path, request_query = request_uri.split('?')
137
- request_params = request_query ?
138
- Faraday::Utils.parse_nested_query(request_query) :
139
- {}
140
- # meta is a hash use as carrier
165
+ # Stub request
166
+ class Stub < Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) # rubocop:disable Style/StructInheritance
167
+ # @param env [Faraday::Env]
168
+ def matches?(env)
169
+ request_host = env[:url].host
170
+ request_path = Faraday::Utils.normalize_path(env[:url].path)
171
+ request_headers = env.request_headers
172
+ request_body = env[:body]
173
+
174
+ # meta is a hash used as carrier
141
175
  # that will be yielded to consumer block
142
176
  meta = {}
143
- return path_match?(request_path, meta) &&
144
- params_match?(request_params) &&
177
+ [(host.nil? || host == request_host) &&
178
+ path_match?(request_path, meta) &&
179
+ params_match?(env) &&
145
180
  (body.to_s.size.zero? || request_body == body) &&
146
- headers_match?(request_headers), meta
181
+ headers_match?(request_headers), meta]
147
182
  end
148
183
 
149
184
  def path_match?(request_path, meta)
150
- if path.is_a? Regexp
185
+ if path.is_a?(Regexp)
151
186
  !!(meta[:match_data] = path.match(request_path))
152
187
  else
153
188
  path == request_path
154
189
  end
155
190
  end
156
191
 
157
- def params_match?(request_params)
192
+ # @param env [Faraday::Env]
193
+ def params_match?(env)
194
+ request_params = env[:params]
195
+ params = env.params_encoder.decode(query) || {}
196
+
197
+ if strict_mode
198
+ return Set.new(params) == Set.new(request_params)
199
+ end
200
+
158
201
  params.keys.all? do |key|
159
202
  request_params[key] == params[key]
160
203
  end
161
204
  end
162
205
 
163
206
  def headers_match?(request_headers)
207
+ if strict_mode
208
+ headers_with_user_agent = headers.dup.tap do |hs|
209
+ # NOTE: Set User-Agent in case it's not set when creating Stubs.
210
+ # Users would not want to set Faraday's User-Agent explicitly.
211
+ hs[:user_agent] ||= Connection::USER_AGENT
212
+ end
213
+ return Set.new(headers_with_user_agent) == Set.new(request_headers)
214
+ end
215
+
164
216
  headers.keys.all? do |key|
165
217
  request_headers[key] == headers[key]
166
218
  end
@@ -171,7 +223,7 @@ module Faraday
171
223
  end
172
224
  end
173
225
 
174
- def initialize(app, stubs=nil, &block)
226
+ def initialize(app, stubs = nil, &block)
175
227
  super(app)
176
228
  @stubs = stubs || Stubs.new
177
229
  configure(&block) if block
@@ -181,25 +233,32 @@ module Faraday
181
233
  yield(stubs)
182
234
  end
183
235
 
236
+ # @param env [Faraday::Env]
184
237
  def call(env)
185
238
  super
186
- normalized_path = Faraday::Utils.normalize_path(env[:url])
187
- params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder
188
-
189
- stub, meta = stubs.match(env[:method], normalized_path, env.request_headers, env[:body])
190
- if stub
191
- env[:params] = (query = env[:url].query) ?
192
- params_encoder.decode(query) : {}
193
- block_arity = stub.block.arity
194
- status, headers, body = (block_arity >= 0) ?
195
- stub.block.call(*[env, meta].take(block_arity)) :
196
- stub.block.call(env, meta)
197
- save_response(env, status, body, headers)
198
- else
199
- raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
239
+
240
+ env.request.params_encoder ||= Faraday::Utils.default_params_encoder
241
+ env[:params] = env.params_encoder.decode(env[:url].query) || {}
242
+ stub, meta = stubs.match(env)
243
+
244
+ unless stub
245
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
246
+ "#{env[:url]} #{env[:body]}"
200
247
  end
248
+
249
+ block_arity = stub.block.arity
250
+ status, headers, body =
251
+ if block_arity >= 0
252
+ stub.block.call(*[env, meta].take(block_arity))
253
+ else
254
+ stub.block.call(env, meta)
255
+ end
256
+ save_response(env, status, body, headers)
257
+
201
258
  @app.call(env)
202
259
  end
203
260
  end
204
261
  end
205
262
  end
263
+
264
+ Faraday::Adapter.register_middleware(test: Faraday::Adapter::Test)
@@ -1,53 +1,102 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
- # Public: This is a base class for all Faraday adapters. Adapters are
4
+ # Base class for all Faraday adapters. Adapters are
3
5
  # responsible for fulfilling a Faraday request.
4
- class Adapter < Middleware
5
- CONTENT_LENGTH = 'Content-Length'.freeze
6
-
7
- register_middleware File.expand_path('../adapter', __FILE__),
8
- :test => [:Test, 'test'],
9
- :net_http => [:NetHttp, 'net_http'],
10
- :net_http_persistent => [:NetHttpPersistent, 'net_http_persistent'],
11
- :typhoeus => [:Typhoeus, 'typhoeus'],
12
- :patron => [:Patron, 'patron'],
13
- :em_synchrony => [:EMSynchrony, 'em_synchrony'],
14
- :em_http => [:EMHttp, 'em_http'],
15
- :excon => [:Excon, 'excon'],
16
- :rack => [:Rack, 'rack'],
17
- :httpclient => [:HTTPClient, 'httpclient']
18
-
19
- # Public: This module marks an Adapter as supporting parallel requests.
6
+ class Adapter
7
+ extend MiddlewareRegistry
8
+
9
+ CONTENT_LENGTH = 'Content-Length'
10
+
11
+ # This module marks an Adapter as supporting parallel requests.
20
12
  module Parallelism
21
13
  attr_writer :supports_parallel
22
- def supports_parallel?() @supports_parallel end
14
+
15
+ def supports_parallel?
16
+ @supports_parallel
17
+ end
23
18
 
24
19
  def inherited(subclass)
25
20
  super
26
- subclass.supports_parallel = self.supports_parallel?
21
+ subclass.supports_parallel = supports_parallel?
27
22
  end
28
23
  end
29
24
 
30
25
  extend Parallelism
31
26
  self.supports_parallel = false
32
27
 
33
- def initialize(app = nil, opts = {}, &block)
34
- super(app)
28
+ def initialize(_app = nil, opts = {}, &block)
29
+ @app = ->(env) { env.response }
35
30
  @connection_options = opts
36
31
  @config_block = block
37
32
  end
38
33
 
34
+ # Yields or returns an adapter's configured connection. Depends on
35
+ # #build_connection being defined on this adapter.
36
+ #
37
+ # @param env [Faraday::Env, Hash] The env object for a faraday request.
38
+ #
39
+ # @return The return value of the given block, or the HTTP connection object
40
+ # if no block is given.
41
+ def connection(env)
42
+ conn = build_connection(env)
43
+ return conn unless block_given?
44
+
45
+ yield conn
46
+ end
47
+
48
+ # Close any persistent connections. The adapter should still be usable
49
+ # after calling close.
50
+ def close
51
+ # Possible implementation:
52
+ # @app.close if @app.respond_to?(:close)
53
+ end
54
+
39
55
  def call(env)
40
56
  env.clear_body if env.needs_body?
57
+ env.response = Response.new
41
58
  end
42
59
 
60
+ private
61
+
43
62
  def save_response(env, status, body, headers = nil, reason_phrase = nil)
44
63
  env.status = status
45
64
  env.body = body
46
- env.reason_phrase = reason_phrase && reason_phrase.to_s.strip
65
+ env.reason_phrase = reason_phrase&.to_s&.strip
47
66
  env.response_headers = Utils::Headers.new.tap do |response_headers|
48
67
  response_headers.update headers unless headers.nil?
49
68
  yield(response_headers) if block_given?
50
69
  end
70
+
71
+ env.response.finish(env) unless env.parallel?
72
+ env.response
73
+ end
74
+
75
+ # Fetches either a read, write, or open timeout setting. Defaults to the
76
+ # :timeout value if a more specific one is not given.
77
+ #
78
+ # @param type [Symbol] Describes which timeout setting to get: :read,
79
+ # :write, or :open.
80
+ # @param options [Hash] Hash containing Symbol keys like :timeout,
81
+ # :read_timeout, :write_timeout, :open_timeout, or
82
+ # :timeout
83
+ #
84
+ # @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
85
+ # has been set.
86
+ def request_timeout(type, options)
87
+ key = TIMEOUT_KEYS.fetch(type) do
88
+ msg = "Expected :read, :write, :open. Got #{type.inspect} :("
89
+ raise ArgumentError, msg
90
+ end
91
+ options[key] || options[:timeout]
51
92
  end
93
+
94
+ TIMEOUT_KEYS = {
95
+ read: :read_timeout,
96
+ open: :open_timeout,
97
+ write: :write_timeout
98
+ }.freeze
52
99
  end
53
100
  end
101
+
102
+ require 'faraday/adapter/test'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'monitor'
4
+
5
+ module Faraday
6
+ # AdapterRegistry registers adapter class names so they can be looked up by a
7
+ # String or Symbol name.
8
+ class AdapterRegistry
9
+ def initialize
10
+ @lock = Monitor.new
11
+ @constants = {}
12
+ end
13
+
14
+ def get(name)
15
+ klass = @lock.synchronize do
16
+ @constants[name]
17
+ end
18
+ return klass if klass
19
+
20
+ Object.const_get(name).tap { |c| set(c, name) }
21
+ end
22
+
23
+ def set(klass, name = nil)
24
+ name ||= klass.to_s
25
+ @lock.synchronize do
26
+ @constants[name] = klass
27
+ end
28
+ end
29
+ end
30
+ end