Added extension exchange request builder

Necessary to build fully rendered requests -- via Liquid templates -- for an exchange.

Issue: 352
Milestone: minor
This commit is contained in:
Brooke Kuhlmann
2026-07-30 17:02:13 -06:00
parent 239b6b0dae
commit ed2997af08
2 changed files with 122 additions and 0 deletions
@@ -0,0 +1,38 @@
# frozen_string_literal: true
require "dry/core"
require "dry/monads"
require "initable"
module Terminus
module Aspects
module Extensions
module Exchanges
# Builds fully rendereed requests.
class RequestBuilder
include Deps["aspects.extensions.contextualizer", renderer: "liquid.basic"]
include Initable[request: Fetchers::Request]
include Dry::Monads[:result]
def call exchange, extension
context = contextualizer.call extension
headers = render_values :headers, exchange, context
body = render_values :body, exchange, context
renderer.call(exchange.template, context).split.map do |uri|
request[headers:, verb: exchange.verb, uri:, body:]
end
end
private
def render_values method, exchange, context
Hash(exchange.public_send(method)).each.with_object({}) do |(key, value), all|
all[key] = renderer.call value, context
end
end
end
end
end
end
end
@@ -0,0 +1,84 @@
# frozen_string_literal: true
require "hanami_helper"
RSpec.describe Terminus::Aspects::Extensions::Exchanges::RequestBuilder do
subject(:builder) { described_class.new }
describe "#call" do
let :extension do
Factory.structs[
:extension,
data: {
"content_type" => "application/json",
"sort" => "desc",
"source_1" => "1",
"source_2" => "2"
}
]
end
it "answers single request without rendering" do
exchange = Factory.structs[:extension_exchange]
expect(builder.call(exchange, extension)).to contain_exactly(
Terminus::Aspects::Extensions::Fetchers::Request[uri: exchange.template]
)
end
it "answers single request when headers and data are nil" do
exchange = Factory.structs[:extension_exchange, headers: nil, body: nil]
expect(builder.call(exchange, extension)).to contain_exactly(
Terminus::Aspects::Extensions::Fetchers::Request[uri: exchange.template]
)
end
it "answers single request, fully rendered" do
exchange = Factory.structs[
:extension_exchange,
headers: {"content_type" => "{{ extension.data.content_type }}"},
verb: "post",
template: "https://test.io/{{ extension.data.source_1 }}",
body: {"sort" => "{{ extension.data.sort }}"}
]
expect(builder.call(exchange, extension)).to contain_exactly(
Terminus::Aspects::Extensions::Fetchers::Request[
headers: {"content_type" => "application/json"},
verb: "post",
uri: "https://test.io/1",
body: {"sort" => "desc"}
]
)
end
it "answers mulitiple requests, fully rendered" do
exchange = Factory.structs[
:extension_exchange,
headers: {"content_type" => "{{ extension.data.content_type }}"},
verb: "post",
template: "https://test.io/{{ extension.data.source_1 }}\n" \
"https://test.io/{{ extension.data.source_2 }}",
body: {"sort" => "{{ extension.data.sort }}"}
]
expect(builder.call(exchange, extension)).to eq(
[
Terminus::Aspects::Extensions::Fetchers::Request[
headers: {"content_type" => "application/json"},
verb: "post",
uri: "https://test.io/1",
body: {"sort" => "desc"}
],
Terminus::Aspects::Extensions::Fetchers::Request[
headers: {"content_type" => "application/json"},
verb: "post",
uri: "https://test.io/2",
body: {"sort" => "desc"}
]
]
)
end
end
end