dbus_message_parser/value/
object_path.rs

1use std::cmp::{Eq, PartialEq};
2use std::convert::{From, TryFrom};
3use std::fmt::{Display, Formatter, Result as FmtResult};
4use std::str::Split;
5use thiserror::Error;
6
7enum Input {
8    /// [A-Z][a-z][0-9]_
9    AlphanumericAndUnderscore,
10    /// /
11    Slash,
12}
13
14impl TryFrom<u8> for Input {
15    type Error = ObjectPathError;
16
17    fn try_from(c: u8) -> Result<Self, Self::Error> {
18        if c.is_ascii_alphanumeric() || c == b'_' {
19            Ok(Input::AlphanumericAndUnderscore)
20        } else if c == b'/' {
21            Ok(Input::Slash)
22        } else {
23            Err(ObjectPathError::InvalidChar(c))
24        }
25    }
26}
27
28enum State {
29    /// Start state.
30    Start,
31    /// The root slash.
32    Root,
33    /// The begining of the first or subsequent element.
34    ElementBegin,
35    /// The second or subsequent element.
36    Element,
37}
38
39impl State {
40    fn consume(self, i: Input) -> Result<State, ObjectPathError> {
41        match self {
42            State::Start => match i {
43                Input::AlphanumericAndUnderscore => {
44                    Err(ObjectPathError::BeginAlphanumericAndUnderscoreAndHyphen)
45                }
46                Input::Slash => Ok(State::Root),
47            },
48            State::Root => match i {
49                Input::AlphanumericAndUnderscore => Ok(State::Element),
50                Input::Slash => Err(ObjectPathError::ElementEmtpy),
51            },
52            State::ElementBegin => match i {
53                Input::AlphanumericAndUnderscore => Ok(State::Element),
54                Input::Slash => Err(ObjectPathError::ElementEmtpy),
55            },
56            State::Element => match i {
57                Input::AlphanumericAndUnderscore => Ok(State::Element),
58                Input::Slash => Ok(State::ElementBegin),
59            },
60        }
61    }
62}
63
64/// Check if the given bytes is a valid [object path].
65///
66/// [object path]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path
67fn check(error: &[u8]) -> Result<(), ObjectPathError> {
68    let mut state = State::Start;
69    for c in error {
70        let i = Input::try_from(*c)?;
71        state = state.consume(i)?;
72    }
73
74    match state {
75        State::Start => Err(ObjectPathError::Empty),
76        State::Root => Ok(()),
77        State::ElementBegin => Err(ObjectPathError::EndSlash),
78        State::Element => Ok(()),
79    }
80}
81
82/// This represents a [object path].
83///
84/// [object path]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path
85#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
86pub struct ObjectPath(String);
87
88/// An enum representing all errors, which can occur during the handling of a [`ObjectPath`].
89#[derive(Debug, PartialEq, Eq, Error)]
90pub enum ObjectPathError {
91    #[error("ObjectPath must not begin with an alphanumeric or with a '_' or with a '-'")]
92    BeginAlphanumericAndUnderscoreAndHyphen,
93    #[error("ObjectPath must not end with '.'")]
94    EndSlash,
95    #[error("ObjectPath must not be empty")]
96    Empty,
97    #[error("ObjectPath element must not be empty")]
98    ElementEmtpy,
99    #[error("ObjectPath must only contain '[A-Z][a-z][0-9]_/': {0}")]
100    InvalidChar(u8),
101}
102
103impl From<ObjectPath> for String {
104    fn from(object_path: ObjectPath) -> Self {
105        object_path.0
106    }
107}
108
109impl TryFrom<String> for ObjectPath {
110    type Error = ObjectPathError;
111
112    fn try_from(object_path: String) -> Result<Self, Self::Error> {
113        check(object_path.as_bytes())?;
114        Ok(ObjectPath(object_path))
115    }
116}
117
118impl TryFrom<&str> for ObjectPath {
119    type Error = ObjectPathError;
120
121    fn try_from(object_path: &str) -> Result<Self, Self::Error> {
122        check(object_path.as_bytes())?;
123        Ok(ObjectPath(object_path.to_owned()))
124    }
125}
126
127impl TryFrom<&[u8]> for ObjectPath {
128    type Error = ObjectPathError;
129
130    fn try_from(object_path: &[u8]) -> Result<Self, Self::Error> {
131        check(object_path)?;
132        let object_path = object_path.to_vec();
133        //  The vector only contains valid UTF-8 (ASCII) characters because it was already
134        //  checked by the `check` function above
135        unsafe { Ok(ObjectPath(String::from_utf8_unchecked(object_path))) }
136    }
137}
138
139impl Display for ObjectPath {
140    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
141        write!(f, "{}", self.0)
142    }
143}
144
145impl AsRef<str> for ObjectPath {
146    fn as_ref(&self) -> &str {
147        &self.0
148    }
149}
150
151impl Default for ObjectPath {
152    fn default() -> Self {
153        ObjectPath("/".to_string())
154    }
155}
156
157impl PartialEq<str> for ObjectPath {
158    fn eq(&self, other: &str) -> bool {
159        self.as_ref() == other
160    }
161}
162
163impl ObjectPath {
164    /// Append an element to the object path.
165    ///
166    /// Returns `true` if the new element could be appended, otherwise `false`.
167    /// An element cannot be appended if the element contain a character, which is not allowed.
168    ///
169    /// # Example
170    /// ```
171    /// # use std::convert::TryFrom;
172    /// # use dbus_message_parser::value::ObjectPath;
173    /// #
174    /// let mut object_path = ObjectPath::try_from("/object").unwrap();
175    ///
176    /// assert!(object_path.append("path"));
177    /// assert!(!object_path.append("/path"));
178    ///
179    /// assert_eq!(&object_path, "/object/path");
180    /// ```
181    pub fn append(&mut self, element: &str) -> bool {
182        for c in element.as_bytes() {
183            if !c.is_ascii_alphanumeric() && *c != b'_' {
184                return false;
185            }
186        }
187        if self.0 != "/" {
188            self.0 += "/";
189        }
190        self.0 += element;
191        true
192    }
193
194    /// Determines whether `base` is a prefix of `self`.
195    ///
196    /// # Example
197    /// ```
198    /// # use std::convert::TryFrom;
199    /// # use dbus_message_parser::value::ObjectPath;
200    /// #
201    /// let base = ObjectPath::try_from("/object").unwrap();
202    ///
203    /// let path_1 = ObjectPath::try_from("/object/path").unwrap();
204    /// let path_2 = ObjectPath::try_from("/object_/path").unwrap();
205    ///
206    /// assert!(path_1.starts_with(&base));
207    /// assert!(!path_2.starts_with(&base));
208    /// assert!(!base.starts_with(&base));
209    /// ```
210    pub fn starts_with(&self, base: &ObjectPath) -> bool {
211        if let Some(mut iter) = self.strip_prefix_elements(base) {
212            iter.next().is_some()
213        } else {
214            false
215        }
216    }
217
218    /// Returns a [`Split`] object with the prefix removed.
219    ///
220    /// [`Split`]: std::str::Split
221    ///
222    /// # Example
223    /// ```
224    /// # use std::convert::TryFrom;
225    /// # use dbus_message_parser::value::ObjectPath;
226    /// #
227    /// let base = ObjectPath::try_from("/object").unwrap();
228    ///
229    /// let path_1 = ObjectPath::try_from("/object/path").unwrap();
230    /// let path_2 = ObjectPath::try_from("/object_/path").unwrap();
231    /// let path_3 = ObjectPath::try_from("/object/path/element").unwrap();
232    ///
233    /// let path_1_base_vec: Vec<&str> = path_1.strip_prefix_elements(&base).unwrap().collect();
234    /// let path_3_base_vec: Vec<&str> = path_3.strip_prefix_elements(&base).unwrap().collect();
235    ///
236    /// assert_eq!(path_1_base_vec, vec!["path"]);
237    /// assert!(path_2.strip_prefix_elements(&base).is_none());
238    /// assert_eq!(path_3_base_vec, vec!["path", "element"]);
239    /// assert!(base.strip_prefix_elements(&base).is_none());
240    /// ```
241    pub fn strip_prefix_elements<'a, 'b>(
242        &'a self,
243        base: &'b ObjectPath,
244    ) -> Option<Split<'a, char>> {
245        let mut self_iter = self.0.split('/');
246        if self != "/" && base == "/" {
247            self_iter.next()?;
248            return Some(self_iter);
249        }
250        let mut base_iter = base.0.split('/');
251        loop {
252            let self_iter_prev = self_iter.clone();
253            match (self_iter.next(), base_iter.next()) {
254                (Some(ref x), Some(ref y)) => {
255                    if x != y {
256                        return None;
257                    }
258                }
259                (Some(_), None) => return Some(self_iter_prev),
260                (None, None) => return None,
261                (None, Some(_)) => return None,
262            }
263        }
264    }
265}