Compare commits

...

40 Commits

Author SHA1 Message Date
Matt
d099fafd65
Version bump to 2.13.4 2025-07-25 13:19:35 +01:00
Matt
cf32578f25
Improve error handling logic and add missing test coverage (#1633) 2025-07-25 13:18:53 +01:00
Matt
e76e60d3c0
Version bump to 2.13.3 2025-07-22 09:34:48 +01:00
Matt
674fc1583f
Fix type assumption in Faraday::Error (#1630)
Following #1627, we began to assume that the parameter passed to `Faraday::Error#new` could only be either and `Exception` or a `Hash`.

As demonstrated in #1629, it turns out in the real world we're also passing `Faraday::Env` instances when building errors, which also respond to `each_key` and `[]` too.
2025-07-22 09:34:19 +01:00
Matt
da86ebae9c
Version bump to 2.13.2 2025-07-04 14:14:39 +01:00
Niels Buus
ad8fe1e89a
Include HTTP method and URL in Faraday::Error messages for improved exception log transparency (#1628) 2025-07-04 14:09:46 +01:00
Olle Jonsson
1ddd281893 CONTRIBUTING: update socials links to Mastodon 2025-06-18 09:41:40 +02:00
Josef Šimánek
976369857e
Add migrating from rest-client docs section. (#1625) 2025-06-15 13:46:10 +03:00
Olle Jonsson
64e8a2bdb1
Lint rack_builder.rb: avoid naming a method (#1626)
Lint rack_builder.rb: avoid naming a method

The method name gave a complaint
2025-06-15 10:03:01 +03:00
Earlopain
bbaa093dbc
Only load what is required from cgi (#1623)
In Ruby 3.5 most of the `cgi` gem will be removed. Only the various escape/unescape methods will be retained by default.

On older versions, `require "cgi/util"` is needed because the unescape* methods don't work otherwise.

https://bugs.ruby-lang.org/issues/21258
2025-05-12 11:12:42 +01:00
Earlopain
fa9424b05a CI against Ruby 3.4 2025-05-11 15:10:53 +02:00
Matt
4018769a30
Version bump to 2.13.1 2025-04-25 15:37:39 +02:00
Matt
b5a02d7300
Fix Style/RedundantParentheses in options/env.rb (#1620) 2025-04-25 15:28:56 +02:00
Robert Keresnyei
b63eb9121f
Logger middleware default options (#1618) 2025-04-25 15:20:50 +02:00
Matt
77204cc7e8
Version bump to 2.13.0 2025-04-08 21:19:06 +01:00
Benjamin Fleischer
919dc8fdb3
feat(ssl options): support SNI hostname (#1615) 2025-04-08 21:16:16 +01:00
Matt
064a54be6c
Version bump to 2.12.3 2025-04-08 14:05:30 +01:00
Hiroaki Osawa
cd1c44a4aa
Fix thread safety issue by avoiding mutation of proxy options hash (#1617) 2025-04-08 13:53:16 +01:00
Sebastian Cohnen
1551c32371 removes ruby2_keywords usage 2025-03-03 06:54:32 +01:00
Matt
a9cf00425e
Version bump to 2.12.2 2024-12-09 10:50:18 +00:00
kodram
529b5b043e
Formatting the log using parameter progname for the logger (#1606) 2024-12-09 10:45:36 +00:00
Mamoru TASAKA
b7b2bc19e9 [TEST] fix compatibility with ruby 3.4.0dev
ruby 3.4 changes Hash#inspect formatting as:
https://bugs.ruby-lang.org/issues/20433

Closes #1602
2024-12-03 08:47:00 +01:00
chaymaeBZ
f9f4ce5bc1 Use generic argument forwarding + remove ruby2_keywords 2024-11-26 09:48:05 +01:00
Matt
93ef9e0ea9
Version bump to 2.12.1 2024-11-14 11:38:20 +00:00
Richard Marbach
fff02307ac
Allow faraday-net_http 3.4.x (#1599) 2024-11-14 11:37:28 +00:00
Matt
59c5003ceb
Version bump to 2.12.0 2024-09-18 10:03:36 +01:00
Clemens Kofler
98d5adf924
Make RaiseError middleware configurable to not raise error on certain status codes (e.g. 404) (#1590) 2024-09-13 15:08:50 +01:00
David Rodríguez
9e5c8a113f
Add json as an explicit dependency (#1589) 2024-09-11 08:31:10 +02:00
JC (Jonathan Chen)
9fcff671ff
docs: fix grammar (#1588) 2024-09-10 08:19:42 +02:00
Matt
3170e7df6f
Version bump to 2.11.0 2024-08-26 09:48:45 +01:00
Matt
f208ffcc9a
Allow faraday-net_http 3.3.x 2024-08-26 09:48:10 +01:00
womblep
9056eccea6
Add ciphers attribute to SSLOptions (#1582) 2024-08-24 11:25:57 +01:00
Geremia Taglialatela
99228e4743
Fix typos (#1585)
Found via `codespell`.
2024-08-24 09:23:20 +02:00
Geremia Taglialatela
9cdc025759
Opt-in for MFA requirement explicitly (#1580)
As a popular gem, `faraday` implicitly requires that all privileged
operations by any of the owners require OTP.

However, by explicitly setting `rubygems_mfa_required` metadata, the
gem will show "NEW VERSIONS REQUIRE MFA" and
"VERSION PUBLISHED WITH MFA" in the sidebar at
https://rubygems.org/gems/faraday

Ref:
- https://blog.rubygems.org/2022/08/15/requiring-mfa-on-popular-gems.html
- https://guides.rubygems.org/mfa-requirement-opt-in/
2024-08-22 17:17:20 +01:00
Matt
3835b48d80
Add support for a new ParallelManager#execute method. (#1584)
* Add support for a new `ParallelManager#execute` method.

The new interface passes the parallel block with all the requests to the ParallelManager, instead of running it beforehand.

This allows for better, stateless ParallelManager implementations.

Fixes https://github.com/lostisland/faraday/issues/1583

* Update docs/adapters/custom/parallel-requests.md

Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>

---------

Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
2024-08-22 13:05:07 +01:00
dependabot[bot]
3efc0a8982
Update faraday-net_http requirement from >= 2.0, < 3.2 to >= 2.0, < 3.3 (#1579)
Updates the requirements on [faraday-net_http](https://github.com/lostisland/faraday-net_http) to permit the latest version.
- [Release notes](https://github.com/lostisland/faraday-net_http/releases)
- [Commits](https://github.com/lostisland/faraday-net_http/compare/v2.0.0...v3.2.0)

---
updated-dependencies:
- dependency-name: faraday-net_http
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 15:23:59 +02:00
Olle Jonsson
f27f7ab801 v2.10.1 2024-07-31 14:07:48 +02:00
Olle Jonsson
051a635f4b
fix: Avoid lazy-initialized lock (#1577) 2024-07-31 14:02:46 +02:00
Olle Jonsson
4860f75372 Update JS deps
We use docsify to render the documentation site, and so we have a package.json.

This updates its lockfile.

I used `npm audit fix` to update only the reported issues.
2024-07-22 13:05:29 +02:00
Matt
073faf7539
Remove rubygems-await version pinning
The commit being pinned has now been merged to master and released
2024-07-22 12:00:20 +01:00
31 changed files with 613 additions and 141 deletions

View File

@ -24,7 +24,7 @@ These resources can help:
This project attempts to improve in these areas. Join us in doing that important work.
If you want to privately raise any breach to this policy with the Faraday team, feel free to reach out to [@iMacTia](https://twitter.com/iMacTia) and [@olleolleolle](https://twitter.com/olleolleolle) on Twitter.
If you want to privately raise any breach to this policy with the Faraday team, feel free to reach out to [@iMacTia](https://ruby.social/@iMacTia) and [@olleolleolle](https://ruby.social/@olleolleolle) on the Mastodon instance ruby.social.
### Required Checks

View File

@ -43,7 +43,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ '3.0', '3.1', '3.2', '3.3' ]
ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4' ]
experimental: [false]
include:
- ruby: head

View File

@ -31,7 +31,7 @@ Metrics/AbcSize:
# Offense count: 3
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 225
Max: 230
# Offense count: 9
# Configuration parameters: AllowedMethods, AllowedPatterns.

View File

@ -517,7 +517,7 @@ Breaking changes:
- Drop support for Ruby 1.8
Features:
- Include wrapped exception/reponse in ClientErrors
- Include wrapped exception/response in ClientErrors
- Add `response.reason_phrase`
- Provide option to selectively skip logging request/response headers
- Add regex support for pattern matching in `test` adapter

View File

@ -26,8 +26,4 @@ group :development, :lint do
gem 'yard-junk'
end
group :deployment do
gem 'rubygems-await', github: 'segiddins/rubygems-await', ref: 'f5e2b0413ec6f17e35d9bb7902dcb28b31804701'
end
gemspec

View File

@ -19,7 +19,7 @@ We've taken this decision for the following technical reasons:
focused on the Faraday API more quickly, without having to push it on all adapters immediately.
* With the community creating more and more adapters, we wanted to avoid having first and second-class adapters
by having some of them included with the gem and others available externally.
* Moving adapters into separate gems allow to solve the dependency issues once and for all.
* Moving adapters into separate gems solve the dependency issues once and for all.
Faraday will remain a dependency-free gem, while adapter gems will be able to automatically pull
any necessary dependency, without having to rely on the developer to do so.

View File

@ -2,6 +2,7 @@
* [Quick Start](getting-started/quick-start.md)
* [The Env Object](getting-started/env-object.md)
* [Dealing with Errors](getting-started/errors.md)
* [Migrating from rest-client](getting-started/rest-client-migration.md)
* Customization
* [Configuration](customization/index.md)
* [Connection Options](customization/connection-options.md)

View File

@ -42,18 +42,34 @@ class FlorpHttp < ::Faraday::Adapter
def self.setup_parallel_manager(_options = nil)
FlorpParallelManager.new # NB: we will need to define this
end
def call(env)
# NB: you can call `in_parallel?` here to check if the current request
# is part of a parallel batch. Useful if you need to collect all requests
# into the ParallelManager before running them.
end
end
class FlorpParallelManager
def add(request, method, *args, &block)
# Collect the requests
end
def run
# Process the requests
# The execute method will be passed the same block as `in_parallel`,
# so you can either collect the requests or just wrap them into a wrapper,
# depending on how your adapter works.
def execute(&block)
run_async(&block)
end
end
```
Compare to the finished example [em-synchrony](https://github.com/lostisland/faraday-em_synchrony/blob/main/lib/faraday/adapter/em_synchrony.rb)
### A note on the old, deprecated interface
Prior to the introduction of the `execute` method, the `ParallelManager` was expected to implement a `run` method
and the execution of the block was done by the Faraday connection BEFORE calling that method.
This approach made the `ParallelManager` implementation harder and forced you to keep state around.
The new `execute` implementation allows to avoid this shortfall and support different flows.
As of Faraday 2.0, `run` is still supported in case `execute` is not implemented by the `ParallelManager`,
but this method should be considered deprecated.
For reference, please see an example using `run` from [em-synchrony](https://github.com/lostisland/faraday-em_synchrony/blob/main/lib/faraday/adapter/em_synchrony.rb)
and its [ParallelManager implementation](https://github.com/lostisland/faraday-em_synchrony/blob/main/lib/faraday/adapter/em_synchrony/parallel_manager.rb).

View File

@ -3,9 +3,10 @@
Faraday supports a number of SSL options, which can be provided while initializing the connection.
| Option | Type | Default | Description |
|--------------------|----------------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------|
|--------------------|----------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------|
| `:verify` | Boolean | true | Verify SSL certificate. Defaults to `true`. |
| `:verify_hostname` | Boolean | true | Verify SSL certificate hostname. Defaults to `true`. |
| `:hostname` | String | nil | Server hostname for SNI (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLSocket.html#method-i-hostname-3D)). |
| `:ca_file` | String | nil | Path to a CA file in PEM format. |
| `:ca_path` | String | nil | Path to a CA directory. |
| `:verify_mode` | Integer | nil | Any `OpenSSL::SSL::` constant (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL.html)). |
@ -18,6 +19,7 @@ Faraday supports a number of SSL options, which can be provided while initializi
| `:version` | Integer | nil | SSL version (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D)). |
| `:min_version` | Integer | nil | Minimum SSL version (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D)). |
| `:max_version` | Integer | nil | Maximum SSL version (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-max_version-3D)). |
| `:ciphers` | String | nil | Ciphers supported (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-ciphers-3D)). |
## Example

View File

@ -0,0 +1,225 @@
# Migrating from `rest-client` to `Faraday`
The `rest-client` gem is in maintenance mode, and developers are encouraged to migrate to actively maintained alternatives like [`faraday`](https://github.com/lostisland/faraday). This guide highlights common usage patterns in `rest-client` and how to migrate them to `faraday`.
---
## Quick Comparison
| Task | rest-client example | faraday example |
| ----------------- | -------------------------------------------------------- | -------------------------------------------------------------------------- |
| Simple GET | `RestClient.get("https://httpbingo.org/get")` | `Faraday.get("https://httpbingo.org/get")` |
| GET with params | `RestClient.get(url, params: { id: 1 })` | `Faraday.get(url, { id: 1 })` |
| POST form data | `RestClient.post(url, { a: 1 })` | `Faraday.post(url, { a: 1 })` |
| POST JSON | `RestClient.post(url, obj.to_json, content_type: :json)` | `Faraday.post(url, obj.to_json, { 'Content-Type' => 'application/json' })` |
| Custom headers | `RestClient.get(url, { Authorization: 'Bearer token' })` | `Faraday.get(url, nil, { 'Authorization' => 'Bearer token' })` |
| Get response body | `response.body` | `response.body` |
| Get status code | `response.code` | `response.status` |
| Get headers | `response.headers` (returns `Hash<Symbol, String>`) | `response.headers` (returns `Hash<String, String>`) |
---
## Installation
In your `Gemfile`, replace `rest-client` with:
```ruby
gem "faraday"
```
Then run:
```sh
bundle install
```
---
## Basic HTTP Requests
### GET request
**rest-client:**
```ruby
RestClient.get("https://httpbingo.org/get")
```
**faraday:**
```ruby
Faraday.get("https://httpbingo.org/get")
```
---
### GET with Params
**rest-client:**
```ruby
RestClient.get("https://httpbingo.org/get", params: { id: 1, foo: "bar" })
```
**faraday:**
```ruby
Faraday.get("https://httpbingo.org/get", { id: 1, foo: "bar" })
```
---
### POST Requests
**rest-client:**
```ruby
RestClient.post("https://httpbingo.org/post", { foo: "bar" })
```
**faraday:**
```ruby
Faraday.post("https://httpbingo.org/post", { foo: "bar" })
```
---
### Sending JSON
**rest-client:**
```ruby
RestClient.post("https://httpbingo.org/post", { foo: "bar" }.to_json, content_type: :json)
```
**faraday (manual):**
```ruby
Faraday.post("https://httpbingo.org/post", { foo: "bar" }.to_json, { 'Content-Type' => 'application/json' })
```
**faraday (with middleware):**
```ruby
conn = Faraday.new(url: "https://httpbingo.org") do |f|
f.request :json # encode request body as JSON and set Content-Type
f.response :json # parse response body as JSON
end
conn.post("/post", { foo: "bar" })
```
---
## Handling Responses
**rest-client:**
```ruby
response = RestClient.get("https://httpbingo.org/headers")
response.code # => 200
response.body # => "..."
response.headers # => { content_type: "application/json", ... }
```
**faraday:**
> notice headers Hash keys are stringified, not symbolized like in rest-client
```ruby
response = Faraday.get("https://httpbingo.org/headers")
response.status # => 200
response.body # => "..."
response.headers # => { "content-type" => "application/json", ... }
```
---
## Error Handling
**rest-client:**
```ruby
begin
RestClient.get("https://httpbingo.org/status/404")
rescue RestClient::NotFound => e
puts e.response.code # 404
end
```
**faraday:**
> By default, Faraday does **not** raise exceptions for HTTP errors (like 404 or 500); it simply returns the response. If you want exceptions to be raised on HTTP error responses, include the `:raise_error` middleware.
>
> With `:raise_error`, Faraday will raise `Faraday::ResourceNotFound` for 404s and other exceptions for other 4xx/5xx responses.
>
> See also:
>
> * [Dealing with Errors](getting-started/errors.md)
> * [Raising Errors](middleware/included/raising-errors.md)
```ruby
conn = Faraday.new(url: "https://httpbingo.org") do |f|
f.response :raise_error
end
begin
conn.get("/status/404")
rescue Faraday::ResourceNotFound => e
puts e.response[:status] # 404
end
```
---
## Advanced Request Configuration
**rest-client:**
```ruby
RestClient::Request.execute(method: :get, url: "https://httpbingo.org/get", timeout: 10)
```
**faraday:**
```ruby
conn = Faraday.new(url: "https://httpbingo.org", request: { timeout: 10 })
conn.get("/get")
```
---
## Headers
**rest-client:**
```ruby
RestClient.get("https://httpbingo.org/headers", { Authorization: "Bearer token" })
```
**faraday:**
> Notice headers Hash expects stringified keys.
```ruby
Faraday.get("https://httpbingo.org/headers", nil, { "Authorization" => "Bearer token" })
```
---
## Redirects
**rest-client:**
Automatically follows GET/HEAD redirects by default.
**faraday:**
Use the `follow_redirects` middleware (not included by default):
```ruby
require "faraday/follow_redirects"
conn = Faraday.new(url: "https://httpbingo.org") do |f|
f.response :follow_redirects
end
```

View File

@ -64,22 +64,27 @@ and raised as `Faraday::NilStatusError`, which inherits from `Faraday::ServerErr
The behavior of this middleware can be customized with the following options:
| Option | Default | Description |
|---------------------|---------|-------------|
|----------------------|---------|-------------|
| **include_request** | true | When true, exceptions are initialized with request information including `method`, `url`, `url_path`, `params`, `headers`, and `body`. |
| **allowed_statuses** | [] | An array of status codes that should not raise an error. |
### Example Usage
```ruby
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
faraday.response :raise_error, include_request: true
faraday.response :raise_error, include_request: true, allowed_statuses: [404]
end
begin
conn.get('/wrong-url') # => Assume this raises a 404 response
rescue Faraday::ResourceNotFound => e
e.response[:status] #=> 404
e.response[:headers] #=> { ... }
e.response[:body] #=> "..."
e.response[:request][:url_path] #=> "/wrong-url"
conn.get('/protected-url') # => Assume this raises a 401 response
rescue Faraday::UnauthorizedError => e
e.response[:status] # => 401
e.response[:headers] # => { ... }
e.response[:body] # => "..."
e.response[:request][:url_path] # => "/protected-url"
end
```
In this example, a `Faraday::UnauthorizedError` exception is raised for the `/protected-url` request, while the
`/wrong-url` request does not raise an error because the status code `404` is in the `allowed_statuses` array.

View File

@ -147,7 +147,7 @@ In the case where it is desirable to change the default option for all instances
Faraday::Response::RaiseError.default_options = { include_request: false }
```
After app initialization, all instances of the middleware will have the newly configured option(s). They can still be overriden on a per-instance bases (if handled in the middleware), like this:
After app initialization, all instances of the middleware will have the newly configured option(s). They can still be overridden on a per-instance bases (if handled in the middleware), like this:
```ruby
Faraday.new do |f|

View File

@ -21,7 +21,8 @@ Gem::Specification.new do |spec|
# always fix its required version to the next MINOR version.
# This way, we can release minor versions of the adapter with "breaking" changes for older versions of Faraday
# and then bump the version requirement on the next compatible version of faraday.
spec.add_dependency 'faraday-net_http', '>= 2.0', '< 3.2'
spec.add_dependency 'faraday-net_http', '>= 2.0', '< 3.5'
spec.add_dependency 'json'
spec.add_dependency 'logger'
# Includes `examples` and `spec` to allow external adapter gems to run Faraday unit and integration tests
@ -32,6 +33,7 @@ Gem::Specification.new do |spec|
'changelog_uri' =>
"https://github.com/lostisland/faraday/releases/tag/v#{spec.version}",
'source_code_uri' => 'https://github.com/lostisland/faraday',
'bug_tracker_uri' => 'https://github.com/lostisland/faraday/issues'
'bug_tracker_uri' => 'https://github.com/lostisland/faraday/issues',
'rubygems_mfa_required' => 'true'
}
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'cgi'
require 'cgi/escape'
require 'cgi/util' if RUBY_VERSION < '3.5'
require 'date'
require 'set'
require 'forwardable'

View File

@ -314,15 +314,23 @@ module Faraday
#
# @yield a block to execute multiple requests.
# @return [void]
def in_parallel(manager = nil)
def in_parallel(manager = nil, &block)
@parallel_manager = manager || default_parallel_manager do
warn 'Warning: `in_parallel` called but no parallel-capable adapter ' \
'on Faraday stack'
warn caller[2, 10].join("\n")
nil
end
return yield unless @parallel_manager
if @parallel_manager.respond_to?(:execute)
# Execute is the new method that is responsible for executing the block.
@parallel_manager.execute(&block)
else
# TODO: Old behaviour, deprecate and remove in 3.0
yield
@parallel_manager&.run
@parallel_manager.run
end
ensure
@parallel_manager = nil
end

View File

@ -79,15 +79,49 @@ module Faraday
# Pulls out potential parent exception and response hash.
def exc_msg_and_response(exc, response = nil)
return [exc, exc.message, response] if exc.respond_to?(:backtrace)
return [nil, "the server responded with status #{exc[:status]}", exc] \
if exc.respond_to?(:each_key)
case exc
when Exception
[exc, exc.message, response]
when Hash
[nil, build_error_message_from_hash(exc), exc]
when Faraday::Env
[nil, build_error_message_from_env(exc), exc]
else
[nil, exc.to_s, response]
end
end
private
def build_error_message_from_hash(hash)
# Be defensive with external Hash objects - they might be missing keys
status = hash.fetch(:status, nil)
request = hash.fetch(:request, nil)
return fallback_error_message(status) if request.nil?
method = request.fetch(:method, nil)
url = request.fetch(:url, nil)
build_status_error_message(status, method, url)
end
def build_error_message_from_env(env)
# Faraday::Env is internal - we can make reasonable assumptions about its structure
build_status_error_message(env.status, env.method, env.url)
end
def build_status_error_message(status, method, url)
method_str = method ? method.to_s.upcase : ''
url_str = url ? url.to_s : ''
"the server responded with status #{status} for #{method_str} #{url_str}"
end
def fallback_error_message(status)
"the server responded with status #{status} - method and url are not available " \
'due to include_request: false on Faraday::Response::RaiseError middleware'
end
end
# Faraday client error class. Represents 4xx status responses.
class ClientError < Error
end

View File

@ -23,8 +23,8 @@ module Faraday
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
def request(env)
public_send(log_level, 'request') do
"#{env.method.upcase} #{apply_filters(env.url.to_s)}"
public_send(log_level) do
"request: #{env.method.upcase} #{apply_filters(env.url.to_s)}"
end
log_headers('request', env.request_headers) if log_headers?(:request)
@ -32,7 +32,7 @@ module Faraday
end
def response(env)
public_send(log_level, 'response') { "Status #{env.status}" }
public_send(log_level) { "response: Status #{env.status}" }
log_headers('response', env.response_headers) if log_headers?(:response)
log_body('response', env[:body]) if env[:body] && log_body?(:response)
@ -41,7 +41,7 @@ module Faraday
def exception(exc)
return unless log_errors?
public_send(log_level, 'error') { exc.full_message }
public_send(log_level) { "error: #{exc.full_message}" }
log_headers('error', exc.response_headers) if exc.respond_to?(:response_headers) && log_headers?(:error)
return unless exc.respond_to?(:response_body) && exc.response_body && log_body?(:error)
@ -107,11 +107,11 @@ module Faraday
end
def log_headers(type, headers)
public_send(log_level, type) { apply_filters(dump_headers(headers)) }
public_send(log_level) { "#{type}: #{apply_filters(dump_headers(headers))}" }
end
def log_body(type, body)
public_send(log_level, type) { apply_filters(dump_body(body)) }
public_send(log_level) { "#{type}: #{apply_filters(dump_body(body))}" }
end
end
end

View File

@ -10,6 +10,7 @@ module Faraday
attr_reader :app, :options
DEFAULT_OPTIONS = {}.freeze
LOCK = Mutex.new
def initialize(app = nil, options = {})
@app = app
@ -27,7 +28,7 @@ module Faraday
#
def default_options=(options = {})
validate_default_options(options)
lock.synchronize do
LOCK.synchronize do
@default_options = default_options.merge(options)
end
end
@ -41,10 +42,6 @@ module Faraday
private
def lock
@lock ||= Monitor.new
end
def validate_default_options(options)
invalid_keys = options.keys.reject { |opt| self::DEFAULT_OPTIONS.key?(opt) }
return unless invalid_keys.any?

View File

@ -60,7 +60,7 @@ module Faraday
:reason_phrase, :response_body) do
const_set(:ContentLength, 'Content-Length')
const_set(:StatusesWithoutBody, Set.new([204, 304]))
const_set(:SuccessfulStatuses, (200..299))
const_set(:SuccessfulStatuses, 200..299)
# A Set of HTTP verbs that typically send a body. If no body is set for
# these requests, the Content-Length header is set to 0.

View File

@ -22,8 +22,10 @@ module Faraday
when URI
value = { uri: value }
when Hash, Options
if (uri = value.delete(:uri))
value[:uri] = Utils.URI(uri)
if value[:uri]
value = value.dup.tap do |duped|
duped[:uri] = Utils.URI(duped[:uri])
end
end
end

View File

@ -11,6 +11,9 @@ module Faraday
# # @return [Boolean] whether to enable hostname verification on server certificates
# # during the handshake or not (see https://github.com/ruby/openssl/pull/60)
# #
# # @!attribute hostname
# # @return [String] Server hostname used for SNI (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLSocket.html#method-i-hostname-3D)
# #
# # @!attribute ca_file
# # @return [String] CA file
# #
@ -46,12 +49,15 @@ module Faraday
# #
# # @!attribute max_version
# # @return [String, Symbol] maximum SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-max_version-3D)
# #
# # @!attribute ciphers
# # @return [String] cipher list in OpenSSL format (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-ciphers-3D)
# class SSLOptions < Options; end
SSLOptions = Options.new(:verify, :verify_hostname,
SSLOptions = Options.new(:verify, :verify_hostname, :hostname,
:ca_file, :ca_path, :verify_mode,
:cert_store, :client_cert, :client_key,
:certificate, :private_key, :verify_depth,
:version, :min_version, :max_version) do
:version, :min_version, :max_version, :ciphers) do
# @return [Boolean] true if should verify
def verify?
verify != false

View File

@ -27,10 +27,11 @@ module Faraday
attr_reader :name
ruby2_keywords def initialize(klass, *args, &block)
def initialize(klass, *args, **kwargs, &block)
@name = klass.to_s
REGISTRY.set(klass) if klass.respond_to?(:name)
@args = args
@kwargs = kwargs
@block = block
end
@ -53,7 +54,7 @@ module Faraday
end
def build(app = nil)
klass.new(app, *@args, &@block)
klass.new(app, *@args, **@kwargs, &@block)
end
end
@ -88,52 +89,52 @@ module Faraday
@handlers.frozen?
end
ruby2_keywords def use(klass, *args, &block)
def use(klass, ...)
if klass.is_a? Symbol
use_symbol(Faraday::Middleware, klass, *args, &block)
use_symbol(Faraday::Middleware, klass, ...)
else
raise_if_locked
raise_if_adapter(klass)
@handlers << self.class::Handler.new(klass, *args, &block)
@handlers << self.class::Handler.new(klass, ...)
end
end
ruby2_keywords def request(key, *args, &block)
use_symbol(Faraday::Request, key, *args, &block)
def request(key, ...)
use_symbol(Faraday::Request, key, ...)
end
ruby2_keywords def response(key, *args, &block)
use_symbol(Faraday::Response, key, *args, &block)
def response(...)
use_symbol(Faraday::Response, ...)
end
ruby2_keywords def adapter(klass = NO_ARGUMENT, *args, &block)
def adapter(klass = NO_ARGUMENT, *args, **kwargs, &block)
return @adapter if klass == NO_ARGUMENT || klass.nil?
klass = Faraday::Adapter.lookup_middleware(klass) if klass.is_a?(Symbol)
@adapter = self.class::Handler.new(klass, *args, &block)
@adapter = self.class::Handler.new(klass, *args, **kwargs, &block)
end
## methods to push onto the various positions in the stack:
ruby2_keywords def insert(index, *args, &block)
def insert(index, ...)
raise_if_locked
index = assert_index(index)
handler = self.class::Handler.new(*args, &block)
handler = self.class::Handler.new(...)
@handlers.insert(index, handler)
end
alias insert_before insert
ruby2_keywords def insert_after(index, *args, &block)
def insert_after(index, ...)
index = assert_index(index)
insert(index + 1, *args, &block)
insert(index + 1, ...)
end
ruby2_keywords def swap(index, *args, &block)
def swap(index, ...)
raise_if_locked
index = assert_index(index)
@handlers.delete_at(index)
insert(index, *args, &block)
insert(index, ...)
end
def delete(handler)
@ -220,7 +221,7 @@ module Faraday
end
def raise_if_adapter(klass)
return unless is_adapter?(klass)
return unless klass <= Faraday::Adapter
raise 'Adapter should be set using the `adapter` method, not `use`'
end
@ -233,12 +234,8 @@ module Faraday
!@adapter.nil?
end
def is_adapter?(klass) # rubocop:disable Naming/PredicateName
klass <= Faraday::Adapter
end
ruby2_keywords def use_symbol(mod, key, *args, &block)
use(mod.lookup_middleware(key), *args, &block)
def use_symbol(mod, key, ...)
use(mod.lookup_middleware(key), ...)
end
def assert_index(index)

View File

@ -10,11 +10,13 @@ module Faraday
# lifecycle to a given Logger object. By default, this logs to STDOUT. See
# Faraday::Logging::Formatter to see specifically what is logged.
class Logger < Middleware
DEFAULT_OPTIONS = { formatter: Logging::Formatter }.merge(Logging::Formatter::DEFAULT_OPTIONS).freeze
def initialize(app, logger = nil, options = {})
super(app)
super(app, options)
logger ||= ::Logger.new($stdout)
formatter_class = options.delete(:formatter) || Logging::Formatter
@formatter = formatter_class.new(logger: logger, options: options)
formatter_class = @options.delete(:formatter)
@formatter = formatter_class.new(logger: logger, options: @options)
yield @formatter if block_given?
end

View File

@ -8,32 +8,30 @@ module Faraday
# rubocop:disable Naming/ConstantName
ClientErrorStatuses = (400...500)
ServerErrorStatuses = (500...600)
ClientErrorStatusesWithCustomExceptions = {
400 => Faraday::BadRequestError,
401 => Faraday::UnauthorizedError,
403 => Faraday::ForbiddenError,
404 => Faraday::ResourceNotFound,
408 => Faraday::RequestTimeoutError,
409 => Faraday::ConflictError,
422 => Faraday::UnprocessableEntityError,
429 => Faraday::TooManyRequestsError
}.freeze
# rubocop:enable Naming/ConstantName
DEFAULT_OPTIONS = { include_request: true }.freeze
DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [] }.freeze
def on_complete(env)
return if Array(options[:allowed_statuses]).include?(env[:status])
case env[:status]
when 400
raise Faraday::BadRequestError, response_values(env)
when 401
raise Faraday::UnauthorizedError, response_values(env)
when 403
raise Faraday::ForbiddenError, response_values(env)
when 404
raise Faraday::ResourceNotFound, response_values(env)
when *ClientErrorStatusesWithCustomExceptions.keys
raise ClientErrorStatusesWithCustomExceptions[env[:status]], response_values(env)
when 407
# mimic the behavior that we get with proxy requests with HTTPS
msg = %(407 "Proxy Authentication Required")
raise Faraday::ProxyAuthError.new(msg, response_values(env))
when 408
raise Faraday::RequestTimeoutError, response_values(env)
when 409
raise Faraday::ConflictError, response_values(env)
when 422
raise Faraday::UnprocessableEntityError, response_values(env)
when 429
raise Faraday::TooManyRequestsError, response_values(env)
when ClientErrorStatuses
raise Faraday::ClientError, response_values(env)
when ServerErrorStatuses

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
module Faraday
VERSION = '2.10.0'
VERSION = '2.13.4'
end

32
package-lock.json generated
View File

@ -169,11 +169,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -612,9 +613,10 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@ -931,6 +933,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@ -1444,9 +1447,10 @@
}
},
"node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
@ -1631,6 +1635,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@ -1895,9 +1900,10 @@
}
},
"node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},

View File

@ -23,8 +23,12 @@ RSpec.describe Faraday::Error do
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 400') }
it { expect(subject.message).to eq('the server responded with status 400 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
if RUBY_VERSION >= '3.4'
it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
else
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
end
it { expect(subject.response_status).to eq(400) }
it { expect(subject.response_headers).to be_nil }
it { expect(subject.response_body).to be_nil }
@ -61,7 +65,11 @@ RSpec.describe Faraday::Error do
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(response) }
it { expect(subject.message).to eq('custom message') }
if RUBY_VERSION >= '3.4'
it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
else
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
end
it { expect(subject.response_status).to eq(400) }
it { expect(subject.response_headers).to be_nil }
it { expect(subject.response_body).to be_nil }
@ -81,5 +89,87 @@ RSpec.describe Faraday::Error do
it { expect(subject.response_headers).to eq(headers) }
it { expect(subject.response_body).to eq(body) }
end
context 'with hash missing status key' do
let(:exception) { { body: 'error body' } }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
end
context 'with hash with status but missing request data' do
let(:exception) { { status: 404, body: 'not found' } } # missing request key
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
end
context 'with hash with status and request but missing method in request' do
let(:exception) { { status: 404, body: 'not found', request: { url: 'http://example.com/test' } } } # missing method
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 for http://example.com/test') }
end
context 'with hash with status and request but missing url in request' do
let(:exception) { { status: 404, body: 'not found', request: { method: :get } } } # missing url
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 for GET ') }
end
context 'with properly formed Faraday::Env' do
# This represents the normal case - a well-formed Faraday::Env object
# with all the standard properties populated as they would be during
# a typical HTTP request/response cycle
let(:exception) { Faraday::Env.new }
before do
exception.status = 500
exception.method = :post
exception.url = URI('https://api.example.com/users')
exception.request = Faraday::RequestOptions.new
exception.response_headers = { 'content-type' => 'application/json' }
exception.response_body = '{"error": "Internal server error"}'
exception.request_headers = { 'authorization' => 'Bearer token123' }
exception.request_body = '{"name": "John"}'
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 500 for POST https://api.example.com/users') }
end
context 'with Faraday::Env missing status key' do
let(:exception) { Faraday::Env.new }
before do
exception[:body] = 'error body'
# Intentionally not setting status
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status for ') }
end
context 'with Faraday::Env with direct method and url properties' do
let(:exception) { Faraday::Env.new }
before do
exception.status = 404
exception.method = :get
exception.url = URI('http://example.com/test')
exception[:body] = 'not found'
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 for GET http://example.com/test') }
end
end
end

View File

@ -27,6 +27,33 @@ RSpec.describe Faraday::ProxyOptions do
expect(options.inspect).to eq('#<Faraday::ProxyOptions (empty)>')
end
it 'works with hash' do
hash = { user: 'user', password: 'pass', uri: 'http://@example.org' }
options = Faraday::ProxyOptions.from(hash)
expect(options.user).to eq('user')
expect(options.password).to eq('pass')
expect(options.uri).to be_a_kind_of(URI)
expect(options.path).to eq('')
expect(options.port).to eq(80)
expect(options.host).to eq('example.org')
expect(options.scheme).to eq('http')
expect(options.inspect).to match('#<Faraday::ProxyOptions uri=')
end
it 'works with option' do
opt_arg = { user: 'user', password: 'pass', uri: 'http://@example.org' }
option = Faraday::ConnectionOptions.from(proxy: opt_arg)
options = Faraday::ProxyOptions.from(option.proxy)
expect(options.user).to eq('user')
expect(options.password).to eq('pass')
expect(options.uri).to be_a_kind_of(URI)
expect(options.path).to eq('')
expect(options.port).to eq(80)
expect(options.host).to eq('example.org')
expect(options.scheme).to eq('http')
expect(options.inspect).to match('#<Faraday::ProxyOptions uri=')
end
it 'works with no auth' do
proxy = Faraday::ProxyOptions.from 'http://example.org'
expect(proxy.user).to be_nil

View File

@ -55,6 +55,26 @@ RSpec.describe Faraday::Response::Logger do
end
end
context 'when logger with program name' do
let(:logger) { Logger.new(string_io, progname: 'my_best_program') }
it 'logs with program name' do
conn.get '/hello'
expect(string_io.string).to match('-- my_best_program: request:')
expect(string_io.string).to match('-- my_best_program: response:')
end
end
context 'when logger without program name' do
it 'logs without program name' do
conn.get '/hello'
expect(string_io.string).to match('-- : request:')
expect(string_io.string).to match('-- : response:')
end
end
context 'with default formatter' do
let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
@ -169,7 +189,7 @@ RSpec.describe Faraday::Response::Logger do
context 'when logging request body' do
let(:logger_options) { { bodies: { request: true } } }
it 'log only request body' do
it 'logs only request body' do
conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
expect(string_io.string).to match(%(name=Tamago))
expect(string_io.string).not_to match(%(pebbles))
@ -179,7 +199,7 @@ RSpec.describe Faraday::Response::Logger do
context 'when logging response body' do
let(:logger_options) { { bodies: { response: true } } }
it 'log only response body' do
it 'logs only response body' do
conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
expect(string_io.string).to match(%(pebbles))
expect(string_io.string).not_to match(%(name=Hamachi))
@ -189,13 +209,13 @@ RSpec.describe Faraday::Response::Logger do
context 'when logging request and response bodies' do
let(:logger_options) { { bodies: true } }
it 'log request and response body' do
it 'logs request and response body' do
conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
expect(string_io.string).to match(%(name=Ebi))
expect(string_io.string).to match(%(pebbles))
end
it 'log response body object' do
it 'logs response body object' do
conn.get '/rubbles', nil, accept: 'text/html'
expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
end
@ -208,6 +228,21 @@ RSpec.describe Faraday::Response::Logger do
end
end
context 'when bodies are logged by default' do
before do
described_class.default_options = { bodies: true }
end
it 'logs response body' do
conn.post '/ohai'
expect(string_io.string).to match(%(fred))
end
after do
described_class.default_options = { bodies: false }
end
end
context 'when logging errors' do
let(:logger_options) { { errors: true } }

View File

@ -28,7 +28,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::BadRequestError for 400 responses' do
expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError) do |ex|
expect(ex.message).to eq('the server responded with status 400')
expect(ex.message).to eq('the server responded with status 400 for GET http:/bad-request')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(400)
expect(ex.response_status).to eq(400)
@ -39,7 +39,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::UnauthorizedError for 401 responses' do
expect { conn.get('unauthorized') }.to raise_error(Faraday::UnauthorizedError) do |ex|
expect(ex.message).to eq('the server responded with status 401')
expect(ex.message).to eq('the server responded with status 401 for GET http:/unauthorized')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(401)
expect(ex.response_status).to eq(401)
@ -50,7 +50,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::ForbiddenError for 403 responses' do
expect { conn.get('forbidden') }.to raise_error(Faraday::ForbiddenError) do |ex|
expect(ex.message).to eq('the server responded with status 403')
expect(ex.message).to eq('the server responded with status 403 for GET http:/forbidden')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(403)
expect(ex.response_status).to eq(403)
@ -61,7 +61,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::ResourceNotFound for 404 responses' do
expect { conn.get('not-found') }.to raise_error(Faraday::ResourceNotFound) do |ex|
expect(ex.message).to eq('the server responded with status 404')
expect(ex.message).to eq('the server responded with status 404 for GET http:/not-found')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(404)
expect(ex.response_status).to eq(404)
@ -83,7 +83,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::RequestTimeoutError for 408 responses' do
expect { conn.get('request-timeout') }.to raise_error(Faraday::RequestTimeoutError) do |ex|
expect(ex.message).to eq('the server responded with status 408')
expect(ex.message).to eq('the server responded with status 408 for GET http:/request-timeout')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(408)
expect(ex.response_status).to eq(408)
@ -94,7 +94,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::ConflictError for 409 responses' do
expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex|
expect(ex.message).to eq('the server responded with status 409')
expect(ex.message).to eq('the server responded with status 409 for GET http:/conflict')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(409)
expect(ex.response_status).to eq(409)
@ -105,7 +105,7 @@ RSpec.describe Faraday::Response::RaiseError do
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')
expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-entity')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(422)
expect(ex.response_status).to eq(422)
@ -116,7 +116,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::TooManyRequestsError for 429 responses' do
expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
expect(ex.message).to eq('the server responded with status 429')
expect(ex.message).to eq('the server responded with status 429 for GET http:/too-many-requests')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(429)
expect(ex.response_status).to eq(429)
@ -138,7 +138,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::ClientError for other 4xx responses' do
expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
expect(ex.message).to eq('the server responded with status 499')
expect(ex.message).to eq('the server responded with status 499 for GET http:/4xx')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(499)
expect(ex.response_status).to eq(499)
@ -149,7 +149,7 @@ RSpec.describe Faraday::Response::RaiseError do
it 'raises Faraday::ServerError for 500 responses' do
expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
expect(ex.message).to eq('the server responded with status 500')
expect(ex.message).to eq('the server responded with status 500 for GET http:/server-error')
expect(ex.response[:headers]['X-Error']).to eq('bailout')
expect(ex.response[:status]).to eq(500)
expect(ex.response_status).to eq(500)
@ -252,4 +252,24 @@ RSpec.describe Faraday::Response::RaiseError do
end
end
end
describe 'allowing certain status codes' do
let(:conn) do
Faraday.new do |b|
b.response :raise_error, allowed_statuses: [404]
b.adapter :test do |stub|
stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
end
end
end
it 'raises an error for status codes that are not explicitly allowed' do
expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError)
end
it 'does not raise an error for allowed status codes' do
expect { conn.get('not-found') }.not_to raise_error
end
end
end

View File

@ -103,7 +103,9 @@ RSpec.describe Faraday::Utils do
version: '2',
min_version: nil,
max_version: nil,
verify_hostname: nil
verify_hostname: nil,
hostname: nil,
ciphers: nil
}
end