Skip to content

Middleware mutex class #1075

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/faraday/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class Middleware
extend MiddlewareRegistry
extend DependencyLoader

register_middleware('')

def initialize(app = nil)
@app = app
end
Expand Down
242 changes: 179 additions & 63 deletions lib/faraday/middleware_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,133 @@
require 'monitor'

module Faraday
# ClassRegistry tracks potential middleware dependencies for a given parent
# class. These potential dependencies are lazily required upon first access
# during runtime.
class ClassRegistry
attr_reader :autoload_path

def initialize(klass, autoload_path, mapping = nil)
@klass = klass
@autoload_path = autoload_path.to_s.freeze
@monitor = Monitor.new
@mutex = @monitor.method(:synchronize)
@registered = {}
register(mapping) if mapping
end

# Register middleware class(es) on the current module.
#
# @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`.
# @return [void]
#
# @example
#
# # builds a registry for Faraday::Adapter, lazily loading from
# # 'path/to/adapters/*.rb'
# cr = ClassRegistry.new(Faraday::Adapter, 'path/to/adapters')
# cr.register(
# # Lookup constant
# some_adapter: SomeAdapter,
#
# # Lookup symbol constant name
# # Same as Faraday::Adapter.const_get(:SomeAdapter2)
# some_adapter_2: :SomeAdapter2,
#
# # Require lib and then lookup class
# # require('some-adapter-3')
# # Returns Faraday::Adapter::SomeAdapter3
# some_adapter_3: [:SomeAdapter3, 'some-adapter-3']
# )
#
def register(mapping)
@mutex.call { @registered.update(mapping) }
end

# Unregister a previously registered middleware class.
#
# @param key [Symbol] key for the registered middleware.
def unregister(key)
@mutex.call { @registered.delete(key) }
end

# Lookup middleware class with a registered Symbol shortcut.
#
# @param key [Symbol] key for the registered middleware.
# @return [Class] a middleware Class.
# @raise [Faraday::Error] if given key is not registered
#
# @example
#
# cr = ClassRegistry.new(Faraday::Adapter, .path/to/adapters.)
# cr.register(some_adapter: SomeAdapter)
#
# cr.lookup(:some_adapter)
# # => SomeAdapter
#
def lookup(key)
load_class(key) ||
raise(Faraday::Error,
"#{key.inspect} is not registered on #{@klass}")
end

private

# Expands the registered value for key until it comes back as a Module,
# Class, or nil.
def load_class(key)
@mutex.call do
loop do
klass, register = expand_entry(key)
return klass unless register

@registered.update(key => klass)
end
end
end

def expand_entry(key)
case value = @registered[key]
when Module, NilClass
value
when Symbol, String
[@klass.const_get(value), true]
when Proc
[value.call, true]
when Array
const, path = value
if (root = @autoload_path) && !root.empty?
path = "#{root}/#{path}"
end
require(path)
[const, true]
else
msg = "unexpected #{@klass} value for #{key.inspect}: #{value.inspect}"
raise ArgumentError, msg
end
end
end

# Adds the ability for other modules to register and lookup
# middleware classes.
module MiddlewareRegistry
def self.extended(klass)
class << klass
attr_accessor :class_registry
end
super
end

# Register middleware class(es) on the current module.
#
# @param autoload_path [String] Middleware autoload path
Expand All @@ -22,51 +146,40 @@ module MiddlewareRegistry
# and its second is a file to `require`.
# @return [void]
#
# @example Lookup by a constant
# @example
#
# module Faraday
# class Whatever
# # Middleware looked up by :foo returns Faraday::Whatever::Foo.
# register_middleware foo: Foo
# end
# end
# class Adapter
# extend MiddlewareRegistry
#
# @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
# register_middleware 'path/to/adapters',
# # Lookup constant
# some_adapter: SomeAdapter,
#
# @example Lookup by a symbol and string in an array
# # Lookup symbol constant name
# # Same as Faraday::Adapter.const_get(:SomeAdapter2)
# some_adapter_2: :SomeAdapter2,
#
# module Faraday
# class Whatever
# # Middleware looked up by :baz requires 'baz' and returns
# # Faraday::Whatever.const_get(:Baz)
# register_middleware baz: [:Baz, 'baz']
# # Require lib and then lookup class
# # require('some-adapter-3')
# # Returns Faraday::Adapter::SomeAdapter3
# some_adapter_3: [:SomeAdapter3, 'some-adapter-3']
# end
# end
#
def register_middleware(autoload_path = nil, mapping = nil)
if mapping.nil?
mapping = autoload_path
autoload_path = nil
end
middleware_mutex do
@middleware_autoload_path = autoload_path if autoload_path
(@registered_middleware ||= {}).update(mapping)
if class_registry.nil?
return initialize_class_registry(autoload_path, mapping)
end

update_class_registry(autoload_path, mapping)
end

# Unregister a previously registered middleware class.
#
# @param key [Symbol] key for the registered middleware.
def unregister_middleware(key)
@registered_middleware.delete(key)
class_registry.unregister(key)
end

# Lookup middleware class with a registered Symbol shortcut.
Expand All @@ -78,52 +191,55 @@ def unregister_middleware(key)
# @example
#
# module Faraday
# class Whatever
# register_middleware foo: Foo
# extend MiddlewareRegistry
# class Adapter
# register_middleware('path/to/adapters',
# some_adapter: SomeAdapter,
# )
# end
# end
#
# Faraday::Whatever.lookup_middleware(:foo)
# # => Faraday::Whatever::Foo
# Faraday::Adapter.lookup_middleware(:some_adapter)
# # => SomeAdapter
#
def lookup_middleware(key)
load_middleware(key) ||
raise(Faraday::Error, "#{key.inspect} is not registered on #{self}")
class_registry.lookup(key)
end

def middleware_mutex(&block)
@middleware_mutex ||= Monitor.new
@middleware_mutex.synchronize(&block)
def load_middleware(key)
warn "Deprecated, use #{self}.lookup_middleware"
lookup_middleware(key)
end

def fetch_middleware(key)
defined?(@registered_middleware) && @registered_middleware[key]
def middleware_mutex
warn "Deprecated, see #{self}.class_registry"
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)
def fetch_middleware(_)
warn "Deprecated, see #{self}.class_registry"
end

private

def initialize_class_registry(autoload_path = nil, mapping = nil)
if autoload_path.nil?
raise ArgumentError, 'needs autoload_path to initialize ClassRegistry'
end

self.class_registry = ClassRegistry.new(self, autoload_path, mapping)
end

def update_class_registry(autoload_path = nil, mapping = nil)
if mapping.nil?
mapping = autoload_path
autoload_path = nil
end

unless autoload_path.nil? || autoload_path.to_s == @autoload_path
warn "Cannot change autoload_path of existing #{self}.class_registry"
end

class_registry.register(mapping)
end
end
end
2 changes: 1 addition & 1 deletion lib/faraday/request/basic_authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
module Faraday
class Request
# Authorization middleware for Basic Authentication.
class BasicAuthentication < load_middleware(:authorization)
class BasicAuthentication < lookup_middleware(:authorization)
# @param login [String]
# @param pass [String]
#
Expand Down
2 changes: 1 addition & 1 deletion lib/faraday/request/token_authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Faraday
class Request
# TokenAuthentication is a middleware that adds a 'Token' header to a
# Faraday request.
class TokenAuthentication < load_middleware(:authorization)
class TokenAuthentication < lookup_middleware(:authorization)
# Public
def self.header(token, options = nil)
options ||= {}
Expand Down