class Hanami::Slice::Routing::Middleware::Stack

Wraps a rack app with a middleware stack

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) “`

Middlewares can be mounted on specific paths:

“‘ stack.with(“/api”) do

stack.use(Rack::ContentType, "application/json")

end “‘

@see Hanami::Config#middleware

@since 2.0.0 @api private

Attributes

namespaces[R]

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

stack[R]

@since 2.0.0 @api private

Public Class Methods

new() click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 57
def initialize
  @stack = Hash.new { |hash, key| hash[key] = [] }
  @namespaces = [Hanami::Middleware]
end

Public Instance Methods

each(&blk) click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 147
def each(&blk)
  @stack.each(&blk)
end
empty?() click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 141
def empty?
  @stack.empty?
end
initialize_copy(source) click to toggle source

@since 2.0.0 @api private

Calls superclass method
# File lib/hanami/slice/routing/middleware/stack.rb, line 64
def initialize_copy(source)
  super
  @stack = stack.dup
  @namespaces = namespaces.dup
end
mapped(builder, prefix, &blk) click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 153
def mapped(builder, prefix, &blk)
  if prefix == ::Hanami::Router::DEFAULT_PREFIX
    builder.instance_eval(&blk)
  else
    builder.map(prefix, &blk)
  end
end
to_hash() click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 133
def to_hash
  @stack.each_with_object({}) do |(path, _), result|
    result[path] = stack_for(path)
  end
end
to_rack_app(app) click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 120
def to_rack_app(app)
  unless Hanami.bundled?("rack")
    raise "Add \"rack\" to your `Gemfile` to run Hanami as a rack app"
  end

  mapping = to_hash
  return app if mapping.empty?

  Hanami::Middleware::App.new(app, mapping)
end
update(other) click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 111
def update(other)
  other.stack.each do |path_prefix, items|
    stack[path_prefix].concat(items)
  end
  self
end
use(spec, *args, path_prefix: ::Hanami::Router::DEFAULT_PREFIX, before: nil, after: nil, **kwargs, &blk) click to toggle source

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

# File lib/hanami/slice/routing/middleware/stack.rb, line 94
def use(spec, *args, path_prefix: ::Hanami::Router::DEFAULT_PREFIX, before: nil, after: nil, **kwargs, &blk)
  middleware = resolve_middleware_class(spec)
  item = [middleware, args, kwargs, blk]

  if before
    @stack[path_prefix].insert((idx = index_of(before, path_prefix)).zero? ? 0 : idx - 1, item)
  elsif after
    @stack[path_prefix].insert(index_of(after, path_prefix) + 1, item)
  else
    @stack[path_prefix].push(item)
  end

  self
end

Private Instance Methods

index_of(middleware, path_prefix) click to toggle source

@since 2.0.0

# File lib/hanami/slice/routing/middleware/stack.rb, line 164
def index_of(middleware, path_prefix)
  @stack[path_prefix].index { |(m, *)| m.equal?(middleware) }
end
load_middleware_class(spec) click to toggle source

@since 2.0.0

# File lib/hanami/slice/routing/middleware/stack.rb, line 193
def load_middleware_class(spec)
  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)
  else
    raise(
      UnsupportedMiddlewareSpecError,
      "Failed to find corresponding middleware class for `#{spec}` in #{namespaces.join(', ')}"
    )
  end
end
resolve_middleware_class(spec) click to toggle source

@since 2.0.0

# File lib/hanami/slice/routing/middleware/stack.rb, line 179
def resolve_middleware_class(spec)
  case spec
  when Symbol then load_middleware_class(spec)
  when Class, Module then spec
  else
    if spec.respond_to?(:call)
      spec
    else
      raise UnsupportedMiddlewareSpecError, spec
    end
  end
end
stack_for(current_path) click to toggle source

@since 2.0.0 @api private

# File lib/hanami/slice/routing/middleware/stack.rb, line 170
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