camino/
serde_impls.rs

1// Copyright (c) The camino Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Serde implementations for the types in this crate.
5//!
6//! * `Utf8Path` is an unsized type which the derive impls can't handle.
7//! * `Utf8PathBuf` could be derived, but we don't depend on serde_derive to
8//!   improve compile times. It's also very straightforward to implement.
9
10use crate::{Utf8Path, Utf8PathBuf};
11use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
12use std::fmt;
13
14struct Utf8PathBufVisitor;
15
16impl<'a> de::Visitor<'a> for Utf8PathBufVisitor {
17    type Value = Utf8PathBuf;
18
19    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
20        formatter.write_str("a UTF-8 path string")
21    }
22
23    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
24    where
25        E: de::Error,
26    {
27        Ok(v.into())
28    }
29
30    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
31    where
32        E: de::Error,
33    {
34        Ok(v.into())
35    }
36
37    fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
38    where
39        E: de::Error,
40    {
41        std::str::from_utf8(v)
42            .map(Into::into)
43            .map_err(|_| de::Error::invalid_value(de::Unexpected::Bytes(v), &self))
44    }
45
46    fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
47    where
48        E: de::Error,
49    {
50        String::from_utf8(v)
51            .map(Into::into)
52            .map_err(|e| de::Error::invalid_value(de::Unexpected::Bytes(&e.into_bytes()), &self))
53    }
54}
55
56impl<'de> Deserialize<'de> for Utf8PathBuf {
57    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58    where
59        D: Deserializer<'de>,
60    {
61        deserializer.deserialize_string(Utf8PathBufVisitor)
62    }
63}
64
65impl Serialize for Utf8PathBuf {
66    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
67    where
68        S: Serializer,
69    {
70        self.as_str().serialize(serializer)
71    }
72}
73
74struct Utf8PathVisitor;
75
76impl<'a> de::Visitor<'a> for Utf8PathVisitor {
77    type Value = &'a Utf8Path;
78
79    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
80        formatter.write_str("a borrowed UTF-8 path")
81    }
82
83    fn visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E>
84    where
85        E: de::Error,
86    {
87        Ok(v.as_ref())
88    }
89
90    fn visit_borrowed_bytes<E>(self, v: &'a [u8]) -> Result<Self::Value, E>
91    where
92        E: de::Error,
93    {
94        std::str::from_utf8(v)
95            .map(AsRef::as_ref)
96            .map_err(|_| de::Error::invalid_value(de::Unexpected::Bytes(v), &self))
97    }
98}
99
100impl<'de: 'a, 'a> Deserialize<'de> for &'a Utf8Path {
101    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
102    where
103        D: Deserializer<'de>,
104    {
105        deserializer.deserialize_str(Utf8PathVisitor)
106    }
107}
108
109impl Serialize for Utf8Path {
110    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111    where
112        S: Serializer,
113    {
114        self.as_str().serialize(serializer)
115    }
116}
117
118impl<'de> Deserialize<'de> for Box<Utf8Path> {
119    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120    where
121        D: Deserializer<'de>,
122    {
123        Ok(Utf8PathBuf::deserialize(deserializer)?.into())
124    }
125}
126
127// impl Serialize for Box<Utf8Path> comes from impl Serialize for Utf8Path.
128
129// Can't provide impls for Arc/Rc due to orphan rule issues, but we could provide
130// `with` impls in the future as requested.
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::Utf8PathBuf;
136    use serde_bytes::ByteBuf;
137    use serde_derive::{Deserialize, Serialize};
138
139    #[test]
140    fn valid_utf8() {
141        let valid_utf8 = &["", "bar", "💩"];
142        for input in valid_utf8 {
143            let encode = Encode {
144                path: ByteBuf::from(*input),
145            };
146            let encoded = bincode::serialize(&encode).expect("encoded correctly");
147
148            assert_valid_utf8::<DecodeOwned>(input, &encoded);
149            assert_valid_utf8::<DecodeBorrowed>(input, &encoded);
150            assert_valid_utf8::<DecodeBoxed>(input, &encoded);
151        }
152    }
153
154    fn assert_valid_utf8<'de, T: TestTrait<'de>>(input: &str, encoded: &'de [u8]) {
155        let output = bincode::deserialize::<T>(encoded).expect("valid UTF-8 should be fine");
156        assert_eq!(
157            output.path(),
158            input,
159            "for input, with {}, paths should match",
160            T::description()
161        );
162        let roundtrip = bincode::serialize(&output).expect("message should roundtrip");
163        assert_eq!(roundtrip, encoded, "encoded path matches");
164    }
165
166    #[test]
167    fn invalid_utf8() {
168        let invalid_utf8: &[(&[u8], _, _)] = &[
169            (b"\xff", 0, 1),
170            (b"foo\xfe", 3, 1),
171            (b"a\xC3\xA9 \xED\xA0\xBD\xF0\x9F\x92\xA9", 4, 1),
172        ];
173
174        for (input, valid_up_to, error_len) in invalid_utf8 {
175            let encode = Encode {
176                path: ByteBuf::from(*input),
177            };
178            let encoded = bincode::serialize(&encode).expect("encoded correctly");
179
180            assert_invalid_utf8::<DecodeOwned>(input, &encoded, *valid_up_to, *error_len);
181            assert_invalid_utf8::<DecodeBorrowed>(input, &encoded, *valid_up_to, *error_len);
182            assert_invalid_utf8::<DecodeBoxed>(input, &encoded, *valid_up_to, *error_len);
183        }
184    }
185
186    fn assert_invalid_utf8<'de, T: TestTrait<'de>>(
187        input: &[u8],
188        encoded: &'de [u8],
189        valid_up_to: usize,
190        error_len: usize,
191    ) {
192        let error = bincode::deserialize::<T>(encoded).expect_err("invalid UTF-8 should error out");
193        let utf8_error = match *error {
194            bincode::ErrorKind::InvalidUtf8Encoding(utf8_error) => utf8_error,
195            other => panic!(
196                "for input {:?}, with {}, expected ErrorKind::InvalidUtf8Encoding, found: {}",
197                input,
198                T::description(),
199                other
200            ),
201        };
202        assert_eq!(
203            utf8_error.valid_up_to(),
204            valid_up_to,
205            "for input {:?}, with {}, valid_up_to didn't match",
206            input,
207            T::description(),
208        );
209        assert_eq!(
210            utf8_error.error_len(),
211            Some(error_len),
212            "for input {:?}, with {}, error_len didn't match",
213            input,
214            T::description(),
215        );
216    }
217
218    #[derive(Serialize, Debug)]
219    struct Encode {
220        path: ByteBuf,
221    }
222
223    trait TestTrait<'de>: Serialize + Deserialize<'de> + fmt::Debug {
224        fn description() -> &'static str;
225        fn path(&self) -> &Utf8Path;
226    }
227
228    #[derive(Serialize, Deserialize, Debug)]
229    #[allow(unused)]
230    struct DecodeOwned {
231        path: Utf8PathBuf,
232    }
233
234    impl TestTrait<'_> for DecodeOwned {
235        fn description() -> &'static str {
236            "DecodeOwned"
237        }
238
239        fn path(&self) -> &Utf8Path {
240            &self.path
241        }
242    }
243
244    #[derive(Serialize, Deserialize, Debug)]
245    #[allow(unused)]
246    struct DecodeBorrowed<'a> {
247        #[serde(borrow)]
248        path: &'a Utf8Path,
249    }
250
251    impl<'de> TestTrait<'de> for DecodeBorrowed<'de> {
252        fn description() -> &'static str {
253            "DecodeBorrowed"
254        }
255
256        fn path(&self) -> &Utf8Path {
257            self.path
258        }
259    }
260
261    #[derive(Serialize, Deserialize, Debug)]
262    #[allow(unused)]
263    struct DecodeBoxed {
264        path: Box<Utf8Path>,
265    }
266
267    impl TestTrait<'_> for DecodeBoxed {
268        fn description() -> &'static str {
269            "DecodeBoxed"
270        }
271
272        fn path(&self) -> &Utf8Path {
273            &self.path
274        }
275    }
276}