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(path:"/")
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(path:value)?;
148
149 // SAFETY: ensure_correct_object_path_str checks UTF-8
150 unsafe { Ok(Self::from_bytes_unchecked(bytes: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(path:value.as_bytes())?;
168
169 Ok(Self::from_string_unchecked(path: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: &str) => Self::try_from(s),
179 Cow::Owned(s: String) => 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(name:"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 = 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(data:"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(op: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(path: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}
392