View in context in the libbitcoin Slack #general mirror.
struct value_t
{
using inner_t = std::variant
<
null_t,
boolean_t,
number_t,
string_t,
array_t,
object_t,
any_t
>;
...
};any_t is our own class that wraps std::any. This provides entirely safe and strongly typed object containment with full type erasure. In order to not have this inflate the value_t variant it always contains a std::shared_ptr<Type> in the std::any and exposes it as the the shared pointer. The important thing is that it's just a single type added to the variant, but it's mapped in dispatch based on the inner Type. So when the http channel wants to dispatch a request, it just does this...
if (const auto code = dispatcher_.notify(request_t
{
.method = "get",
.params = { array_t{ any_t{ tag_request<verb::get>(request) } } }
}))
{
stop(code);
return;
}any_t wraps the std::shared_ptr<request>, not to be confused with request_t which is the rpc base object (e.g. a json-rpc request). The any_t drops into an array_t as the .params property of the rpc request. .params can be array_t (unnamed elements) or object_t (named elements), and the .method is a string_t that selects the rpc method. in a json-rpc call the whole thing is just deserialized off the wire using boost::json. Eventually that will be happening with all of the request types.
Note tag_request<verb::get>(...) above wraps the http request, which is itself a shared_ptr<http::request>. This is an "tag" that allows dispatch based on the inner type of the pointer. It adds no size to the original pointer, and aliases the request as get/put/post, etc. That routing is defined here:
struct http_methods
{
static constexpr std::tuple methods
{
method<"get", http::method::get::cptr>{},
method<"head", http::method::head::cptr>{},
method<"post", http::method::post::cptr>{},
method<"put", http::method::put::cptr>{},
method<"delete", http::method::delete_::cptr>{},
method<"trace", http::method::trace::cptr>{},
method<"options", http::method::options::cptr>{},
method<"connect", http::method::connect::cptr>{},
method<"unknown", http::method::unknown::cptr>{}
};
};request_t.method). This is how the request_t.params end up in a method call. Those are defined in a protocol, and subscribed to messages by the inner type:
void protocol_http::start() NOEXCEPT
{
BC_ASSERT(stranded());
if (started())
return;
SUBSCRIBE_CHANNEL(method::get::cptr, handle_receive_get, _1, _2);
SUBSCRIBE_CHANNEL(method::head::cptr, handle_receive_head, _1, _2);
SUBSCRIBE_CHANNEL(method::post::cptr, handle_receive_post, _1, _2);
SUBSCRIBE_CHANNEL(method::put::cptr, handle_receive_put, _1, _2);
SUBSCRIBE_CHANNEL(method::delete_::cptr, handle_receive_delete, _1, _2);
SUBSCRIBE_CHANNEL(method::trace::cptr, handle_receive_trace, _1, _2);
SUBSCRIBE_CHANNEL(method::options::cptr, handle_receive_options, _1, _2);
SUBSCRIBE_CHANNEL(method::connect::cptr, handle_receive_connect, _1, _2);
SUBSCRIBE_CHANNEL(method::unknown::cptr, handle_receive_unknown, _1, _2);
protocol::start();
}
// Handle disallowed-by-default methods (override to implement).
// ----------------------------------------------------------------------------
bool protocol_http::handle_receive_get(const code& ec,
const method::get::cptr& get) NOEXCEPT
{
send_method_not_allowed(*get, ec);
return true;
}
bool protocol_http::handle_receive_post(const code& ec,
const method::post::cptr& post) NOEXCEPT
{
send_method_not_allowed(*post, ec);
return true;
}
bool protocol_http::handle_receive_put(const code& ec,
const method::put::cptr& put) NOEXCEPT
{
send_method_not_allowed(*put, ec);
return true;
}
bool protocol_http::handle_receive_head(const code& ec,
const method::head::cptr& head) NOEXCEPT
{
send_method_not_allowed(*head, ec);
return true;
}
bool protocol_http::handle_receive_delete(const code& ec,
const method::delete_::cptr& delete_) NOEXCEPT
{
send_method_not_allowed(*delete_, ec);
return true;
}
bool protocol_http::handle_receive_trace(const code& ec,
const method::trace::cptr& trace) NOEXCEPT
{
send_method_not_allowed(*trace, ec);
return true;
}
bool protocol_http::handle_receive_options(const code& ec,
const method::options::cptr& options) NOEXCEPT
{
send_method_not_allowed(*options, ec);
return true;
}
bool protocol_http::handle_receive_connect(const code& ec,
const method::connect::cptr& connect) NOEXCEPT
{
send_method_not_allowed(*connect, ec);
return true;
}
bool protocol_http::handle_receive_unknown(const code& ec,
const method::unknown::cptr& unknown) NOEXCEPT
{
send_method_not_allowed(*unknown, ec);
return true;
} instance.subscribe(
[&](const code& ec, mock::all_required, bool a, double b, std::string c)
{
called = true;
result_a = a;
result_b = b;
result_c = c;
promise8.set_value(ec);
return true;
});mock::all_required is the actual method type and uniquely identifies the handler as a unique signature. For object_t params, the method descriptors also support names for correlation (a, b, c, below)...
struct mock_methods
{
static constexpr std::tuple methods
{
method<"empty_method">{},
method<"all_required", bool, double, std::string>{ "a", "b", "c" },
method<"with_options", std::string, optional<4.2>, optional<true>>{ "a", "b", "c" },
method<"with_nullify", std::string, nullable<double>, nullable<bool>>{ "a", "b", "c" },
method<"with_combine", std::string, nullable<bool>, optional<4.2>>{ "a", "b", "c" },
method<"not_required", nullable<bool>, optional<4.2>>{ "a", "b" }
};
// Derive this from above in c++26 using reflection.
using empty_method = at<0, decltype(methods)>;
using all_required = at<1, decltype(methods)>;
using with_options = at<2, decltype(methods)>;
using with_nullify = at<3, decltype(methods)>;
using with_combine = at<4, decltype(methods)>;
using not_required = at<5, decltype(methods)>;
};