Skip to content

Commit

Permalink
feat(middleware): introduce new middleware backend
Browse files Browse the repository at this point in the history
  • Loading branch information
marian13 committed Sep 4, 2024
1 parent 695b60e commit 66c1c0f
Show file tree
Hide file tree
Showing 12 changed files with 967 additions and 2 deletions.
47 changes: 47 additions & 0 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,18 @@ To hide overriden [eql?](https://github.com/marian13/convenient_service/blob/v0.
| - | - | - | - |
| Medium | Moderate | TODO | performance, included-modules |

---

### Loop middleware backends in performance benchmarks

| Priority | Complexity | Status | Tags |
| - | - | - | - |
| Medium | Moderate | TODO | middleware-backend |

Consider to extract common benchmark setup.

---

## Memory

### Consider to drop references to already calculated steps
Expand All @@ -755,6 +767,41 @@ To hide overriden [eql?](https://github.com/marian13/convenient_service/blob/v0.
| - | - | - | - |
| Medium | High | TODO | compile, middleware-stack |

```ruby
module ConvenientService
module Support
module Middleware
class StackBuilder
module Constants
module Backends
##
# @return [Symbol]
#
COMPILED = :compiled
end
end
end
end
end
end
```

---

### Unify middleware backends interfaces

| Priority | Complexity | Status | Tags |
| - | - | - | - |
| Medium | Moderate | TODO | middleware-backend, interface |

```ruby
def run(env, original)
# ...
end
```

---

### Cache unmodified configs.

| Priority | Complexity | Status | Tags |
Expand Down
2 changes: 2 additions & 0 deletions lib/convenient_service/support/middleware/stack_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def by(backend)
Entities::Builders::RubyMiddleware
when Constants::Backends::RACK
Entities::Builders::Rack
when Constants::Backends::STATEFUL
Entities::Builders::Stateful
else
::ConvenientService.raise Exceptions::NotSupportedBackend.new(backend: backend)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ module Backends
#
RACK = :rack

##
# @return [Symbol]
#
STATEFUL = :stateful

##
# @return [Array<Symbol>]
#
ALL = [RUBY_MIDDLEWARE, RACK]
ALL = [RUBY_MIDDLEWARE, RACK, STATEFUL]

##
# @return [Symbol]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

require_relative "builders/ruby_middleware"
require_relative "builders/rack"
require_relative "builders/stateful"
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# frozen_string_literal: true

require_relative "stateful/exceptions"

module ConvenientService
module Support
module Middleware
class StackBuilder
module Entities
module Builders
class Stateful
##
# @!attribute [r] stack
# @return [Array<#call<Hash>>]
#
attr_reader :stack

##
# @!attribute [r] name
# @return [String]
#
attr_reader :name

##
# @param kwargs [Hash{Symbol => Object}]
# @return [void]
#
def initialize(**kwargs)
@name = kwargs.fetch(:name) { "Stack" }
@stack = kwargs.fetch(:stack) { [] }
@index = -1
end

##
# @return [Boolean]
#
def empty?
stack.empty?
end

##
# @param some_middleware [#call<Hash>]
# @return [Boolean]
#
def has?(some_middleware)
stack.any? { |middleware| middleware == some_middleware }
end

##
# @return [Boolean]
#
def clear
stack.clear

self
end

##
# @param env [Hash{Symbol => Object}]
# @return [Object] Can be any type.
#
# @internal
# NOTE: When stack is empty - `env` is returned. Just like `ruby-middleware` does.
# NOTE: Once middleware backends are unified, consider to create new object to ensure thread-safety.
# NOTE: Once middleware backends are unified, move `stack.empty?` and `index == stack.size - 1` to entrypoint method.
#
# TODO: Direct specs.
#
def call(env)
return env if stack.empty?

self.index += 1

if index == stack.size - 1
stack[index].call(env)
else
stack[index].new(self).call(env)
end
ensure
self.index = -1
end

##
# @param middleware [#call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
def unshift(middleware)
stack.unshift(middleware)

self
end

##
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
alias_method :prepend, :unshift

##
# @param middleware [#call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
def use(middleware)
stack << middleware

self
end

##
# @param index_or_middleware [Integer, #call<Hash>]
# @param other_middleware [#call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
# @raise [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack::Exceptions::MissingMiddleware]
#
def insert(index_or_middleware, other_middleware)
index = cast_index(index_or_middleware)

stack.insert(index, other_middleware)

self
end

##
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
alias_method :insert_before, :insert

##
# @param index_or_middleware [Integer, #call<Hash>]
# @param other_middleware [#call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
# @raise [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack::Exceptions::MissingMiddleware]
#
def insert_after(index_or_middleware, other_middleware)
index = cast_index(index_or_middleware)

stack.insert(index + 1, other_middleware)

self
end

##
# @param other_middleware [#call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
def insert_before_each(other_middleware)
@stack = stack.reduce([]) { |stack, middleware| stack.push(other_middleware, middleware) }

self
end

##
# @param other_middleware [#call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
def insert_after_each(other_middleware)
@stack = stack.reduce([]) { |stack, middleware| stack.push(middleware, other_middleware) }

self
end

##
# @param index_or_middleware [Integer, #call<Hash>]
# @param other_middleware [#call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
# @raise [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack::Exceptions::MissingMiddleware]
#
def replace(index_or_middleware, other_middleware)
index = cast_index(index_or_middleware)

stack[index] = other_middleware

self
end

##
# @param index_or_middleware [Integer, #call<Hash>]
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
# @raise [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack::Exceptions::MissingMiddleware]
#
def delete(index_or_middleware)
index = cast_index(index_or_middleware)

stack.delete_at(index)

self
end

##
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
alias_method :remove, :delete

##
# @param other [Object] Can be any type.
# @return [Boolean, nil]
#
def ==(other)
return unless other.instance_of?(self.class)

return false if name != other.name
return false if stack != other.stack

true
end

##
# @return [Array]
#
def to_a
stack
end

##
# @return [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack]
#
def dup
self.class.new(name: name.dup, stack: stack.dup)
end

private

##
# @!attribute [r] index
# @return [Integer]
#
attr_accessor :index

##
# @param index_or_middleware [Integer, #call<Hash>]
# @return [Integer]
# @raise [ConvenientService::Support::Middleware::StackBuilder::Entities::Builders::Rack::Exceptions::MissingMiddleware]
#
def cast_index(index_or_middleware)
return index_or_middleware if index_or_middleware.instance_of?(Integer)

index = stack.find_index { |middleware| middleware == index_or_middleware }

::ConvenientService.raise Exceptions::MissingMiddleware.new(middleware: index_or_middleware) unless index

index
end
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module ConvenientService
module Support
module Middleware
class StackBuilder
module Entities
module Builders
class Stateful
module Exceptions
class MissingMiddleware < ::ConvenientService::Exception
##
# @param middleware [#call<Hash>]
# @return [void]
#
def initialize_with_kwargs(middleware:)
message = <<~TEXT
Middleware `#{middleware.inspect}` is NOT found in the stack.
TEXT

initialize(message)
end
end
end
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
end
end

describe "::Backends::STATEFUL" do
it "returns `:stateful`" do
expect(described_class::Backends::STATEFUL).to eq(:stateful)
end
end

describe "::Backends::ALL" do
it "returns `[:ruby_middleware, :rack]`" do
expect(described_class::Backends::ALL).to eq([described_class::Backends::RUBY_MIDDLEWARE, described_class::Backends::RACK])
expect(described_class::Backends::ALL).to eq([described_class::Backends::RUBY_MIDDLEWARE, described_class::Backends::RACK, described_class::Backends::STATEFUL])
end
end

Expand Down
Loading

0 comments on commit 66c1c0f

Please sign in to comment.