1 | use core::{convert::TryFrom, 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::{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)] |
37 | pub struct ObjectPath<'a>(Str<'a>); |
38 | |
39 | assert_impl_all!(ObjectPath<'_>: Send, Sync, Unpin); |
40 | |
41 | impl<'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 | |
118 | impl std::default::Default for ObjectPath<'_> { |
119 | fn default() -> Self { |
120 | ObjectPath::from_str_unchecked(path:"/" ) |
121 | } |
122 | } |
123 | |
124 | impl<'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 | |
137 | impl<'a> Type for ObjectPath<'a> { |
138 | fn signature() -> Signature<'static> { |
139 | Signature::from_static_str_unchecked(Self::SIGNATURE_STR) |
140 | } |
141 | } |
142 | |
143 | impl<'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. |
155 | impl<'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 | |
163 | impl<'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 | |
173 | impl<'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 | |
184 | impl<'o> From<&ObjectPath<'o>> for ObjectPath<'o> { |
185 | fn from(o: &ObjectPath<'o>) -> Self { |
186 | o.clone() |
187 | } |
188 | } |
189 | |
190 | impl<'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 | |
198 | impl<'a> PartialEq<str> for ObjectPath<'a> { |
199 | fn eq(&self, other: &str) -> bool { |
200 | self.as_str() == other |
201 | } |
202 | } |
203 | |
204 | impl<'a> PartialEq<&str> for ObjectPath<'a> { |
205 | fn eq(&self, other: &&str) -> bool { |
206 | self.as_str() == *other |
207 | } |
208 | } |
209 | |
210 | impl<'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 | |
216 | impl<'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 | |
222 | impl<'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 | |
231 | impl<'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 | |
242 | struct ObjectPathVisitor; |
243 | |
244 | impl<'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 | |
260 | fn 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)] |
306 | pub struct OwnedObjectPath(ObjectPath<'static>); |
307 | |
308 | assert_impl_all!(OwnedObjectPath: Send, Sync, Unpin); |
309 | |
310 | impl OwnedObjectPath { |
311 | pub fn into_inner(self) -> ObjectPath<'static> { |
312 | self.0 |
313 | } |
314 | } |
315 | |
316 | impl std::ops::Deref for OwnedObjectPath { |
317 | type Target = ObjectPath<'static>; |
318 | |
319 | fn deref(&self) -> &Self::Target { |
320 | &self.0 |
321 | } |
322 | } |
323 | |
324 | impl std::convert::From<OwnedObjectPath> for ObjectPath<'static> { |
325 | fn from(o: OwnedObjectPath) -> Self { |
326 | o.into_inner() |
327 | } |
328 | } |
329 | |
330 | impl std::convert::From<OwnedObjectPath> for crate::Value<'static> { |
331 | fn from(o: OwnedObjectPath) -> Self { |
332 | o.into_inner().into() |
333 | } |
334 | } |
335 | |
336 | impl<'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 | |
342 | impl<'a> std::convert::From<ObjectPath<'a>> for OwnedObjectPath { |
343 | fn from(o: ObjectPath<'a>) -> Self { |
344 | OwnedObjectPath(o.into_owned()) |
345 | } |
346 | } |
347 | |
348 | impl 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 | |
356 | impl 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 | |
364 | impl<'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 | |
375 | impl 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)] |
382 | mod 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 | |