faraday 0.15.4 → 1.0.1

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +276 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +18 -344
  5. data/Rakefile +7 -0
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday/adapter/em_http.rb +144 -101
  9. data/lib/faraday/adapter/em_http_ssl_patch.rb +24 -18
  10. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +18 -15
  11. data/lib/faraday/adapter/em_synchrony.rb +104 -60
  12. data/lib/faraday/adapter/excon.rb +98 -56
  13. data/lib/faraday/adapter/httpclient.rb +83 -59
  14. data/lib/faraday/adapter/net_http.rb +130 -63
  15. data/lib/faraday/adapter/net_http_persistent.rb +51 -28
  16. data/lib/faraday/adapter/patron.rb +80 -43
  17. data/lib/faraday/adapter/rack.rb +30 -13
  18. data/lib/faraday/adapter/test.rb +86 -53
  19. data/lib/faraday/adapter/typhoeus.rb +4 -1
  20. data/lib/faraday/adapter.rb +82 -22
  21. data/lib/faraday/adapter_registry.rb +30 -0
  22. data/lib/faraday/autoload.rb +47 -36
  23. data/lib/faraday/connection.rb +312 -182
  24. data/lib/faraday/dependency_loader.rb +37 -0
  25. data/lib/faraday/encoders/flat_params_encoder.rb +98 -0
  26. data/lib/faraday/encoders/nested_params_encoder.rb +171 -0
  27. data/lib/faraday/error.rb +103 -37
  28. data/lib/faraday/file_part.rb +128 -0
  29. data/lib/faraday/logging/formatter.rb +105 -0
  30. data/lib/faraday/middleware.rb +12 -28
  31. data/lib/faraday/middleware_registry.rb +129 -0
  32. data/lib/faraday/options/connection_options.rb +22 -0
  33. data/lib/faraday/options/env.rb +181 -0
  34. data/lib/faraday/options/proxy_options.rb +28 -0
  35. data/lib/faraday/options/request_options.rb +22 -0
  36. data/lib/faraday/options/ssl_options.rb +59 -0
  37. data/lib/faraday/options.rb +35 -186
  38. data/lib/faraday/param_part.rb +53 -0
  39. data/lib/faraday/parameters.rb +4 -197
  40. data/lib/faraday/rack_builder.rb +67 -56
  41. data/lib/faraday/request/authorization.rb +44 -30
  42. data/lib/faraday/request/basic_authentication.rb +14 -7
  43. data/lib/faraday/request/instrumentation.rb +45 -27
  44. data/lib/faraday/request/multipart.rb +79 -48
  45. data/lib/faraday/request/retry.rb +198 -169
  46. data/lib/faraday/request/token_authentication.rb +15 -10
  47. data/lib/faraday/request/url_encoded.rb +43 -23
  48. data/lib/faraday/request.rb +68 -36
  49. data/lib/faraday/response/logger.rb +22 -69
  50. data/lib/faraday/response/raise_error.rb +38 -14
  51. data/lib/faraday/response.rb +27 -17
  52. data/lib/faraday/utils/headers.rb +139 -0
  53. data/lib/faraday/utils/params_hash.rb +61 -0
  54. data/lib/faraday/utils.rb +36 -245
  55. data/lib/faraday.rb +94 -176
  56. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  57. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  58. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  59. data/spec/faraday/adapter/excon_spec.rb +49 -0
  60. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  61. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  62. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  63. data/spec/faraday/adapter/patron_spec.rb +18 -0
  64. data/spec/faraday/adapter/rack_spec.rb +8 -0
  65. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  66. data/spec/faraday/adapter_registry_spec.rb +28 -0
  67. data/spec/faraday/adapter_spec.rb +55 -0
  68. data/spec/faraday/composite_read_io_spec.rb +80 -0
  69. data/spec/faraday/connection_spec.rb +691 -0
  70. data/spec/faraday/error_spec.rb +45 -0
  71. data/spec/faraday/middleware_spec.rb +26 -0
  72. data/spec/faraday/options/env_spec.rb +70 -0
  73. data/spec/faraday/options/options_spec.rb +297 -0
  74. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  75. data/spec/faraday/options/request_options_spec.rb +19 -0
  76. data/spec/faraday/params_encoders/flat_spec.rb +34 -0
  77. data/spec/faraday/params_encoders/nested_spec.rb +134 -0
  78. data/spec/faraday/rack_builder_spec.rb +196 -0
  79. data/spec/faraday/request/authorization_spec.rb +88 -0
  80. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  81. data/spec/faraday/request/multipart_spec.rb +274 -0
  82. data/spec/faraday/request/retry_spec.rb +242 -0
  83. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  84. data/spec/faraday/request_spec.rb +109 -0
  85. data/spec/faraday/response/logger_spec.rb +220 -0
  86. data/spec/faraday/response/middleware_spec.rb +68 -0
  87. data/spec/faraday/response/raise_error_spec.rb +106 -0
  88. data/spec/faraday/response_spec.rb +75 -0
  89. data/spec/faraday/utils/headers_spec.rb +82 -0
  90. data/spec/faraday/utils_spec.rb +56 -0
  91. data/spec/faraday_spec.rb +37 -0
  92. data/spec/spec_helper.rb +132 -0
  93. data/spec/support/disabling_stub.rb +14 -0
  94. data/spec/support/fake_safe_buffer.rb +15 -0
  95. data/spec/support/helper_methods.rb +133 -0
  96. data/spec/support/shared_examples/adapter.rb +104 -0
  97. data/spec/support/shared_examples/params_encoder.rb +18 -0
  98. data/spec/support/shared_examples/request_method.rb +234 -0
  99. data/spec/support/streaming_response_checker.rb +35 -0
  100. data/spec/support/webmock_rack_app.rb +68 -0
  101. metadata +78 -9
  102. data/lib/faraday/upload_io.rb +0 -67
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ # DependencyLoader helps Faraday adapters and middleware load dependencies.
5
+ module DependencyLoader
6
+ attr_reader :load_error
7
+
8
+ # Executes a block which should try to require and reference dependent
9
+ # libraries
10
+ def dependency(lib = nil)
11
+ lib ? require(lib) : yield
12
+ rescue LoadError, NameError => e
13
+ self.load_error = e
14
+ end
15
+
16
+ def new(*)
17
+ unless loaded?
18
+ raise "missing dependency for #{self}: #{load_error.message}"
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ def loaded?
25
+ load_error.nil?
26
+ end
27
+
28
+ def inherited(subclass)
29
+ super
30
+ subclass.send(:load_error=, load_error)
31
+ end
32
+
33
+ private
34
+
35
+ attr_writer :load_error
36
+ end
37
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ # FlatParamsEncoder manages URI params as a flat hash. Any Array values repeat
5
+ # the parameter multiple times.
6
+ module FlatParamsEncoder
7
+ class << self
8
+ extend Forwardable
9
+ def_delegators :'Faraday::Utils', :escape, :unescape
10
+ end
11
+
12
+ # Encode converts the given param into a URI querystring. Keys and values
13
+ # will converted to strings and appropriately escaped for the URI.
14
+ #
15
+ # @param params [Hash] query arguments to convert.
16
+ #
17
+ # @example
18
+ #
19
+ # encode({a: %w[one two three], b: true, c: "C"})
20
+ # # => 'a=one&a=two&a=three&b=true&c=C'
21
+ #
22
+ # @return [String] the URI querystring (without the leading '?')
23
+ def self.encode(params)
24
+ return nil if params.nil?
25
+
26
+ unless params.is_a?(Array)
27
+ unless params.respond_to?(:to_hash)
28
+ raise TypeError,
29
+ "Can't convert #{params.class} into Hash."
30
+ end
31
+ params = params.to_hash
32
+ params = params.map do |key, value|
33
+ key = key.to_s if key.is_a?(Symbol)
34
+ [key, value]
35
+ end
36
+ # Useful default for OAuth and caching.
37
+ # Only to be used for non-Array inputs. Arrays should preserve order.
38
+ params.sort!
39
+ end
40
+
41
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
42
+ buffer = +''
43
+ params.each do |key, value|
44
+ encoded_key = escape(key)
45
+ if value.nil?
46
+ buffer << "#{encoded_key}&"
47
+ elsif value.is_a?(Array)
48
+ if value.empty?
49
+ buffer << "#{encoded_key}=&"
50
+ else
51
+ value.each do |sub_value|
52
+ encoded_value = escape(sub_value)
53
+ buffer << "#{encoded_key}=#{encoded_value}&"
54
+ end
55
+ end
56
+ else
57
+ encoded_value = escape(value)
58
+ buffer << "#{encoded_key}=#{encoded_value}&"
59
+ end
60
+ end
61
+ buffer.chop
62
+ end
63
+
64
+ # Decode converts the given URI querystring into a hash.
65
+ #
66
+ # @param query [String] query arguments to parse.
67
+ #
68
+ # @example
69
+ #
70
+ # decode('a=one&a=two&a=three&b=true&c=C')
71
+ # # => {"a"=>["one", "two", "three"], "b"=>"true", "c"=>"C"}
72
+ #
73
+ # @return [Hash] parsed keys and value strings from the querystring.
74
+ def self.decode(query)
75
+ return nil if query.nil?
76
+
77
+ empty_accumulator = {}
78
+
79
+ split_query = (query.split('&').map do |pair|
80
+ pair.split('=', 2) if pair && !pair.empty?
81
+ end).compact
82
+ split_query.each_with_object(empty_accumulator.dup) do |pair, accu|
83
+ pair[0] = unescape(pair[0])
84
+ pair[1] = true if pair[1].nil?
85
+ if pair[1].respond_to?(:to_str)
86
+ pair[1] = unescape(pair[1].to_str.tr('+', ' '))
87
+ end
88
+ if accu[pair[0]].is_a?(Array)
89
+ accu[pair[0]] << pair[1]
90
+ elsif accu[pair[0]]
91
+ accu[pair[0]] = [accu[pair[0]], pair[1]]
92
+ else
93
+ accu[pair[0]] = pair[1]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ # Sub-module for encoding parameters into query-string.
5
+ module EncodeMethods
6
+ # @param params [nil, Array, #to_hash] parameters to be encoded
7
+ #
8
+ # @return [String] the encoded params
9
+ #
10
+ # @raise [TypeError] if params can not be converted to a Hash
11
+ def encode(params)
12
+ return nil if params.nil?
13
+
14
+ unless params.is_a?(Array)
15
+ unless params.respond_to?(:to_hash)
16
+ raise TypeError, "Can't convert #{params.class} into Hash."
17
+ end
18
+
19
+ params = params.to_hash
20
+ params = params.map do |key, value|
21
+ key = key.to_s if key.is_a?(Symbol)
22
+ [key, value]
23
+ end
24
+ # Useful default for OAuth and caching.
25
+ # Only to be used for non-Array inputs. Arrays should preserve order.
26
+ params.sort!
27
+ end
28
+
29
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
30
+ buffer = +''
31
+ params.each do |parent, value|
32
+ encoded_parent = escape(parent)
33
+ buffer << "#{encode_pair(encoded_parent, value)}&"
34
+ end
35
+ buffer.chop
36
+ end
37
+
38
+ protected
39
+
40
+ def encode_pair(parent, value)
41
+ if value.is_a?(Hash)
42
+ encode_hash(parent, value)
43
+ elsif value.is_a?(Array)
44
+ encode_array(parent, value)
45
+ elsif value.nil?
46
+ parent
47
+ else
48
+ encoded_value = escape(value)
49
+ "#{parent}=#{encoded_value}"
50
+ end
51
+ end
52
+
53
+ def encode_hash(parent, value)
54
+ value = value.map { |key, val| [escape(key), val] }.sort
55
+
56
+ buffer = +''
57
+ value.each do |key, val|
58
+ new_parent = "#{parent}%5B#{key}%5D"
59
+ buffer << "#{encode_pair(new_parent, val)}&"
60
+ end
61
+ buffer.chop
62
+ end
63
+
64
+ def encode_array(parent, value)
65
+ new_parent = "#{parent}%5B%5D"
66
+ return new_parent if value.empty?
67
+
68
+ buffer = +''
69
+ value.each { |val| buffer << "#{encode_pair(new_parent, val)}&" }
70
+ buffer.chop
71
+ end
72
+ end
73
+
74
+ # Sub-module for decoding query-string into parameters.
75
+ module DecodeMethods
76
+ # @param query [nil, String]
77
+ #
78
+ # @return [Array<Array, String>] the decoded params
79
+ #
80
+ # @raise [TypeError] if the nesting is incorrect
81
+ def decode(query)
82
+ return nil if query.nil?
83
+
84
+ params = {}
85
+ query.split('&').each do |pair|
86
+ next if pair.empty?
87
+
88
+ key, value = pair.split('=', 2)
89
+ key = unescape(key)
90
+ value = unescape(value.tr('+', ' ')) if value
91
+ decode_pair(key, value, params)
92
+ end
93
+
94
+ dehash(params, 0)
95
+ end
96
+
97
+ protected
98
+
99
+ SUBKEYS_REGEX = /[^\[\]]+(?:\]?\[\])?/.freeze
100
+
101
+ def decode_pair(key, value, context)
102
+ subkeys = key.scan(SUBKEYS_REGEX)
103
+ subkeys.each_with_index do |subkey, i|
104
+ is_array = subkey =~ /[\[\]]+\Z/
105
+ subkey = $` if is_array
106
+ last_subkey = i == subkeys.length - 1
107
+
108
+ context = prepare_context(context, subkey, is_array, last_subkey)
109
+ add_to_context(is_array, context, value, subkey) if last_subkey
110
+ end
111
+ end
112
+
113
+ def prepare_context(context, subkey, is_array, last_subkey)
114
+ if !last_subkey || is_array
115
+ context = new_context(subkey, is_array, context)
116
+ end
117
+ if context.is_a?(Array) && !is_array
118
+ context = match_context(context, subkey)
119
+ end
120
+ context
121
+ end
122
+
123
+ def new_context(subkey, is_array, context)
124
+ value_type = is_array ? Array : Hash
125
+ if context[subkey] && !context[subkey].is_a?(value_type)
126
+ raise TypeError, "expected #{value_type.name} " \
127
+ "(got #{context[subkey].class.name}) for param `#{subkey}'"
128
+ end
129
+
130
+ context[subkey] ||= value_type.new
131
+ end
132
+
133
+ def match_context(context, subkey)
134
+ context << {} if !context.last.is_a?(Hash) || context.last.key?(subkey)
135
+ context.last
136
+ end
137
+
138
+ def add_to_context(is_array, context, value, subkey)
139
+ is_array ? context << value : context[subkey] = value
140
+ end
141
+
142
+ # Internal: convert a nested hash with purely numeric keys into an array.
143
+ # FIXME: this is not compatible with Rack::Utils.parse_nested_query
144
+ # @!visibility private
145
+ def dehash(hash, depth)
146
+ hash.each do |key, value|
147
+ hash[key] = dehash(value, depth + 1) if value.is_a?(Hash)
148
+ end
149
+
150
+ if depth.positive? && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
151
+ hash.sort.map(&:last)
152
+ else
153
+ hash
154
+ end
155
+ end
156
+ end
157
+
158
+ # This is the default encoder for Faraday requests.
159
+ # Using this encoder, parameters will be encoded respecting their structure,
160
+ # so you can send objects such as Arrays or Hashes as parameters
161
+ # for your requests.
162
+ module NestedParamsEncoder
163
+ class << self
164
+ extend Forwardable
165
+ def_delegators :'Faraday::Utils', :escape, :unescape
166
+ end
167
+
168
+ extend EncodeMethods
169
+ extend DecodeMethods
170
+ end
171
+ end
data/lib/faraday/error.rb CHANGED
@@ -1,22 +1,15 @@
1
- module Faraday
2
- class Error < StandardError; end
1
+ # frozen_string_literal: true
3
2
 
