Skip to main content

lgui_core/events/
contract.rs

1use std::{
2    borrow::Borrow,
3    fmt,
4    future::Future,
5    hash::{Hash, Hasher},
6    marker::PhantomData,
7    pin::Pin,
8    sync::Arc,
9};
10
11use crate::core::UiAsyncContext;
12
13const MAX_EVENT_KEY_BYTES: usize = 128;
14
15/// A stable routing key whose payload type is checked by Rust.
16///
17/// Keys are compared by name. An Application rejects attempts to subscribe to
18/// the same name with different payload types.
19pub struct EventKey<T> {
20    name: EventKeyName,
21    _payload: PhantomData<fn(T)>,
22}
23
24enum EventKeyName {
25    Static(&'static str),
26    Shared(Arc<str>),
27}
28
29impl<T> EventKey<T> {
30    /// Creates a static Event key.
31    ///
32    /// A key must be non-empty, at most 128 bytes, and contain only lowercase
33    /// ASCII letters, digits, `.`, `_`, `-`, or `:`.
34    pub const fn new(name: &'static str) -> Self {
35        assert_valid_static_key(name);
36        Self {
37            name: EventKeyName::Static(name),
38            _payload: PhantomData,
39        }
40    }
41
42    /// Creates a runtime Event key, for example from a server-provided event
43    /// name. Invalid names are rejected before they reach the Event bus.
44    pub fn dynamic(name: impl Into<Arc<str>>) -> Result<Self, InvalidEventKey> {
45        let name = name.into();
46        validate_key(&name)?;
47        Ok(Self {
48            name: EventKeyName::Shared(name),
49            _payload: PhantomData,
50        })
51    }
52
53    pub fn as_str(&self) -> &str {
54        match &self.name {
55            EventKeyName::Static(name) => name,
56            EventKeyName::Shared(name) => name,
57        }
58    }
59
60    pub(crate) fn shared_name(&self) -> Arc<str> {
61        match &self.name {
62            EventKeyName::Static(name) => Arc::from(*name),
63            EventKeyName::Shared(name) => Arc::clone(name),
64        }
65    }
66}
67
68impl<T> Clone for EventKey<T> {
69    fn clone(&self) -> Self {
70        Self {
71            name: self.name.clone(),
72            _payload: PhantomData,
73        }
74    }
75}
76
77impl Clone for EventKeyName {
78    fn clone(&self) -> Self {
79        match self {
80            Self::Static(name) => Self::Static(name),
81            Self::Shared(name) => Self::Shared(Arc::clone(name)),
82        }
83    }
84}
85
86impl<T> fmt::Debug for EventKey<T> {
87    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
88        formatter
89            .debug_tuple("EventKey")
90            .field(&self.as_str())
91            .finish()
92    }
93}
94
95impl<T> fmt::Display for EventKey<T> {
96    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97        formatter.write_str(self.as_str())
98    }
99}
100
101impl<T> PartialEq for EventKey<T> {
102    fn eq(&self, other: &Self) -> bool {
103        self.as_str() == other.as_str()
104    }
105}
106
107impl<T> Eq for EventKey<T> {}
108
109impl<T> Hash for EventKey<T> {
110    fn hash<H: Hasher>(&self, state: &mut H) {
111        self.as_str().hash(state);
112    }
113}
114
115impl<T> Borrow<str> for EventKey<T> {
116    fn borrow(&self) -> &str {
117        self.as_str()
118    }
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct InvalidEventKey {
123    name: Arc<str>,
124}
125
126impl InvalidEventKey {
127    pub fn name(&self) -> &str {
128        &self.name
129    }
130}
131
132impl fmt::Display for InvalidEventKey {
133    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(
135            formatter,
136            "invalid event key `{}`; expected 1..={MAX_EVENT_KEY_BYTES} bytes containing only lowercase ASCII letters, digits, `.`, `_`, `-`, or `:`",
137            self.name
138        )
139    }
140}
141
142impl std::error::Error for InvalidEventKey {}
143
144const fn assert_valid_static_key(name: &str) {
145    let bytes = name.as_bytes();
146    assert!(!bytes.is_empty(), "event key must not be empty");
147    assert!(
148        bytes.len() <= MAX_EVENT_KEY_BYTES,
149        "event key exceeds 128 bytes"
150    );
151    let mut index = 0;
152    while index < bytes.len() {
153        let byte = bytes[index];
154        assert!(
155            matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'.' | b'_' | b'-' | b':'),
156            "event key contains an invalid byte"
157        );
158        index += 1;
159    }
160}
161
162fn validate_key(name: &str) -> Result<(), InvalidEventKey> {
163    let valid = !name.is_empty()
164        && name.len() <= MAX_EVENT_KEY_BYTES
165        && name
166            .bytes()
167            .all(|byte| matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'.' | b'_' | b'-' | b':'));
168    if valid {
169        Ok(())
170    } else {
171        Err(InvalidEventKey {
172            name: Arc::from(name),
173        })
174    }
175}
176
177/// A typed, Application-scoped event.
178///
179/// This compatibility contract routes through `EventKey::new(NAME)`. New code
180/// should declare an `EventKey<T>` directly.
181pub trait Event: Clone + Send + Sync + 'static {
182    const NAME: &'static str;
183}
184
185pub type EventFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
186
187pub trait AsyncEventHandler<E>: Send + Sync + 'static
188where
189    E: Event,
190{
191    fn call(&self, context: UiAsyncContext, event: E) -> EventFuture;
192}
193
194impl<E, F, Fut> AsyncEventHandler<E> for F
195where
196    E: Event,
197    F: Fn(UiAsyncContext, E) -> Fut + Send + Sync + 'static,
198    Fut: Future<Output = ()> + Send + 'static,
199{
200    fn call(&self, context: UiAsyncContext, event: E) -> EventFuture {
201        Box::pin((self)(context, event))
202    }
203}