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 9 use crate::{Utf8Path, Utf8PathBuf}; 10 use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; 11 use std::fmt; 12 13 struct Utf8PathVisitor; 14 15 impl<'a> de::Visitor<'a> for Utf8PathVisitor { 16 type Value = &'a Utf8Path; 17 expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result18 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 19 formatter.write_str("a borrowed path") 20 } 21 visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E> where E: de::Error,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 visit_borrowed_bytes<E>(self, v: &'a [u8]) -> Result<Self::Value, E> where E: de::Error,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 39 impl<'de: 'a, 'a> Deserialize<'de> for &'a Utf8Path { deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,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 48 impl Serialize for Utf8Path { serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,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 57 impl<'de> Deserialize<'de> for Box<Utf8Path> { deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,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)] 72 mod tests { 73 use super::*; 74 use crate::Utf8PathBuf; 75 use serde_bytes::ByteBuf; 76 77 #[test] valid_utf8()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 assert_valid_utf8<'de, T: TestTrait<'de>>(input: &str, encoded: &'de [u8])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] invalid_utf8()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 assert_invalid_utf8<'de, T: TestTrait<'de>>( input: &[u8], encoded: &'de [u8], valid_up_to: usize, error_len: usize, )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 { description() -> &'static str162 fn description() -> &'static str; path(&self) -> &Utf8Path163 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 { description() -> &'static str173 fn description() -> &'static str { 174 "DecodeOwned" 175 } 176 path(&self) -> &Utf8Path177 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> { description() -> &'static str190 fn description() -> &'static str { 191 "DecodeBorrowed" 192 } 193 path(&self) -> &Utf8Path194 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 { description() -> &'static str206 fn description() -> &'static str { 207 "DecodeBoxed" 208 } 209 path(&self) -> &Utf8Path210 fn path(&self) -> &Utf8Path { 211 &self.path 212 } 213 } 214 } 215