zbus/
guid.rs

1use std::{
2    borrow::{Borrow, Cow},
3    fmt::{self, Debug, Display, Formatter},
4    iter::repeat_with,
5    ops::Deref,
6    str::FromStr,
7    time::{SystemTime, UNIX_EPOCH},
8};
9
10use serde::{de, Deserialize, Serialize};
11use static_assertions::assert_impl_all;
12use zvariant::{Str, Type};
13
14/// A D-Bus server GUID.
15///
16/// See the D-Bus specification [UUIDs chapter] for details.
17///
18/// You can create a `Guid` from an existing string with [`Guid::try_from::<&str>`][TryFrom].
19///
20/// [UUIDs chapter]: https://dbus.freedesktop.org/doc/dbus-specification.html#uuids
21/// [TryFrom]: #impl-TryFrom%3C%26%27_%20str%3E
22#[derive(Clone, Debug, PartialEq, Eq, Hash, Type, Serialize)]
23pub struct Guid<'g>(Str<'g>);
24
25assert_impl_all!(Guid<'_>: Send, Sync, Unpin);
26
27impl Guid<'_> {
28    /// Generate a D-Bus GUID that can be used with e.g.
29    /// [`connection::Builder::server`](crate::connection::Builder::server).
30    pub fn generate() -> Guid<'static> {
31        let r: Vec<u32> = repeat_with(rand::random::<u32>).take(3).collect();
32        let r3 = match SystemTime::now().duration_since(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    /// Returns 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    /// Creates 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    /// Creates 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    if value.as_bytes().len() != 32 || value.chars().any(|c| !char::is_ascii_hexdigit(&c)) {
135        return Err(crate::Error::InvalidGUID);
136    }
137
138    Ok(())
139}
140
141impl From<Guid<'_>> for String {
142    fn from(guid: Guid<'_>) -> Self {
143        guid.0.into()
144    }
145}
146
147impl Deref for Guid<'_> {
148    type Target = str;
149
150    fn deref(&self) -> &Self::Target {
151        self.as_str()
152    }
153}
154
155impl AsRef<str> for Guid<'_> {
156    fn as_ref(&self) -> &str {
157        self.as_str()
158    }
159}
160
161impl Borrow<str> for Guid<'_> {
162    fn borrow(&self) -> &str {
163        self.as_str()
164    }
165}
166
167/// Owned version of [`Guid`].
168#[derive(Clone, Debug, PartialEq, Eq, Hash, Type, Serialize)]
169pub struct OwnedGuid(#[serde(borrow)] Guid<'static>);
170
171assert_impl_all!(OwnedGuid: Send, Sync, Unpin);
172
173impl OwnedGuid {
174    /// Get a reference to the inner [`Guid`].
175    pub fn inner(&self) -> &Guid<'static> {
176        &self.0
177    }
178}
179
180impl Deref for OwnedGuid {
181    type Target = Guid<'static>;
182
183    fn deref(&self) -> &Self::Target {
184        &self.0
185    }
186}
187
188impl Borrow<str> for OwnedGuid {
189    fn borrow(&self) -> &str {
190        self.0.as_str()
191    }
192}
193
194impl From<OwnedGuid> for Guid<'_> {
195    fn from(o: OwnedGuid) -> Self {
196        o.0
197    }
198}
199
200impl<'unowned, 'owned: 'unowned> From<&'owned OwnedGuid> for Guid<'unowned> {
201    fn from(guid: &'owned OwnedGuid) -> Self {
202        guid.0.clone()
203    }
204}
205
206impl From<Guid<'_>> for OwnedGuid {
207    fn from(guid: Guid<'_>) -> Self {
208        OwnedGuid(guid.to_owned())
209    }
210}
211
212impl From<OwnedGuid> for Str<'_> {
213    fn from(value: OwnedGuid) -> Self {
214        value.0 .0
215    }
216}
217
218impl<'de> Deserialize<'de> for OwnedGuid {
219    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
220    where
221        D: de::Deserializer<'de>,
222    {
223        String::deserialize(deserializer)
224            .and_then(|n| Guid::try_from(n).map_err(|e| de::Error::custom(e.to_string())))
225            .map(Self)
226    }
227}
228
229impl PartialEq<&str> for OwnedGuid {
230    fn eq(&self, other: &&str) -> bool {
231        self.as_str() == *other
232    }
233}
234
235impl PartialEq<Guid<'_>> for OwnedGuid {
236    fn eq(&self, other: &Guid<'_>) -> bool {
237        self.0 == *other
238    }
239}
240
241impl Display for OwnedGuid {
242    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
243        Display::fmt(&Guid::from(self), f)
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use crate::Guid;
250    use test_log::test;
251
252    #[test]
253    fn generate() {
254        let u1 = Guid::generate();
255        let u2 = Guid::generate();
256        assert_eq!(u1.as_str().len(), 32);
257        assert_eq!(u2.as_str().len(), 32);
258        assert_ne!(u1, u2);
259        assert_ne!(u1.as_str(), u2.as_str());
260    }
261}