Skip to main content

lash_core/
host_events.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
6pub struct HostEvent {
7    pub resource_type: String,
8    pub alias: String,
9    pub event: String,
10    pub payload_ty: lashlang::NamedDataType,
11}
12
13impl HostEvent {
14    pub fn new(
15        resource_type: impl Into<String>,
16        alias: impl Into<String>,
17        event: impl Into<String>,
18        payload_ty: lashlang::NamedDataType,
19    ) -> Self {
20        Self {
21            resource_type: resource_type.into(),
22            alias: alias.into(),
23            event: event.into(),
24            payload_ty,
25        }
26    }
27
28    pub fn payload_type(&self) -> &lashlang::NamedDataType {
29        &self.payload_ty
30    }
31
32    pub fn key(&self) -> HostEventKey {
33        HostEventKey {
34            resource_type: self.resource_type.clone(),
35            alias: self.alias.clone(),
36            event: self.event.clone(),
37        }
38    }
39
40    pub fn source_type(&self) -> String {
41        host_event_source_type(&self.alias, &self.event)
42    }
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
46pub struct HostEventKey {
47    pub resource_type: String,
48    pub alias: String,
49    pub event: String,
50}
51
52impl HostEventKey {
53    pub fn new(
54        resource_type: impl Into<String>,
55        alias: impl Into<String>,
56        event: impl Into<String>,
57    ) -> Self {
58        Self {
59            resource_type: resource_type.into(),
60            alias: alias.into(),
61            event: event.into(),
62        }
63    }
64
65    pub fn source_type(&self) -> String {
66        host_event_source_type(&self.alias, &self.event)
67    }
68}
69
70pub fn host_event_source_type(alias: &str, event: &str) -> String {
71    format!("{alias}.{event}")
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
75pub struct HostEventCatalog {
76    events: BTreeMap<HostEventKey, HostEvent>,
77}
78
79impl HostEventCatalog {
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    pub fn declare(&mut self, event: HostEvent) -> Result<(), String> {
85        let key = event.key();
86        if self.events.contains_key(&key) {
87            return Err(format!(
88                "duplicate host event `{}.{}.{}`",
89                key.resource_type, key.alias, key.event
90            ));
91        }
92        let source_type = event.source_type();
93        if let Some(existing) = self
94            .events
95            .values()
96            .find(|existing| existing.source_type() == source_type)
97        {
98            return Err(format!(
99                "duplicate host event source `{source_type}` declared by `{}.{}.{}` and `{}.{}.{}`",
100                existing.resource_type,
101                existing.alias,
102                existing.event,
103                key.resource_type,
104                key.alias,
105                key.event
106            ));
107        }
108        self.events.insert(key, event);
109        Ok(())
110    }
111
112    pub fn from_events(events: impl IntoIterator<Item = HostEvent>) -> Result<Self, String> {
113        let mut catalog = Self::new();
114        for event in events {
115            catalog.declare(event)?;
116        }
117        Ok(catalog)
118    }
119
120    pub fn get(&self, resource_type: &str, alias: &str, event: &str) -> Option<&HostEvent> {
121        self.events
122            .get(&HostEventKey::new(resource_type, alias, event))
123    }
124
125    pub fn is_empty(&self) -> bool {
126        self.events.is_empty()
127    }
128
129    pub fn events(&self) -> impl Iterator<Item = &HostEvent> {
130        self.events.values()
131    }
132}
133
134#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
135pub struct HostEventEmitReport {
136    pub started_process_ids: Vec<String>,
137}
138
139impl HostEventEmitReport {
140    pub fn empty() -> Self {
141        Self::default()
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    fn button_payload_type() -> lashlang::NamedDataType {
150        lashlang::NamedDataType::object(
151            "ui.button.Pressed",
152            vec![lashlang::TypeField {
153                name: "button".into(),
154                ty: lashlang::TypeExpr::Str,
155                optional: false,
156            }],
157        )
158        .expect("valid host event payload")
159    }
160
161    #[test]
162    fn host_event_catalog_rejects_duplicate_trigger_source_identity() {
163        let mut catalog = HostEventCatalog::new();
164        catalog
165            .declare(HostEvent::new(
166                "Button",
167                "ui.button",
168                "pressed",
169                button_payload_type(),
170            ))
171            .expect("first host event");
172
173        let err = catalog
174            .declare(HostEvent::new(
175                "AlternateButton",
176                "ui.button",
177                "pressed",
178                button_payload_type(),
179            ))
180            .expect_err("duplicate public source identity should be rejected");
181
182        assert!(err.contains("duplicate host event source `ui.button.pressed`"));
183    }
184}