muster/domain/
notification.rs1use getset::Getters;
2use nutype::nutype;
3use typed_builder::TypedBuilder;
4
5use crate::domain::value::{PaneId, ProcessName};
6
7#[nutype(
10 validate(
11 not_empty,
12 predicate = |identifier: &str| identifier.bytes().all(|byte| byte.is_ascii_alphanumeric()
13 || matches!(byte, b'_' | b'-' | b'+' | b'.'))
14 ),
15 derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Display)
16)]
17pub struct NotificationId(String);
18
19#[nutype(derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display))]
22pub struct NotificationScope(u64);
23
24impl NotificationScope {
25 pub fn next(self) -> Self {
28 Self::new(self.into_inner().wrapping_add(1))
29 }
30}
31
32#[derive(Clone, Debug, Getters, TypedBuilder)]
35#[getset(get = "pub")]
36pub struct Notification {
37 pane: PaneId,
39 scope: NotificationScope,
41 source: ProcessName,
43 #[builder(default)]
45 title: Option<String>,
46 #[builder(default)]
49 body: Option<String>,
50 #[builder(default)]
52 identifier: Option<NotificationId>,
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn kitty_identifiers_accept_only_protocol_safe_characters() {
61 assert!(NotificationId::try_new("build_1-done+now.").is_ok());
62 assert!(NotificationId::try_new("").is_err());
63 assert!(NotificationId::try_new("unsafe/id").is_err());
64 }
65
66 #[test]
67 fn notification_scopes_advance_without_raw_integer_arithmetic() {
68 assert_eq!(NotificationScope::new(0).next(), NotificationScope::new(1));
69 }
70}