1 | use core::{fmt::Debug, str}; |
2 | use serde::{ |
3 | de::{self, Deserialize, Deserializer, Visitor}, |
4 | ser::{Serialize, Serializer}, |
5 | }; |
6 | use static_assertions::assert_impl_all; |
7 | use std::borrow::Cow; |
8 | |
9 | use 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)] |
36 | pub struct ObjectPath<'a>(Str<'a>); |
37 | |
38 | assert_impl_all!(ObjectPath<'_>: Send, Sync, Unpin); |
39 | |
40 | impl<'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 | |
117 | impl std::default::Default for ObjectPath<'_> { |
118 | fn default() -> Self { |
119 | ObjectPath::from_static_str_unchecked(name:"/" ) |
120 | } |
121 | } |
122 | |
123 | impl<'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 | |
136 | impl<'a> Type for ObjectPath<'a> { |
137 | fn signature() -> Signature<'static> { |
138 | Signature::from_static_str_unchecked(Self::SIGNATURE_STR) |
139 | } |
140 | } |
141 | |
142 | impl<'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(path:value)?; |
147 | |
148 | // SAFETY: ensure_correct_object_path_str checks UTF-8 |
149 | unsafe { Ok(Self::from_bytes_unchecked(bytes:value)) } |
150 | } |
151 | } |
152 | |
153 | /// Try to create an ObjectPath from a string. |
154 | impl<'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 | |
162 | impl<'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(path:value.as_bytes())?; |
167 | |
168 | Ok(Self::from_string_unchecked(path:value)) |
169 | } |
170 | } |
171 | |
172 | impl<'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: &str) => Self::try_from(s), |
178 | Cow::Owned(s: String) => Self::try_from(s), |
179 | } |
180 | } |
181 | } |
182 | |
183 | impl<'o> From<&ObjectPath<'o>> for ObjectPath<'o> { |
184 | fn from(o: &ObjectPath<'o>) -> Self { |
185 | o.clone() |
186 | } |
187 | } |
188 | |
189 | impl<'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 | |
197 | impl<'a> PartialEq<str> for ObjectPath<'a> { |
198 | fn eq(&self, other: &str) -> bool { |
199 | self.as_str() == other |
200 | } |
201 | } |
202 | |
203 | impl<'a> PartialEq<&str> for ObjectPath<'a> { |
204 | fn eq(&self, other: &&str) -> bool { |
205 | self.as_str() == *other |
206 | } |
207 | } |
208 | |
209 | impl<'a> Debug for ObjectPath<'a> { |
210 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
211 | f.debug_tuple(name:"ObjectPath" ).field(&self.as_str()).finish() |
212 | } |
213 | } |
214 | |
215 | impl<'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 | |
221 | impl<'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 | |
230 | impl<'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 = ObjectPathVisitor; |
236 | |
237 | deserializer.deserialize_str(visitor) |
238 | } |
239 | } |
240 | |
241 | struct ObjectPathVisitor; |
242 | |
243 | impl<'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(data:"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(op:serde::de::Error::custom) |
256 | } |
257 | } |
258 | |
259 | fn 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)] |
305 | pub struct OwnedObjectPath(ObjectPath<'static>); |
306 | |
307 | assert_impl_all!(OwnedObjectPath: Send, Sync, Unpin); |
308 | |
309 | impl OwnedObjectPath { |
310 | pub fn into_inner(self) -> ObjectPath<'static> { |
311 | self.0 |
312 | } |
313 | } |
314 | |
315 | impl 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 | |
324 | impl std::ops::Deref for OwnedObjectPath { |
325 | type Target = ObjectPath<'static>; |
326 | |
327 | fn deref(&self) -> &Self::Target { |
328 | &self.0 |
329 | } |
330 | } |
331 | |
332 | impl std::convert::From<OwnedObjectPath> for ObjectPath<'static> { |
333 | fn from(o: OwnedObjectPath) -> Self { |
334 | o.into_inner() |
335 | } |
336 | } |
337 | |
338 | impl std::convert::From<OwnedObjectPath> for crate::Value<'_> { |
339 | fn from(o: OwnedObjectPath) -> Self { |
340 | o.into_inner().into() |
341 | } |
342 | } |
343 | |
344 | impl<'unowned, 'owned: 'unowned> From<&'owned OwnedObjectPath> for ObjectPath<'unowned> { |
345 | fn from(o: &'owned OwnedObjectPath) -> Self { |
346 | ObjectPath::from_str_unchecked(path:o.as_str()) |
347 | } |
348 | } |
349 | |
350 | impl<'a> std::convert::From<ObjectPath<'a>> for OwnedObjectPath { |
351 | fn from(o: ObjectPath<'a>) -> Self { |
352 | OwnedObjectPath(o.into_owned()) |
353 | } |
354 | } |
355 | |
356 | impl 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 | |
364 | impl 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 | |
372 | impl<'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 | |
383 | impl 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)] |
390 | mod 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 | } |
400 | |