faraday 0.17.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -8
  3. data/LICENSE.md +1 -1
  4. data/README.md +18 -358
  5. data/Rakefile +1 -7
  6. data/UPGRADING.md +55 -0
  7. data/examples/client_spec.rb +65 -0
  8. data/examples/client_test.rb +79 -0
  9. data/lib/faraday/adapter/em_http.rb +141 -99
  10. data/lib/faraday/adapter/em_http_ssl_patch.rb +24 -18
  11. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +18 -15
  12. data/lib/faraday/adapter/em_synchrony.rb +104 -60
  13. data/lib/faraday/adapter/excon.rb +98 -56
  14. data/lib/faraday/adapter/httpclient.rb +82 -59
  15. data/lib/faraday/adapter/net_http.rb +119 -62
  16. data/lib/faraday/adapter/net_http_persistent.rb +50 -27
  17. data/lib/faraday/adapter/patron.rb +80 -43
  18. data/lib/faraday/adapter/rack.rb +30 -13
  19. data/lib/faraday/adapter/test.rb +86 -53
  20. data/lib/faraday/adapter/typhoeus.rb +4 -1
  21. data/lib/faraday/adapter.rb +82 -22
  22. data/lib/faraday/adapter_registry.rb +28 -0
  23. data/lib/faraday/autoload.rb +47 -36
  24. data/lib/faraday/connection.rb +312 -182
  25. data/lib/faraday/dependency_loader.rb +37 -0
  26. data/lib/faraday/encoders/flat_params_encoder.rb +98 -0
  27. data/lib/faraday/encoders/nested_params_encoder.rb +171 -0
  28. data/lib/faraday/error.rb +9 -35
  29. data/lib/faraday/file_part.rb +128 -0
  30. data/lib/faraday/logging/formatter.rb +105 -0
  31. data/lib/faraday/middleware.rb +12 -28
  32. data/lib/faraday/middleware_registry.rb +129 -0
  33. data/lib/faraday/options/connection_options.rb +22 -0
  34. data/lib/faraday/options/env.rb +181 -0
  35. data/lib/faraday/options/proxy_options.rb +28 -0
  36. data/lib/faraday/options/request_options.rb +22 -0
  37. data/lib/faraday/options/ssl_options.rb +59 -0
  38. data/lib/faraday/options.rb +32 -183
  39. data/lib/faraday/param_part.rb +53 -0
  40. data/lib/faraday/parameters.rb +4 -197
  41. data/lib/faraday/rack_builder.rb +66 -55
  42. data/lib/faraday/request/authorization.rb +42 -30
  43. data/lib/faraday/request/basic_authentication.rb +14 -7
  44. data/lib/faraday/request/instrumentation.rb +45 -27
  45. data/lib/faraday/request/multipart.rb +79 -48
  46. data/lib/faraday/request/retry.rb +197 -171
  47. data/lib/faraday/request/token_authentication.rb +15 -10
  48. data/lib/faraday/request/url_encoded.rb +41 -23
  49. data/lib/faraday/request.rb +68 -36
  50. data/lib/faraday/response/logger.rb +22 -69
  51. data/lib/faraday/response/raise_error.rb +38 -18
  52. data/lib/faraday/response.rb +20 -13
  53. data/lib/faraday/utils/headers.rb +139 -0
  54. data/lib/faraday/utils/params_hash.rb +61 -0
  55. data/lib/faraday/utils.rb +28 -245
  56. data/lib/faraday.rb +93 -174
  57. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  58. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  59. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  60. data/spec/faraday/adapter/excon_spec.rb +49 -0
  61. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  62. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  63. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  64. data/spec/faraday/adapter/patron_spec.rb +18 -0
  65. data/spec/faraday/adapter/rack_spec.rb +8 -0
  66. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  67. data/spec/faraday/adapter_registry_spec.rb +28 -0
  68. data/spec/faraday/adapter_spec.rb +55 -0
  69. data/spec/faraday/composite_read_io_spec.rb +80 -0
  70. data/spec/faraday/connection_spec.rb +691 -0
  71. data/spec/faraday/error_spec.rb +0 -57
  72. data/spec/faraday/middleware_spec.rb +26 -0
  73. data/spec/faraday/options/env_spec.rb +70 -0
  74. data/spec/faraday/options/options_spec.rb +297 -0
  75. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  76. data/spec/faraday/options/request_options_spec.rb +19 -0
  77. data/spec/faraday/params_encoders/flat_spec.rb +34 -0
  78. data/spec/faraday/params_encoders/nested_spec.rb +134 -0
  79. data/spec/faraday/rack_builder_spec.rb +196 -0
  80. data/spec/faraday/request/authorization_spec.rb +88 -0
  81. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  82. data/spec/faraday/request/multipart_spec.rb +274 -0
  83. data/spec/faraday/request/retry_spec.rb +242 -0
  84. data/spec/faraday/request/url_encoded_spec.rb +70 -0
  85. data/spec/faraday/request_spec.rb +109 -0
  86. data/spec/faraday/response/logger_spec.rb +220 -0
  87. data/spec/faraday/response/middleware_spec.rb +52 -0
  88. data/spec/faraday/response/raise_error_spec.rb +15 -15
  89. data/spec/faraday/response_spec.rb +75 -0
  90. data/spec/faraday/utils/headers_spec.rb +82 -0
  91. data/spec/faraday/utils_spec.rb +56 -0
  92. data/spec/faraday_spec.rb +37 -0
  93. data/spec/spec_helper.rb +63 -36
  94. data/spec/support/disabling_stub.rb +14 -0
  95. data/spec/support/fake_safe_buffer.rb +15 -0
  96. data/spec/support/helper_methods.rb +133 -0
  97. data/spec/support/shared_examples/adapter.rb +104 -0
  98. data/spec/support/shared_examples/params_encoder.rb +18 -0
  99. data/spec/support/shared_examples/request_method.rb +234 -0
  100. data/spec/support/streaming_response_checker.rb +35 -0
  101. data/spec/support/webmock_rack_app.rb +68 -0
  102. metadata +65 -37
  103. data/lib/faraday/deprecate.rb +0 -107
  104. data/lib/faraday/upload_io.rb +0 -67
  105. data/spec/faraday/deprecate_spec.rb +0 -69
  106. data/test/adapters/default_test.rb +0 -14
  107. data/test/adapters/em_http_test.rb +0 -30
  108. data/test/adapters/em_synchrony_test.rb +0 -32
  109. data/test/adapters/excon_test.rb +0 -30
  110. data/test/adapters/httpclient_test.rb +0 -34
  111. data/test/adapters/integration.rb +0 -263
  112. data/test/adapters/logger_test.rb +0 -136
  113. data/test/adapters/net_http_persistent_test.rb +0 -114
  114. data/test/adapters/net_http_test.rb +0 -79
  115. data/test/adapters/patron_test.rb +0 -40
  116. data/test/adapters/rack_test.rb +0 -38
  117. data/test/adapters/test_middleware_test.rb +0 -157
  118. data/test/adapters/typhoeus_test.rb +0 -38
  119. data/test/authentication_middleware_test.rb +0 -65
  120. data/test/composite_read_io_test.rb +0 -109
  121. data/test/connection_test.rb +0 -738
  122. data/test/env_test.rb +0 -268
  123. data/test/helper.rb +0 -75
  124. data/test/live_server.rb +0 -67
  125. data/test/middleware/instrumentation_test.rb +0 -88
  126. data/test/middleware/retry_test.rb +0 -282
  127. data/test/middleware_stack_test.rb +0 -260
  128. data/test/multibyte.txt +0 -1
  129. data/test/options_test.rb +0 -333
  130. data/test/parameters_test.rb +0 -157
  131. data/test/request_middleware_test.rb +0 -126
  132. data/test/response_middleware_test.rb +0 -72
  133. data/test/strawberry.rb +0 -2
  134. data/test/utils_test.rb +0 -98
