dbus_message_parser/value/
object_path.rs1use 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 AlphanumericAndUnderscore,
10 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,
31 Root,
33 ElementBegin,
35 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
64fn 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#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
86pub struct ObjectPath(String);
87
88#[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 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 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 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 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}