camino/
serde_impls.rs

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