lib/hanami/slice/routing/middleware/stack.rb in hanami-2.0.0.beta4 vs lib/hanami/slice/routing/middleware/stack.rb in hanami-2.0.0.rc1
- old
+ new
@@ -1,20 +1,22 @@
# frozen_string_literal: true
+require "hanami/router"
require "hanami/middleware"
+require "hanami/middleware/app"
require "hanami/errors"
module Hanami
class Slice
module Routing
# @since 2.0.0
# @api private
module Middleware
# Wraps a rack app with a middleware stack
#
- # We use this class to add middlewares to the rack application generated
- # from {Hanami::Slice::Router}.
+ # We use this class to add middlewares to the rack application generated from
+ # {Hanami::Slice::Router}.
#
# ```
# stack = Hanami::Slice::Routing::Middleware::Stack.new
# stack.use(Rack::ContentType, "text/html")
# stack.to_rack_app(a_rack_app)
@@ -26,102 +28,113 @@
# stack.with("/api") do
# stack.use(Rack::ContentType, "application/json")
# end
# ```
#
+ # @see Hanami::Config#middleware
+ #
# @since 2.0.0
# @api private
class Stack
include Enumerable
# @since 2.0.0
# @api private
- ROOT_PREFIX = "/"
- private_constant :ROOT_PREFIX
-
- # @since 2.0.0
- # @api private
attr_reader :stack
- # @since 2.0.0
+ # Returns an array of Ruby namespaces from which to load middleware classes specified by
+ # symbol names given to {#use}.
+ #
+ # Defaults to `[Hanami::Middleware]`.
+ #
+ # @return [Array<Object>]
+ #
# @api public
+ # @since 2.0.0
attr_reader :namespaces
# @since 2.0.0
# @api private
def initialize
- @prefix = ROOT_PREFIX
@stack = Hash.new { |hash, key| hash[key] = [] }
@namespaces = [Hanami::Middleware]
end
# @since 2.0.0
# @api private
def initialize_copy(source)
super
- @prefix = source.instance_variable_get(:@prefix).dup
@stack = stack.dup
@namespaces = namespaces.dup
end
+ # Adds a middleware to the stack.
+ #
+ # @example
+ # # Using a symbol name; adds Hanami::Middleware::BodyParser.new([:json])
+ # middleware.use :body_parser, :json
+ #
+ # # Using a class name
+ # middleware.use MyMiddleware
+ #
+ # # Adding a middleware before or after others
+ # middleware.use MyMiddleware, before: SomeMiddleware
+ # middleware.use MyMiddleware, after: OtherMiddleware
+ #
+ # @param spec [Symbol, Class] the middleware name or class name
+ # @param args [Array, nil] Arguments to pass to the middleware's `.new` method
+ # @param before [Class, nil] an optional (already added) middleware class to add the
+ # middleware before
+ # @param after [Class, nil] an optional (already added) middleware class to add the
+ # middleware after
+ #
+ # @return [self]
+ #
+ # @api public
# @since 2.0.0
- # @api private
- def use(spec, *args, before: nil, after: nil, &blk)
+ def use(spec, *args, path_prefix: ::Hanami::Router::DEFAULT_PREFIX, before: nil, after: nil, &blk)
middleware = resolve_middleware_class(spec)
item = [middleware, args, blk]
if before
- @stack[@prefix].insert((idx = index_of(before)).zero? ? 0 : idx - 1, item)
+ @stack[path_prefix].insert((idx = index_of(before, path_prefix)).zero? ? 0 : idx - 1, item)
elsif after
- @stack[@prefix].insert(index_of(after) + 1, item)
+ @stack[path_prefix].insert(index_of(after, path_prefix) + 1, item)
else
- @stack[@prefix].push([middleware, args, blk])
+ @stack[path_prefix].push([middleware, args, blk])
end
self
end
# @since 2.0.0
# @api private
def update(other)
- other.stack.each do |prefix, items|
- stack[prefix].concat(items)
+ other.stack.each do |path_prefix, items|
+ stack[path_prefix].concat(items)
end
self
end
# @since 2.0.0
# @api private
- def with(path)
- prefix = @prefix
- @prefix = path
- yield
- ensure
- @prefix = prefix
- end
-
- # @since 2.0.0
- # @api private
def to_rack_app(app)
unless Hanami.bundled?("rack")
raise "Add \"rack\" to your `Gemfile` to run Hanami as a rack app"
end
- require "rack/builder"
+ mapping = to_hash
+ return app if mapping.empty?
- s = self
+ Hanami::Middleware::App.new(app, mapping)
+ end
- Rack::Builder.new do
- s.each do |prefix, stack|
- s.mapped(self, prefix) do
- stack.each do |middleware, args, blk|
- use(middleware, *args, &blk)
- end
- end
-
- run app
- end
+ # @since 2.0.0
+ # @api private
+ def to_hash
+ @stack.each_with_object({}) do |(path, _), result|
+ result[path] = stack_for(path)
end
end
# @since 2.0.0
# @api private
@@ -136,25 +149,35 @@
end
# @since 2.0.0
# @api private
def mapped(builder, prefix, &blk)
- if prefix == ROOT_PREFIX
+ if prefix == ::Hanami::Router::DEFAULT_PREFIX
builder.instance_eval(&blk)
else
builder.map(prefix, &blk)
end
end
private
# @since 2.0.0
- def index_of(middleware)
- @stack[@prefix].index { |(m, *)| m.equal?(middleware) }
+ def index_of(middleware, path_prefix)
+ @stack[path_prefix].index { |(m, *)| m.equal?(middleware) }
end
# @since 2.0.0
+ # @api private
+ def stack_for(current_path)
+ @stack.each_with_object([]) do |(path, stack), result|
+ next unless current_path.start_with?(path)
+
+ result.push(stack)
+ end.flatten(1)
+ end
+
+ # @since 2.0.0
def resolve_middleware_class(spec)
case spec
when Symbol then load_middleware_class(spec)
when Class, Module then spec
else
@@ -171,9 +194,10 @@
begin
require "hanami/middleware/#{spec}"
rescue LoadError # rubocop:disable Lint/SuppressedException
end
+ # FIXME: Classify must use App inflector
class_name = Hanami::Utils::String.classify(spec.to_s)
namespace = namespaces.detect { |ns| ns.const_defined?(class_name) }
if namespace
namespace.const_get(class_name)