4
- class ClientError < Error
3
+ # Faraday namespace.
4
+ module Faraday
5
+ # Faraday error base class.
6
+ class Error < StandardError
5
7
  attr_reader :response, :wrapped_exception
6
8
 
7
- def initialize(ex, response = nil)
8
- @wrapped_exception = nil
9
- @response = response
10
-
11
- if ex.respond_to?(:backtrace)
12
- super(ex.message)
13
- @wrapped_exception = ex
14
- elsif ex.respond_to?(:each_key)
15
- super("the server responded with status #{ex[:status]}")
16
- @response = ex
17
- else
18
- super(ex.to_s)
19
- end
9
+ def initialize(exc, response = nil)
10
+ @wrapped_exception = nil unless defined?(@wrapped_exception)
11
+ @response = nil unless defined?(@response)
12
+ super(exc_msg_and_response!(exc, response))
20
13
  end
21
14
 
22
15
  def backtrace
@@ -28,39 +21,112 @@ module Faraday
28
21
  end
29
22
 
30
23
  def inspect
31
- inner = ''
32
- if @wrapped_exception
33
- inner << " wrapped=#{@wrapped_exception.inspect}"
34
- end
35
- if @response
36
- inner << " response=#{@response.inspect}"
37
- end
38
- if inner.empty?
39
- inner << " #{super}"
40
- end
24
+ inner = +''
25
+ inner << " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
26
+ inner << " response=#{@response.inspect}" if @response
27
+ inner << " #{super}" if inner.empty?
41
28
  %(#<#{self.class}#{inner}>)
42
29
  end
30
+
31
+ protected
32
+
33
+ # Pulls out potential parent exception and response hash, storing them in
34
+ # instance variables.
35
+ # exc - Either an Exception, a string message, or a response hash.
36
+ # response - Hash
37
+ # :status - Optional integer HTTP response status
38
+ # :headers - String key/value hash of HTTP response header
39
+ # values.
40
+ # :body - Optional string HTTP response body.
41
+ #
42
+ # If a subclass has to call this, then it should pass a string message
43
+ # to `super`. See NilStatusError.
44
+ def exc_msg_and_response!(exc, response = nil)
45
+ if @response.nil? && @wrapped_exception.nil?
46
+ @wrapped_exception, msg, @response = exc_msg_and_response(exc, response)
47
+ return msg
48
+ end
49
+
50
+ exc.to_s
51
+ end
52
+
53
+ # Pulls out potential parent exception and response hash.
54
+ def exc_msg_and_response(exc, response = nil)
55
+ return [exc, exc.message, response] if exc.respond_to?(:backtrace)
56
+
57
+ return [nil, "the server responded with status #{exc[:status]}", exc] \
58
+ if exc.respond_to?(:each_key)
59
+
60
+ [nil, exc.to_s, response]
61
+ end
43
62
  end
44
63
 
45
- class ConnectionFailed < ClientError; end
46
- class ResourceNotFound < ClientError; end
47
- class ParsingError < ClientError; end
64
+ # Faraday client error class. Represents 4xx status responses.
65
+ class ClientError < Error
66
+ end
48
67
 
49
- class TimeoutError < ClientError
50
- def initialize(ex = nil)
51
- super(ex || "timeout")
68
+ # Raised by Faraday::Response::RaiseError in case of a 400 response.
69
+ class BadRequestError < ClientError
70
+ end
71
+
72
+ # Raised by Faraday::Response::RaiseError in case of a 401 response.
73
+ class UnauthorizedError < ClientError
74
+ end
75
+
76
+ # Raised by Faraday::Response::RaiseError in case of a 403 response.
77
+ class ForbiddenError < ClientError
78
+ end
79
+
80
+ # Raised by Faraday::Response::RaiseError in case of a 404 response.
81
+ class ResourceNotFound < ClientError
82
+ end
83
+
84
+ # Raised by Faraday::Response::RaiseError in case of a 407 response.
85
+ class ProxyAuthError < ClientError
86
+ end
87
+
88
+ # Raised by Faraday::Response::RaiseError in case of a 409 response.
89
+ class ConflictError < ClientError
90
+ end
91
+
92
+ # Raised by Faraday::Response::RaiseError in case of a 422 response.
93
+ class UnprocessableEntityError < ClientError
94
+ end
95
+
96
+ # Faraday server error class. Represents 5xx status responses.
97
+ class ServerError < Error
98
+ end
99
+
100
+ # A unified client error for timeouts.
101
+ class TimeoutError < ServerError
102
+ def initialize(exc = 'timeout', response = nil)
103
+ super(exc, response)
52
104
  end
53
105
  end
54
106
 
55
- class SSLError < ClientError
107
+ # Raised by Faraday::Response::RaiseError in case of a nil status in response.
108
+ class NilStatusError < ServerError
109
+ def initialize(exc, response = nil)
110
+ exc_msg_and_response!(exc, response)
111
+ super('http status could not be derived from the server response')
112
+ end
56
113
  end
57
114
 
58
- class RetriableResponse < ClientError; end
115
+ # A unified error for failed connections.
116
+ class ConnectionFailed < Error
117
+ end
59
118
 
60
- [:ClientError, :ConnectionFailed, :ResourceNotFound,
61
- :ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
62
- Error.const_set(const, Faraday.const_get(const))
119
+ # A unified client error for SSL errors.
120
+ class SSLError < Error
63
121
  end
64
122
 
123
+ # Raised by FaradayMiddleware::ResponseMiddleware
124
+ class ParsingError < Error
125
+ end
65
126
 
127
+ # Exception used to control the Retry middleware.
128
+ #
129
+ # @see Faraday::Request::Retry
130
+ class RetriableResponse < Error
131
+ end
66
132
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ # multipart-post gem
6
+ require 'composite_io'
7
+ require 'parts'
8
+
9
+ module Faraday
10
+ # Multipart value used to POST a binary data from a file or
11
+ #
12
+ # @example
13
+ # payload = { file: Faraday::FilePart.new("file_name.ext", "content/type") }
14
+ # http.post("/upload", payload)
15
+ #
16
+
17
+ # @!method initialize(filename_or_io, content_type, filename = nil, opts = {})
18
+ #
19
+ # @param filename_or_io [String, IO] Either a String filename to a local
20
+ # file or an open IO object.
21
+ # @param content_type [String] String content type of the file data.
22
+ # @param filename [String] Optional String filename, usually to add context
23
+ # to a given IO object.
24
+ # @param opts [Hash] Optional Hash of String key/value pairs to describethis
25
+ # this uploaded file. Expected Header keys include:
26
+ # * Content-Transfer-Encoding - Defaults to "binary"
27
+ # * Content-Disposition - Defaults to "form-data"
28
+ # * Content-Type - Defaults to the content_type argument.
29
+ # * Content-ID - Optional.
30
+ #
31
+ # @return [Faraday::FilePart]
32
+ #
33
+ # @!attribute [r] content_type
34
+ # The uploaded binary data's content type.
35
+ #
36
+ # @return [String]
37
+ #
38
+ # @!attribute [r] original_filename
39
+ # The base filename, taken either from the filename_or_io or filename
40
+ # arguments in #initialize.
41
+ #
42
+ # @return [String]
43
+ #
44
+ # @!attribute [r] opts
45
+ # Extra String key/value pairs to make up the header for this uploaded file.
46
+ #
47
+ # @return [Hash]
48
+ #
49
+ # @!attribute [r] io
50
+ # The open IO object for the uploaded file.
51
+ #
52
+ # @return [IO]
53
+ FilePart = ::UploadIO
54
+
55
+ # Multipart value used to POST a file.
56
+ #
57
+ # @deprecated Use FilePart instead of this class. It behaves identically, with
58
+ # a matching name to ParamPart.
59
+ UploadIO = ::UploadIO
60
+
61
+ Parts = ::Parts
62
+
63
+ # Similar to, but not compatible with CompositeReadIO provided by the
64
+ # multipart-post gem.
65
+ # https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb
66
+ class CompositeReadIO
67
+ def initialize(*parts)
68
+ @parts = parts.flatten
69
+ @ios = @parts.map(&:to_io)
70
+ @index = 0
71
+ end
72
+
73
+ # @return [Integer] sum of the lengths of all the parts
74
+ def length
75
+ @parts.inject(0) { |sum, part| sum + part.length }
76
+ end
77
+
78
+ # Rewind each of the IOs and reset the index to 0.
79
+ #
80
+ # @return [void]
81
+ def rewind
82
+ @ios.each(&:rewind)
83
+ @index = 0
84
+ end
85
+
86
+ # Read from IOs in order until `length` bytes have been received.
87
+ #
88
+ # @param length [Integer, nil]
89
+ # @param outbuf [String, nil]
90
+ def read(length = nil, outbuf = nil)
91
+ got_result = false
92
+ outbuf = outbuf ? (+outbuf).replace('') : +''
93
+
94
+ while (io = current_io)
95
+ if (result = io.read(length))
96
+ got_result ||= !result.nil?
97
+ result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
98
+ outbuf << result
99
+ length -= result.length if length
100
+ break if length&.zero?
101
+ end
102
+ advance_io
103
+ end
104
+ !got_result && length ? nil : outbuf
105
+ end
106
+
107
+ # Close each of the IOs.
108
+ #
109
+ # @return [void]
110
+ def close
111
+ @ios.each(&:close)
112
+ end
113
+
114
+ def ensure_open_and_readable
115
+ # Rubinius compatibility
116
+ end
117
+
118
+ private
119
+
120
+ def current_io
121
+ @ios[@index]
122
+ end
123
+
124
+ def advance_io
125
+ @index += 1
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pp'
4
+ module Faraday
5
+ module Logging
6
+ # Serves as an integration point to customize logging
7
+ class Formatter
8
+ extend Forwardable
9
+
10
+ DEFAULT_OPTIONS = { headers: true, bodies: false,
11
+ log_level: :info }.freeze
12
+
13
+ def initialize(logger:, options:)
14
+ @logger = logger
15
+ @filter = []
16
+ @options = DEFAULT_OPTIONS.merge(options)
17
+ end
18
+
19
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
20
+
21
+ def request(env)
22
+ request_log = proc do
23
+ "#{env.method.upcase} #{apply_filters(env.url.to_s)}"
24
+ end
25
+ public_send(log_level, 'request', &request_log)
26
+
27
+ log_headers('request', env.request_headers) if log_headers?(:request)
28
+ log_body('request', env[:body]) if env[:body] && log_body?(:request)
29
+ end
30
+
31
+ def response(env)
32
+ status = proc { "Status #{env.status}" }
33
+ public_send(log_level, 'response', &status)
34
+
35
+ log_headers('response', env.response_headers) if log_headers?(:response)
36
+ log_body('response', env[:body]) if env[:body] && log_body?(:response)
37
+ end
38
+
39
+ def filter(filter_word, filter_replacement)
40
+ @filter.push([filter_word, filter_replacement])
41
+ end
42
+
43
+ private
44
+
45
+ def dump_headers(headers)
46
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
47
+ end
48
+
49
+ def dump_body(body)
50
+ if body.respond_to?(:to_str)
51
+ body.to_str
52
+ else
53
+ pretty_inspect(body)
54
+ end
55
+ end
56
+
57
+ def pretty_inspect(body)
58
+ body.pretty_inspect
59
+ end
60
+
61
+ def log_headers?(type)
62
+ case @options[:headers]
63
+ when Hash
64
+ @options[:headers][type]
65
+ else
66
+ @options[:headers]
67
+ end
68
+ end
69
+
70
+ def log_body?(type)
71
+ case @options[:bodies]
72
+ when Hash
73
+ @options[:bodies][type]
74
+ else
75
+ @options[:bodies]
76
+ end
77
+ end
78
+
79
+ def apply_filters(output)
80
+ @filter.each do |pattern, replacement|
81
+ output = output.to_s.gsub(pattern, replacement)
82
+ end
83
+ output
84
+ end
85
+
86
+ def log_level
87
+ unless %i[debug info warn error fatal].include?(@options[:log_level])
88
+ return :info
89
+ end
90
+
91
+ @options[:log_level]
92
+ end
93
+
94
+ def log_headers(type, headers)
95
+ headers_log = proc { apply_filters(dump_headers(headers)) }
96
+ public_send(log_level, type, &headers_log)
97
+ end
98
+
99
+ def log_body(type, body)
100
+ body_log = proc { apply_filters(dump_body(body)) }
101
+ public_send(log_level, type, &body_log)
102
+ end
103
+ end
104
+ end
105
+ end