1use std::fmt;
4use std::sync::Arc;
5
6macro_rules! arc_str_newtype {
8 ($(#[$meta:meta])* $Name:ident) => {
9 $(#[$meta])*
10 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
11 pub struct $Name(Arc<str>);
12
13 impl serde::Serialize for $Name {
14 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
15 s.serialize_str(&self.0)
16 }
17 }
18
19 impl<'de> serde::Deserialize<'de> for $Name {
20 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
21 let s = String::deserialize(d)?;
22 Ok(Self(Arc::from(s.as_str())))
23 }
24 }
25
26 impl $Name {
27 pub fn new(s: impl Into<String>) -> Self {
29 Self(Arc::from(s.into().as_str()))
30 }
31
32 pub fn as_str(&self) -> &str {
34 &self.0
35 }
36 }
37
38 impl fmt::Display for $Name {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 f.write_str(&self.0)
41 }
42 }
43
44 impl From<&str> for $Name {
45 fn from(s: &str) -> Self { Self(Arc::from(s)) }
46 }
47
48 impl From<String> for $Name {
49 fn from(s: String) -> Self { Self(Arc::from(s.as_str())) }
50 }
51 };
52}
53
54arc_str_newtype!(
55 AppName
58);
59
60arc_str_newtype!(
61 StreamId
64);
65
66#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
68pub struct StreamKey {
69 pub app: AppName,
71 pub stream_id: StreamId,
73}
74
75impl StreamKey {
76 pub fn new(app: impl Into<AppName>, stream_id: impl Into<StreamId>) -> Self {
78 Self {
79 app: app.into(),
80 stream_id: stream_id.into(),
81 }
82 }
83
84 pub fn layer(&self, variant: &str) -> StreamKey {
93 StreamKey::new(
94 self.app.as_str(),
95 format!("{}~{}", self.stream_id.as_str(), variant),
96 )
97 }
98
99 pub fn layer_base(&self) -> &str {
103 let s = self.stream_id.as_str();
104 s.split_once('~').map(|(base, _)| base).unwrap_or(s)
105 }
106}
107
108impl fmt::Display for StreamKey {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 write!(f, "{}/{}", self.app, self.stream_id)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn newtypes_are_cheap_clones_with_value_equality() {
120 let a = AppName::from("live");
121 let b = a.clone();
122 assert_eq!(a, b);
123 assert_eq!(a.as_str(), "live");
124 assert!(std::ptr::eq(a.as_str().as_ptr(), b.as_str().as_ptr()));
126 }
127
128 #[test]
129 fn layer_key_round_trips_with_base() {
130 let base = StreamKey::new("live", "show");
131 let high = base.layer("f");
132 assert_eq!(high.stream_id.as_str(), "show~f");
133 assert_eq!(high.app, base.app);
134 assert_eq!(high.layer_base(), "show");
137 assert_eq!(base.layer_base(), "show");
138 }
139
140 #[test]
141 fn stream_key_display_is_app_slash_stream() {
142 let key = StreamKey::new("live", "cam-1");
143 assert_eq!(key.to_string(), "live/cam-1");
144 }
145
146 #[test]
147 fn identifiers_roundtrip_through_serde() {
148 let key = StreamKey::new("vod", "movie");
149 let json = serde_json::to_string(&key).expect("serialize");
150 let back: StreamKey = serde_json::from_str(&json).expect("deserialize");
151 assert_eq!(key, back);
152 }
153}