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