Autoloading, dependency loading and middleware registry cleanup (#1301)

This commit is contained in:
Matt 2021-08-09 08:25:02 +01:00 committed by GitHub
parent ca37a9420e
commit 08b7d4d3b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 78 additions and 410 deletions

View File

@ -31,9 +31,25 @@ We did our best to make this transition as painless as possible for you, so here
* We've setup an [Awesome Faraday](https://github.com/lostisland/awesome-faraday) repository, where you can find and discover adapters.
We also highlighted their unique features and level of compliance with Faraday's features.
### Autoloading and dependencies
Faraday has until now provided and relied on a complex dynamic dependencies system.
This would allow to reference classes and require dependencies only when needed (e.g. when running the first request) based
on the middleware/adapters used.
As part of Faraday v2.0, we've removed all external dependencies, which means we don't really need this anymore.
This change should not affect you directly, but if you're registering middleware then be aware of the new syntax:
```ruby
# `name` here can be anything you want.
# `klass` is your custom middleware class.
# This method can also be called on `Faraday::Adapter`, `Faraday::Request` and `Faraday::Response`
Faraday::Middleware.register_middleware(name: klass)
```
### Others
* Rename `Faraday::Request#method` to `#http_method`.
* Remove `Faraday::Response::Middleware`. You can now use the new `on_complete` callback provided by `Faraday::Middleware`.
## Faraday 1.0

View File

@ -2,7 +2,7 @@
require_relative 'lib/faraday/version'
Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
Gem::Specification.new do |spec|
spec.name = 'faraday'
spec.version = Faraday::VERSION
@ -15,14 +15,7 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
spec.required_ruby_version = '>= 2.4'
spec.add_dependency 'faraday-em_http', '~> 1.0'
spec.add_dependency 'faraday-em_synchrony', '~> 1.0'
spec.add_dependency 'faraday-excon', '~> 1.1'
spec.add_dependency 'faraday-httpclient', '~> 1.0.1'
spec.add_dependency 'faraday-net_http', '~> 1.0'
spec.add_dependency 'faraday-net_http_persistent', '~> 1.1'
spec.add_dependency 'faraday-patron', '~> 1.0'
spec.add_dependency 'faraday-rack', '~> 1.0'
spec.add_dependency 'multipart-post', '>= 1.2', '< 3'
spec.add_dependency 'ruby2_keywords', '>= 0.0.4'

View File

@ -4,16 +4,10 @@ require 'cgi'
require 'date'
require 'set'
require 'forwardable'
require 'faraday/middleware_registry'
require 'faraday/dependency_loader'
unless defined?(::Faraday::Timer)
require 'timeout'
::Faraday::Timer = Timeout
end
require 'faraday/version'
require 'faraday/methods'
require 'faraday/error'
require 'faraday/middleware_registry'
require 'faraday/utils'
require 'faraday/options'
require 'faraday/connection'
@ -23,7 +17,6 @@ require 'faraday/middleware'
require 'faraday/adapter'
require 'faraday/request'
require 'faraday/response'
require 'faraday/error'
require 'faraday/file_part'
require 'faraday/param_part'
@ -101,19 +94,6 @@ module Faraday
Faraday::Connection.new(url, options, &block)
end
# @private
# Internal: Requires internal Faraday libraries.
#
# @param libs [Array] one or more relative String names to Faraday classes.
# @return [void]
def require_libs(*libs)
libs.each do |lib|
require "#{lib_path}/#{lib}"
end
end
alias require_lib require_libs
# Documented elsewhere, see default_adapter reader
def default_adapter=(adapter)
@default_connection = nil
@ -169,6 +149,4 @@ module Faraday
self.root_path = File.expand_path __dir__
self.lib_path = File.expand_path 'faraday', __dir__
self.default_adapter = :net_http
require_lib 'autoload' unless ENV['FARADAY_NO_AUTOLOAD']
end

View File

@ -5,14 +5,9 @@ module Faraday
# responsible for fulfilling a Faraday request.
class Adapter
extend MiddlewareRegistry
extend DependencyLoader
CONTENT_LENGTH = 'Content-Length'
register_middleware File.expand_path('adapter', __dir__),
test: [:Test, 'test'],
typhoeus: [:Typhoeus, 'typhoeus']
# This module marks an Adapter as supporting parallel requests.
module Parallelism
attr_writer :supports_parallel

View File

@ -273,3 +273,5 @@ module Faraday
end
end
end
Faraday::Adapter.register_middleware(test: Faraday::Adapter::Test)

View File

@ -1,15 +0,0 @@
# frozen_string_literal: true
module Faraday
class Adapter
# Typhoeus adapter. This class is just a stub, the real adapter is in
# https://github.com/typhoeus/typhoeus/blob/master/lib/typhoeus/adapters/faraday.rb
class Typhoeus < Faraday::Adapter
# Needs to define this method in order to support Typhoeus <= 1.3.0
def call; end
dependency 'typhoeus'
dependency 'typhoeus/adapters/faraday'
end
end
end

View File

@ -1,87 +0,0 @@
# frozen_string_literal: true
module Faraday
# Adds the ability for other modules to manage autoloadable
# constants.
#
# @api private
module AutoloadHelper
# Registers the constants to be auto loaded.
#
# @param prefix [String] The require prefix. If the path is inside Faraday,
# then it will be prefixed with the root path of this loaded
# Faraday version.
# @param options [{ Symbol => String }] library names.
#
# @example
#
# Faraday.autoload_all 'faraday/foo',
# Bar: 'bar'
#
# # requires faraday/foo/bar to load Faraday::Bar.
# Faraday::Bar
#
# @return [void]
def autoload_all(prefix, options)
if prefix.match? %r{^faraday(/|$)}i
prefix = File.join(Faraday.root_path, prefix)
end
options.each do |const_name, path|
autoload const_name, File.join(prefix, path)
end
end
# Loads each autoloaded constant. If thread safety is a concern,
# wrap this in a Mutex.
#
# @return [void]
def load_autoloaded_constants
constants.each do |const|
const_get(const) if autoload?(const)
end
end
# Filters the module's contents with those that have been already
# autoloaded.
#
# @return [Array<Class, Module>]
def all_loaded_constants
constants
.map { |c| const_get(c) }
.select { |a| a.respond_to?(:loaded?) && a.loaded? }
end
end
# Adapter is the base class for all Faraday adapters.
# @see lib/faraday/adapter.rb Original class location
class Adapter
extend AutoloadHelper
autoload_all 'faraday/adapter',
Typhoeus: 'typhoeus',
Test: 'test'
end
# Request represents a single HTTP request for a Faraday adapter to make.
# @see lib/faraday/request.rb Original class location
class Request
extend AutoloadHelper
autoload_all 'faraday/request',
UrlEncoded: 'url_encoded',
Multipart: 'multipart',
Retry: 'retry',
Authorization: 'authorization',
BasicAuthentication: 'basic_authentication',
TokenAuthentication: 'token_authentication',
Instrumentation: 'instrumentation'
end
# Response represents the returned value of a sent Faraday request.
# @see lib/faraday/response.rb Original class location
class Response
extend AutoloadHelper
autoload_all 'faraday/response',
RaiseError: 'raise_error',
Logger: 'logger'
end
end

View File

@ -1,37 +0,0 @@
# frozen_string_literal: true
module Faraday
# DependencyLoader helps Faraday adapters and middleware load dependencies.
module DependencyLoader
attr_reader :load_error
# Executes a block which should try to require and reference dependent
# libraries
def dependency(lib = nil)
lib ? require(lib) : yield
rescue LoadError, NameError => e
self.load_error = e
end
def new(*)
unless loaded?
raise "missing dependency for #{self}: #{load_error.message}"
end
super
end
def loaded?
load_error.nil?
end
def inherited(subclass)
super
subclass.send(:load_error=, load_error)
end
private
attr_writer :load_error
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'pp'
module Faraday
module Logging
# Serves as an integration point to customize logging

View File

@ -4,7 +4,6 @@ module Faraday
# Middleware is the basic base class of any Faraday middleware.
class Middleware
extend MiddlewareRegistry
extend DependencyLoader
attr_reader :app, :options

View File

@ -6,59 +6,26 @@ module Faraday
# Adds the ability for other modules to register and lookup
# middleware classes.
module MiddlewareRegistry
def registered_middleware
@registered_middleware ||= {}
end
# Register middleware class(es) on the current module.
#
# @param autoload_path [String] Middleware autoload path
# @param mapping [Hash{
# Symbol => Module,
# Symbol => Array<Module, Symbol, String>,
# }] Middleware mapping from a lookup symbol to a reference to the
# middleware.
# Classes can be expressed as:
# - a fully qualified constant
# - a Symbol
# - a Proc that will be lazily called to return the former
# - an array is given, its first element is the constant or symbol,
# and its second is a file to `require`.
# @param mappings [Hash] Middleware mappings from a lookup symbol to a middleware class.
# @return [void]
#
# @example Lookup by a constant
#
# module Faraday
# class Whatever
# class Whatever < Middleware
# # Middleware looked up by :foo returns Faraday::Whatever::Foo.
# register_middleware foo: Foo
# register_middleware(foo: Whatever)
# end
# end
#
# @example Lookup by a symbol
#
# module Faraday
# class Whatever
# # Middleware looked up by :bar returns
# # Faraday::Whatever.const_get(:Bar)
# register_middleware bar: :Bar
# end
# end
#
# @example Lookup by a symbol and string in an array
#
# module Faraday
# class Whatever
# # Middleware looked up by :baz requires 'baz' and returns
# # Faraday::Whatever.const_get(:Baz)
# register_middleware baz: [:Baz, 'baz']
# end
# end
#
def register_middleware(autoload_path = nil, mapping = nil)
if mapping.nil?
mapping = autoload_path
autoload_path = nil
end
def register_middleware(**mappings)
middleware_mutex do
@middleware_autoload_path = autoload_path if autoload_path
(@registered_middleware ||= {}).update(mapping)
registered_middleware.update(mappings)
end
end
@ -66,7 +33,7 @@ module Faraday
#
# @param key [Symbol] key for the registered middleware.
def unregister_middleware(key)
@registered_middleware.delete(key)
registered_middleware.delete(key)
end
# Lookup middleware class with a registered Symbol shortcut.
@ -78,16 +45,15 @@ module Faraday
# @example
#
# module Faraday
# class Whatever
# register_middleware foo: Foo
# class Whatever < Middleware
# register_middleware(foo: Whatever)
# end
# end
#
# Faraday::Whatever.lookup_middleware(:foo)
# # => Faraday::Whatever::Foo
#
# Faraday::Middleware.lookup_middleware(:foo)
# # => Faraday::Whatever
def lookup_middleware(key)
load_middleware(key) ||
registered_middleware[key] ||
raise(Faraday::Error, "#{key.inspect} is not registered on #{self}")
end
@ -95,35 +61,5 @@ module Faraday
@middleware_mutex ||= Monitor.new
@middleware_mutex.synchronize(&block)
end
def fetch_middleware(key)
defined?(@registered_middleware) && @registered_middleware[key]
end
def load_middleware(key)
value = fetch_middleware(key)
case value
when Module
value
when Symbol, String
middleware_mutex do
@registered_middleware[key] = const_get(value)
end
when Proc
middleware_mutex do
@registered_middleware[key] = value.call
end
when Array
middleware_mutex do
const, path = value
if (root = @middleware_autoload_path)
path = "#{root}/#{path}"
end
require(path)
@registered_middleware[key] = const
end
load_middleware(key)
end
end
end
end

View File

@ -26,28 +26,11 @@ module Faraday
# @return [RequestOptions] options
#
# rubocop:disable Style/StructInheritance
class Request < Struct.new(
:http_method, :path, :params, :headers, :body, :options
)
class Request < Struct.new(:http_method, :path, :params, :headers, :body, :options)
# rubocop:enable Style/StructInheritance
extend MiddlewareRegistry
register_middleware File.expand_path('request', __dir__),
url_encoded: [:UrlEncoded, 'url_encoded'],
multipart: [:Multipart, 'multipart'],
retry: [:Retry, 'retry'],
authorization: [:Authorization, 'authorization'],
basic_auth: [
:BasicAuthentication,
'basic_authentication'
],
token_auth: [
:TokenAuthentication,
'token_authentication'
],
instrumentation: [:Instrumentation, 'instrumentation']
# @param request_method [String]
# @yield [request] for block customization, if block given
# @yieldparam request [Request]
@ -154,3 +137,11 @@ module Faraday
end
end
end
require 'faraday/request/authorization'
require 'faraday/request/basic_authentication'
require 'faraday/request/instrumentation'
require 'faraday/request/multipart'
require 'faraday/request/retry'
require 'faraday/request/token_authentication'
require 'faraday/request/url_encoded'

View File

@ -4,9 +4,7 @@ module Faraday
class Request
# Request middleware for the Authorization HTTP header
class Authorization < Faraday::Middleware
unless defined?(::Faraday::Request::Authorization::KEY)
KEY = 'Authorization'
end
KEY = 'Authorization'
# @param type [String, Symbol]
# @param token [String, Symbol, Hash]
@ -53,3 +51,5 @@ module Faraday
end
end
end
Faraday::Request.register_middleware(authorization: Faraday::Request::Authorization)

View File

@ -1,11 +1,12 @@
# frozen_string_literal: true
require 'base64'
require 'faraday/request/authorization'
module Faraday
class Request
# Authorization middleware for Basic Authentication.
class BasicAuthentication < load_middleware(:authorization)
class BasicAuthentication < Authorization
# @param login [String]
# @param pass [String]
#
@ -18,3 +19,5 @@ module Faraday
end
end
end
Faraday::Request.register_middleware(basic_auth: Faraday::Request::BasicAuthentication)

View File

@ -52,3 +52,5 @@ module Faraday
end
end
end
Faraday::Request.register_middleware(instrumentation: Faraday::Request::Instrumentation)

View File

@ -104,3 +104,5 @@ module Faraday
end
end
end
Faraday::Request.register_middleware(multipart: Faraday::Request::Multipart)

View File

@ -237,3 +237,5 @@ module Faraday
end
end
end
Faraday::Request.register_middleware(retry: Faraday::Request::Retry)

View File

@ -1,10 +1,12 @@
# frozen_string_literal: true
require 'faraday/request/authorization'
module Faraday
class Request
# TokenAuthentication is a middleware that adds a 'Token' header to a
# Faraday request.
class TokenAuthentication < load_middleware(:authorization)
class TokenAuthentication < Authorization
# Public
def self.header(token, options = nil)
options ||= {}
@ -18,3 +20,5 @@ module Faraday
end
end
end
Faraday::Request.register_middleware(token_auth: Faraday::Request::TokenAuthentication)

View File

@ -54,3 +54,5 @@ module Faraday
end
end
end
Faraday::Request.register_middleware(url_encoded: Faraday::Request::UrlEncoded)

View File

@ -5,25 +5,9 @@ require 'forwardable'
module Faraday
# Response represents an HTTP response from making an HTTP request.
class Response
# Used for simple response middleware.
class Middleware < Faraday::Middleware
# Override this to modify the environment after the response has finished.
# Calls the `parse` method if defined
# `parse` method can be defined as private, public and protected
def on_complete(env)
return unless respond_to?(:parse, true) && env.parse_body?
env.body = parse(env.body)
end
end
extend Forwardable
extend MiddlewareRegistry
register_middleware File.expand_path('response', __dir__),
raise_error: [:RaiseError, 'raise_error'],
logger: [:Logger, 'logger']
def initialize(env = nil)
@env = Env.from(env) if env
@on_complete_callbacks = []
@ -99,3 +83,6 @@ module Faraday
end
end
end
require 'faraday/response/logger'
require 'faraday/response/raise_error'

View File

@ -31,3 +31,5 @@ module Faraday
end
end
end
Faraday::Response.register_middleware(logger: Faraday::Response::Logger)

View File

@ -54,3 +54,5 @@ module Faraday
end
end
end
Faraday::Response.register_middleware(raise_error: Faraday::Response::RaiseError)

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
require 'uri'
require 'faraday/utils/headers'
require 'faraday/utils/params_hash'
@ -71,10 +72,7 @@ module Faraday
end
def default_uri_parser
@default_uri_parser ||= begin
require 'uri'
Kernel.method(:URI)
end
@default_uri_parser ||= Kernel.method(:URI)
end
def default_uri_parser=(parser)

View File

@ -17,10 +17,6 @@ RSpec.describe Faraday::RackBuilder do
class Banana < Handler
end
class Broken < Faraday::Middleware
dependency 'zomg/i_dont/exist'
end
subject { conn.builder }
context 'with default stack' do
@ -127,24 +123,6 @@ RSpec.describe Faraday::RackBuilder do
subject.use(:apple)
expect(subject.handlers).to eq([Apple])
end
it 'allows to register with symbol' do
Faraday::Middleware.register_middleware(apple: :Apple)
subject.use(:apple)
expect(subject.handlers).to eq([Apple])
end
it 'allows to register with string' do
Faraday::Middleware.register_middleware(apple: 'Apple')
subject.use(:apple)
expect(subject.handlers).to eq([Apple])
end
it 'allows to register with Proc' do
Faraday::Middleware.register_middleware(apple: -> { Apple })
subject.use(:apple)
expect(subject.handlers).to eq([Apple])
end
end
context 'when having two handlers' do
@ -176,24 +154,6 @@ RSpec.describe Faraday::RackBuilder do
end
end
context 'when having a handler with broken dependency' do
let(:conn) do
Faraday::Connection.new do |builder|
builder.adapter :test do |stub|
stub.get('/') { |_| [200, {}, ''] }
end
end
end
before { subject.use(Broken) }
it 'raises an error while making a request' do
expect { conn.get('/') }.to raise_error(RuntimeError) do |err|
expect(err.message).to match(%r{missing dependency for Broken: .+ -- zomg/i_dont/exist})
end
end
end
context 'when middleware is added with named arguments' do
let(:conn) { Faraday::Connection.new {} }
@ -220,7 +180,7 @@ RSpec.describe Faraday::RackBuilder do
end
end
context 'when a request adapter is added with named arguments' do
context 'when a middleware is added with named arguments' do
let(:conn) { Faraday::Connection.new {} }
let(:cat_request) do
@ -247,11 +207,11 @@ RSpec.describe Faraday::RackBuilder do
end
end
context 'when a response adapter is added with named arguments' do
context 'when a middleware is added with named arguments' do
let(:conn) { Faraday::Connection.new {} }
let(:fish_response) do
Class.new(Faraday::Response::Middleware) do
Class.new(Faraday::Middleware) do
attr_accessor :name
def initialize(app, name:)

View File

@ -1,68 +0,0 @@
# frozen_string_literal: true
RSpec.describe Faraday::Response::Middleware do
let(:conn) do
Faraday.new do |b|
b.use custom_middleware
b.adapter :test do |stub|
stub.get('ok') { [200, { 'Content-Type' => 'text/html' }, '<body></body>'] }
stub.get('not_modified') { [304, nil, nil] }
stub.get('no_content') { [204, nil, nil] }
end
end
end
context 'with a custom ResponseMiddleware' do
let(:custom_middleware) do
Class.new(Faraday::Response::Middleware) do
def parse(body)
body.upcase
end
end
end
it 'parses the response' do
expect(conn.get('ok').body).to eq('<BODY></BODY>')
end
end
context 'with a custom ResponseMiddleware and private parse' do
let(:custom_middleware) do
Class.new(Faraday::Response::Middleware) do
private
def parse(body)
body.upcase
end
end
end
it 'parses the response' do
expect(conn.get('ok').body).to eq('<BODY></BODY>')
end
end
context 'with a custom ResponseMiddleware but empty response' do
let(:custom_middleware) do
Class.new(Faraday::Response::Middleware) do
def parse(_body)
raise 'this should not be called'
end
end
end
it 'raises exception for 200 responses' do
expect { conn.get('ok') }.to raise_error(StandardError)
end
it 'doesn\'t call the middleware for 204 responses' do
expect_any_instance_of(custom_middleware).not_to receive(:parse)
expect(conn.get('no_content').body).to be_nil
end
it 'doesn\'t call the middleware for 304 responses' do
expect_any_instance_of(custom_middleware).not_to receive(:parse)
expect(conn.get('not_modified').body).to be_nil
end
end
end