morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
records.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4#include <cassert>
5#include <cstddef>
6#include <glaze/glaze.hpp>
7#include <memory>
8#include <optional>
9#include <string>
10#include <string_view>
11#include <type_traits>
12#include <typeindex>
13#include <utility>
14#include <vector>
15
17#include "model_key.hpp"
18#include "registry.hpp"
19
20/// @file
21/// The registry of record types: the rows a server can push changes about.
22///
23/// A record is a plain reflected struct identified by a key member. Registration
24/// is a static registrar, like an action's, so it is checked by the same
25/// registration-phase latch. The registry stores what was declared; cross-entry
26/// validation (duplicate names, derivation cycles, keyless records that an effect
27/// names) happens when the registry is read for matching, not here.
28
29namespace morph::record {
30
31/// @brief Option for `BRIDGE_REGISTER_RECORD`: the record's key is the named member instead of `id`.
32/// @tparam Member Pointer to a data member of the record, e.g. `&Row::code`.
33template <auto Member>
34struct key {};
35
36/// @brief Option for `BRIDGE_REGISTER_RECORD`: the record has no key.
37struct keyless {};
38
39/// @brief Option for `BRIDGE_REGISTER_RECORD`: a change to any of @p S is a change to this record type.
40/// @tparam S The source record types.
41template <typename... S>
42struct derivedFrom {};
43
44/// @brief Option for `BRIDGE_REGISTER_RECORD`: the record's keys must not reach other principals.
45struct privateKeys {};
46
47/// @brief What one `BRIDGE_REGISTER_RECORD` declared.
48struct RecordInfo {
49 /// The wire name of the record type.
50 std::string name;
51 /// The record's C++ type; records and effects are matched by this identity.
52 std::type_index type;
53 /// The glaze JSON name of the key member; empty for a keyless record or one without an `id` member.
54 std::optional<std::string> keyField;
55 /// The source types this record is derived from.
56 std::vector<std::type_index> derivedFrom;
57 /// True when the record's keys must not reach other principals.
58 bool privateKeys = false;
59 /// True when the key's raw type is `std::string`; false when it is integral or there is no key.
60 bool keyIsString = false;
61};
62
63namespace detail {
64
65/// @brief Detects a `key<>` option and exposes its member pointer.
66/// @tparam T An option.
67template <typename T>
68struct KeyOption {
69 /// True for a `key<>` option.
70 static constexpr bool value = false;
71};
72template <auto Member>
73struct KeyOption<key<Member>> {
74 /// True for a `key<>` option.
75 static constexpr bool value = true;
76 /// The member pointer the option names.
77 static constexpr auto member = Member;
78};
79
80/// @brief Detects a `derivedFrom<>` option.
81/// @tparam T An option.
82template <typename T>
83struct IsDerivedFrom : std::false_type {};
84template <typename... S>
85struct IsDerivedFrom<derivedFrom<S...>> : std::true_type {};
86
87/// @brief The type of the data member a member pointer names.
88/// @tparam M A pointer-to-data-member type.
89template <typename M>
91template <typename Class, typename Member>
92struct MemberType<Member Class::*> {
93 using type = Member;
94};
95
96/// @brief Whether a key member's raw type is `std::string`, looking through a wrapped strong id.
97/// @tparam K The key member's type.
98/// @return True when the raw key type is `std::string`.
99template <typename K>
100constexpr bool keyTypeIsString() {
101 using Plain = std::remove_cvref_t<K>;
102 if constexpr (model::WrappedModelKey<Plain>) {
103 return std::is_same_v<model::detail::UnwrappedKeyOf<Plain>, std::string>;
104 } else {
105 return std::is_same_v<Plain, std::string>;
106 }
107}
108
109/// @brief The reflected name of a data member, found by its address in a value-initialised object.
110/// @tparam Class The record type.
111/// @tparam Member The member's type.
112/// @param member The member.
113/// @return The glaze JSON key of the member, or empty when glaze does not reflect it.
114template <typename Class, typename Member>
115[[nodiscard]] std::string memberKeyName(Member Class::* member) {
116 Class const probe{};
117 void const* const target = std::addressof(probe.*member);
118 std::string found;
119 [&]<std::size_t... Index>(std::index_sequence<Index...>) {
120 auto visit = [&]<std::size_t I>() {
121 decltype(auto) value = morph::detail::reflectedMember<I>(probe);
122 if constexpr (std::is_reference_v<decltype(value)>) {
123 if (static_cast<void const*>(std::addressof(value)) == target) {
124 found = std::string{glz::reflect<Class>::keys[I]};
125 }
126 }
127 };
128 (visit.template operator()<Index>(), ...);
129 }(std::make_index_sequence<glz::reflect<Class>::size>{});
130 return found;
131}
132
133/// @brief The index of the reflected member named `id`, or `size` when there is none.
134/// @tparam T The record type.
135/// @return The index, or `glz::reflect<T>::keys.size()` when no member is named `id`.
136template <typename T>
137constexpr std::size_t idIndex() {
138 constexpr auto keys = glz::reflect<T>::keys;
139 for (std::size_t i = 0; i < keys.size(); ++i) {
140 if (keys[i] == "id") {
141 return i;
142 }
143 }
144 return keys.size();
145}
146
147/// @brief Fills the key fields of @p info from the options.
148/// @tparam T The record type.
149/// @tparam Opts The options.
150/// @param info The entry being built.
151template <typename T, typename... Opts>
152void applyKey(RecordInfo& info) {
153 constexpr bool keyless_ = (std::is_same_v<Opts, keyless> || ...);
154 constexpr int explicitKeys = (static_cast<int>(KeyOption<Opts>::value) + ... + 0);
155 static_assert(explicitKeys <= 1, "BRIDGE_REGISTER_RECORD: at most one key<> option");
156 static_assert(!(keyless_ && explicitKeys > 0), "BRIDGE_REGISTER_RECORD: keyless and key<> contradict");
157 if constexpr (keyless_) {
158 return;
159 } else if constexpr (explicitKeys == 1) {
160 auto const apply = []<typename O>(RecordInfo& target) {
161 if constexpr (KeyOption<O>::value) {
162 using Raw = typename MemberType<std::remove_cv_t<decltype(KeyOption<O>::member)>>::type;
164 "BRIDGE_REGISTER_RECORD: a key member must be a ModelKey");
166 assert(!name.empty() && "BRIDGE_REGISTER_RECORD: key<> names a member glaze does not reflect");
167 if (!name.empty()) {
168 target.keyField = std::move(name);
169 }
170 target.keyIsString = keyTypeIsString<Raw>();
171 }
172 };
173 (apply.template operator()<Opts>(info), ...);
174 } else if constexpr (constexpr std::size_t index = idIndex<T>(); index < glz::reflect<T>::size) {
175 using Raw = std::remove_cvref_t<decltype(morph::detail::reflectedMember<index>(std::declval<T&>()))>;
176 static_assert(model::ModelKey<Raw>, "BRIDGE_REGISTER_RECORD: the `id` member must be a ModelKey");
177 info.keyField = "id";
178 info.keyIsString = keyTypeIsString<Raw>();
179 }
180}
181
182/// @brief The process-level record list, appended to by static registrars.
183/// @return The mutable list.
184inline std::vector<RecordInfo>& mutableRecordRegistry() {
185 static std::vector<RecordInfo> entries;
186 return entries;
187}
188
189/// @brief The records declared so far, raw and unvalidated.
190/// @return The entries in registration order.
191inline std::vector<RecordInfo> const& recordRegistry() { return mutableRecordRegistry(); }
192
193/// @brief Static-init helper for `BRIDGE_REGISTER_RECORD`.
194/// @tparam T The record type.
195/// @tparam Opts Any of `key<>`, `keyless`, `derivedFrom<>`, `privateKeys`.
196/// @param name The record's wire name.
197/// @return Always true, so the result can initialise a namespace-scope variable.
198template <typename T, typename... Opts>
199inline bool registerRecordOnce(std::string_view name) noexcept {
200 // NOLINTNEXTLINE(misc-static-assert,cert-dcl03-c)
201 assert(!::morph::model::registrationPhaseClosed() &&
202 "registerRecordOnce: registration after the registration phase closed. The process-level "
203 "registries are unsynchronised and are read-only once dispatch begins -- registering now "
204 "races their internals against concurrent lookups. Load and register plugin modules before "
205 "the first dispatch.");
206 RecordInfo info{.name = std::string{name}, .type = std::type_index{typeid(T)}, .keyField = {}, .derivedFrom = {}};
207 applyKey<T, Opts...>(info);
208 info.privateKeys = (std::is_same_v<Opts, privateKeys> || ... || false);
209 // Unused when the record takes no options: the fold below is then empty.
210 [[maybe_unused]] auto const addSources = []<typename O>(RecordInfo& target) {
211 if constexpr (IsDerivedFrom<O>::value) {
212 []<typename... S>(RecordInfo& out, derivedFrom<S...>) { (out.derivedFrom.emplace_back(typeid(S)), ...); }(
213 target, O{});
214 }
215 };
216 (addSources.template operator()<Opts>(info), ...);
217 mutableRecordRegistry().push_back(std::move(info));
218 return true;
219}
220
221} // namespace detail
222} // namespace morph::record
223
224/// @def BRIDGE_REGISTER_RECORD
225/// @brief Registers a record type under a wire name, with optional key, derivation and privacy options.
226///
227/// The key defaults to the member reflected as `id`. Options are `morph::record::key<&T::m>`,
228/// `morph::record::keyless`, `morph::record::derivedFrom<S...>` and `morph::record::privateKeys`. Expands to
229/// nothing under `MORPH_CLIENT_ONLY`.
230/// @param T The record type.
231/// @param NAME The record's wire name.
232/// @param ... The options.
233// Each translation unit that includes a registration registers its own copy, which is the point, so the unnamed
234// namespace in a header and its static initialiser are suppressed here, once, rather than at every use.
235// NOLINTBEGIN(bugprone-throwing-static-initialization,cert-err58-cpp,misc-anonymous-namespace-in-header,cert-dcl59-cpp)
236#ifdef MORPH_CLIENT_ONLY
237#define BRIDGE_REGISTER_RECORD(T, NAME, ...)
238#else
239#define BRIDGE_REGISTER_RECORD(T, NAME, ...) \
240 namespace { \
241 [[maybe_unused]] const bool BRIDGE_DETAIL_CAT(bridge_record_reg_, __COUNTER__) = \
242 morph::record::detail::registerRecordOnce<T __VA_OPT__(, ) __VA_ARGS__>(NAME); \
243 }
244#endif
245// NOLINTEND(bugprone-throwing-static-initialization,cert-err58-cpp,misc-anonymous-namespace-in-header,cert-dcl59-cpp)
Key types a model may declare as its PrimaryKey.
Definition model_key.hpp:74
Key types that wrap a RawModelKey — the ladder's strong ids.
Definition model_key.hpp:62
std::string memberKeyName(Member Class::*member)
The reflected name of a data member, found by its address in a value-initialised object.
Definition records.hpp:115
std::vector< RecordInfo > const & recordRegistry()
The records declared so far, raw and unvalidated.
Definition records.hpp:191
void applyKey(RecordInfo &info)
Fills the key fields of info from the options.
Definition records.hpp:152
constexpr std::size_t idIndex()
The index of the reflected member named id, or size when there is none.
Definition records.hpp:137
bool registerRecordOnce(std::string_view name) noexcept
Static-init helper for BRIDGE_REGISTER_RECORD.
Definition records.hpp:199
std::vector< RecordInfo > & mutableRecordRegistry()
The process-level record list, appended to by static registrars.
Definition records.hpp:184
constexpr bool keyTypeIsString()
Whether a key member's raw type is std::string, looking through a wrapped strong id.
Definition records.hpp:100
The value glaze reads and writes under a reflected key.
constexpr decltype(auto) reflectedMember(T &object)
The value glaze reads and writes under glz::reflect<T>::keys[I].
What one BRIDGE_REGISTER_RECORD declared.
Definition records.hpp:48
std::vector< std::type_index > derivedFrom
The source types this record is derived from.
Definition records.hpp:56
std::type_index type
The record's C++ type; records and effects are matched by this identity.
Definition records.hpp:52
bool keyIsString
True when the key's raw type is std::string; false when it is integral or there is no key.
Definition records.hpp:60
std::string name
The wire name of the record type.
Definition records.hpp:50
bool privateKeys
True when the record's keys must not reach other principals.
Definition records.hpp:58
std::optional< std::string > keyField
The glaze JSON name of the key member; empty for a keyless record or one without an id member.
Definition records.hpp:54
Option for BRIDGE_REGISTER_RECORD: a change to any of S is a change to this record type.
Definition records.hpp:42
Detects a derivedFrom<> option.
Definition records.hpp:83
Detects a key<> option and exposes its member pointer.
Definition records.hpp:68
static constexpr bool value
True for a key<> option.
Definition records.hpp:70
The type of the data member a member pointer names.
Definition records.hpp:90
Option for BRIDGE_REGISTER_RECORD: the record's key is the named member instead of id.
Definition records.hpp:34
Option for BRIDGE_REGISTER_RECORD: the record has no key.
Definition records.hpp:37
Option for BRIDGE_REGISTER_RECORD: the record's keys must not reach other principals.
Definition records.hpp:45