Skip to main content

guinea_core/
remote.rs

1//! Actions and events a tool can send from outside, as JSON.
2//!
3//! Always compiled, and empty until something is registered. A type opts in
4//! with `#[derive(guinea::Remote)]` and says which it is -
5//! `#[remote(action)]`, `#[remote(event)]` or both - and the derive registers
6//! it here. Nothing is reachable by default: what an agent may do to a
7//! running application is what its author listed.
8//!
9//! A type is sent by its short name, `Sort`, or by its path,
10//! `app::processes::Sort`. Two pages may each have a `Sort`: the short name
11//! means whichever one the open page answers, and only when two would answer
12//! in one place does it take the path to say which.
13//!
14//! An action goes to whichever scope answers it, the way a page's
15//! [`Dispatch`](crate::feature::Dispatch) would send it; an event goes out on
16//! the global bus as if it had arrived on the UI thread. Either is one
17//! [`Point::Action`](crate::trace::Point::Action), and its id is handed back,
18//! so what it set off can be followed in the trace.
19
20use serde::de::DeserializeOwned;
21
22use crate::actor::event_bus::{Event, GlobalEventBus};
23use crate::actor::short_type_name;
24use crate::scope::Scope;
25use crate::trace::{self, Point};
26
27/// Hands the decoded action to a scope if the scope answers it: `None` when
28/// it does not, the action's id when it did.
29pub type Emit = fn(Scope, &str) -> Option<Result<u64, String>>;
30
31/// An action a tool may send, registered by the derive.
32pub struct RemoteAction {
33    /// The type's name, without its path.
34    pub name: &'static str,
35    /// The type's module path and name.
36    pub path: &'static str,
37    /// Whether a scope answers it.
38    pub answered_by: fn(Scope) -> bool,
39    pub emit: Emit,
40}
41
42/// An event a tool may publish, registered by the derive.
43pub struct RemoteEvent {
44    pub name: &'static str,
45    pub path: &'static str,
46    pub publish: fn(&str) -> Result<u64, String>,
47}
48
49inventory::collect!(RemoteAction);
50inventory::collect!(RemoteEvent);
51
52/// Sends the action `named` - a short name or a path - decoded from `json`,
53/// to the first of `scopes` that answers one by that name, innermost first:
54/// `scopes` run from the outermost layout to the page. Answers the action's
55/// id in the trace.
56pub fn act_in(scopes: &[Scope], named: &str, json: &str) -> Result<u64, String> {
57    let candidates: Vec<&RemoteAction> = inventory::iter::<RemoteAction>()
58        .filter(|action| action.name == named || action.path == named)
59        .collect();
60
61    if candidates.is_empty() {
62        return Err(format!(
63            "no action is registered as {named:?} - these are: {:?}",
64            actions()
65        ));
66    }
67
68    for &scope in scopes.iter().rev() {
69        let answering: Vec<&RemoteAction> = candidates
70            .iter()
71            .copied()
72            .filter(|action| (action.answered_by)(scope))
73            .collect();
74
75        match answering.as_slice() {
76            [] => continue,
77            [one] => {
78                return (one.emit)(scope, json)
79                    .unwrap_or_else(|| Err(format!("{} stopped answering", one.path)));
80            }
81            several => {
82                let paths: Vec<&str> = several.iter().map(|action| action.path).collect();
83                return Err(format!(
84                    "{} actions called {named:?} are answered here - send one by its path: {paths:?}",
85                    several.len()
86                ));
87            }
88        }
89    }
90
91    Err(format!("nothing on the open page answers {named}"))
92}
93
94/// Publishes the event `named` - a short name or a path - decoded from
95/// `json`, on the global bus. On the UI thread, where the bus lives.
96pub fn publish(named: &str, json: &str) -> Result<u64, String> {
97    let candidates: Vec<&RemoteEvent> = inventory::iter::<RemoteEvent>()
98        .filter(|event| event.name == named || event.path == named)
99        .collect();
100
101    match candidates.as_slice() {
102        [] => Err(format!(
103            "no event is registered as {named:?} - these are: {:?}",
104            events()
105        )),
106        [one] => (one.publish)(json),
107        several => {
108            let paths: Vec<&str> = several.iter().map(|event| event.path).collect();
109            Err(format!(
110                "{} events are called {named:?} - send one by its path: {paths:?}",
111                several.len()
112            ))
113        }
114    }
115}
116
117/// Every action a tool may send, by name, each once.
118pub fn actions() -> Vec<&'static str> {
119    let mut names: Vec<&'static str> = inventory::iter::<RemoteAction>()
120        .map(|action| action.name)
121        .collect();
122    names.sort_unstable();
123    names.dedup();
124    names
125}
126
127/// Every event a tool may publish, by name, each once.
128pub fn events() -> Vec<&'static str> {
129    let mut names: Vec<&'static str> = inventory::iter::<RemoteEvent>()
130        .map(|event| event.name)
131        .collect();
132    names.sort_unstable();
133    names.dedup();
134    names
135}
136
137/// What the derive registers for an action, to ask a scope about it.
138pub fn answered_by<M: 'static>(scope: Scope) -> bool {
139    scope.first_answerer::<M>().is_some()
140}
141
142/// What the derive registers for an action.
143pub fn emit_json<M: DeserializeOwned + 'static>(
144    scope: Scope,
145    json: &str,
146) -> Option<Result<u64, String>> {
147    let answer = scope.first_answerer::<M>()?;
148
149    let action: M = match serde_json::from_str(json) {
150        Ok(action) => action,
151        Err(error) => return Some(Err(format!("{}: {error}", short_type_name::<M>()))),
152    };
153
154    let entered = trace::enter(|| Point::Action {
155        message: short_type_name::<M>(),
156    });
157    let cause = entered.id().get();
158    answer(action);
159
160    Some(Ok(cause))
161}
162
163/// What the derive registers for an event. On the UI thread, where the global
164/// bus lives.
165pub fn publish_json<M: Event + DeserializeOwned>(json: &str) -> Result<u64, String> {
166    let event: M = serde_json::from_str(json)
167        .map_err(|error| format!("{}: {error}", short_type_name::<M>()))?;
168
169    let entered = trace::enter(|| Point::Action {
170        message: short_type_name::<M>(),
171    });
172    let cause = entered.id().get();
173    GlobalEventBus::bus().publish(event);
174
175    Ok(cause)
176}