ores.cpp.protocol.protocol_header

Table of Contents

Request/response message types for the entity's NATS API. protocol profile. NATS messaging contract: request and response message structs used by the service layer for inter-component communication.

Model flags consumed by this template:

For a junction model the block renders the same canonical protocol an entity renders: a paged list of the junction rows, a read of one row by its pair, a batch read by pair, a paged read per :list_by: side, and the three write verbs as single and batch forms. A junction that declares :read_only: or :client_read_only: renders no write verb. The first also stops the repository writing, for a table provisioned outside the application; the second leaves the repository writable and stops the write surface short of it, at the service methods and the wire verbs, so a server-side producer writes through the repository rather than the service.

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_protocol.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_include_upper}}_MESSAGING_{{entity_singular_upper}}_PROTOCOL_HPP
#define ORES_{{component_include_upper}}_MESSAGING_{{entity_singular_upper}}_PROTOCOL_HPP

#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include <boost/uuid/uuid.hpp>
#include "ores.utility/domain/protocol.hpp"
#include "ores.{{component_include}}/domain/{{entity_singular}}.hpp"
<<paste:8A2E4C7F-1D9B-4E6A-9F3C-5B8D2A6E4C1F>>

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

{{#messages}}
{{#comment}}
{{{.}}}
{{/comment}}
struct {{name}} {
{{#response_type}}
    using response_type = struct {{response_type}};
{{/response_type}}
{{#subject}}
    static constexpr std::string_view nats_subject = "{{subject}}";
    /**
     * @brief Whether the caller must have established a session first.
     *
     * An operation that produces the session cannot present one, so a client
     * reads this rather than assuming every call carries a token.
     */
    static constexpr bool requires_session = {{requires_session}};
{{/subject}}
{{#fields}}
{{#comment}}
{{{.}}}
{{/comment}}
    {{{cpp_type}}} {{name}}{{#default}} = {{{default}}}{{/default}};
{{/fields}}
};

{{/messages}}
/**
 * @brief The subjects this resource's changes are announced on.
 *
 * An event reports what happened and no caller asked for it, so its last
 * segment is the action rather than a verb. One payload is therefore addressed
 * by three subjects, and a subscriber that wants one action subscribes to one
 * of them.
 */
namespace {{entity_singular}}_event_subjects {
{{#events}}
inline constexpr std::string_view {{action}} = "{{subject}}";
{{/events}}
}

<<paste:2C4E8F1A-6B9D-4A3E-8F2C-7D1E5A9B3C6F>>
}

#endif
{{/domain_entity}}
{{#junction}}
#ifndef ORES_{{component_include_upper}}_MESSAGING_{{name_singular_upper}}_PROTOCOL_HPP
#define ORES_{{component_include_upper}}_MESSAGING_{{name_singular_upper}}_PROTOCOL_HPP

#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include <boost/uuid/uuid.hpp>
#include "ores.utility/domain/protocol.hpp"
#include "ores.{{component_include}}/domain/{{name_singular}}.hpp"

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

{{#messages}}
{{#comment}}
{{{.}}}
{{/comment}}
struct {{name}} {
{{#response_type}}
    using response_type = struct {{response_type}};
{{/response_type}}
{{#subject}}
    static constexpr std::string_view nats_subject = "{{subject}}";
    /**
     * @brief Whether the caller must have established a session first.
     *
     * An operation that produces the session cannot present one, so a client
     * reads this rather than assuming every call carries a token.
     */
    static constexpr bool requires_session = {{requires_session}};
{{/subject}}
{{#fields}}
{{#comment}}
{{{.}}}
{{/comment}}
    {{{cpp_type}}} {{name}}{{#default}} = {{{default}}}{{/default}};
{{/fields}}
};

{{/messages}}
}

#endif
{{/junction}}
{{#operation}}
#ifndef ORES_{{component_include_upper}}_MESSAGING_{{entity_singular_upper}}_PROTOCOL_HPP
#define ORES_{{component_include_upper}}_MESSAGING_{{entity_singular_upper}}_PROTOCOL_HPP

{{#includes}}
#include {{{.}}}
{{/includes}}

namespace {{namespace}} {

{{#messages}}
{{#comment}}
{{{.}}}
{{/comment}}
struct {{name}} {
{{#response_type}}
    using response_type = struct {{response_type}};
{{/response_type}}
{{#subject}}
{{^subject_is_derived}}
    static constexpr std::string_view nats_subject = "{{subject}}";
{{/subject_is_derived}}
    /**
     * @brief Whether the caller must have established a session first.
     *
     * An operation that produces the session cannot present one, so a client
     * reads this rather than assuming every call carries a token.
     */
    static constexpr bool requires_session = {{requires_session}};
{{/subject}}
{{#fields}}
{{#comment}}
{{{.}}}
{{/comment}}
    {{{cpp_type}}} {{name}}{{#default}} = {{{default}}}{{/default}};
{{/fields}}
};

{{/messages}}
{{#derived_subject}}

/**
 * @brief The subject these messages are addressed at, for one component.
 *
 * The pattern is stated once, in the model, and this is its only derivation: a
 * service composes the subject it listens on with it, and every client
 * composes the subject it sends to with it, so no two callers can disagree
 * about a subject that is not a constant.
 */
[[nodiscard]] inline std::string {{subject_function_by_component}}(std::string_view component) {
    return {{#derived_subject_prefix}}std::string("{{derived_subject_prefix}}") + {{/derived_subject_prefix}}std::string(component) + std::string("{{derived_subject_suffix}}");
}

/**
 * @brief The subject these messages are addressed at, for the resource a
 * dispatch key names.
 *
 * The same rule as {{subject_function_by_component}}(), reached from the key a
 * client holds rather than from the component a service knows. The hole is the
 * component segment of the key, which is always
 * "<product>.<component>.<entity>" -- so "ores.iam.tenant_type" is addressed
 * at iam.v1.history.get.
 */
[[nodiscard]] inline std::string {{subject_function}}(std::string_view entity_type) {
    const auto first_dot = entity_type.find('.');
    const auto second_dot = first_dot == std::string_view::npos
                                ? std::string_view::npos
                                : entity_type.find('.', first_dot + 1);
    const std::string component =
        first_dot != std::string_view::npos && second_dot != std::string_view::npos
            ? std::string(entity_type.substr(first_dot + 1, second_dot - first_dot - 1))
            : std::string("unknown");
    return {{subject_function_by_component}}(component);
}
{{/derived_subject}}
}

#endif
{{/operation}}

2. See also

Emacs 29.3 (Org mode 9.6.15)