zvariant/
object_path.rs

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