Skip to main content

gravityfile_core/
path_serde.rs

1//! JSON-compatible native paths. Unicode paths retain their legacy string shape.
2use serde::ser::{SerializeSeq, SerializeStruct};
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::path::{Path, PathBuf};
5
6pub fn serialize<S: Serializer>(path: &Path, serializer: S) -> Result<S::Ok, S::Error> {
7    if !serializer.is_human_readable() || path.to_str().is_some() {
8        return path.serialize(serializer);
9    }
10    let mut record = serializer.serialize_struct("NativePath", 2)?;
11    #[cfg(unix)]
12    {
13        use std::os::unix::ffi::OsStrExt;
14        record.serialize_field("platform", "unix")?;
15        record.serialize_field("bytes", path.as_os_str().as_bytes())?;
16    }
17    #[cfg(windows)]
18    {
19        use std::os::windows::ffi::OsStrExt;
20        record.serialize_field("platform", "windows")?;
21        record.serialize_field("wide", &path.as_os_str().encode_wide().collect::<Vec<_>>())?;
22    }
23    #[cfg(not(any(unix, windows)))]
24    return Err(serde::ser::Error::custom(
25        "native path encoding unavailable on this platform",
26    ));
27    record.end()
28}
29
30#[derive(Deserialize)]
31#[serde(tag = "platform", rename_all = "lowercase", deny_unknown_fields)]
32enum NativePath {
33    Unix { bytes: Vec<u8> },
34    Windows { wide: Vec<u16> },
35}
36
37#[derive(Deserialize)]
38#[serde(untagged)]
39enum HumanPath {
40    Text(String),
41    Native(NativePath),
42}
43
44pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<PathBuf, D::Error> {
45    if !deserializer.is_human_readable() {
46        return PathBuf::deserialize(deserializer);
47    }
48    match HumanPath::deserialize(deserializer)? {
49        HumanPath::Text(text) => Ok(PathBuf::from(text)),
50        HumanPath::Native(NativePath::Unix { bytes }) => {
51            #[cfg(unix)]
52            {
53                use std::os::unix::ffi::OsStringExt;
54                Ok(std::ffi::OsString::from_vec(bytes).into())
55            }
56            #[cfg(not(unix))]
57            {
58                let _ = bytes;
59                Err(serde::de::Error::custom(
60                    "Unix native path cannot be decoded on this platform",
61                ))
62            }
63        }
64        HumanPath::Native(NativePath::Windows { wide }) => {
65            #[cfg(windows)]
66            {
67                use std::os::windows::ffi::OsStringExt;
68                Ok(std::ffi::OsString::from_wide(&wide).into())
69            }
70            #[cfg(not(windows))]
71            {
72                let _ = wide;
73                Err(serde::de::Error::custom(
74                    "Windows native path cannot be decoded on this platform",
75                ))
76            }
77        }
78    }
79}
80
81struct PathRef<'a>(&'a Path);
82impl Serialize for PathRef<'_> {
83    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
84        serialize(self.0, serializer)
85    }
86}
87#[derive(Deserialize)]
88struct OwnedPath(#[serde(deserialize_with = "deserialize")] PathBuf);
89
90/// Adapter for lists of paths.
91pub mod list {
92    use super::*;
93    pub fn serialize<S: Serializer>(paths: &[PathBuf], serializer: S) -> Result<S::Ok, S::Error> {
94        let mut sequence = serializer.serialize_seq(Some(paths.len()))?;
95        for path in paths {
96            sequence.serialize_element(&PathRef(path))?;
97        }
98        sequence.end()
99    }
100    pub fn deserialize<'de, D: Deserializer<'de>>(
101        deserializer: D,
102    ) -> Result<Vec<PathBuf>, D::Error> {
103        Ok(Vec::<OwnedPath>::deserialize(deserializer)?
104            .into_iter()
105            .map(|path| path.0)
106            .collect())
107    }
108}
109
110/// Adapter for an optional path/value tuple, preserving its array shape.
111pub mod optional_pair {
112    use super::*;
113    pub fn serialize<S: Serializer, T: Serialize>(
114        pair: &Option<(PathBuf, T)>,
115        serializer: S,
116    ) -> Result<S::Ok, S::Error> {
117        match pair {
118            Some((path, value)) => serializer.serialize_some(&(PathRef(path), value)),
119            None => serializer.serialize_none(),
120        }
121    }
122    pub fn deserialize<'de, D: Deserializer<'de>, T: Deserialize<'de>>(
123        deserializer: D,
124    ) -> Result<Option<(PathBuf, T)>, D::Error> {
125        Ok(Option::<(OwnedPath, T)>::deserialize(deserializer)?
126            .map(|(path, value)| (path.0, value)))
127    }
128}
129
130/// Adapter for age-report path/size/time tuples.
131pub mod triples {
132    use super::*;
133    pub fn serialize<S: Serializer, A: Serialize, B: Serialize>(
134        values: &[(PathBuf, A, B)],
135        serializer: S,
136    ) -> Result<S::Ok, S::Error> {
137        let mut sequence = serializer.serialize_seq(Some(values.len()))?;
138        for (path, a, b) in values {
139            sequence.serialize_element(&(PathRef(path), a, b))?;
140        }
141        sequence.end()
142    }
143    pub fn deserialize<'de, D: Deserializer<'de>, A: Deserialize<'de>, B: Deserialize<'de>>(
144        deserializer: D,
145    ) -> Result<Vec<(PathBuf, A, B)>, D::Error> {
146        Ok(Vec::<(OwnedPath, A, B)>::deserialize(deserializer)?
147            .into_iter()
148            .map(|(path, a, b)| (path.0, a, b))
149            .collect())
150    }
151}