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