better-faraday 3.1.1 → 3.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63e3069002b8c492c1974502babbe1ab1b0815a499fcb1fb6384860953957051
4
- data.tar.gz: 79af46da23df8de021919a3e1fab6990b927524634cb705b3e88a06eeb115170
3
+ metadata.gz: 6bf92306e4c791a1171310958827a2236faef36480dbed3c111b544035797a68
4
+ data.tar.gz: 36208257d64565f775bc395beea0b1c72905ad60f9f4c04ea6107375f4ebf2a0
5
5
  SHA512:
6
- metadata.gz: ef42fd021a83dc99654aef97736ac6364942774748865be68ca3b379fcb097213043274f0ab6ab16b0912918b5b9b68438ab69fc0326b7404b7d2d112c13780d
7
- data.tar.gz: 1dba086f05f40addb2577ea44905cb7e7114abb6b2176b13884324df8dc4c3c8cee5a7d0d334cad6da6c1467cd6a58a91532b109071532df1edba4ed3c5cbce8
6
+ metadata.gz: 6c47f524f903183214f43302ab03126b69d09b44660daf4034b5285ef8072d98bfc59fb5a6d614c9863faad704e9e4f9f8afab9e26938ec38295f264df1d974b
7
+ data.tar.gz: 52e00284afec4daa2797d836f61819b9aee8821cc51daa18f40e19438401001b82e0076e7086c4607a2f9e645a77dbd9f2a9c061709d0f2319a25b787e1025d9
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- better-faraday (3.1.1)
4
+ better-faraday (3.1.2)
5
5
  faraday (>= 1.0, < 3.0)
6
6
 
7
7
  GEM
@@ -86,7 +86,7 @@ DEPENDENCIES
86
86
 
87
87
  CHECKSUMS
88
88
  addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
89
- better-faraday (3.1.1)
89
+ better-faraday (3.1.2)
90
90
  bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66
91
91
  bundler (4.0.22) sha256=d8d5ec84c8555e0af71db63ed7aee4d1a8fb839ec46d84212d61979242a5d75a
92
92
  byebug (13.0.0) sha256=d2263efe751941ca520fa29744b71972d39cbc41839496706f5d9b22e92ae05d
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "better-faraday"
5
- s.version = "3.1.1"
5
+ s.version = "3.1.2"
6
6
  s.author = "Yaroslav Konoplov"
7
7
  s.email = "eahome00@gmail.com"
8
8
  s.summary = "Extends Faraday with useful features"
@@ -188,6 +188,18 @@ module Faraday
188
188
 
189
189
  private
190
190
 
191
+ # Fallback for missing reason phrases in the response
192
+ def bf_reason_phrase
193
+ phrase = reason_phrase.to_s.strip
194
+ return phrase.upcase unless phrase.empty? || phrase.casecmp("unknown").zero?
195
+
196
+ klass = ::Net::HTTPResponse::CODE_TO_OBJ[status.to_s]
197
+ return "UNKNOWN" unless klass
198
+
199
+ # Converts "Net::HTTPUnprocessableEntity" -> "Unprocessable Entity" -> "UNPROCESSABLE ENTITY"
200
+ klass.name.split("::").last.sub(/\AHTTP/, "").gsub(/([a-z])([A-Z])/, '\1 \2').upcase
201
+ end
202
+
191
203
  def bf_truncate(str, limit = 2048, omission = "... (truncated)")
192
204
  return str if str.length <= limit
193
205
 
@@ -238,14 +250,32 @@ module Faraday
238
250
  end
239
251
  request_headers_hash = {} if request_headers_hash.nil?
240
252
 
241
- request_body = env.bf_request_body.then { _1.is_a?(String) ? _1 : _1.to_s }
242
- request_byte_count = request_body.bytesize
253
+ # Safely handle bodies that were already parsed by Faraday middlewares (like f.request :json)
254
+ request_body = env.bf_request_body
255
+ request_body_json = nil
243
256
 
