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

Emacs 29.3 (Org mode 9.6.15)