ores.cpp.outcome.outcome_code_header

Table of Contents

The outcome_code enum with its coarse-outcome map and its message renderer, from the outcome catalogue. outcome_catalogue 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 codegen_templates to regenerate library/templates/cpp_outcome_code.hpp.mustache.

{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/ores.cpp.outcome.outcome_code_header.org. Edit the org source. }}
{{{cpp_license}}}
#ifndef ORES_DATABASE_DOMAIN_OUTCOME_CODE_HPP
#define ORES_DATABASE_DOMAIN_OUTCOME_CODE_HPP

#include "ores.utility/domain/protocol.hpp"

#include <string>
#include <string_view>

namespace ores::database::domain {

/**
 * @brief The values a refusal's message is filled from.
 *
 * Every name in the outcome catalogue's argument vocabulary is a member here,
 * whether or not the outcome being described uses it. A message names only the
 * members its own entry declared, so an unfilled member is never read.
 */
struct outcome_args {
    std::string entity;
    std::string field;
    std::string value;
    std::string expected;
    std::string current;
    std::string reason;
    std::string limit;
};

/**
 * @brief Every way this system refuses a request.
 *
 * Rendered from the outcome catalogue, which is the single source of truth for
 * the code, the coarse outcome, the SQLSTATE and the wording. A store outcome
 * is refused by a PostgreSQL trigger, which composes the same sentence through
 * the matching ores_outcome_<code>_fn; a request outcome is refused here.
 */
enum class outcome_code {
{{#outcome_catalogue.outcomes}}
    {{code}},
{{/outcome_catalogue.outcomes}}
};

/**
 * @brief The string a reply carries in @c result.code.
 */
[[nodiscard]] constexpr std::string_view to_string(outcome_code v) noexcept {
    switch (v) {
{{#outcome_catalogue.outcomes}}
    case outcome_code::{{code}}: return "{{code}}";
{{/outcome_catalogue.outcomes}}
    default: return {};
    }
}

/**
 * @brief The coarse outcome a reply carries for @p v.
 */
[[nodiscard]] constexpr ores::utility::domain::outcome outcome_of(outcome_code v) noexcept {
    switch (v) {
{{#outcome_catalogue.outcomes}}
    case outcome_code::{{code}}: return ores::utility::domain::outcome::{{class}};
{{/outcome_catalogue.outcomes}}
    default: return ores::utility::domain::outcome::failed;
    }
}

namespace detail {

inline void replace_all(std::string& text, std::string_view from, std::string_view to) {
    for (auto pos = text.find(from); pos != std::string::npos;
         pos = text.find(from, pos + to.size()))
        text.replace(pos, from.size(), to);
}

/**
 * @brief Fills a message template's {name} placeholders from @p a.
 *
 * The one renderer, shared by every outcome. It substitutes each name whether
 * the template names it or not, so a template that names a subset is filled
 * from the same call as one that names every member.
 */
inline std::string fill(std::string_view tmpl, const outcome_args& a) {
    std::string text(tmpl);
    replace_all(text, "{entity}", a.entity);
    replace_all(text, "{field}", a.field);
    replace_all(text, "{value}", a.value);
    replace_all(text, "{expected}", a.expected);
    replace_all(text, "{current}", a.current);
    replace_all(text, "{reason}", a.reason);
    replace_all(text, "{limit}", a.limit);
    return text;
}

}

/**
 * @brief The sentence a caller reads for @p v, filled from @p a.
 *
 * The same sentence the store composes through ores_outcome_<code>_fn, so a
 * client cannot tell which side refused from the words alone.
 */
[[nodiscard]] inline std::string describe(outcome_code v, const outcome_args& a) {
    switch (v) {
{{#outcome_catalogue.outcomes}}
    case outcome_code::{{code}}: return detail::fill("{{message_cpp}}", a);
{{/outcome_catalogue.outcomes}}
    default: return {};
    }
}

/**
 * @brief The reply for a refusal the caller can name but not describe.
 *
 * A repository reports a status, not the row behind it, so a service that
 * refuses on that status has no arguments to fill a sentence with. The code
 * and the coarse outcome still come from the catalogue, so nothing restates
 * them; the reply carries no message.
 */
[[nodiscard]] inline ores::utility::domain::result refuse(outcome_code v) {
    ores::utility::domain::result r;
    r.outcome = outcome_of(v);
    r.code = std::string(to_string(v));
    return r;
}

/**
 * @brief The reply for a refusal, with its catalogue sentence filled from @p a.
 *
 * The one constructor for a described refusal, so a service states which
 * outcome it reached and nothing else about how to say it.
 */
[[nodiscard]] inline ores::utility::domain::result refuse(outcome_code v,
                                                          const outcome_args& a) {
    auto r = refuse(v);
    r.message = describe(v, a);
    return r;
}

}

#endif

2. See also

Emacs 29.3 (Org mode 9.6.15)