244
- request_is_json = request_headers_hash.any? do |k, v|
245
- k.to_s.downcase == "content-type" && v.to_s.match?(%r{\b(?:application|text)/json\b}i)
246
- end
257
+ if request_body.is_a?(Hash) || request_body.is_a?(Array)
258
+ request_body_json = bf_json_dump(bf_protect_data(request_body))
259
+ request_body_string = request_body.to_json
260
+ request_byte_count = request_body_string.bytesize
261
+ else
262
+ request_body_is_json = request_headers_hash.any? do |k, v|
263
+ k.to_s.casecmp("content-type").zero? && v.to_s.match?(%r{\b(?:application|text)/json\b}i)
264
+ end
265
+
266
+ request_body_string = if request_body.is_a?(String)
267
+ request_body
268
+ else
269
+ request_body.respond_to?(:to_json) ? request_body.to_json : request_body.to_s
270
+ end
247
271
 
248
- request_json = bf_json_parse(request_body)&.then { bf_json_dump(bf_protect_data(_1)) } if request_is_json
272
+ request_byte_count = request_body_string.bytesize
273
+
274
+ if request_body_is_json
275
+ parsed = bf_json_parse(request_body_string)
276
+ request_body_json = bf_json_dump(bf_protect_data(parsed)) unless parsed.nil?
277
+ end
278
+ end
249
279
 
250
280
  request_byte_label = request_byte_count == 1 ? "byte" : "bytes"
251
281
 
@@ -255,19 +285,37 @@ module Faraday
255
285
  end
256
286
  response_headers_hash = {} if response_headers_hash.nil?
257
287
 
258
- response_body = env.body.then { _1.is_a?(String) ? _1 : _1.to_s }
259
- response_byte_count = response_body.bytesize
288
+ # Safely handle bodies that were already parsed by Faraday middlewares (like f.response :json)
289
+ response_body = env.body
290
+ response_body_json = nil
260
291
 
261
- response_is_json = response_headers_hash.any? do |k, v|
262
- k.to_s.downcase == "content-type" && v.to_s.match?(%r{\b(?:application|text)/json\b}i)
263
- end
292
+ if response_body.is_a?(Hash) || response_body.is_a?(Array)
293
+ response_body_json = bf_json_dump(bf_protect_data(response_body))
294
+ response_body_string = response_body.to_json
295
+ response_byte_count = response_body_string.bytesize
296
+ else
297
+ response_body_is_json = response_headers_hash.any? do |k, v|
298
+ k.to_s.casecmp("content-type").zero? && v.to_s.match?(%r{\b(?:application|text)/json\b}i)
299
+ end
264
300
 
265
- response_json = bf_json_parse(response_body)&.then { bf_json_dump(bf_protect_data(_1)) } if response_is_json
301
+ response_body_string = if response_body.is_a?(String)
302
+ response_body
303
+ else
304
+ response_body.respond_to?(:to_json) ? response_body.to_json : response_body.to_s
305
+ end
306
+
307
+ response_byte_count = response_body_string.bytesize
308
+
309
+ if response_body_is_json
310
+ parsed = bf_json_parse(response_body_string)
311
+ response_body_json = bf_json_dump(bf_protect_data(parsed)) unless parsed.nil?
312
+ end
313
+ end
266
314
 
267
315
  response_byte_label = response_byte_count == 1 ? "byte" : "bytes"
268
316
 
