ores.cpp.nats-handler.nats_handler_header

Table of Contents

Subject handler skeleton dispatching protocol messages to the entity service. nats-handler profile.

Model flags consumed by this template:

See the Template variable reference for the complete list of available variables and their semantics.

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}}
{{#read_for_cache}}
#include "ores.database/service/tenant_context.hpp"
#include <limits>
{{/read_for_cache}}
<<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 and history
 * 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)
        : nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)) {}

    void list(ores::nats::message msg) {
        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;
{{#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}}
        get_{{entity_plural}}_response resp;
{{#list_filter_column}}
        if (auto req = decode<get_{{entity_plural}}_request>(msg)) {
            try {
                // Note: has_as_of_lookup is not currently supported in
                // combination with list_filter_column -- the at_timepoint
                // service method filters by primary key, not by
                // {{list_filter_column}}, so req->as_of is intentionally
                // ignored here. Wire this up (with a filter argument that
                // matches list_filter_column's semantics) if/when this
                // facet is rolled out to an entity that needs both.
                resp.{{entity_plural_short}} = svc.list_{{entity_plural_short}}(req->{{list_filter_column}});
                resp.total_available_count =
                    static_cast<int>(resp.{{entity_plural_short}}.size());
                resp.success = true;
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                resp.success = false;
                resp.message = e.what();
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
            return;
        }
{{/list_filter_column}}
{{^list_filter_column}}
        if (auto req = decode<get_{{entity_plural}}_request>(msg)) {
            try {
{{#has_as_of_lookup}}
                if (!req->as_of.empty()) {
                    resp.{{entity_plural_short}} = svc.list_{{entity_plural_short}}_at_timepoint(req->as_of);
                    resp.total_available_count =
                        static_cast<int>(resp.{{entity_plural_short}}.size());
                } else {
                    resp.{{entity_plural_short}} = svc.list_{{entity_plural_short}}(req->offset, req->limit);
                    resp.total_available_count = static_cast<int>(svc.count_{{entity_plural_short}}());
                }
{{/has_as_of_lookup}}
{{^has_as_of_lookup}}
                resp.{{entity_plural_short}} = svc.list_{{entity_plural_short}}(req->offset, req->limit);
                resp.total_available_count = static_cast<int>(svc.count_{{entity_plural_short}}());
{{/has_as_of_lookup}}
                resp.success = true;
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                resp.success = false;
                resp.message = e.what();
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
            return;
        }
{{/list_filter_column}}
        BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
            << "Completed " << msg.subject;
        reply(nats_, msg, resp);
    }

    void save(ores::nats::message msg) {
<<paste:6D3A9F1E-7C2B-4E8A-9D5F-1B6C4E2A8F3D>>
        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;
        if (!has_permission(req_ctx, "{{component}}::{{entity_plural}}:write")) {
            error_reply(nats_, msg, ores::service::error_code::forbidden);
            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}}
        if (auto req = decode<save_{{entity_singular}}_request>(msg)) {
            try {
                svc.save_{{entity_singular_short}}(req->data);
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject;
                reply(nats_, msg,
                    save_{{entity_singular}}_response{.success = true});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, save_{{entity_singular}}_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

    void history(ores::nats::message msg) {
        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;
{{#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}}
        if (auto req = decode<get_{{entity_singular}}_history_request>(msg)) {
            try {
                auto hist = svc.get_{{entity_singular_short}}_history(req->{{history_request_id_field}}{{#history_request_extra_args}}, req->{{name}}{{/history_request_extra_args}});
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject;
                reply(nats_, msg, get_{{entity_singular}}_history_response{
                    .history = std::move(hist), .success = true});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, get_{{entity_singular}}_history_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

    void remove(ores::nats::message msg) {
        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;
        if (!has_permission(req_ctx, "{{component}}::{{entity_plural}}:delete")) {
            error_reply(nats_, msg, ores::service::error_code::forbidden);
            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}}
        if (auto req = decode<delete_{{entity_singular}}_request>(msg)) {
            try {
{{#single_delete}}
                svc.delete_{{entity_singular_short}}(req->{{delete_request_id_field}}{{#delete_request_extra_args}}, req->{{name}}{{/delete_request_extra_args}});
{{/single_delete}}
{{^single_delete}}
                svc.delete_{{entity_plural_short}}(req->{{delete_request_id_field}}{{#delete_request_extra_args}}, req->{{name}}{{/delete_request_extra_args}});
{{/single_delete}}
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject;
                reply(nats_, msg,
                    delete_{{entity_singular}}_response{.success = true});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, delete_{{entity_singular}}_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

{{#extra_list_requests}}
    void list_{{name_suffix}}(ores::nats::message msg) {
        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;
        service::{{entity_singular}}_service svc(req_ctx);
        if (auto req = decode<get_{{entity_plural}}_{{name_suffix}}_request>(msg)) {
            get_{{entity_plural}}_{{name_suffix}}_response resp;
            try {
                resp.{{entity_plural_short}} =
                    svc.{{service_method}}(req->{{filter_column}}, req->offset, req->limit);
                resp.total_available_count =
                    static_cast<int>(svc.{{count_service_method}}(req->{{filter_column}}));
                resp.success = true;
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                resp.success = false;
                resp.message = e.what();
            }
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                << "Completed " << msg.subject;
            reply(nats_, msg, resp);
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

{{/extra_list_requests}}
{{#has_parent_id}}
    void hierarchy(ores::nats::message msg) {
        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;
        service::{{entity_singular}}_service svc(req_ctx);
        if (auto req = decode<get_{{entity_singular}}_hierarchy_request>(msg)) {
            try {
                boost::uuids::string_generator gen;
                auto roots = svc.get_hierarchy(gen(req->root_id), req->from_root);
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject;
                reply(nats_, msg, get_{{entity_singular}}_hierarchy_response{
                    .success = true, .roots = std::move(roots)});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, get_{{entity_singular}}_hierarchy_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

{{/has_parent_id}}
{{#read_for_cache}}
    void read_for_cache(ores::nats::message msg) {
        BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
            << "Handling " << msg.subject;
        // Authentication-only, deliberately not tenant-scoped: this proves
        // the caller holds *a* valid signed JWT, but does not check that
        // token's own tenant against req->tenant_id below (the tenant a
        // cache-warming service account reads is unrelated to any tenant
        // its own token carries). Do not copy this method as a template
        // for a tenant-authorized endpoint.
        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;
        }
        if (auto req = decode<read_{{entity_plural}}_for_cache_request>(msg)) {
            try {
                using ores::database::service::tenant_context;
                auto tctx = tenant_context::with_tenant(ctx_, req->tenant_id);
                service::{{entity_singular}}_service svc(tctx);
                // No dedicated unpaginated list method is generated; reuse
                // the paginated one with an unbounded limit.
                auto {{entity_plural_short}} = svc.list_{{entity_plural_short}}(
                    0, std::numeric_limits<std::uint32_t>::max());
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject << " (tenant=" << req->tenant_id
                    << ", count=" << {{entity_plural_short}}.size() << ")";
                reply(nats_, msg, read_{{entity_plural}}_for_cache_response{
                    .success = true, .{{entity_plural_short}} = std::move({{entity_plural_short}})});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, read_{{entity_plural}}_for_cache_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

{{/read_for_cache}}
<<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"

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 namespace ores::logging;

/**
 * @brief NATS message handler for {{name_words}} operations.
 */
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)
        : nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)) {}

{{#left.list_by}}
    void list_by_{{left.column_short}}(ores::nats::message msg) {
        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;
        service::{{name_singular}}_service svc(req_ctx);
        if (auto req = decode<get_{{name}}_by_{{left.column_short}}_request>(msg)) {
            get_{{name}}_by_{{left.column_short}}_response resp;
            try {
                resp.{{name}} = svc.list_{{name_short}}_by_{{left.column_short}}(
                    req->{{left.column}}, req->offset, req->limit);
                resp.total_available_count = static_cast<int>(
                    svc.get_total_{{name_singular_short}}_count_by_{{left.column_short}}(req->{{left.column}}));
                resp.success = true;
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{name_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                resp.success = false;
                resp.message = e.what();
            }
            BOOST_LOG_SEV({{name_singular}}_handler_lg(), debug)
                << "Completed " << msg.subject;
            reply(nats_, msg, resp);
        } else {
            BOOST_LOG_SEV({{name_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

{{/left.list_by}}
{{#right.list_by}}
    void list_by_{{right.column_short}}(ores::nats::message msg) {
        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;
        service::{{name_singular}}_service svc(req_ctx);
        if (auto req = decode<get_{{name}}_by_{{right.column_short}}_request>(msg)) {
            get_{{name}}_by_{{right.column_short}}_response resp;
            try {
                resp.{{name}} = svc.list_{{name_short}}_by_{{right.column_short}}(
                    req->{{right.column}}, req->offset, req->limit);
                resp.total_available_count = static_cast<int>(
                    svc.get_total_{{name_singular_short}}_count_by_{{right.column_short}}(req->{{right.column}}));
                resp.success = true;
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{name_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                resp.success = false;
                resp.message = e.what();
            }
            BOOST_LOG_SEV({{name_singular}}_handler_lg(), debug)
                << "Completed " << msg.subject;
            reply(nats_, msg, resp);
        } else {
            BOOST_LOG_SEV({{name_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

{{/right.list_by}}
<<paste:29297654-A74D-492F-B77C-B3A201474D86>>
private:
    ores::nats::service::client& nats_;
    ores::database::context ctx_;
    std::optional<ores::security::jwt::jwt_authenticator> verifier_;
};

} // namespace ores::{{component}}::messaging

#endif
{{/junction}}
{{#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}}

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 and history
 * 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)
        : nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)) {}

    void list(ores::nats::message msg) {
        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;
{{#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}}
        get_{{entity_plural}}_response resp;
        if (auto req = decode<get_{{entity_plural}}_request>(msg)) {
            try {
                resp.{{entity_plural_short}} = svc.list_{{entity_plural_short}}(req->offset, req->limit);
                resp.total_available_count = static_cast<int>(svc.count_{{entity_plural_short}}());
                resp.success = true;
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                resp.success = false;
                resp.message = e.what();
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
            return;
        }
        BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
            << "Completed " << msg.subject;
        reply(nats_, msg, resp);
    }

    void save(ores::nats::message msg) {
        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;
        if (!has_permission(req_ctx, "{{component}}::{{entity_plural}}:write")) {
            error_reply(nats_, msg, ores::service::error_code::forbidden);
            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}}
        if (auto req = decode<save_{{entity_singular}}_request>(msg)) {
            try {
                svc.save_{{entity_singular_short}}(req->data);
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject;
                reply(nats_, msg,
                    save_{{entity_singular}}_response{.success = true});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, save_{{entity_singular}}_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

    void history(ores::nats::message msg) {
        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;
{{#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}}
        if (auto req = decode<get_{{entity_singular}}_history_request>(msg)) {
            try {
                auto hist = svc.get_{{entity_singular_short}}_history(req->{{history_request_id_field}}{{#history_request_extra_args}}, req->{{name}}{{/history_request_extra_args}});
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject;
                reply(nats_, msg, get_{{entity_singular}}_history_response{
                    .history = std::move(hist), .success = true});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, get_{{entity_singular}}_history_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

    void remove(ores::nats::message msg) {
        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;
        if (!has_permission(req_ctx, "{{component}}::{{entity_plural}}:delete")) {
            error_reply(nats_, msg, ores::service::error_code::forbidden);
            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}}
        if (auto req = decode<delete_{{entity_singular}}_request>(msg)) {
            try {
{{#single_delete}}
                svc.delete_{{entity_singular_short}}(req->{{delete_request_id_field}}{{#delete_request_extra_args}}, req->{{name}}{{/delete_request_extra_args}});
{{/single_delete}}
{{^single_delete}}
                svc.delete_{{entity_plural_short}}(req->{{delete_request_id_field}}{{#delete_request_extra_args}}, req->{{name}}{{/delete_request_extra_args}});
{{/single_delete}}
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), debug)
                    << "Completed " << msg.subject;
                reply(nats_, msg,
                    delete_{{entity_singular}}_response{.success = true});
            } catch (const std::exception& e) {
                BOOST_LOG_SEV({{entity_singular}}_handler_lg(), error)
                    << msg.subject << " failed: " << e.what();
                reply(nats_, msg, delete_{{entity_singular}}_response{
                    .success = false, .message = e.what()});
            }
        } else {
            BOOST_LOG_SEV({{entity_singular}}_handler_lg(), warn)
                << "Failed to decode: " << msg.subject;
            error_reply(nats_, msg, ores::service::error_code::bad_request);
        }
    }

private:
    ores::nats::service::client& nats_;
    ores::database::context ctx_;
    std::optional<ores::security::jwt::jwt_authenticator> verifier_;
};

} // namespace ores::{{component}}::messaging

#endif
{{/entity}}

See also

Emacs 29.3 (Org mode 9.6.15)