@@ -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,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faraday/deprecate'
4
-
5
3
  # Faraday namespace.
6
4
  module Faraday
7
5
  # Faraday error base class.
@@ -23,10 +21,10 @@ module Faraday
23
21
  end
24
22
 
25
23
  def inspect
26
- inner = ''
27
- inner += " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
28
- inner += " response=#{@response.inspect}" if @response
29
- inner += " #{super}" if inner.empty?
24
+ inner = +''
25
+ inner << " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
26
+ inner << " response=#{@response.inspect}" if @response
27
+ inner << " #{super}" if inner.empty?
30
28
  %(#<#{self.class}#{inner}>)
31
29
  end
32
30
 
@@ -100,7 +98,7 @@ module Faraday
100
98
  end
101
99
 
102
100
  # A unified client error for timeouts.
103
- class TimeoutError < ClientError
101
+ class TimeoutError < ServerError
104
102
  def initialize(exc = 'timeout', response = nil)
105
103
  super(exc, response)
106
104
  end
@@ -110,49 +108,25 @@ module Faraday
110
108
  class NilStatusError < ServerError
111
109
  def initialize(exc, response = nil)
112
110
  exc_msg_and_response!(exc, response)
113
- @response = unwrap_resp!(@response)
114
111
  super('http status could not be derived from the server response')
115
112
  end
116
-
117
- private
118
-
119
- extend Faraday::Deprecate
120
-
121
- def unwrap_resp(resp)
122
- if inner = (resp.keys.size == 1 && resp[:response])
123
- return unwrap_resp(inner)
124
- end
125
-
126
- resp
127
- end
128
-
129
- alias_method :unwrap_resp!, :unwrap_resp
130
- deprecate('unwrap_resp', nil, '1.0')
131
113
  end
132
114
 
133
115
  # A unified error for failed connections.
134
- class ConnectionFailed < ClientError
116
+ class ConnectionFailed < Error
135
117
  end
136
118
 
137
119
  # A unified client error for SSL errors.
138
- class SSLError < ClientError
120
+ class SSLError < Error
139
121
  end
140
122
 
141
123
  # Raised by FaradayMiddleware::ResponseMiddleware
142
- class ParsingError < ClientError
124
+ class ParsingError < Error
143
125
  end
144
126
 
145
127
  # Exception used to control the Retry middleware.
146
128
  #
147
129
  # @see Faraday::Request::Retry
148
- class RetriableResponse < ClientError
149
- end
150
-
151
- [:ClientError, :ConnectionFailed, :ResourceNotFound,
152
- :ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
153
- Error.const_set(
154
- const,
155
- DeprecatedClass.proxy_class(Faraday.const_get(const))
156
- )
130
+ class RetriableResponse < Error
157
131
  end
158
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
@@ -1,37 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
4
+ # Middleware is the basic base class of any Faraday middleware.
2
5
  class Middleware
3
6
  extend MiddlewareRegistry
4
-
5
- class << self
6
- attr_accessor :load_error
7
- private :load_error=
8
- end
9
-
10
- self.load_error = nil
11
-
12
- # Executes a block which should try to require and reference dependent libraries
13
- def self.dependency(lib = nil)
14
- lib ? require(lib) : yield
15
- rescue LoadError, NameError => error
16
- self.load_error = error
17
- end
18
-
19
- def self.new(*)
20
- raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
21
- super
22
- end
23
-
24
- def self.loaded?
25
- load_error.nil?
26
- end
27
-
28
- def self.inherited(subclass)
29
- super
30
- subclass.send(:load_error=, self.load_error)
31
- end
7
+ extend DependencyLoader
32
8
 
33
9
  def initialize(app = nil)
34
10
  @app = app
35
11
  end
12
+
13
+ def close
14
+ if @app.respond_to?(:close)
15
+ @app.close
16
+ else
17
+ warn "#{@app} does not implement \#close!"
18
+ end
19
+ end
36
20
  end
37
21
  end