zvariant/
object_path.rs

1use core::{convert::TryFrom, fmt::Debug, str};
2use serde::{
3    de::{self, Deserialize, Deserializer, Visitor},
4    ser::{Serialize, Serializer},
5};
6use static_assertions::assert_impl_all;
7use std::borrow::Cow;
8
9use crate::{Basic, EncodingFormat, Error, Result, Signature, Str, Type};
10
11/// String that identifies objects at a given destination on the D-Bus bus.
12///
13/// Mostly likely this is only useful in the D-Bus context.
14///
15/// # Examples
16///
17/// ```
18/// use core::convert::TryFrom;
19/// use zvariant::ObjectPath;
20///
21/// // Valid object paths
22/// let o = ObjectPath::try_from("/").unwrap();
23/// assert_eq!(o, "/");
24/// let o = ObjectPath::try_from("/Path/t0/0bject").unwrap();
25/// assert_eq!(o, "/Path/t0/0bject");
26/// let o = ObjectPath::try_from("/a/very/looooooooooooooooooooooooo0000o0ng/path").unwrap();
27/// assert_eq!(o, "/a/very/looooooooooooooooooooooooo0000o0ng/path");
28///
29/// // Invalid object paths
30/// ObjectPath::try_from("").unwrap_err();
31/// ObjectPath::try_from("/double//slashes/").unwrap_err();
32/// ObjectPath::try_from(".").unwrap_err();
33/// ObjectPath::try_from("/end/with/slash/").unwrap_err();
34/// ObjectPath::try_from("/ha.d").unwrap_err();
35/// ```
36#[derive(PartialEq, Eq, Hash, Clone)]
37pub struct ObjectPath<'a>(Str<'a>);
38
39assert_impl_all!(ObjectPath<'_>: Send, Sync, Unpin);
40
41impl<'a> ObjectPath<'a> {
42    /// A borrowed clone (this never allocates, unlike clone).
43    pub fn as_ref(&self) -> ObjectPath<'_> {
44        ObjectPath(self.0.as_ref())
45    }
46
47    /// The object path as a string.
48    pub fn as_str(&self) -> &str {
49        self.0.as_str()
50    }
51
52    /// The object path as bytes.
53    pub fn as_bytes(&self) -> &[u8] {
54        self.0.as_bytes()
55    }
56
57    /// Create a new `ObjectPath` from given bytes.
58    ///
59    /// Since the passed bytes are not checked for correctness, prefer using the
60    /// `TryFrom<&[u8]>` implementation.
61    ///
62    /// # Safety
63    ///
64    /// See [`std::str::from_utf8_unchecked`].
65    pub unsafe fn from_bytes_unchecked<'s: 'a>(bytes: &'s [u8]) -> Self {
66        Self(std::str::from_utf8_unchecked(bytes).into())
67    }
68
69    /// Create a new `ObjectPath` from the given string.
70    ///
71    /// Since the passed string is not checked for correctness, prefer using the
72    /// `TryFrom<&str>` implementation.
73    pub fn from_str_unchecked<'s: 'a>(path: &'s str) -> Self {
74        Self(path.into())
75    }
76
77    /// Same as `try_from`, except it takes a `&'static str`.
78    pub fn from_static_str(name: &'static str) -> Result<Self> {
79        ensure_correct_object_path_str(name.as_bytes())?;
80
81        Ok(Self::from_static_str_unchecked(name))
82    }
83
84    /// Same as `from_str_unchecked`, except it takes a `&'static str`.
85    pub const fn from_static_str_unchecked(name: &'static str) -> Self {
86        Self(Str::from_static(name))
87    }
88
89    /// Same as `from_str_unchecked`, except it takes an owned `String`.
90    ///
91    /// Since the passed string is not checked for correctness, prefer using the
92    /// `TryFrom<String>` implementation.
93    pub fn from_string_unchecked(path: String) -> Self {
94        Self(path.into())
95    }
96
97    /// the object path's length.
98    pub fn len(&self) -> usize {
99        self.0.len()
100    }
101
102    /// if the object path is empty.
103    pub fn is_empty(&self) -> bool {
104        self.0.is_empty()
105    }
106
107    /// Creates an owned clone of `self`.
108    pub fn to_owned(&self) -> ObjectPath<'static> {
109        ObjectPath(self.0.to_owned())
110    }
111
112    /// Creates an owned clone of `self`.
113    pub fn into_owned(self) -> ObjectPath<'static> {
114        ObjectPath(self.0.into_owned())
115    }
116}
117
118impl std::default::Default for ObjectPath<'_> {
119    fn default() -> Self {
120        ObjectPath::from_str_unchecked("/")
121    }
122}
123
124impl<'a> Basic for ObjectPath<'a> {
125    const SIGNATURE_CHAR: char = 'o';
126    const SIGNATURE_STR: &'static str = "o";
127
128    fn alignment(format: EncodingFormat) -> usize {
129        match format {
130            EncodingFormat::DBus => <&str>::alignment(format),
131            #[cfg(feature = "gvariant")]
132            EncodingFormat::GVariant => 1,
133        }
134    }
135}
136
137impl<'a> Type for ObjectPath<'a> {
138    fn signature() -> Signature<'static> {
139        Signature::from_static_str_unchecked(Self::SIGNATURE_STR)
140    }
141}
142
143impl<'a> TryFrom<&'a [u8]> for ObjectPath<'a> {
144    type Error = Error;
145
146    fn try_from(value: &'a [u8]) -> Result<Self> {
147        ensure_correct_object_path_str(value)?;
148
149        // SAFETY: ensure_correct_object_path_str checks UTF-8
150        unsafe { Ok(Self::from_bytes_unchecked(value)) }
151    }
152}
153
154/// Try to create an ObjectPath from a string.
155impl<'a> TryFrom<&'a str> for ObjectPath<'a> {
156    type Error = Error;
157
158    fn try_from(value: &'a str) -> Result<Self> {
159        Self::try_from(value.as_bytes())
160    }
161}
162
163impl<'a> TryFrom<String> for ObjectPath<'a> {
164    type Error = Error;
165
166    fn try_from(value: String) -> Result<Self> {
167        ensure_correct_object_path_str(value.as_bytes())?;
168
169        Ok(Self::from_string_unchecked(value))
170    }
171}
172
173impl<'a> TryFrom<Cow<'a, str>> for ObjectPath<'a> {
174    type Error = Error;
175
176    fn try_from(value: Cow<'a, str>) -> Result<Self> {
177        match value {
178            Cow::Borrowed(s) => Self::try_from(s),
179            Cow::Owned(s) => Self::try_from(s),
180        }
181    }
182}
183
184impl<'o> From<&ObjectPath<'o>> for ObjectPath<'o> {
185    fn from(o: &ObjectPath<'o>) -> Self {
186        o.clone()
187    }
188}
189
190impl<'a> std::ops::Deref for ObjectPath<'a> {
191    type Target = str;
192
193    fn deref(&self) -> &Self::Target {
194        self.as_str()
195    }
196}
197
198impl<'a> PartialEq<str> for ObjectPath<'a> {
199    fn eq(&self, other: &str) -> bool {
200        self.as_str() == other
201    }
202}
203
204impl<'a> PartialEq<&str> for ObjectPath<'a> {
205    fn eq(&self, other: &&str) -> bool {
206        self.as_str() == *other
207    }
208}
209
210impl<'a> Debug for ObjectPath<'a> {
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        f.debug_tuple("ObjectPath").field(&self.as_str()).finish()
213    }
214}
215
216impl<'a> std::fmt::Display for ObjectPath<'a> {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        std::fmt::Display::fmt(&self.as_str(), f)
219    }
220}
221
222impl<'a> Serialize for ObjectPath<'a> {
223    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
224    where
225        S: Serializer,
226    {
227        serializer.serialize_str(self.as_str())
228    }
229}
230
231impl<'de: 'a, 'a> Deserialize<'de> for ObjectPath<'a> {
232    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
233    where
234        D: Deserializer<'de>,
235    {
236        let visitor = ObjectPathVisitor;
237
238        deserializer.deserialize_str(visitor)
239    }
240}
241
242struct ObjectPathVisitor;
243
244impl<'de> Visitor<'de> for ObjectPathVisitor {
245    type Value = ObjectPath<'de>;
246
247    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        formatter.write_str("an ObjectPath")
249    }
250
251    #[inline]
252    fn visit_borrowed_str<E>(self, value: &'de str) -> core::result::Result<ObjectPath<'de>, E>
253    where
254        E: serde::de::Error,
255    {
256        ObjectPath::try_from(value).map_err(serde::de::Error::custom)
257    }
258}
259
260fn ensure_correct_object_path_str(path: &[u8]) -> Result<()> {
261    let mut prev = b'\0';
262
263    // Rules
264    //
265    // * At least 1 character.
266    // * First character must be `/`
267    // * No trailing `/`
268    // * No `//`
269    // * Only ASCII alphanumeric, `_` or '/'
270    if path.is_empty() {
271        return Err(serde::de::Error::invalid_length(0, &"> 0 character"));
272    }
273
274    for i in 0..path.len() {
275        let c = path[i];
276
277        if i == 0 && c != b'/' {
278            return Err(serde::de::Error::invalid_value(
279                serde::de::Unexpected::Char(c as char),
280                &"/",
281            ));
282        } else if c == b'/' && prev == b'/' {
283            return Err(serde::de::Error::invalid_value(
284                serde::de::Unexpected::Str("//"),
285                &"/",
286            ));
287        } else if path.len() > 1 && i == (path.len() - 1) && c == b'/' {
288            return Err(serde::de::Error::invalid_value(
289                serde::de::Unexpected::Char('/'),
290                &"an alphanumeric character or `_`",
291            ));
292        } else if !c.is_ascii_alphanumeric() && c != b'/' && c != b'_' {
293            return Err(serde::de::Error::invalid_value(
294                serde::de::Unexpected::Char(c as char),
295                &"an alphanumeric character, `_` or `/`",
296            ));
297        }
298        prev = c;
299    }
300
301    Ok(())
302}
303
304/// Owned [`ObjectPath`](struct.ObjectPath.html)
305#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, serde::Serialize, Type)]
306pub struct OwnedObjectPath(ObjectPath<'static>);
307
308assert_impl_all!(OwnedObjectPath: Send, Sync, Unpin);
309
310impl OwnedObjectPath {
311    pub fn into_inner(self) -> ObjectPath<'static> {
312        self.0
313    }
314}
315
316impl std::ops::Deref for OwnedObjectPath {
317    type Target = ObjectPath<'static>;
318
319    fn deref(&self) -> &Self::Target {
320        &self.0
321    }
322}
323
324impl std::convert::From<OwnedObjectPath> for ObjectPath<'static> {
325    fn from(o: OwnedObjectPath) -> Self {
326        o.into_inner()
327    }
328}
329
330impl std::convert::From<OwnedObjectPath> for crate::Value<'static> {
331    fn from(o: OwnedObjectPath) -> Self {
332        o.into_inner().into()
333    }
334}
335
336impl<'unowned, 'owned: 'unowned> From<&'owned OwnedObjectPath> for ObjectPath<'unowned> {
337    fn from(o: &'owned OwnedObjectPath) -> Self {
338        ObjectPath::from_str_unchecked(o.as_str())
339    }
340}
341
342impl<'a> std::convert::From<ObjectPath<'a>> for OwnedObjectPath {
343    fn from(o: ObjectPath<'a>) -> Self {
344        OwnedObjectPath(o.into_owned())
345    }
346}
347
348impl TryFrom<&'_ str> for OwnedObjectPath {
349    type Error = Error;
350
351    fn try_from(value: &str) -> Result<Self> {
352        Ok(Self::from(ObjectPath::try_from(value)?))
353    }
354}
355
356impl TryFrom<String> for OwnedObjectPath {
357    type Error = Error;
358
359    fn try_from(value: String) -> Result<Self> {
360        Ok(Self::from(ObjectPath::try_from(value)?))
361    }
362}
363
364impl<'de> Deserialize<'de> for OwnedObjectPath {
365    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
366    where
367        D: Deserializer<'de>,
368    {
369        String::deserialize(deserializer)
370            .and_then(|s| ObjectPath::try_from(s).map_err(|e| de::Error::custom(e.to_string())))
371            .map(Self)
372    }
373}
374
375impl std::fmt::Display for OwnedObjectPath {
376    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        std::fmt::Display::fmt(&self.as_str(), f)
378    }
379}
380
381#[cfg(test)]
382mod unit {
383    use super::*;
384
385    #[test]
386    fn owned_from_reader() {
387        // See https://github.com/dbus2/zbus/issues/287
388        let json_str = "\"/some/path\"";
389        serde_json::de::from_reader::<_, OwnedObjectPath>(json_str.as_bytes()).unwrap();
390    }
391}