Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions lib/protocol/http/middleware/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ def initialize(default_app = NotFound)
@app = default_app
end

# Build the middleware application using the given block.
#
# @parameter block [Proc] The block to pass to the middleware constructor.
# @returns [Builder] The builder.
def build(&block)
if block_given?
if block.arity == 0
instance_exec(&block)
else
yield self
end
end

return self
end

# Use the given middleware with the given arguments and options.
#
# @parameter middleware [Class | Object] The middleware class to use.
Expand All @@ -44,19 +60,34 @@ def to_app
end

# Build a middleware application using the given block.
def self.build(&block)
builder = Builder.new
def self.build(*arguments, &block)
builder = Builder.new(*arguments)

builder.build(&block)

return builder.to_app
end

# Load a middleware application from the given path.
#
# @parameter path [String] The path to the middleware application.
# @parameter arguments [Array] The arguments to pass to the middleware constructor.
# @parameter options [Hash] The options to pass to the middleware constructor.
# @parameter block [Proc] The block to pass to the middleware constructor.
def self.load(path, *arguments, &block)
builder = Builder.new(*arguments)

binding = Builder::TOPLEVEL_BINDING.call(builder)
eval(File.read(path), binding, path)

if block_given?
if block.arity == 0
builder.instance_exec(&block)
else
yield builder
end
builder.build(&block)
end

return builder.to_app
end
end
end
end

Protocol::HTTP::Middleware::Builder::TOPLEVEL_BINDING = ->(builder){builder.instance_eval{binding}}
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- Introduce `Protocol::HTTP::Middleware.load` method for loading middleware applications from files.

## v0.58.1

- `Protocol::HTTP::DuplicateHeaderError` now includes the existing and new values for better debugging.
Expand Down
167 changes: 167 additions & 0 deletions test/protocol/http/middleware/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

require "protocol/http/middleware"
require "protocol/http/middleware/builder"
require "tempfile"

describe Protocol::HTTP::Middleware::Builder do
it "can make an app" do
Expand Down Expand Up @@ -41,4 +42,170 @@

expect(app).to be_a(Protocol::HTTP::Middleware)
end

it "can initialize with custom default app" do
builder = Protocol::HTTP::Middleware::Builder.new(Protocol::HTTP::Middleware::Okay)

expect(builder.to_app).to be_equal(Protocol::HTTP::Middleware::Okay)
end

it "can build without block" do
builder = Protocol::HTTP::Middleware::Builder.new
result = builder.build

expect(result).to be_equal(builder)
expect(builder.to_app).to be_equal(Protocol::HTTP::Middleware::NotFound)
end

it "can build with zero-arity block using instance_exec" do
builder = Protocol::HTTP::Middleware::Builder.new

builder.build do
use Protocol::HTTP::Middleware
end

expect(builder.to_app).to be_a(Protocol::HTTP::Middleware)
end

it "can use middleware with arguments" do
middleware_class = Class.new(Protocol::HTTP::Middleware) do
def initialize(app, argument1, argument2)
super(app)
@argument1 = argument1
@argument2 = argument2
end

attr :argument1, :argument2
end

builder = Protocol::HTTP::Middleware::Builder.new(Protocol::HTTP::Middleware::Okay)
builder.use(middleware_class, "test1", "test2")

app = builder.to_app
expect(app).to be_a(middleware_class)
expect(app.argument1).to be == "test1"
expect(app.argument2).to be == "test2"
end

it "can use middleware with options" do
middleware_class = Class.new(Protocol::HTTP::Middleware) do
def initialize(app, option1:, option2:)
super(app)
@option1 = option1
@option2 = option2
end

attr :option1, :option2
end

builder = Protocol::HTTP::Middleware::Builder.new(Protocol::HTTP::Middleware::Okay)
builder.use(middleware_class, option1: "value1", option2: "value2")

app = builder.to_app
expect(app).to be_a(middleware_class)
expect(app.option1).to be == "value1"
expect(app.option2).to be == "value2"
end

it "can use middleware with block" do
middleware_class = Class.new(Protocol::HTTP::Middleware) do
def initialize(app, &block)
super(app)
@block = block
end

attr :block
end

block_called = false
builder = Protocol::HTTP::Middleware::Builder.new(Protocol::HTTP::Middleware::Okay)
builder.use(middleware_class) do
block_called = true
end

app = builder.to_app
expect(app).to be_a(middleware_class)
expect(app.block).to be_a(Proc)
app.block.call
expect(block_called).to be == true
end

it "can run to set default app" do
builder = Protocol::HTTP::Middleware::Builder.new
builder.run(Protocol::HTTP::Middleware::Okay)

expect(builder.to_app).to be_equal(Protocol::HTTP::Middleware::Okay)
end

it "can chain multiple middleware" do
middleware1 = Class.new(Protocol::HTTP::Middleware) do
def initialize(app)
super(app)
@name = "middleware1"
end

attr :name
end

middleware2 = Class.new(Protocol::HTTP::Middleware) do
def initialize(app)
super(app)
@name = "middleware2"
end

attr :name
end

builder = Protocol::HTTP::Middleware::Builder.new(Protocol::HTTP::Middleware::Okay)
builder.use(middleware1)
builder.use(middleware2)

app = builder.to_app
# Middleware are reversed, so first added becomes outermost
expect(app).to be_a(middleware1)
expect(app.delegate).to be_a(middleware2)
expect(app.delegate.delegate).to be_equal(Protocol::HTTP::Middleware::Okay)
end

it "can convert to app directly" do
builder = Protocol::HTTP::Middleware::Builder.new(Protocol::HTTP::Middleware::HelloWorld)

app = builder.to_app
expect(app).to be_equal(Protocol::HTTP::Middleware::HelloWorld)
end

it "can load middleware from file" do
temp_file = Tempfile.new(["middleware", ".rb"])
temp_file.write(<<~RUBY)
use Protocol::HTTP::Middleware
run Protocol::HTTP::Middleware::HelloWorld
RUBY
temp_file.close

app = Protocol::HTTP::Middleware.load(temp_file.path)

expect(app).to be_a(Protocol::HTTP::Middleware)
expect(app.delegate).to be_equal(Protocol::HTTP::Middleware::HelloWorld)

ensure
temp_file&.unlink
end

it "can load middleware from file with block" do
temp_file = Tempfile.new(["middleware", ".rb"])
temp_file.write(<<~RUBY)
use Protocol::HTTP::Middleware
RUBY
temp_file.close

app = Protocol::HTTP::Middleware.load(temp_file.path) do
run Protocol::HTTP::Middleware::HelloWorld
end

expect(app).to be_a(Protocol::HTTP::Middleware)
expect(app.delegate).to be_equal(Protocol::HTTP::Middleware::HelloWorld)

ensure
temp_file&.unlink
end
end
Loading