zbus/
guid.rs

1use std::{
2    borrow::{Borrow, Cow},
3    fmt::{self, Debug, Display, Formatter},
4    ops::Deref,
5    str::FromStr,
6};
7
8use serde::{de, Deserialize, Serialize};
9use zvariant::{Str, Type};
10
11/// A D-Bus server GUID.
12///
13/// See the D-Bus specification [UUIDs chapter] for details.
14///
15/// You can create a `Guid` from an existing string with [`Guid::try_from::<&str>`][TryFrom].
16///
17/// [UUIDs chapter]: https://dbus.freedesktop.org/doc/dbus-specification.html#uuids
18/// [TryFrom]: #impl-TryFrom%3C%26%27_%20str%3E
19#[derive(Clone, Debug, PartialEq, Eq, Hash, Type, Serialize)]
20pub struct Guid<'g>(Str<'g>);
21
22impl Guid<'_> {
23    /// Generate a D-Bus GUID that can be used with e.g.
24    /// [`connection::Builder::server`](crate::connection::Builder::server).
25    ///
26    /// This method is only available when the `p2p` feature is enabled (disabled by default).
27    #[cfg(feature = "p2p")]
28    pub fn generate() -> Guid<'static> {
29        let r: Vec<u32> = std::iter::repeat_with(rand::random::<u32>)
30            .take(3)
31            .collect();
32        let r3 = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
33            Ok(n) => n.as_secs() as u32,
34            Err(_) => rand::random::<u32>(),
35        };
36
37        let s = format!("{:08x}{:08x}{:08x}{:08x}", r[0], r[1], r[2], r3);
38        Guid(s.into())
39    }
40
41    /// Return a string slice for the GUID.
42    pub fn as_str(&self) -> &str {
43        self.0.as_str()
44    }
45
46    /// Same as `try_from`, except it takes a `&'static str`.
47    pub fn from_static_str(guid: &'static str) -> crate::Result<Self> {
48        validate_guid(guid)?;
49
50        Ok(Self(Str::from_static(guid)))
51    }
52
53    /// Create an owned copy of the GUID.
54    pub fn to_owned(&self) -> Guid<'static> {
55        Guid(self.0.to_owned())
56    }
57}
58
59impl fmt::Display for Guid<'_> {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(f, "{}", self.as_str())
62    }
63}
64
65impl<'g> TryFrom<&'g str> for Guid<'g> {
66    type Error = crate::Error;
67
68    /// Create a GUID from a string with 32 hex digits.
69    ///
70    /// Returns `Err(`[`Error::InvalidGUID`]`)` if the provided string is not a well-formed GUID.
71    ///
72    /// [`Error::InvalidGUID`]: enum.Error.html#variant.InvalidGUID
73    fn try_from(value: &'g str) -> std::result::Result<Self, Self::Error> {
74        validate_guid(value)?;
75
76        Ok(Self(Str::from(value)))
77    }
78}
79
80impl<'g> TryFrom<Str<'g>> for Guid<'g> {
81    type Error = crate::Error;
82
83    /// Create a GUID from a string with 32 hex digits.
84    ///
85    /// Returns `Err(`[`Error::InvalidGUID`]`)` if the provided string is not a well-formed GUID.
86    ///
87    /// [`Error::InvalidGUID`]: enum.Error.html#variant.InvalidGUID
88    fn try_from(value: Str<'g>) -> std::result::Result<Self, Self::Error> {
89        validate_guid(&value)?;
90
91        Ok(Guid(value))
92    }
93}
94
95impl TryFrom<String> for Guid<'_> {
96    type Error = crate::Error;
97
98    fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
99        validate_guid(&value)?;
100
101        Ok(Guid(value.into()))
102    }
103}
104
105impl<'g> TryFrom<Cow<'g, str>> for Guid<'g> {
106    type Error = crate::Error;
107
108    fn try_from(value: Cow<'g, str>) -> std::result::Result<Self, Self::Error> {
109        validate_guid(&value)?;
110
111        Ok(Guid(value.into()))
112    }
113}
114
115impl FromStr for Guid<'static> {
116    type Err = crate::Error;
117
118    fn from_str(s: &str) -> Result<Self, Self::Err> {
119        s.try_into().map(|guid: Guid<'_>| guid.to_owned())
120    }
121}
122
123impl<'de> Deserialize<'de> for Guid<'de> {
124    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
125    where
126        D: serde::Deserializer<'de>,
127    {
128        <Cow<'de, str>>::deserialize(deserializer)
129            .and_then(|s| s.try_into().map_err(serde::de::Error::custom))
130    }
131}
132
133fn validate_guid(value: &str) -> crate::Result<()> {
134    use winnow::{stream::AsChar, token::take_while, Parser};
135
136    take_while::<_, _, ()>(32, AsChar::is_hex_digit)
137        .map(|_| ())
138        .parse(value.as_bytes())
139        .map_err(|_| crate::Error::InvalidGUID)
140}
141
142impl From<Guid<'_>> for String {
143    fn from(guid: Guid<'_>) -> Self {
144        guid.0.into()
145    }
146}
147
148impl Deref for Guid<'_> {
149    type Target = str;
150
151    fn deref(&self) -> &Self::Target {
152        self.as_str()
153    }
154}
155
156impl<'a> Borrow<Guid<'a>> for OwnedGuid {
157    fn borrow(&self) -> &Guid<'a> {
158        &self.0
159    }
160}
161
162impl AsRef<str> for Guid<'_> {
163    fn as_ref(&self) -> &str {
164        self.as_str()
165    }
166}
167
168impl Borrow<str> for Guid<'_> {
169    fn borrow(&self) -> &str {
170        self.as_str()
171    }
172}
173
174/// Owned version of [`Guid`].
175#[derive(Clone, Debug, PartialEq, Eq, Hash, Type, Serialize)]
176pub struct OwnedGuid(#[serde(borrow)] Guid<'static>);
177
178impl OwnedGuid {
179    /// Get a reference to the inner [`Guid`].
180    pub fn inner(&self) -> &Guid<'static> {
181        &self.0
182    }
183}
184
185impl Deref for OwnedGuid {
186    type Target = Guid<'static>;
187
188    fn deref(&self) -> &Self::Target {
189        &self.0
190    }
191}
192
193impl Borrow<str> for OwnedGuid {
194    fn borrow(&self) -> &str {
195        self.0.as_str()
196    }
197}
198
199impl From<OwnedGuid> for Guid<'_> {
200    fn from(o: OwnedGuid) -> Self {
201        o.0
202    }
203}
204
205impl<'unowned, 'owned: 'unowned> From<&'owned OwnedGuid> for Guid<'unowned> {
206    fn from(guid: &'owned OwnedGuid) -> Self {
207        guid.0.clone()
208    }
209}
210
211impl From<Guid<'_>> for OwnedGuid {
212    fn from(guid: Guid<'_>) -> Self {
213        OwnedGuid(guid.to_owned())
214    }
215}
216
217impl From<OwnedGuid> for Str<'_> {
218    fn from(value: OwnedGuid) -> Self {
219        value.0 .0
220    }
221}
222
223impl<'de> Deserialize<'de> for OwnedGuid {
224    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
225    where
226        D: de::Deserializer<'de>,
227    {
228        String::deserialize(deserializer)
229            .and_then(|n| Guid::try_from(n).map_err(|e| de::Error::custom(e.to_string())))
230            .map(Self)
231    }
232}
233
234impl PartialEq<&str> for OwnedGuid {
235    fn eq(&self, other: &&str) -> bool {
236        self.as_str() == *other
237    }
238}
239
240impl PartialEq<Guid<'_>> for OwnedGuid {
241    fn eq(&self, other: &Guid<'_>) -> bool {
242        self.0 == *other
243    }
244}
245
246impl Display for OwnedGuid {
247    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
248        Display::fmt(&Guid::from(self), f)
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use crate::Guid;
255    use test_log::test;
256
257    #[test]
258    #[cfg(feature = "p2p")]
259    fn generate() {
260        let u1 = Guid::generate();
261        let u2 = Guid::generate();
262        assert_eq!(u1.as_str().len(), 32);
263        assert_eq!(u2.as_str().len(), 32);
264        assert_ne!(u1, u2);
265        assert_ne!(u1.as_str(), u2.as_str());
266    }
267
268    #[test]
269    fn parse() {
270        let valid = "0123456789ABCDEF0123456789ABCDEF";
271        // Not 32 chars.
272        let invalid = "0123456789ABCDEF0123456789ABCD";
273
274        assert!(Guid::try_from(valid).is_ok());
275        assert!(Guid::try_from(invalid).is_err());
276    }
277}