1use moq_net::{AsPath, Path, PathOwned};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub enum Kind {
12 Camera,
14 Screen,
16}
17
18impl Kind {
19 pub const fn as_str(self) -> &'static str {
21 match self {
22 Self::Camera => "camera",
23 Self::Screen => "screen",
24 }
25 }
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct Parsed {
31 pub identity: PathOwned,
33 pub kind: Kind,
35}
36
37pub fn kind_from_segment(segment: &str) -> Option<Kind> {
39 let kind = segment.strip_suffix(".hang").unwrap_or(segment);
40 match kind {
41 "camera" => Some(Kind::Camera),
42 "screen" => Some(Kind::Screen),
43 _ => None,
44 }
45}
46
47pub fn parse(path: impl AsPath) -> Option<Parsed> {
51 let path = path.as_path();
52 let mut parts: Vec<&str> = path.parts().collect();
53 let last = parts.pop()?;
54 if parts.is_empty() {
55 return None;
56 }
57 let kind = kind_from_segment(last)?;
58 let identity = Path::new(&parts.join("/")).to_owned();
59 Some(Parsed { identity, kind })
60}
61
62pub fn broadcast_path(identity: impl AsPath, kind: Kind) -> PathOwned {
64 identity.as_path().join(format!("{}.hang", kind.as_str()))
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 fn p(s: &str) -> PathOwned {
72 Path::new(s).to_owned()
73 }
74
75 #[test]
76 fn parse_splits_identity_and_kind() {
77 assert_eq!(
78 parse(Path::new("alice/camera")),
79 Some(Parsed {
80 identity: p("alice"),
81 kind: Kind::Camera
82 })
83 );
84 assert_eq!(
85 parse(Path::new("alice/screen")),
86 Some(Parsed {
87 identity: p("alice"),
88 kind: Kind::Screen
89 })
90 );
91 }
92
93 #[test]
94 fn parse_accepts_hang_suffix() {
95 assert_eq!(
96 parse(Path::new("alice/camera.hang")).map(|p| p.kind),
97 Some(Kind::Camera)
98 );
99 assert_eq!(
100 parse(Path::new("alice/screen.hang")).map(|p| p.kind),
101 Some(Kind::Screen)
102 );
103 }
104
105 #[test]
106 fn parse_keeps_multi_segment_identity() {
107 assert_eq!(
108 parse(Path::new("guest/uuid/camera")),
109 Some(Parsed {
110 identity: p("guest/uuid"),
111 kind: Kind::Camera
112 })
113 );
114 }
115
116 #[test]
117 fn parse_rejects_unknown() {
118 assert_eq!(parse(Path::new("alice")), None);
119 assert_eq!(parse(Path::new("alice/chat")), None);
120 assert_eq!(parse(Path::new("camera")), None);
121 assert_eq!(parse(Path::empty()), None);
122 }
123
124 #[test]
125 fn broadcast_path_joins_with_hang_suffix() {
126 assert_eq!(broadcast_path(p("alice"), Kind::Camera).as_str(), "alice/camera.hang");
127 assert_eq!(
128 broadcast_path(p("guest/uuid"), Kind::Screen).as_str(),
129 "guest/uuid/screen.hang"
130 );
131 }
132}