Extending
Teaching Burl to send a type as a request body, or to read a response body
into a type, is a matter of writing a tag_invoke overload found by
argument-dependent lookup. The built-in conversions for strings, JSON, and
files are written exactly this way.
Sending a Type as a Request Body
To make a type usable with the request_builder::body function, provide a
tag_invoke overload taking body_from_tag<T>. It returns an
any_request_body, a type-erased wrapper around an object satisfying the
RequestBody concept:
struct RequestBody
{
std::optional<std::string> content_type() const;
std::optional<std::uint64_t> content_length() const;
capy::io_task<> write(capy::any_buffer_sink& sink) const;
};
Here is a complete body that serializes an
nlohmann::json document. Because
the serialized text is materialized first, its size is known, so the body
reports a Content-Length:
burl::any_request_body
tag_invoke(burl::body_from_tag<nlohmann::json>, const nlohmann::json& value)
{
class json_body
{
std::string text_;
public:
explicit json_body(const nlohmann::json& value)
: text_(value.dump())
{
}
std::optional<std::string>
content_type() const
{
return "application/json";
}
std::optional<std::uint64_t>
content_length() const noexcept
{
return text_.size();
}
capy::io_task<>
write(capy::any_buffer_sink& sink) const
{
auto [ec, n] = co_await sink.write(capy::make_buffer(text_));
co_return { ec };
}
};
return json_body{ value };
}
Because content_length() returns a size, the request goes out with a
Content-Length header; had it returned std::nullopt, the body would be
sent with chunked transfer encoding instead. The application/json from
content_type() is used unless the request already sets that header explicitly.
The overload is found by argument-dependent lookup, so placing it in the type’s
own namespace is enough. The type then works with request_builder::body
like any built-in:
auto r = co_await client.post("https://example.com/post")
.body<nlohmann::json>({ { "user", "John" } })
.send();
Reading a Response Body into a Type
To read a response into a value with as<T> or
try_as<T>, provide a tag_invoke overload
taking body_to_tag<T> and the response. It returns a
capy::io_task<T> that reads the body and converts it:
capy::io_task<nlohmann::json>
tag_invoke(burl::body_to_tag<nlohmann::json>, burl::response& resp)
{
// Try the parser's in-place buffer first; it is allocation-free
// when the body fits.
auto [ec, sv] = co_await resp.try_as_view();
// Fall back to a heap string when the body is larger than the buffer.
std::string st;
if(ec == boost::http::error::in_place_overflow)
{
auto [sec, body] = co_await resp.try_as<std::string>();
ec = sec;
st = std::move(body);
sv = st;
}
if(ec)
co_return { ec, {} };
// Surface a parse failure as an error rather than a discarded value.
auto doc = nlohmann::json::parse(sv, nullptr, false);
if(doc.is_discarded())
co_return { make_error_code(std::errc::bad_message), {} };
co_return { {}, std::move(doc) };
}
This follows the recommended pattern for reading a body. It first calls
try_as_view, which is allocation-free when the body
fits the parser’s in-place buffer, and only on http::error::in_place_overflow
falls back to reading into a std::string.
auto doc = co_await client.get("https://example.com/data")
.as<nlohmann::json>();
Forwarding Extra Arguments
Both body and as forward any trailing arguments to the matching
tag_invoke, positioned after its fixed parameters. An overload can therefore
take configuration that Burl knows nothing about.
The built-in file conversion uses this for its destination path. Its overload
declares the extra parameter after the response:
capy::io_task<std::filesystem::path>
tag_invoke(
burl::body_to_tag<std::filesystem::path>,
burl::response& resp,
std::filesystem::path dest);
so the trailing argument at the call site lands in dest:
co_await client.get("https://example.com/file")
.as<std::filesystem::path>("./out.bin");
Next Steps
-
Request Bodies — The built-in request body types
-
Responses — The built-in response conversions and
try_as_view -
nlohmann/json — The full
nlohmann::jsonexample