morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
handler_submitter.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4#include <exception>
5#include <functional>
6#include <optional>
7#include <stdexcept>
8#include <string>
9#include <string_view>
10#include <utility>
11#include <vector>
12
13#include "../../core/bridge.hpp"
14#include "../../core/completion.hpp"
15#include "../../core/executor.hpp"
16#include "form_session.hpp"
17
18/// @file
19/// @brief Submitters and choice fetchers bound to an application's own `BridgeHandler`s.
20///
21/// Specified in `docs/spec/forms/engine.md`, "Submitters".
22
23namespace morph::forms {
24
25namespace detail::engine {
26
27/// @brief Executes an action through one handler, or declines when the handler does not serve it.
28using JsonRoute =
29 std::function<std::optional<async::Completion<std::string>>(std::string_view actionType, std::string const& body)>;
30
31/// @brief The route through @p handler.
32/// @tparam Model The handler's model.
33/// @tparam Sharing The handler's sharing policy.
34/// @param handler The handler; it must outlive the route.
35/// @return A route that serves exactly the actions @p handler's model registers.
36template <class Model, class Sharing>
37[[nodiscard]] JsonRoute routeThrough(bridge::BridgeHandler<Model, Sharing>& handler) {
38 return [&handler](std::string_view actionType,
39 std::string const& body) -> std::optional<async::Completion<std::string>> {
40 if (!handler.servesAction(actionType)) {
41 return std::nullopt;
42 }
43 return handler.executeJson(actionType, body);
44 };
45}
46
47/// @brief Runs @p body through the first route that serves @p actionType.
48/// @param routes The routes, in priority order.
49/// @param callbacks Where a refusal is delivered.
50/// @param actionType The registered action id.
51/// @param body The JSON body.
52/// @return The route's completion, or one failed with `std::invalid_argument` when no route serves the action.
53[[nodiscard]] inline async::Completion<std::string> routeFirst(std::vector<JsonRoute> const& routes,
54 exec::IExecutor& callbacks, std::string_view actionType,
55 std::string const& body) {
56 for (JsonRoute const& route : routes) {
57 if (std::optional<async::Completion<std::string>> completion = route(actionType, body)) {
58 return std::move(*completion);
59 }
60 }
61 auto [failed, promise] = async::Completion<std::string>::makeSettleable(&callbacks);
62 promise.reject(std::make_exception_ptr(
63 std::invalid_argument{"no handler serves the action \"" + std::string{actionType} + "\""}));
64 return std::move(failed);
65}
66
67} // namespace detail::engine
68
69/// @brief A `Submitter` that sends each action through the first of @p handlers whose model registers it.
70///
71/// Use it when a form's submissions must reach the model instance the rest of the screen uses — an in-memory
72/// model, or a shared instance another handler attached. `bridgeSubmitter` keeps handlers of its own instead.
73/// @tparam Handlers `bridge::BridgeHandler<M, S>` types.
74/// @param callbacks The executor a refusal is delivered on: the handlers' GUI executor.
75/// @param handlers The handlers, in priority order; they must outlive the returned submitter.
76/// @return The submitter.
77template <class... Handlers>
78 requires(sizeof...(Handlers) > 0)
79[[nodiscard]] Submitter handlerSubmitter(exec::IExecutor& callbacks, Handlers&... handlers) {
80 std::vector<detail::engine::JsonRoute> routes{detail::engine::routeThrough(handlers)...};
81 return [routes = std::move(routes), &callbacks](std::string_view actionType, const std::string& bodyJson) {
82 return detail::engine::routeFirst(routes, callbacks, actionType, bodyJson);
83 };
84}
85
86/// @brief A `ChoiceFetcher` that runs each options action through the first of @p handlers that serves it.
87/// @tparam Handlers `bridge::BridgeHandler<M, S>` types.
88/// @param callbacks The executor a refusal is delivered on: the handlers' GUI executor.
89/// @param handlers The handlers, in priority order; they must outlive the returned fetcher.
90/// @return The fetcher.
91template <class... Handlers>
92 requires(sizeof...(Handlers) > 0)
93[[nodiscard]] ChoiceFetcher handlerChoiceFetcher(exec::IExecutor& callbacks, Handlers&... handlers) {
94 std::vector<detail::engine::JsonRoute> routes{detail::engine::routeThrough(handlers)...};
95 return [routes = std::move(routes), &callbacks](std::string_view optionsAction, const std::string& bodyJson) {
96 return detail::engine::routeFirst(routes, callbacks, optionsAction, bodyJson);
97 };
98}
99
100} // namespace morph::forms
static std::pair< Completion< T >, Promise > makeSettleable(::morph::exec::IExecutor *execPtr)
Constructs a Completion<T>/Promise<T> pair sharing one settleable state.
FormSession: the reactive state of one form over a FormModel — drafts, encoded values,...
std::function< async::Completion< std::string >(std::string_view optionsAction, std::string bodyJson)> ChoiceFetcher
Executes a Choice's options action: the action and its JSON request in, the reply out.
std::function< async::Completion< std::string >(std::string_view actionType, std::string bodyJson)> Submitter
Executes a form's action: the action type and its JSON body in, the JSON reply out.
Submitter handlerSubmitter(exec::IExecutor &callbacks, Handlers &... handlers)
A Submitter that sends each action through the first of handlers whose model registers it.
ChoiceFetcher handlerChoiceFetcher(exec::IExecutor &callbacks, Handlers &... handlers)
A ChoiceFetcher that runs each options action through the first of handlers that serves it.
Abstract executor interface.
Definition executor.hpp:70