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}