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)