morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
choice.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5/// @file forms/choice.hpp
6/// @brief Fields whose value is picked from a list served by another action.
7///
8/// A `morph::forms::Choice<T, "ListPayees">` member declares, in the type:
9/// *this field is not free input — its options are the result of executing
10/// the named action.* Renderers turn it into a combo box: they execute the
11/// options action (over the same wire the form actions use), read the result
12/// rows, and offer `labelField` as the display text while submitting
13/// `valueField` as the payload.
14///
15/// @code{.cpp}
16/// struct SampleInfo { std::int64_t id = 0; std::string name; };
17/// struct SampleList { std::vector<SampleInfo> samples; };
18/// struct ListSamples {}; // registered like any other action (a pure query)
19///
20/// struct RecordMeasurement {
21/// morph::forms::Choice<std::int64_t, "ListSamples"> sampleId; // combo box
22/// ...
23/// };
24/// @endcode
25///
26/// In the generated schema the declaration surfaces as
27/// `x-optionsAction` / `x-optionValue` / `x-optionLabel` on the property
28/// (added by `morph::forms::schemaJson`), so a client knows *which action to
29/// call* and *which result fields to use* without hardcoding anything.
30///
31/// A `Choice` can also declare a `DependsOn` pack naming sibling fields whose
32/// current values parameterise the options action — a cascading picklist
33/// (e.g. the list of cities depends on the selected country). The options
34/// action then receives `{name: value, ...}` instead of an empty body, and
35/// the schema additionally carries `x-optionsDependsOn`. See
36/// `optionsDependsOn()` below; a `Choice` with no `DependsOn` is unaffected.
37///
38/// On the wire a `Choice` is its nullable underlying value (`T`); the
39/// options metadata never travels with payloads. Like `Quantity` and
40/// `Timestamp`, the blank state ("nothing selected") lives inside, and a
41/// non-optional `Choice` member is *required* by the `morph::forms` rules.
42
43#include <array>
44#include <cstddef>
45#include <glaze/glaze.hpp>
46#include <optional>
47#include <string_view>
48#include <type_traits>
49
50#include "../attributes.hpp"
53
54namespace morph::forms {
55
56/// @brief A structural, NTTP-capable compile-time string (for naming the
57/// options action and its value/label result fields in the type).
58///
59/// An alias for the shared `morph::detail::FixedString` — the same underlying
60/// type the units layer's `NamedQuantity` uses — so there is one definition, not
61/// two look-alikes. Kept as a public `morph::forms::` name for source
62/// compatibility.
63/// @tparam N Storage size including the terminating null.
64template <std::size_t N>
66
67/// @brief An optionally-empty value selected from options served by another
68/// action.
69///
70/// @tparam T Underlying value type submitted on the wire (e.g.
71/// `std::int64_t` for ids, `std::string` for codes).
72/// @tparam OptionsAction Type id of the registered action whose result
73/// provides the options. Executed with an empty body
74/// when `DependsOn` is empty (the common case);
75/// otherwise with a body built from the current
76/// values of the named sibling fields.
77/// @tparam ValueField Field of each result row submitted as the value.
78/// @tparam LabelField Field of each result row shown to the user.
79/// @tparam DependsOn Wire (JSON) field names of sibling fields in the
80/// same action whose current values parameterise the
81/// options action — a cascading picklist. Empty by
82/// default, which makes the options action
83/// independent (today's behavior, unchanged).
84template <typename T, FixedString OptionsAction, FixedString ValueField = "id", FixedString LabelField = "name",
85 FixedString... DependsOn>
86struct Choice {
87 /// @brief The payload; `std::nullopt` means "nothing selected".
88 std::optional<T> value;
89
90 /// @brief Constructs the empty state.
91 constexpr Choice() noexcept = default;
92
93 /// @brief Engages with @p selected.
94 /// @param selected The selected value.
95 constexpr Choice(T selected) noexcept(std::is_nothrow_move_constructible_v<T>) : value{std::move(selected)} {}
96
97 /// @brief Adopts an optional payload as-is.
98 /// @param payload Engaged or empty payload.
99 constexpr Choice(std::optional<T> payload) noexcept(std::is_nothrow_move_constructible_v<T>)
100 : value{std::move(payload)} {}
101
102 /// @brief Type id of the action that serves the options.
103 /// @return The action name declared in the field's type.
104 [[nodiscard]] static constexpr std::string_view optionsAction() noexcept { return OptionsAction.view(); }
105
106 /// @brief Result-row field submitted as the value.
107 /// @return The field name declared in the field's type.
108 [[nodiscard]] static constexpr std::string_view valueField() noexcept { return ValueField.view(); }
109
110 /// @brief Result-row field displayed to the user.
111 /// @return The field name declared in the field's type.
112 [[nodiscard]] static constexpr std::string_view labelField() noexcept { return LabelField.view(); }
113
114 /// @brief Wire field names of sibling fields whose current values
115 /// parameterise the options action (a cascading picklist).
116 /// @return The declared `DependsOn` names, in declaration order; empty
117 /// for an independent `Choice` (the default).
118 [[nodiscard]] static constexpr std::array<std::string_view, sizeof...(DependsOn)> optionsDependsOn() noexcept {
119 return {DependsOn.view()...};
120 }
121
122 /// @brief Whether a value has been selected.
123 /// @return `true` if the payload is engaged.
124 [[nodiscard]] constexpr bool hasValue() const noexcept { return value.has_value(); }
125
126 /// @brief Unchecked access to the selected value (UB when empty, exactly
127 /// like `std::optional` — the unchecked contract is the point).
128 /// @return The selected value — a reference into this `Choice`, valid only
129 /// for as long as it is.
130 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
131 [[nodiscard]] constexpr const T& operator*() const noexcept MORPH_LIFETIMEBOUND { return *value; }
132
133 /// @brief Equality on the payload; empty equals only empty.
134 /// @param other Choice to compare against.
135 /// @return `true` when both are empty or both hold equal values.
136 [[nodiscard]] constexpr bool operator==(const Choice& other) const noexcept = default;
137};
138
139namespace detail {
140
141/// @brief Trait: is `T` some `Choice<...>`?
142template <typename T>
143struct IsChoice : std::false_type {};
144
145template <typename T, FixedString OptionsAction, FixedString ValueField, FixedString LabelField,
146 FixedString... DependsOn>
147struct IsChoice<Choice<T, OptionsAction, ValueField, LabelField, DependsOn...>> : std::true_type {};
148
149} // namespace detail
150
151/// @brief `true` when `T` (cvref-stripped) is a `morph::forms::Choice`.
152template <typename T>
153inline constexpr bool isChoice = detail::IsChoice<std::remove_cvref_t<T>>::value;
154
155} // namespace morph::forms
156
157/// @brief On the wire a Choice is its nullable underlying value — the options
158/// metadata lives in the C++ type and in generated schemas only.
159///
160/// `name` is composed per instantiation rather than being the literal
161/// `"Choice"`: glaze keys `$defs` by it and fills each entry only once, so one
162/// shared name would make the second `Choice` in an action `$ref` the first
163/// one's definition and be described with the wrong payload type. See
164/// `forms/detail/schema_name.hpp` for how the key is built and why it is not
165/// derived from `glz::name_v`.
166template <typename T, morph::forms::FixedString OptionsAction, morph::forms::FixedString ValueField,
168struct glz::meta<morph::forms::Choice<T, OptionsAction, ValueField, LabelField, DependsOn...>> {
169 static constexpr auto value = &morph::forms::Choice<T, OptionsAction, ValueField, LabelField, DependsOn...>::value;
170 static constexpr std::string_view name =
171 morph::forms::detail::choiceSchemaName<OptionsAction, ValueField, LabelField, DependsOn...>;
172};
::morph::detail::FixedString< N > FixedString
A structural, NTTP-capable compile-time string (for naming the options action and its value/label res...
Definition choice.hpp:65
constexpr bool isChoice
true when T (cvref-stripped) is a morph::forms::Choice.
Definition choice.hpp:153
Shared compile-time, NTTP-capable fixed string.
Compile-time $defs keys for the forms layer's field wrappers.
A structural, NTTP-capable compile-time string.
constexpr std::string_view view() const noexcept
A view of the string (without the terminating null).
An optionally-empty value selected from options served by another action.
Definition choice.hpp:86
constexpr bool operator==(const Choice &other) const noexcept=default
Equality on the payload; empty equals only empty.
constexpr bool hasValue() const noexcept
Whether a value has been selected.
Definition choice.hpp:124
static constexpr std::array< std::string_view, sizeof...(DependsOn)> optionsDependsOn() noexcept
Wire field names of sibling fields whose current values parameterise the options action (a cascading ...
Definition choice.hpp:118
constexpr const T & operator*() const noexcept
Unchecked access to the selected value (UB when empty, exactly like std::optional — the unchecked con...
Definition choice.hpp:131
static constexpr std::string_view labelField() noexcept
Result-row field displayed to the user.
Definition choice.hpp:112
constexpr Choice(std::optional< T > payload) noexcept(std::is_nothrow_move_constructible_v< T >)
Adopts an optional payload as-is.
Definition choice.hpp:99
static constexpr std::string_view valueField() noexcept
Result-row field submitted as the value.
Definition choice.hpp:108
constexpr Choice() noexcept=default
Constructs the empty state.
std::optional< T > value
The payload; std::nullopt means "nothing selected".
Definition choice.hpp:88
static constexpr std::string_view optionsAction() noexcept
Type id of the action that serves the options.
Definition choice.hpp:104