1use std::{
2 ffi::{CString, OsStr},
3 os::unix::ffi::OsStrExt,
4 path::Path,
5};
6
7use serde::{Deserialize, Serialize};
8use zbus::zvariant::Type;
9
10#[derive(Type, Debug, Default, PartialEq)]
12#[zvariant(signature = "ay")]
13pub struct FilePath(CString);
14
15impl AsRef<Path> for FilePath {
16 fn as_ref(&self) -> &Path {
17 OsStr::from_bytes(self.0.as_bytes()).as_ref()
18 }
19}
20
21impl FilePath {
22 pub(crate) fn new<T: AsRef<Path>>(s: T) -> Result<Self, crate::Error> {
23 let c_string = CString::new(s.as_ref().as_os_str().as_bytes())
24 .map_err(|err| crate::Error::NulTerminated(err.nul_position()))?;
25
26 Ok(Self(c_string))
27 }
28}
29
30impl Serialize for FilePath {
31 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32 where
33 S: serde::Serializer,
34 {
35 serializer.serialize_bytes(self.0.as_bytes_with_nul())
36 }
37}
38
39impl<'de> Deserialize<'de> for FilePath {
40 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41 where
42 D: serde::Deserializer<'de>,
43 {
44 let bytes = <Vec<u8>>::deserialize(deserializer)?;
45 let c_string = CString::from_vec_with_nul(bytes)
46 .map_err(|_| serde::de::Error::custom("Bytes are not nul-terminated"))?;
47
48 Ok(Self(c_string))
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use zbus::zvariant::{
55 serialized::{Context, Data},
56 to_bytes, Endian,
57 };
58
59 use super::*;
60
61 #[test]
62 fn test_serialize_is_nul_terminated() {
63 let bytes = vec![97, 98, 99, 0]; assert_eq!(b"abc\0", bytes.as_slice());
66
67 let c_string = CString::from_vec_with_nul(bytes.clone()).unwrap();
68
69 assert_eq!(c_string.as_bytes_with_nul(), &bytes);
70
71 let ctxt = Context::new_dbus(Endian::Little, 0);
72 let file_path = FilePath(c_string);
73
74 let file_path_2 = FilePath::new("abc").unwrap();
75
76 let encoded_filename = to_bytes(ctxt, &file_path).unwrap().to_vec();
77 let encoded_filename_2 = to_bytes(ctxt, &file_path_2).unwrap().to_vec();
78 let encoded_bytes = to_bytes(ctxt, &bytes).unwrap().to_vec();
79
80 assert_eq!(encoded_filename, encoded_bytes);
82 assert_eq!(encoded_filename_2, encoded_bytes);
83
84 let decoded: FilePath = Data::new(encoded_bytes, ctxt).deserialize().unwrap().0;
85 assert_eq!(decoded, file_path);
86 assert_eq!(decoded, file_path_2);
87 }
88}