morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
session_common.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5/// @file forms/detail/session_common.hpp
6/// @brief Machinery shared by the two form session types: `morph::flows::FlowSession`
7/// (ordered wizard steps) and `morph::forms::SectionSet` (unordered sections).
8///
9/// Both declare their units the same way — a registered action, a title, and
10/// zero or more `Bind` prefill declarations — and both walk those declarations
11/// to emit a schema document. Only the sequencing differs, so the declaration
12/// vocabulary and the pack/tuple walkers live here rather than being written
13/// twice.
14
15#include <cstddef>
16#include <glaze/glaze.hpp>
17#include <string>
18#include <string_view>
19#include <tuple>
20#include <type_traits>
21#include <utility>
22
23#include "../forms.hpp"
24
25namespace morph::forms {
26
27/// @brief One `field -> "<ActionTypeId>.<field>"` prefill binding declared on a
28/// wizard step or an unordered section.
29///
30/// A declaration, not a write. Nothing in the framework assigns a bound field:
31/// `wizardSchemaJson`/`sectionGroupSchemaJson` emit it for a renderer, and the
32/// captured value is read back through `resolved()`.
33/// @tparam Field The action's field name to prefill.
34/// @tparam Path Source path, `"<ActionTypeId>.<field>"`, into captured values.
35template <morph::forms::FixedString Field, morph::forms::FixedString Path>
36struct Bind {
37 /// @brief The action field name this binding fills.
38 /// @return The declared field name.
39 [[nodiscard]] static constexpr std::string_view field() noexcept { return Field.view(); }
40
41 /// @brief The source path into captured values.
42 /// @return The declared `"<ActionTypeId>.<field>"` path.
43 [[nodiscard]] static constexpr std::string_view path() noexcept { return Path.view(); }
44};
45
46namespace detail {
47
48/// @brief Invokes `visitor.template operator()<std::tuple_element_t<I, Tuple>, I>()`
49/// for every element of @p Tuple, in order.
50/// @tparam Tuple A `std::tuple<...>` type (only its element types/arity are used).
51/// @tparam Visitor Callable with a `template<typename Element, std::size_t I> operator()()`.
52/// @param visitor Callable invoked once per tuple element.
53template <typename Tuple, typename Visitor>
54// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) — invoked once per element, never moved from
55constexpr void forEachTupleElement(Visitor&& visitor) {
56 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) — same: one call per element
57 []<std::size_t... I>(std::index_sequence<I...>, Visitor&& innerVisitor) {
58 (innerVisitor.template operator()<std::tuple_element_t<I, Tuple>, I>(), ...);
59 }(std::make_index_sequence<std::tuple_size_v<Tuple>>{}, std::forward<Visitor>(visitor));
60}
61
62/// @brief Invokes `visitor.template operator()<T>()` for the pack element of
63/// `Ts...` at runtime position @p index. A no-op when
64/// `index >= sizeof...(Ts)`.
65/// @tparam Ts The pack to index into.
66/// @tparam Visitor Callable with a `template<typename T> operator()()`.
67/// @param index 0-based position to visit.
68/// @param visitor Callable invoked for the element at @p index.
69template <typename... Ts, typename Visitor>
70// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward) — the visited element is a run-time choice
71constexpr void forPackElement(std::size_t index, Visitor&& visitor) {
72 std::size_t i = 0;
73 (void)((i++ == index ? (visitor.template operator()<Ts>(), true) : false) || ...);
74}
75
76/// @brief Trait: `true` when every type in `Ts...` is pairwise distinct.
77/// @tparam Ts Types to check for pairwise distinctness.
78template <typename... Ts>
79struct AllDistinct : std::true_type {};
80
81/// @brief Recursive case: `T` distinct from every type in `Rest...`, and `Rest...` pairwise distinct.
82/// @tparam T The type being checked against `Rest...`.
83/// @tparam Rest The remaining types.
84template <typename T, typename... Rest>
85struct AllDistinct<T, Rest...> : std::bool_constant<(!std::is_same_v<T, Rest> && ...) && AllDistinct<Rest...>::value> {
86};
87
88/// @brief Writes each `Bind` in @p BindsTuple into @p node as `"<field>": "<path>"`.
89///
90/// A no-op for an empty tuple, so callers guard on arity only to avoid
91/// materialising an empty `prefill` object.
92/// @tparam BindsTuple `std::tuple<Bind<...>...>`.
93/// @param node Destination JSON object node.
94// NOLINTBEGIN(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access) -- glaze DOM requires operator[]
95template <typename BindsTuple>
96void emitBindsInto(glz::generic_u64& node) {
97 forEachTupleElement<BindsTuple>([&]<typename BindT, std::size_t J>() {
98 static_cast<void>(J);
99 node[std::string{BindT::field()}] = std::string{BindT::path()};
100 });
101}
102// NOLINTEND(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access)
103
104} // namespace detail
105} // namespace morph::forms
JSON-Forms-style schema generation for morph actions.
One field -> "<ActionTypeId>.<field>" prefill binding declared on a wizard step or an unordered secti...
static constexpr std::string_view path() noexcept
The source path into captured values.
static constexpr std::string_view field() noexcept
The action field name this binding fills.