269
317
  lines = [
270
- "-- HTTP #{status} #{reason_phrase} --".gsub(/\s+/, " ").upcase,
318
+ "-- HTTP #{status} #{bf_reason_phrase} --".gsub(/\s+/, " ").upcase,
271
319
  "",
272
320
  "-- Request URL --",
273
321
  env.url.to_s,
@@ -284,10 +332,10 @@ module Faraday
284
332
  "",
285
333
 
286
334
  %[-- Request Body (#{request_byte_count} #{request_byte_label}) --],
287
- if !request_json.nil?
288
- request_json
335
+ if !request_body_json.nil?
336
+ request_body_json
289
337
  else
290
- bf_dump(request_body)
338
+ bf_dump(request_body_string)
291
339
  end.then { bf_truncate(_1) },
292
340
  "",
293
341
 
@@ -304,10 +352,10 @@ module Faraday
304
352
  "",
305
353
 
306
354
  %[-- Response Body (#{response_byte_count} #{response_byte_label}) --],
307
- if !response_json.nil?
308
- response_json
355
+ if !response_body_json.nil?
356
+ response_body_json
309
357
  else
310
- bf_dump(response_body)
358
+ bf_dump(response_body_string)
311
359
  end.then { bf_truncate(_1) },
312
360
  ""
313
361
  ]
@@ -328,6 +376,7 @@ module Faraday
328
376
  end
329
377
  end
330
378
 
379
+
331
380
  module BetterFaraday
332
381
  class HTTPError < Faraday::Error
333
382
  def initialize(response)
@@ -419,7 +468,7 @@ module BetterFaraday
419
468
  # Various tokens (access_token, refresh_token, csrf_token, bearer_token, auth_token, or just token)
420
469
  /(?:access|refresh|auth|bearer|csrf|authorization)?#{separator}token/i,
421
470
  # API keys (api_key, apikey, x_api_key)
422
- /api#{separator}key/i,
471
+ /api#{separator}(?:key|id)/i,
423
472
  # Private keys
424
473
  /private#{separator}key/i,
425
474
  # Crypto mnemonics
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "BetterFaraday with Faraday JSON Middlewares" do
6
+ let(:conn) do
7
+ Faraday.new(url: "https://api.example.com") do |f|
8
+ f.request :json
9
+ f.response :json
10
+
11
+ f.adapter :test do |stub|
12
+ stub.post("/process") do |env|
13
+ [
14
+ 200,
15
+ { "Content-Type" => "application/json" },
16
+ '{"status": "success", "access_token": "secret_token_123", "user_id": 42}'
17
+ ]
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ it "safely inspects and masks data when bodies are transformed by JSON middlewares" do
24
+ request_payload = {
25
+ action: "login",
26
+ password: "my_super_secret_password"
27
+ }
28
+
29
+ response = conn.post("/process", request_payload)
30
+
31
+ expect(response.body).to be_a(Hash)
32
+ expect(response.body["status"]).to eq("success")
33
+
34
+ inspection = response.inspect
35
+
36
+ expect(inspection).to include('"password": "SECRET"')
37
+ expect(inspection).not_to include("my_super_secret_password")
38
+ expect(inspection).to include('"action": "login"')
39
+
40
+ expect(inspection).to include('"access_token": "SECRET"')
41
+ expect(inspection).not_to include("secret_token_123")
42
+ expect(inspection).to include('"user_id": 42')
43
+
44
+ expect(inspection).not_to include("=>")
45
+ end
46
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "BetterFaraday Pre-parsed Bodies & Status Formatting" do
6
+ let(:env) do
7
+ Faraday::Env.from(
8
+ method: :post,
9
+ url: URI("https://api.example.com/json"),
10
+ status: 422,
11
+ reason_phrase: "UNKNOWN", # Faraday sometimes defaults to UNKNOWN
12
+ request_headers: { "Content-Type" => "application/json" },
13
+ # Simulate body already parsed by Faraday JSON request middleware
14
+ request_body: { "account_type" => "trading", "password" => "secret_123" },
15
+ response_headers: { "Content-Type" => "application/json; charset=UTF-8" },
16
+ # Simulate body already parsed by Faraday JSON response middleware
17
+ body: { "code" => 40010001, "message" => "Invalid currency" }
18
+ )
19
+ end
20
+
21
+ let(:response) { Faraday::Response.new(env) }
22
+
23
+ before do
24
+ response.env.instance_variable_set(:@bf_request_headers, { "Content-Type" => "application/json" })
25
+ # This is what gets captured by the middleware
26
+ response.env.instance_variable_set(:@bf_request_body, { "account_type" => "trading", "password" => "secret_123" })
27
+ end
28
+
29
+ it "formats 'UNKNOWN' reason phrase correctly to UNPROCESSABLE ENTITY" do
30
+ inspection = response.inspect
31
+ expect(inspection).to include("-- HTTP 422 UNPROCESSABLE ENTITY --")
32
+ end
33
+
34
+ it "formats pre-parsed Hash response bodies to JSON strings and masks data" do
35
+ inspection = response.inspect
36
+
37
+ # Request Hash
38
+ expect(inspection).to include('"password": "SECRET"')
39
+ expect(inspection).to include('"account_type": "trading"')
40
+
41
+ # Response Hash
42
+ expect(inspection).to include('"code": 40010001')
43
+ expect(inspection).to include('"message": "Invalid currency"')
44
+
45
+ # Ensure Ruby hash notation is absolutely gone
46
+ expect(inspection).not_to include("=>")
47
+ end
48
+
49
+ it "resolves empty reason phrases to their Net::HTTP default strings" do
50
+ response.env.status = 201
51
+ response.env.reason_phrase = ""
52
+
53
+ expect(response.inspect).to include("-- HTTP 201 CREATED --")
54
+ end
55
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "BetterFaraday Reason Phrase Formatting" do
6
+ let(:env) do
7
+ Faraday::Env.from(
8
+ method: :get,
9
+ url: URI("https://api.example.com/status"),
10
+ status: status,
11
+ reason_phrase: reason_phrase,
12
+ request_headers: {},
13
+ request_body: "",
14
+ response_headers: {},
15
+ body: ""
16
+ )
17
+ end
18
+
19
+ let(:response) { Faraday::Response.new(env) }
20
+
21
+ context "when reason_phrase is UNKNOWN" do
22
+ let(:status) { 422 }
23
+ let(:reason_phrase) { "UNKNOWN" }
24
+
25
+ it "resolves to UNPROCESSABLE ENTITY using Net::HTTP mapping" do
26
+ expect(response.inspect).to include("-- HTTP 422 UNPROCESSABLE ENTITY --")
27
+ end
28
+ end
29
+
30
+ context "when reason_phrase is empty" do
31
+ let(:status) { 201 }
32
+ let(:reason_phrase) { " " }
33
+
34
+ it "resolves to CREATED using Net::HTTP mapping" do
35
+ expect(response.inspect).to include("-- HTTP 201 CREATED --")
36
+ end
37
+ end
38
+
39
+ context "when reason_phrase is valid" do
40
+ let(:status) { 418 }
41
+ let(:reason_phrase) { "I Am A Teapot" }
42
+
43
+ it "uses the provided reason phrase" do
44
+ expect(response.inspect).to include("-- HTTP 418 I AM A TEAPOT --")
45
+ end
46
+ end
47
+
48
+ context "when status code is completely unknown" do
49
+ let(:status) { 999 }
50
+ let(:reason_phrase) { "UNKNOWN" }
51
+
52
+ it "safely falls back to UNKNOWN if no Net::HTTP mapping exists" do
53
+ expect(response.inspect).to include("-- HTTP 999 UNKNOWN --")
54
+ end
55
+ end
56
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better-faraday
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Konoplov
@@ -152,10 +152,13 @@ files:
152
152
  - spec/http_error_spec.rb
153
153
  - spec/inspect_truncation_spec.rb
154
154
  - spec/invalid_json_spec.rb
155
+ - spec/json_middleware_spec.rb
156
+ - spec/json_parsing_spec.rb
155
157
  - spec/logger_spec.rb
156
158
  - spec/middleware_spec.rb
157
159
  - spec/multipart_spec.rb
158
160
  - spec/nested_sensitive_data_spec.rb
161
+ - spec/reason_phrase_spec.rb
159
162
  - spec/sensitive_data_signs_spec.rb
160
163
  - spec/spec_helper.rb
161
164
  - spec/timing_inspect_spec.rb
@@ -192,10 +195,13 @@ test_files:
192
195
  - spec/http_error_spec.rb
193
196
  - spec/inspect_truncation_spec.rb
194
197
  - spec/invalid_json_spec.rb
198
+ - spec/json_middleware_spec.rb
199
+ - spec/json_parsing_spec.rb
195
200
  - spec/logger_spec.rb
196
201
  - spec/middleware_spec.rb
197
202
  - spec/multipart_spec.rb
198
203
  - spec/nested_sensitive_data_spec.rb
204
+ - spec/reason_phrase_spec.rb
199
205
  - spec/sensitive_data_signs_spec.rb
200
206
  - spec/spec_helper.rb
201
207
  - spec/timing_inspect_spec.rb