diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4844cd37f..4234e3bc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: BUNDLE_WITHOUT: development:test steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Ruby 3.x uses: ruby/setup-ruby@v1 @@ -52,7 +52,7 @@ jobs: experimental: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c667d8f48..25ea21dc0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Ruby 3.x uses: ruby/setup-ruby@v1 diff --git a/docs/adapters/custom/index.md b/docs/adapters/custom/index.md index 179412281..c4cf54608 100644 --- a/docs/adapters/custom/index.md +++ b/docs/adapters/custom/index.md @@ -1,6 +1,6 @@ # Writing custom adapters -!> A template for writing your own middleware is available in the [faraday-adapter-template](https://github.com/lostisland/faraday-adapter-template) repository. +!> A template for writing your own custom adapter is available in the [faraday-adapter-template](https://github.com/lostisland/faraday-adapter-template) repository. Adapters have methods that can help you implement support for a new backend. diff --git a/docs/middleware/included/raising-errors.md b/docs/middleware/included/raising-errors.md index 65fff3af3..40d46522c 100644 --- a/docs/middleware/included/raising-errors.md +++ b/docs/middleware/included/raising-errors.md @@ -38,7 +38,7 @@ by the client. They raise error classes inheriting from `Faraday::ClientError`. | [407](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407) | `Faraday::ProxyAuthError` | | [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) | `Faraday::RequestTimeoutError` | | [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) | `Faraday::ConflictError` | -| [422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) | `Faraday::UnprocessableEntityError` | +| [422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) | `Faraday::UnprocessableContentError` | | [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) | `Faraday::TooManyRequestsError` | | 4xx (any other) | `Faraday::ClientError` | diff --git a/lib/faraday/encoders/flat_params_encoder.rb b/lib/faraday/encoders/flat_params_encoder.rb index bc10c8b9a..7bbf9c438 100644 --- a/lib/faraday/encoders/flat_params_encoder.rb +++ b/lib/faraday/encoders/flat_params_encoder.rb @@ -76,9 +76,9 @@ def self.decode(query) empty_accumulator = {} - split_query = (query.split('&').map do |pair| + split_query = query.split('&').filter_map do |pair| pair.split('=', 2) if pair && !pair.empty? - end).compact + end split_query.each_with_object(empty_accumulator.dup) do |pair, accu| pair[0] = unescape(pair[0]) pair[1] = true if pair[1].nil? diff --git a/lib/faraday/error.rb b/lib/faraday/error.rb index 12ff15d77..791f8eefe 100644 --- a/lib/faraday/error.rb +++ b/lib/faraday/error.rb @@ -155,9 +155,12 @@ class ConflictError < ClientError end # Raised by Faraday::Response::RaiseError in case of a 422 response. - class UnprocessableEntityError < ClientError + class UnprocessableContentError < ClientError end + # Used to provide compatibility with legacy error name. + UnprocessableEntityError = UnprocessableContentError + # Raised by Faraday::Response::RaiseError in case of a 429 response. class TooManyRequestsError < ClientError end diff --git a/lib/faraday/logging/formatter.rb b/lib/faraday/logging/formatter.rb index 2fd4bb1da..c066b0fe3 100644 --- a/lib/faraday/logging/formatter.rb +++ b/lib/faraday/logging/formatter.rb @@ -63,7 +63,7 @@ def dump_headers(headers) def dump_body(body) if body.respond_to?(:to_str) - body.to_str + body.to_str.encode(Encoding::UTF_8, undef: :replace, invalid: :replace) else pretty_inspect(body) end diff --git a/lib/faraday/response.rb b/lib/faraday/response.rb index d1fa9320d..738c8498d 100644 --- a/lib/faraday/response.rb +++ b/lib/faraday/response.rb @@ -33,6 +33,10 @@ def body finished? ? env.body : nil end + def url + finished? ? env.url : nil + end + def finished? !!env end @@ -60,9 +64,9 @@ def success? def to_hash { - status: env.status, body: env.body, - response_headers: env.response_headers, - url: env.url + status: status, body: body, + response_headers: headers, + url: url } end diff --git a/lib/faraday/response/raise_error.rb b/lib/faraday/response/raise_error.rb index 0b543219b..48afd75a9 100644 --- a/lib/faraday/response/raise_error.rb +++ b/lib/faraday/response/raise_error.rb @@ -15,7 +15,7 @@ class RaiseError < Middleware 404 => Faraday::ResourceNotFound, 408 => Faraday::RequestTimeoutError, 409 => Faraday::ConflictError, - 422 => Faraday::UnprocessableEntityError, + 422 => Faraday::UnprocessableContentError, 429 => Faraday::TooManyRequestsError }.freeze # rubocop:enable Naming/ConstantName diff --git a/lib/faraday/version.rb b/lib/faraday/version.rb index 10205e427..65d8ad421 100644 --- a/lib/faraday/version.rb +++ b/lib/faraday/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Faraday - VERSION = '2.13.4' + VERSION = '2.14.0' end diff --git a/spec/faraday/response/logger_spec.rb b/spec/faraday/response/logger_spec.rb index e8e0bf3f9..6f8e11ffc 100644 --- a/spec/faraday/response/logger_spec.rb +++ b/spec/faraday/response/logger_spec.rb @@ -21,6 +21,7 @@ stubs.post('/ohai') { [200, { 'Content-Type' => 'text/html' }, 'fred'] } stubs.post('/ohyes') { [200, { 'Content-Type' => 'text/html' }, 'pebbles'] } stubs.get('/rubbles') { [200, { 'Content-Type' => 'application/json' }, rubbles] } + stubs.get('/8bit') { [200, { 'Content-Type' => 'text/html' }, (+'café!').force_encoding(Encoding::ASCII_8BIT)] } stubs.get('/filtered_body') { [200, { 'Content-Type' => 'text/html' }, 'soylent green is people'] } stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] } stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] } @@ -238,6 +239,11 @@ def response(_env) expect(string_io.string).to match(%(fred)) end + it 'converts to UTF-8' do + conn.get '/8bit' + expect(string_io.string).to match(%(caf��!)) + end + after do described_class.default_options = { bodies: false } end diff --git a/spec/faraday/response/raise_error_spec.rb b/spec/faraday/response/raise_error_spec.rb index 18c2044a3..579912621 100644 --- a/spec/faraday/response/raise_error_spec.rb +++ b/spec/faraday/response/raise_error_spec.rb @@ -13,7 +13,7 @@ stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] } stub.get('request-timeout') { [408, { 'X-Reason' => 'because' }, 'keep looking'] } stub.get('conflict') { [409, { 'X-Reason' => 'because' }, 'keep looking'] } - stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] } + stub.get('unprocessable-content') { [422, { 'X-Reason' => 'because' }, 'keep looking'] } stub.get('too-many-requests') { [429, { 'X-Reason' => 'because' }, 'keep looking'] } stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] } stub.get('nil-status') { [nil, { 'X-Reason' => 'nil' }, 'fail'] } @@ -103,9 +103,20 @@ end end - it 'raises Faraday::UnprocessableEntityError for 422 responses' do - expect { conn.get('unprocessable-entity') }.to raise_error(Faraday::UnprocessableEntityError) do |ex| - expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-entity') + it 'raises legacy Faraday::UnprocessableEntityError for 422 responses' do + expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableEntityError) do |ex| + expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content') + expect(ex.response[:headers]['X-Reason']).to eq('because') + expect(ex.response[:status]).to eq(422) + expect(ex.response_status).to eq(422) + expect(ex.response_body).to eq('keep looking') + expect(ex.response_headers['X-Reason']).to eq('because') + end + end + + it 'raises Faraday::UnprocessableContentError for 422 responses' do + expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableContentError) do |ex| + expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content') expect(ex.response[:headers]['X-Reason']).to eq('because') expect(ex.response[:status]).to eq(422) expect(ex.response_status).to eq(422) diff --git a/spec/faraday/response_spec.rb b/spec/faraday/response_spec.rb index e3e2c2378..2050da000 100644 --- a/spec/faraday/response_spec.rb +++ b/spec/faraday/response_spec.rb @@ -13,6 +13,7 @@ it { expect(subject.success?).to be_falsey } it { expect(subject.status).to eq(404) } it { expect(subject.body).to eq('yikes') } + it { expect(subject.url).to eq(URI('https://lostisland.github.io/faraday')) } it { expect(subject.headers['Content-Type']).to eq('text/plain') } it { expect(subject['content-type']).to eq('text/plain') } @@ -31,6 +32,12 @@ it { expect(hash[:response_headers]).to eq(subject.headers) } it { expect(hash[:body]).to eq(subject.body) } it { expect(hash[:url]).to eq(subject.env.url) } + + context 'when response is not finished' do + subject { Faraday::Response.new.to_hash } + + it { is_expected.to eq({ status: nil, body: nil, response_headers: {}, url: nil }) } + end end describe 'marshal serialization support' do