ores.cpp.nats-handler.nats_handler_header
Table of Contents
Subject handler skeleton dispatching protocol messages to the entity service. nats-handler profile.
See the Template variable reference for the complete list of available variables and their semantics.
1. Template
The full template source. Edit here and re-tangle with
compass build --direct tangle_codegen_templates to regenerate
library/templates/cpp_nats_handler.hpp.mustache.
{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/cpp_messaging.org. Edit the org source. }}
{{{cpp_license}}}
{{#domain_entity}}
#ifndef ORES_{{component_core_upper}}_MESSAGING_{{entity_singular_upper}}_HANDLER_HPP
#define ORES_{{component_core_upper}}_MESSAGING_{{entity_singular_upper}}_HANDLER_HPP
#include <optional>
#include "ores.logging/make_logger.hpp"
#include "ores.nats/domain/message.hpp"
#include "ores.nats/service/client.hpp"
#include "ores.database/domain/context.hpp"
#include "ores.security/jwt/jwt_authenticator.hpp"
#include "ores.service/messaging/handler_helpers.hpp"
#include "ores.service/service/request_context.hpp"
#include "ores.{{component_include}}/messaging/{{entity_singular}}_protocol.hpp"
#include "ores.{{component_core}}/service/{{entity_singular}}_service.hpp"
{{#system_tenant_validation}}
#include "ores.utility/uuid/tenant_id.hpp"
{{/system_tenant_validation}}
{{#has_parent_id}}
#include <boost/uuid/string_generator.hpp>
{{/has_parent_id}}
<<paste:F1D4A8E2-9C3B-4F7E-B2A0-5D8C1E6B4A3F>>
namespace ores::{{component}}::messaging {
namespace {
inline auto& {{entity_singular}}_handler_lg() {
static auto instance = ores::logging::make_logger(
"ores.{{component}}.messaging.{{entity_singular}}_handler");
return instance;
}
} // namespace
using ores::service::messaging::reply;
using ores::service::messaging::decode;
using ores::service::messaging::error_reply;
using ores::service::messaging::has_permission;
using namespace ores::logging;
/**
* @brief NATS message handler for {{entity_singular_words}} operations.
{{#system_tenant_validation}}
*
* {{entity_plural_words_cap}} are system-owned global entities; list{{^current_state}} and history{{/current_state}}
* operations use the system tenant context.
{{/system_tenant_validation}}
*/
class {{entity_singular}}_handler {
public:
{{entity_singular}}_handler(ores::nats::service::client& nats,
ores::database::context ctx,
std::optional<ores::security::jwt::jwt_authenticator> verifier{{#handler_ctor_param}},
{{{handler_ctor_param}}}{{/handler_ctor_param}})
: nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)){{#handler_ctor_init}},
{{{handler_ctor_init}}}{{/handler_ctor_init}} {}
{{#operations}}
/**
* @brief Serves {{subject}}.
*
* The adapter decides nothing: it proves the request, checks the
* permission a write needs, decodes the canonical request, calls the
* service and replies with the response the service filled. The outcome
* a caller reads -- missing, conflicting, denied -- is the service's
* answer, so the two cannot disagree about what happened.
*/
void {{method}}(ores::nats::message msg) {
{{#is_put_one}}
<<paste:6D3A9F1E-7C2B-4E8A-9D5F-1B6C4E2A8F3D>>
{{/is_put_one}}
BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug) << "Handling " << msg.subject;
auto req_ctx_expected = ores::service::service::make_request_context(
ctx_, msg, verifier_);
if (!req_ctx_expected) {
error_reply(nats_, msg, req_ctx_expected.error());
return;
}
const auto& req_ctx = *req_ctx_expected;
{{#is_write}}
if (!has_permission(req_ctx, "{{component}}::{{entity_plural}}:{{permission}}")) {
error_reply(nats_, msg, ores::service::error_code::forbidden);
return;
}
{{/is_write}}
auto req = decode<{{request}}>(msg);
if (!req) {
BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
<< "Failed to decode: " << msg.subject;
error_reply(nats_, msg, ores::service::error_code::bad_request);
return;
}
{{#system_tenant_validation}}
const auto sys_ctx = req_ctx.with_tenant(
ores::utility::uuid::tenant_id::system(), req_ctx.actor());
service::{{entity_singular}}_service svc(sys_ctx);
{{/system_tenant_validation}}
{{^system_tenant_validation}}
service::{{entity_singular}}_service svc(req_ctx);
{{/system_tenant_validation}}
try {
auto response = svc.{{method}}(*req);
BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
<< "Completed " << msg.subject;
reply(nats_, msg, response);
} catch (const std::exception& e) {
// The service reports what it decided in the response; an
// exception here is the store failing, which is a different
// thing and is reported as such.
BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
<< msg.subject << " failed: " << e.what();
{{response}} failure;
failure.result.outcome = ores::utility::domain::outcome::failed;
failure.result.code = "internal_error";
failure.result.message = e.what();
reply(nats_, msg, failure);
}
}
{{/operations}}
<<paste:B9E2F4A1-3C7D-4E8B-A5F0-2D6C1B8E3A7F>>
private:
ores::nats::service::client& nats_;
ores::database::context ctx_;
std::optional<ores::security::jwt::jwt_authenticator> verifier_;
<<paste:A1C3E5F7-8B2D-4A6E-9F1C-3D7B5E2A8C4D>>
};
} // namespace ores::{{component}}::messaging
#endif
{{/domain_entity}}
{{#junction}}
#ifndef ORES_{{component_core_upper}}_MESSAGING_{{name_singular_upper}}_HANDLER_HPP
#define ORES_{{component_core_upper}}_MESSAGING_{{name_singular_upper}}_HANDLER_HPP
#include <optional>
#include "ores.logging/make_logger.hpp"
#include "ores.nats/domain/message.hpp"
#include "ores.nats/service/client.hpp"
#include "ores.database/domain/context.hpp"
#include "ores.security/jwt/jwt_authenticator.hpp"
#include "ores.service/messaging/handler_helpers.hpp"
#include "ores.service/service/request_context.hpp"
#include "ores.{{component_include}}/messaging/{{name_singular}}_protocol.hpp"
#include "ores.{{component_core}}/service/{{name_singular}}_service.hpp"
{{#system_tenant_validation}}
#include "ores.utility/uuid/tenant_id.hpp"
{{/system_tenant_validation}}
namespace ores::{{component}}::messaging {
namespace {
inline auto& {{name_singular}}_handler_lg() {
static auto instance = ores::logging::make_logger(
"ores.{{component}}.messaging.{{name_singular}}_handler");
return instance;
}
} // namespace
using ores::service::messaging::reply;
using ores::service::messaging::decode;
using ores::service::messaging::error_reply;
using ores::service::messaging::has_permission;
using namespace ores::logging;
/**
* @brief NATS message handler for {{name_words}} operations.
*
* The adapter decides nothing: it proves the request, checks the permission a
* write needs, decodes the canonical request, calls the service and replies
* with the response the service filled. The outcome a caller reads -- missing,
* conflicting, denied -- is the service's answer, so the two cannot disagree
* about what happened.
*/
class {{name_singular}}_handler {
public:
{{name_singular}}_handler(ores::nats::service::client& nats,
ores::database::context ctx,
std::optional<ores::security::jwt::jwt_authenticator> verifier{{#handler_ctor_param}},
{{{handler_ctor_param}}}{{/handler_ctor_param}})
: nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)){{#handler_ctor_init}},
{{{handler_ctor_init}}}{{/handler_ctor_init}} {}
{{#operations}}
/**
* @brief Serves {{subject}}.
*/
void {{method}}(ores::nats::message msg) {
{{#is_put_one}}
<<paste:6D3A9F1E-7C2B-4E8A-9D5F-1B6C4E2A8F3D>>
{{/is_put_one}}
BOOST_LOG_SEV({{name_singular}}_handler_lg(), debug) << "Handling " << msg.subject;
auto req_ctx_expected = ores::service::service::make_request_context(
ctx_, msg, verifier_);
if (!req_ctx_expected) {
error_reply(nats_, msg, req_ctx_expected.error());
return;
}
const auto& req_ctx = *req_ctx_expected;
{{#is_write}}
if (!has_permission(req_ctx, "{{component}}::{{name}}:{{permission}}")) {
error_reply(nats_, msg, ores::service::error_code::forbidden);
return;
}
{{/is_write}}
auto req = decode<{{request}}>(msg);
if (!req) {
BOOST_LOG_SEV({{name_singular}}_handler_lg(), warn)
<< "Failed to decode: " << msg.subject;
error_reply(nats_, msg, ores::service::error_code::bad_request);
return;
}
{{#system_tenant_validation}}
const auto sys_ctx = req_ctx.with_tenant(
ores::utility::uuid::tenant_id::system(), req_ctx.actor());
service::{{name_singular}}_service svc(sys_ctx);
{{/system_tenant_validation}}
{{^system_tenant_validation}}
service::{{name_singular}}_service svc(req_ctx);
{{/system_tenant_validation}}
try {
auto response = svc.{{method}}(*req);
BOOST_LOG_SEV({{name_singular}}_handler_lg(), debug)
<< "Completed " << msg.subject;
reply(nats_, msg, response);
} catch (const std::exception& e) {
// The service reports what it decided in the response; an
// exception here is the store failing, which is a different
// thing and is reported as such.
BOOST_LOG_SEV({{name_singular}}_handler_lg(), error)
<< msg.subject << " failed: " << e.what();
{{response}} failure;
failure.result.outcome = ores::utility::domain::outcome::failed;
failure.result.code = "internal_error";
failure.result.message = e.what();
reply(nats_, msg, failure);
}
}
{{/operations}}
<<paste:B9E2F4A1-3C7D-4E8B-A5F0-2D6C1B8E3A7F>>
private:
ores::nats::service::client& nats_;
ores::database::context ctx_;
std::optional<ores::security::jwt::jwt_authenticator> verifier_;
<<paste:A1C3E5F7-8B2D-4A6E-9F1C-3D7B5E2A8C4D>>
};
} // namespace ores::{{component}}::messaging
#endif
{{/junction}}
2. See also
- Parent facet: ores.cpp.nats-handler
- Template variable reference