Skip to main content

arcly_stream/
identity.rs

1//! Cheaply-cloneable identifiers for applications and streams.
2
3use std::fmt;
4use std::sync::Arc;
5
6/// Shared implementation for cheaply-cloneable `Arc<str>` newtype identifiers.
7macro_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 {
28                Self(Arc::from(s.into().as_str()))
29            }
30
31            pub fn as_str(&self) -> &str {
32                &self.0
33            }
34        }
35
36        impl fmt::Display for $Name {
37            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38                f.write_str(&self.0)
39            }
40        }
41
42        impl From<&str> for $Name {
43            fn from(s: &str) -> Self { Self(Arc::from(s)) }
44        }
45
46        impl From<String> for $Name {
47            fn from(s: String) -> Self { Self(Arc::from(s.as_str())) }
48        }
49    };
50}
51
52arc_str_newtype!(
53    /// Name of an application (e.g. `"live"`, `"vod"`).
54    /// Cheaply cloneable — backed by an `Arc<str>`.
55    AppName
56);
57
58arc_str_newtype!(
59    /// Unique identifier for a stream within an application.
60    /// Cheaply cloneable — backed by an `Arc<str>`.
61    StreamId
62);
63
64/// Composite key uniquely identifying a stream: `(app_name, stream_id)`.
65#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
66pub struct StreamKey {
67    pub app: AppName,
68    pub stream_id: StreamId,
69}
70
71impl StreamKey {
72    pub fn new(app: impl Into<AppName>, stream_id: impl Into<StreamId>) -> Self {
73        Self {
74            app: app.into(),
75            stream_id: stream_id.into(),
76        }
77    }
78}
79
80impl fmt::Display for StreamKey {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{}/{}", self.app, self.stream_id)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn newtypes_are_cheap_clones_with_value_equality() {
92        let a = AppName::from("live");
93        let b = a.clone();
94        assert_eq!(a, b);
95        assert_eq!(a.as_str(), "live");
96        // Clones share the same Arc allocation (cheap, not a deep copy).
97        assert!(std::ptr::eq(a.as_str().as_ptr(), b.as_str().as_ptr()));
98    }
99
100    #[test]
101    fn stream_key_display_is_app_slash_stream() {
102        let key = StreamKey::new("live", "cam-1");
103        assert_eq!(key.to_string(), "live/cam-1");
104    }
105
106    #[test]
107    fn identifiers_roundtrip_through_serde() {
108        let key = StreamKey::new("vod", "movie");
109        let json = serde_json::to_string(&key).expect("serialize");
110        let back: StreamKey = serde_json::from_str(&json).expect("deserialize");
111        assert_eq!(key, back);
112    }
113}