75 checkName(command.name,
"a host command");
76 if (_commands.contains(command.name)) {
77 throw std::invalid_argument{
"host command `" + command.name +
"` is registered twice"};
80 throw std::invalid_argument{
"host command `" + command.name +
"` has nothing to run"};
82 Registered registered;
83 for (
HostArg const& arg : command.args) {
84 if (arg.
name.empty() || registered.args.contains(arg.
name)) {
85 throw std::invalid_argument{
"host command `" + command.name +
"` has an empty or repeated argument"};
87 registered.args.emplace(arg.
name, typeOf(arg.
type,
"argument `" + arg.
name +
"` of `" + command.name));
89 registered.command = std::move(command);
90 std::string name = registered.command.
name;
91 _commands.emplace(std::move(name), std::move(registered));
99 checkName(value.
name,
"a host value");
100 if (_valueTypes.contains(value.
name)) {
101 throw std::invalid_argument{
"host value `" + value.
name +
"` is registered twice"};
103 Type type = typeOf(value.
type,
"host value `" + value.
name);
105 throw std::invalid_argument{
"the initial value of `" + value.
name +
"` is not a " + type.
name()};
107 _valueTypes.emplace(value.
name, std::move(type));
109 _values.insert_or_assign(std::move(value.
name), std::move(value.
initial));
118 auto const type = _valueTypes.find(name);
119 if (type == _valueTypes.end() || !type->second.admits(value)) {
122 _values.insert_or_assign(name, std::move(value));
123 if (_session !=
nullptr) {
124 _session->setHostValues(_values);
133 _verified = verified;
149 for (
auto const& [name, registered] : _commands) {
150 hello.
hostCommands.insert_or_assign(name, registered.command.version);
152 for (
auto const& [name, version] : _valueVersions) {
153 hello.
hostValues.insert_or_assign(name, version);
163 auto const found = _commands.find(name);
164 if (found == _commands.end()) {
167 Registered
const& registered = found->second;
169 return refused(
"host command `" + name +
"` needs a verified connection");
173 if (given ==
nullptr && !args.
isNull()) {
174 return refused(
"the arguments of host command `" + name +
"` are a record");
176 if (given !=
nullptr) {
177 for (
auto const& [argName, value] : *given) {
178 static_cast<void>(value);
179 if (!registered.args.contains(argName)) {
180 std::string reason =
"host command `" + name;
181 reason +=
"` has no argument `";
184 return refused(std::move(reason));
188 for (
auto const& [argName, type] : registered.args) {
190 if (given !=
nullptr) {
191 if (
auto const supplied = given->find(argName); supplied != given->end()) {
192 value = supplied->second;
195 if (!type.admits(value)) {
196 std::string reason =
"argument `" + argName;
197 reason +=
"` of host command `";
199 reason +=
"` is not a ";
200 reason += type.name();
201 return refused(std::move(reason));
203 checked.insert_or_assign(argName, std::move(value));
205 registered.command.
run(checked);
212 std::map<std::string, Type, std::less<>> args;
215 [[nodiscard]]
static HostCommandResult refused(std::string reason) {
219 static void checkName(std::string
const& name, std::string_view what) {
221 throw std::invalid_argument{std::string{what} +
" has a name"};
223 if (
auto reason = detail::checkName(name, what)) {
224 throw std::invalid_argument{*reason};
229 [[nodiscard]]
static Type typeOf(std::string
const& text, std::string
const& what) {
232 throw std::invalid_argument{
"the type of " + what +
"` does not parse: " + type.error()};
234 return std::move(*type);
239 if (_session ==
nullptr) {
242 std::set<std::string, std::less<>> available;
243 for (
auto const& [name, registered] : _commands) {
244 if (registered.command.safeForUntrusted || _verified) {
245 available.insert(name);
248 _session->runtime().batch([&] {
249 _session->setHostValues(_values);
250 _session->setHostCommands(
251 HostCommands{.available = std::move(available),
252 .run = [
this](std::string
const& name, Value
const& args) {
return run(name, args); }});
256 std::map<std::string, Registered, std::less<>> _commands;
257 std::map<std::string, Type, std::less<>> _valueTypes;
258 std::map<std::string, std::int64_t, std::less<>> _valueVersions;
261 bool _verified =
false;
What the UI service's client and server share (spec 5 §12): content hashes, the bundle's manifest,...
The client's host vocabulary: what it registered, and whether its documents came over a verified conn...
void connect(Session &session)
Gives session the values, the names {"can": "host:name"} answers for, and the runner for {"host": nam...
void add(HostCommand command)
Registers a command.
HostCommandResult run(std::string const &name, Value const &args) const
Runs a command as a document's {"host": name, "args": args} does.
void add(HostValue value)
Registers a value.
bool set(std::string const &name, Value value)
Changes a value; every binding on it updates.
void describe(HelloRequest &hello) const
Fills the host command and value names and versions of a ui-hello request, so the server knows what t...
void setConnectionVerified(bool verified)
Says whether the documents came over a verified connection. A command not marked safeForUntrusted run...
What screens of one client session share (spec 5 §5, §12b): the query cache, the mounted queries,...
The document's typed value (spec 5 §3): what every expression evaluates to.
bool isNull() const noexcept
Whether the value is null.
ValueRecord const * asRecord() const noexcept
The record, if held.
@ Session
{"session": "name"}: a session attribute.
The UI document interpreter: Interpreter::mount turns a loaded Document into reactive state and a vie...
The body of a ui-hello request: what the client speaks and carries.
std::map< std::string, std::int64_t, std::less<> > hostCommands
The host commands the client has, by name, with their versions.
std::map< std::string, std::int64_t, std::less<> > hostValues
The host values the client has, by name, with their versions.
One argument of a host command.
std::string name
The argument's name, the key of the document's args record.
std::string type
Its document type, such as string, int? or a list of strings.
What running a host command (host/1) did.
@ Missing
The client has no such command; nothing ran.
@ Refused
The command exists and did not run: its arguments, or a connection that is not verified.
A command the application registers, such as restartApp or openPath.
std::string name
The command's name, as {"host": name} gives it.
std::int64_t version
Its version, declared in ui-hello.
std::function< void(ValueRecord const &)> run
Runs the command with its checked arguments, by name; an absent optional argument is null.
bool safeForUntrusted
Whether a document that came over a connection that is not verified may run it. A command that opens ...
std::vector< HostArg > args
Its arguments. An optional type makes the argument optional.
A value the application registers, such as sync.needsYouCount.
Value initial
Its value until the application sets another; it must have the type.
std::int64_t version
Its version, declared in ui-hello.
std::string type
Its document type.
std::string name
The value's name, as {"host": name} reads it.
A document type (spec 5 §3): a kind, whether null is allowed, and a list's element type.
std::string name() const
The type as a document spells it.
bool admits(Value const &value) const
Whether a value conforms to the type.
The UI document's typed values (ui::Value), its types (ui::Type), their JSON form,...
std::expected< Type, std::string > parseType(std::string_view text)
Parses a document type: bool, int, string, decimal, quantity, timestamp, key, record,...
std::map< std::string, Value, std::less<> > ValueRecord
A record value: field name to value, ordered by name.