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 builder’s
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:
namespace nlohmann
{
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 };
}
} // namespace nlohmann
The overload is found by argument-dependent lookup, so placing it in the type’s
own namespace is enough. The type then works with body like any built-in:
nlohmann::json doc({ { "user", "John" } });
auto r = co_await client.post("https://example.com/post")
.body(doc)
.send();
Reading a Response Body into a Type
To make a type usable with xref:reference:boost/burl/response/as.adoc[response::as]
and request_builder::as, provide a tag_invoke overload taking
body_to_tag<T>. It takes the response and returns a
capy::io_task<T> that reads and converts the body:
namespace nlohmann
{
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 == 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) };
}
} // namespace nlohmann
This conversion shows the recommended pattern: read with
xref:reference:boost/burl/response/try_as_view.adoc[try_as_view] first,
which is allocation-free when the body fits the parser’s in-place buffer, and on
http::error::in_place_overflow fall back to reading into a std::string.
Build the result on a value-side tag_invoke rather than reaching for raw I/O,
and you inherit the timeout handling and the
buffer reuse the response already provides.
With both overloads in place, the type is a first-class body in both directions:
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 -
Examples — The full
nlohmann::jsonexample as a runnable program