diff --git a/lib/faraday/middleware.rb b/lib/faraday/middleware.rb index 5bbe4a0e1..a754df33f 100644 --- a/lib/faraday/middleware.rb +++ b/lib/faraday/middleware.rb @@ -6,6 +6,8 @@ class Middleware extend MiddlewareRegistry extend DependencyLoader + register_middleware('') + def initialize(app = nil) @app = app end diff --git a/lib/faraday/middleware_registry.rb b/lib/faraday/middleware_registry.rb index 021038fec..0b762911d 100644 --- a/lib/faraday/middleware_registry.rb +++ b/lib/faraday/middleware_registry.rb @@ -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, + # }] 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 @@ -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. @@ -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 diff --git a/lib/faraday/request/basic_authentication.rb b/lib/faraday/request/basic_authentication.rb index 61c9a5bc3..aa76a123c 100644 --- a/lib/faraday/request/basic_authentication.rb +++ b/lib/faraday/request/basic_authentication.rb @@ -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] # diff --git a/lib/faraday/request/token_authentication.rb b/lib/faraday/request/token_authentication.rb index f28264b1c..c483916a1 100644 --- a/lib/faraday/request/token_authentication.rb +++ b/lib/faraday/request/token_authentication.rb @@ -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 ||= {}