Compare commits

...

71 Commits
v2.8.1 ... main

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
Matt
18524c6e89
Version bump to 2.10.0 2024-07-08 10:36:36 +01:00
Matt
d51c392c2c
Use specific version of rubygems-await
See https://github.com/segiddins/rubygems-await/issues/47
2024-07-08 10:35:46 +01:00
Ryan McNeil
d83a2818e7
Introduce Middleware DEFAULT_OPTIONS with Application and Instance Configurability (#1572) 2024-07-05 19:30:43 +01:00
Fabian Winkler
1958cb1ce2
Add logger as explicit dependency (#1573) 2024-07-05 16:08:44 +01:00
Yutaka Kamei
d7d5a6a36f
Configure "npm" package-ecosystem for Dependabot (#1571) 2024-06-25 11:57:48 +01:00
Matt
5996054fd4
Version bump to 2.9.2 2024-06-18 09:52:10 +01:00
ykrods
d8bfca25fa
Merge relative url without escaping (#1569) 2024-06-18 09:50:41 +01:00
Mattia Giuffrida
4abafa5c66 Add Bundler::GemHelper tasks to Rakefile 2024-06-05 16:30:09 +01:00
Matt
89107f9889
Add missing bundle install to publish.yml 2024-06-05 15:57:43 +01:00
Matt
b5245081d9
Version bump to 2.9.1 2024-06-05 15:06:55 +01:00
Max Rozenoer
04515f38b3
Support default json decoder even when nil responds to :load (#1563)
In Rails (some versions at least), nil responds to `:load` (via ActiveSupport::Dependencies::Loadable mixin).
Enable default json decoder to work in this case
2024-06-05 15:06:16 +01:00
Tijmen Brommet
6933e9b70f
Add TooManyRequestsError (429) to error docs (#1565) 2024-06-04 11:34:03 +01:00
Masato Nakamura
6d82d716c2
Fix compatibility with Ruby 3.4.0-preview1 (#1560) 2024-05-24 15:50:52 +01:00
Mattia Giuffrida
7dc694150d Fix Rubocop errors 2024-05-24 16:41:19 +02:00
Vitali Semenyuk
c9cc1b30ec Make dig method case-insensitive in Faraday::Utils::Headers 2024-04-02 14:37:59 +02:00
Olle Jonsson
c0540b7ba3 Lint fix
Lint/RedundantCopDisableDirective: Unnecessary disabling of Performance/RedundantBlockCall
2024-04-02 12:54:46 +02:00
Matt
87e655f306
Use Rubygems Trusted Publishers to publish. (#1552) 2024-01-20 17:09:48 +00:00
dependabot[bot]
cd2cdfd446
Update rack requirement from ~> 2.2 to ~> 3.0 (#1549) 2024-01-20 16:53:12 +00:00
Matt
f56e9387c8
Remove unnecessary rubocop disable comments. (#1551)
These were added in #1550 because of an unsafe autocorrect, but have since been addressed in https://github.com/rubocop/rubocop/pull/12628
2024-01-20 16:33:29 +00:00
Gareth Jones
18154c8332
docs: update body param type for run_request (#1545) 2024-01-20 08:49:37 +00:00
Olle Jonsson
4b34b509fe Add RuboCop disables
The -a autoformatting failed. Putting these in, to pass.
2024-01-15 16:44:18 +01:00
geemus
d820a58314 add bundler config to dependabot 2024-01-15 16:07:29 +01:00
Matt
cc5d607766
Version bump to 2.9.0 2024-01-09 10:35:49 +00:00
Matt
ceb01e42e8
Bump faraday-net_http version to allow 3.1 (#1546)
v3.1 increases the minimum ruby version to 3.0
2024-01-09 10:33:43 +00:00
Mattia Giuffrida
074506e67c Use latest Ruby version to publish and run rubocop 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
898f203584 Run rubocop in CI using Ruby 3.3 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
f0f549d7ef Fix Rubocop offenses 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
caa4ff42f8 Update GitHub workflows, add 3.3 to CI matrix 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
13732f7ff2 Remove ruby2_keywords dependency 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
8cbfd758c2 Make 3.0 the minimum supported Ruby version 2023-12-28 18:09:17 +01:00
Earlopain
9487833b42
Remove runtime dependency on base64 (#1541) 2023-12-27 10:07:35 +00:00
50 changed files with 1022 additions and 192 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

@ -1,6 +1,14 @@
version: 2
updates:
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: /
schedule:
interval: "weekly"

View File

@ -24,10 +24,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Ruby 2.7
- name: Setup Ruby 3.x
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
ruby-version: 3
bundler-cache: true
- name: Rubocop
@ -43,7 +43,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ '2.6', '2.7', '3.0', '3.1', '3.2' ]
ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4' ]
experimental: [false]
include:
- ruby: head
@ -63,7 +63,6 @@ jobs:
run: bundle exec rake
- name: Test External Adapters
if: ${{ matrix.ruby != '2.6' }}
continue-on-error: ${{ matrix.experimental }}
run: bundle exec bake test:external

View File

@ -4,22 +4,23 @@ on:
release:
types: [published]
permissions:
contents: read # to checkout the code (actions/checkout)
jobs:
build:
name: Publish to Rubygems
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Ruby 2.7
- name: Setup Ruby 3.x
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
ruby-version: 3
- name: Publish to RubyGems
uses: dawidd6/action-publish-gem@v1
with:
api_key: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
uses: rubygems/release-gem@v1

View File

@ -7,7 +7,7 @@ require:
AllCops:
DisplayCopNames: true
DisplayStyleGuide: true
TargetRubyVersion: 2.6
TargetRubyVersion: 3.0
# Custom config
Gemspec/RequireMFA: # we don't know if this works with auto-deployments yet

View File

@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2022-08-08 14:26:32 UTC using RuboCop version 1.33.0.
# on 2023-12-27 11:12:52 UTC using RuboCop version 1.59.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@ -23,23 +23,23 @@ Lint/EmptyBlock:
- 'spec/faraday/rack_builder_spec.rb'
- 'spec/faraday/response_spec.rb'
# Offense count: 12
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes.
# Offense count: 13
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 42
# Offense count: 4
# Offense count: 3
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 230
# Offense count: 9
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods.
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
Max: 13
# Offense count: 26
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods.
# Offense count: 27
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 33
@ -48,11 +48,22 @@ Metrics/MethodLength:
Metrics/ParameterLists:
Max: 6
# Offense count: 6
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods.
# Offense count: 7
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
Max: 14
# Offense count: 19
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowOnlyRestArgument, UseAnonymousForwarding, RedundantRestArgumentNames, RedundantKeywordRestArgumentNames, RedundantBlockArgumentNames.
# RedundantRestArgumentNames: args, arguments
# RedundantKeywordRestArgumentNames: kwargs, options, opts
# RedundantBlockArgumentNames: blk, block, proc
Style/ArgumentsForwarding:
Exclude:
- 'lib/faraday.rb'
- 'lib/faraday/rack_builder.rb'
# Offense count: 3
Style/DocumentDynamicEvalDefinition:
Exclude:

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

@ -10,7 +10,7 @@ group :development, :test do
gem 'bake-test-external'
gem 'coveralls_reborn', require: false
gem 'pry'
gem 'rack', '~> 2.2'
gem 'rack', '~> 3.0'
gem 'rake'
gem 'rspec', '~> 3.7'
gem 'rspec_junit_formatter', '~> 0.4'

View File

@ -35,7 +35,7 @@ Need more details? See the [Faraday API Documentation][apidoc] to see how it wor
This library aims to support and is [tested against][actions] the currently officially supported Ruby
implementations. This means that, even without a major release, we could add or drop support for Ruby versions,
following their [EOL](https://endoflife.date/ruby).
Currently that means we support Ruby 2.6+
Currently that means we support Ruby 3.0+
If something doesn't work on one of these Ruby versions, it's a bug.

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true
require 'rspec/core/rake_task'
require 'bundler'
Bundler::GemHelper.install_tasks
RSpec::Core::RakeTask.new(:spec) do |task|
task.ruby_opts = %w[-W]

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

@ -39,6 +39,7 @@ by the client. They raise error classes inheriting from `Faraday::ClientError`.
| [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` |
| [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) | `Faraday::TooManyRequestsError` |
| 4xx (any other) | `Faraday::ClientError` |
## 5xx Errors
@ -63,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
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] #=> "/wrong-url"
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

@ -114,6 +114,49 @@ conn = Faraday.new do |f|
end
```
### DEFAULT_OPTIONS
`DEFAULT_OPTIONS` improve the flexibility and customizability of new and existing middleware. Class-level `DEFAULT_OPTIONS` and the ability to set these defaults at the application level compliment existing functionality in which options can be passed into middleware on a per-instance basis.
#### Using DEFAULT_OPTIONS
Using `RaiseError` as an example, you can see that `DEFAULT_OPTIONS` have been defined at the top of the class:
```ruby
DEFAULT_OPTIONS = { include_request: true }.freeze
```
These options will be set at the class level upon instantiation and referenced as needed within the class. From our same example:
```ruby
def response_values(env)
...
return response unless options[:include_request]
...
```
If the default value provides the desired functionality, no further consideration is needed.
#### Setting Alternative Options per Application
In the case where it is desirable to change the default option for all instances within an application, it can be done by configuring the options in a `/config/initializers` file. For example:
```ruby
# config/initializers/faraday_config.rb
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 overridden on a per-instance bases (if handled in the middleware), like this:
```ruby
Faraday.new do |f|
...
f.response :raise_error, include_request: true
...
end
```
### Available Middleware
The following pages provide detailed configuration for the middleware that ships with Faraday:

View File

@ -13,17 +13,17 @@ Gem::Specification.new do |spec|
spec.homepage = 'https://lostisland.github.io/faraday'
spec.licenses = ['MIT']
spec.required_ruby_version = '>= 2.6'
spec.required_ruby_version = '>= 3.0'
spec.add_dependency 'base64'
# faraday-net_http is the "default adapter", but being a Faraday dependency it can't
# control which version of faraday it will be pulled from.
# To avoid releasing a major version every time there's a new Faraday API, we should
# 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.1'
spec.add_dependency 'ruby2_keywords', '>= 0.0.4'
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
spec.files = Dir['CHANGELOG.md', '{examples,lib,spec}/**/*', 'LICENSE.md', 'Rakefile', 'README.md']
@ -33,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

@ -26,7 +26,7 @@ module Faraday
self.supports_parallel = false
def initialize(_app = nil, opts = {}, &block)
@app = ->(env) { env.response }
@app = lambda(&:response)
@connection_options = opts
@config_block = block
end

View File

@ -15,7 +15,7 @@ module Faraday
class Connection
# A Set of allowed HTTP verbs.
METHODS = Set.new %i[get post put delete head patch options trace]
USER_AGENT = "Faraday v#{VERSION}"
USER_AGENT = "Faraday v#{VERSION}".freeze
# @return [Hash] URI query unencoded key/value pairs.
attr_reader :params
@ -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
@ -423,8 +431,8 @@ module Faraday
#
# @param method [Symbol] HTTP method.
# @param url [String, URI, nil] String or URI to access.
# @param body [String, nil] The request body that will eventually be converted to
# a string.
# @param body [String, Hash, Array, nil] The request body that will eventually be converted to
# a string; middlewares can be used to support more complex types.
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
#
# @return [Faraday::Response]
@ -473,7 +481,8 @@ module Faraday
if url && !base.path.end_with?('/')
base.path = "#{base.path}/" # ensure trailing slash
end
url = url.to_s.gsub(':', '%3A') if URI.parse(url.to_s).opaque
# Ensure relative url will be parsed correctly (such as `service:search` )
url = "./#{url}" if url.respond_to?(:start_with?) && !url.start_with?('http://', 'https://', '/', './', '../')
uri = url ? base + url : base
if params
uri.query = params.to_query(params_encoder || options.params_encoder)

View File

@ -102,7 +102,7 @@ module Faraday
protected
SUBKEYS_REGEX = /[^\[\]]+(?:\]?\[\])?/.freeze
SUBKEYS_REGEX = /[^\[\]]+(?:\]?\[\])?/
def decode_pair(key, value, context)
subkeys = key.scan(SUBKEYS_REGEX)

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
@ -158,4 +192,8 @@ module Faraday
# Raised by middlewares that parse the response, like the JSON response middleware.
class ParsingError < Error
end
# Raised by Faraday::Middleware and subclasses when invalid default_options are used
class InitializationError < Error
end
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

@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'monitor'
module Faraday
# Middleware is the basic base class of any Faraday middleware.
class Middleware
@ -7,9 +9,46 @@ module Faraday
attr_reader :app, :options
DEFAULT_OPTIONS = {}.freeze
LOCK = Mutex.new
def initialize(app = nil, options = {})
@app = app
@options = options
@options = self.class.default_options.merge(options)
end
class << self
# Faraday::Middleware::default_options= allows user to set default options at the Faraday::Middleware
# class level.
#
# @example Set the Faraday::Response::RaiseError option, `include_request` to `false`
# my_app/config/initializers/my_faraday_middleware.rb
#
# Faraday::Response::RaiseError.default_options = { include_request: false }
#
def default_options=(options = {})
validate_default_options(options)
LOCK.synchronize do
@default_options = default_options.merge(options)
end
end
# default_options attr_reader that initializes class instance variable
# with the values of any Faraday::Middleware defaults, and merges with
# subclass defaults
def default_options
@default_options ||= DEFAULT_OPTIONS.merge(self::DEFAULT_OPTIONS)
end
private
def validate_default_options(options)
invalid_keys = options.keys.reject { |opt| self::DEFAULT_OPTIONS.key?(opt) }
return unless invalid_keys.any?
raise(Faraday::InitializationError,
"Invalid options provided. Keys not found in #{self}::DEFAULT_OPTIONS: #{invalid_keys.join(', ')}")
end
end
def call(env)

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).freeze)
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.
@ -169,7 +169,7 @@ module Faraday
def stream_response(&block)
size = 0
yielded = false
block_result = block.call do |chunk| # rubocop:disable Performance/RedundantBlockCall
block_result = block.call do |chunk|
if chunk.bytesize.positive? || size.positive?
yielded = true
size += chunk.bytesize

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

@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'ruby2_keywords'
require 'faraday/adapter_registry'
module Faraday
@ -28,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
@ -54,7 +54,7 @@ module Faraday
end
def build(app = nil)
klass.new(app, *@args, &@block)
klass.new(app, *@args, **@kwargs, &@block)
end
end
@ -89,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)
@ -221,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
@ -234,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

@ -13,7 +13,7 @@ module Faraday
# Doesn't try to encode bodies that already are in string form.
class Json < Middleware
MIME_TYPE = 'application/json'
MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}.freeze
MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}
def on_request(env)
match_content_type(env) do |data|

View File

@ -60,7 +60,8 @@ module Faraday
@decoder_options =
if @decoder_options.is_a?(Array) && @decoder_options.size >= 2
@decoder_options.slice(0, 2)
elsif @decoder_options.respond_to?(:load)
elsif @decoder_options&.respond_to?(:load) # rubocop:disable Lint/RedundantSafeNavigation
# In some versions of Rails, `nil` responds to `load` - hence the safe navigation check above
[@decoder_options, :load]
else
[::JSON, :parse]

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

@ -6,32 +6,32 @@ module Faraday
# client or server error responses.
class RaiseError < Middleware
# rubocop:disable Naming/ConstantName
ClientErrorStatuses = (400...500).freeze
ServerErrorStatuses = (500...600).freeze
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, 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
@ -58,7 +58,7 @@ module Faraday
# Include the request data by default. If the middleware was explicitly
# configured to _not_ include request data, then omit it.
return response unless options.fetch(:include_request, true)
return response unless options[:include_request]
response.merge(
request: {

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'base64'
require 'uri'
require 'faraday/utils/headers'
require 'faraday/utils/params_hash'
@ -26,7 +25,7 @@ module Faraday
attr_writer :default_space_encoding
end
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/.freeze
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
def escape(str)
str.to_s.gsub(ESCAPE_RE) do |match|
@ -38,7 +37,7 @@ module Faraday
CGI.unescape str.to_s
end
DEFAULT_SEP = /[&;] */n.freeze
DEFAULT_SEP = /[&;] */n
# Adapted from Rack
def parse_query(query)
@ -54,7 +53,7 @@ module Faraday
end
def basic_header_from(login, pass)
value = Base64.encode64("#{login}:#{pass}")
value = ["#{login}:#{pass}"].pack('m') # Base64 encoding
value.delete!("\n")
"Basic #{value}"
end

View File

@ -62,10 +62,10 @@ module Faraday
super(key, val)
end
def fetch(key, *args, &block)
def fetch(key, ...)
key = KeyMap[key]
key = @names.fetch(key.downcase, key)
super(key, *args, &block)
super(key, ...)
end
def delete(key)
@ -77,6 +77,12 @@ module Faraday
super(key)
end
def dig(key, *rest)
key = KeyMap[key]
key = @names.fetch(key.downcase, key)
super(key, *rest)
end
def include?(key)
@names.include? key.downcase
end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
module Faraday
VERSION = '2.8.1'
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

@ -300,14 +300,14 @@ RSpec.describe Faraday::Connection do
it 'joins url to base when used relative path' do
conn = Faraday.new(url: url)
uri = conn.build_exclusive_url('service:search?limit=400')
expect(uri.to_s).to eq('http://service.com/service%3Asearch?limit=400')
expect(uri.to_s).to eq('http://service.com/service:search?limit=400')
end
it 'joins url to base when used with path prefix' do
conn = Faraday.new(url: url)
conn.path_prefix = '/api'
uri = conn.build_exclusive_url('service:search?limit=400')
expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
expect(uri.to_s).to eq('http://service.com/api/service:search?limit=400')
end
end

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

@ -67,4 +67,147 @@ RSpec.describe Faraday::Middleware do
end
end
end
describe '::default_options' do
let(:subclass_no_options) { FaradayMiddlewareSubclasses::SubclassNoOptions }
let(:subclass_one_option) { FaradayMiddlewareSubclasses::SubclassOneOption }
let(:subclass_two_options) { FaradayMiddlewareSubclasses::SubclassTwoOptions }
def build_conn(resp_middleware)
Faraday.new do |c|
c.adapter :test do |stub|
stub.get('/success') { [200, {}, 'ok'] }
end
c.response resp_middleware
end
end
RSpec.shared_context 'reset @default_options' do
before(:each) do
FaradayMiddlewareSubclasses::SubclassNoOptions.instance_variable_set(:@default_options, nil)
FaradayMiddlewareSubclasses::SubclassOneOption.instance_variable_set(:@default_options, nil)
FaradayMiddlewareSubclasses::SubclassTwoOptions.instance_variable_set(:@default_options, nil)
Faraday::Middleware.instance_variable_set(:@default_options, nil)
end
end
after(:all) do
FaradayMiddlewareSubclasses::SubclassNoOptions.instance_variable_set(:@default_options, nil)
FaradayMiddlewareSubclasses::SubclassOneOption.instance_variable_set(:@default_options, nil)
FaradayMiddlewareSubclasses::SubclassTwoOptions.instance_variable_set(:@default_options, nil)
Faraday::Middleware.instance_variable_set(:@default_options, nil)
end
context 'with subclass DEFAULT_OPTIONS defined' do
include_context 'reset @default_options'
context 'and without application options configured' do
let(:resp1) { build_conn(:one_option).get('/success') }
it 'has only subclass defaults' do
expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
expect(subclass_one_option.default_options).to eq(subclass_one_option::DEFAULT_OPTIONS)
expect(subclass_two_options.default_options).to eq(subclass_two_options::DEFAULT_OPTIONS)
end
it { expect(resp1.body).to eq('ok') }
end
context "and with one application's options changed" do
let(:resp2) { build_conn(:two_options).get('/success') }
before(:each) do
FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
end
it 'only updates default options of target subclass' do
expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
expect(subclass_one_option.default_options).to eq(subclass_one_option::DEFAULT_OPTIONS)
expect(subclass_two_options.default_options).to eq({ some_option: false, some_other_option: false })
end
it { expect(resp2.body).to eq('ok') }
end
context "and with two applications' options changed" do
let(:resp1) { build_conn(:one_option).get('/success') }
let(:resp2) { build_conn(:two_options).get('/success') }
before(:each) do
FaradayMiddlewareSubclasses::SubclassOneOption.default_options = { some_other_option: true }
FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
end
it 'updates subclasses and parent independent of each other' do
expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
expect(subclass_one_option.default_options).to eq({ some_other_option: true })
expect(subclass_two_options.default_options).to eq({ some_option: false, some_other_option: false })
end
it { expect(resp1.body).to eq('ok') }
it { expect(resp2.body).to eq('ok') }
end
end
context 'with FARADAY::MIDDLEWARE DEFAULT_OPTIONS and with Subclass DEFAULT_OPTIONS' do
before(:each) do
stub_const('Faraday::Middleware::DEFAULT_OPTIONS', { its_magic: false })
end
# Must stub Faraday::Middleware::DEFAULT_OPTIONS before resetting default options
include_context 'reset @default_options'
context 'and without application options configured' do
let(:resp1) { build_conn(:one_option).get('/success') }
it 'has only subclass defaults' do
expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
expect(FaradayMiddlewareSubclasses::SubclassNoOptions.default_options).to eq({ its_magic: false })
expect(FaradayMiddlewareSubclasses::SubclassOneOption.default_options).to eq({ its_magic: false, some_other_option: false })
expect(FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options).to eq({ its_magic: false, some_option: true, some_other_option: false })
end
it { expect(resp1.body).to eq('ok') }
end
context "and with two applications' options changed" do
let(:resp1) { build_conn(:one_option).get('/success') }
let(:resp2) { build_conn(:two_options).get('/success') }
before(:each) do
FaradayMiddlewareSubclasses::SubclassOneOption.default_options = { some_other_option: true }
FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
end
it 'updates subclasses and parent independent of each other' do
expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
expect(FaradayMiddlewareSubclasses::SubclassNoOptions.default_options).to eq({ its_magic: false })
expect(FaradayMiddlewareSubclasses::SubclassOneOption.default_options).to eq({ its_magic: false, some_other_option: true })
expect(FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options).to eq({ its_magic: false, some_option: false, some_other_option: false })
end
it { expect(resp1.body).to eq('ok') }
it { expect(resp2.body).to eq('ok') }
end
end
describe 'default_options input validation' do
include_context 'reset @default_options'
it 'raises error if Faraday::Middleware option does not exist' do
expect { Faraday::Middleware.default_options = { something_special: true } }.to raise_error(Faraday::InitializationError) do |e|
expect(e.message).to eq('Invalid options provided. Keys not found in Faraday::Middleware::DEFAULT_OPTIONS: something_special')
end
end
it 'raises error if subclass option does not exist' do
expect { subclass_one_option.default_options = { this_is_a_typo: true } }.to raise_error(Faraday::InitializationError) do |e|
expect(e.message).to eq('Invalid options provided. Keys not found in FaradayMiddlewareSubclasses::SubclassOneOption::DEFAULT_OPTIONS: this_is_a_typo')
end
end
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

@ -62,7 +62,8 @@ RSpec.describe Faraday::NestedParamsEncoder do
it 'encodes rack compat' do
params = { a: [{ one: '1', two: '2' }, '3', ''] }
result = Faraday::Utils.unescape(Faraday::NestedParamsEncoder.encode(params)).split('&')
expected = Rack::Utils.build_nested_query(params).split('&')
escaped = Rack::Utils.build_nested_query(params)
expected = Rack::Utils.unescape(escaped).split('&')
expect(result).to match_array(expected)
end

View File

@ -184,6 +184,23 @@ RSpec.describe Faraday::Response::Json, type: :response do
response = process(body)
expect(response.body).to eq(result)
end
it 'passes relevant options to JSON parse even when nil responds to :load' do
original_allow_message_expectations_on_nil = RSpec::Mocks.configuration.allow_message_expectations_on_nil
RSpec::Mocks.configuration.allow_message_expectations_on_nil = true
allow(nil).to receive(:respond_to?)
.with(:load)
.and_return(true)
expect(JSON).to receive(:parse)
.with(body, { symbolize_names: true })
.and_return(result)
response = process(body)
expect(response.body).to eq(result)
ensure
RSpec::Mocks.configuration.allow_message_expectations_on_nil = original_allow_message_expectations_on_nil
end
end
end
end

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)
@ -194,9 +194,36 @@ RSpec.describe Faraday::Response::RaiseError do
end
end
context 'when the include_request option is set to false' do
let(:middleware_options) { { include_request: false } }
describe 'DEFAULT_OPTION: include_request' do
before(:each) do
Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
Faraday::Middleware.instance_variable_set(:@default_options, nil)
end
after(:all) do
Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
Faraday::Middleware.instance_variable_set(:@default_options, nil)
end
context 'when RaiseError DEFAULT_OPTION (include_request: true) is used' do
it 'includes request info in the exception' do
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
expect(ex.response.keys).to contain_exactly(
:status,
:headers,
:body,
:request
)
end
end
end
context 'when application sets default_options `include_request: false`' do
before(:each) do
Faraday::Response::RaiseError.default_options = { include_request: false }
end
context 'and when include_request option is omitted' do
it 'does not include request info in the exception' do
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
expect(ex.response.keys).to contain_exactly(
@ -207,5 +234,42 @@ RSpec.describe Faraday::Response::RaiseError do
end
end
end
context 'and when include_request option is explicitly set for instance' do
let(:middleware_options) { { include_request: true } }
it 'includes request info in the exception' do
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
expect(ex.response.keys).to contain_exactly(
:status,
:headers,
:body,
:request
)
end
end
end
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

@ -56,6 +56,15 @@ RSpec.describe Faraday::Utils::Headers do
it { expect(subject.delete('content-type')).to be_nil }
end
describe '#dig' do
before { subject['Content-Type'] = 'application/json' }
it { expect(subject&.dig('Content-Type')).to eq('application/json') }
it { expect(subject&.dig('CONTENT-TYPE')).to eq('application/json') }
it { expect(subject&.dig(:content_type)).to eq('application/json') }
it { expect(subject&.dig('invalid')).to be_nil }
end
describe '#parse' do
context 'when response headers leave http status line out' do
let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" }

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

View File

@ -19,7 +19,9 @@ RSpec.describe Faraday do
it 'uses method_missing on Faraday if there is no proxyable method' do
expected_message =
if RUBY_VERSION >= '3.3'
if RUBY_VERSION >= '3.4'
"undefined method 'this_method_does_not_exist' for module Faraday"
elsif RUBY_VERSION >= '3.3'
"undefined method `this_method_does_not_exist' for module Faraday"
else
"undefined method `this_method_does_not_exist' for Faraday:Module"

View File

@ -29,14 +29,15 @@ SimpleCov.start do
minimum_coverage_by_file 26
end
# Ensure all /lib files are loaded
# so they will be included in the test coverage report.
Dir['./lib/**/*.rb'].sort.each { |file| require file }
require 'faraday'
require 'pry'
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
# Ensure all /lib files are loaded
# so they will be included in the test coverage report.
Dir['./lib/**/*.rb'].each { |file| require file }
# Load all Rspec support files
Dir['./spec/support/**/*.rb'].each { |file| require file }
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module FaradayMiddlewareSubclasses
class SubclassNoOptions < Faraday::Middleware
end
class SubclassOneOption < Faraday::Middleware
DEFAULT_OPTIONS = { some_other_option: false }.freeze
end
class SubclassTwoOptions < Faraday::Middleware
DEFAULT_OPTIONS = { some_option: true, some_other_option: false }.freeze
end
end
Faraday::Response.register_middleware(no_options: FaradayMiddlewareSubclasses::SubclassNoOptions)
Faraday::Response.register_middleware(one_option: FaradayMiddlewareSubclasses::SubclassOneOption)
Faraday::Response.register_middleware(two_options: FaradayMiddlewareSubclasses::SubclassTwoOptions)