| 1 | #![allow (clippy::too_many_lines)] |
| 2 | |
| 3 | use serde::{de, Deserialize}; |
| 4 | use std::fmt; |
| 5 | |
| 6 | macro_rules! bad { |
| 7 | ($toml:expr, $ty:ty, $msg:expr) => { |
| 8 | match basic_toml::from_str::<$ty>($toml) { |
| 9 | Ok(s) => panic!("parsed to: {:#?}" , s), |
| 10 | Err(e) => assert_eq!(e.to_string(), $msg), |
| 11 | } |
| 12 | }; |
| 13 | } |
| 14 | |
| 15 | #[derive(Debug, Deserialize, PartialEq)] |
| 16 | struct Parent<T> { |
| 17 | p_a: T, |
| 18 | p_b: Vec<Child<T>>, |
| 19 | } |
| 20 | |
| 21 | #[derive(Debug, Deserialize, PartialEq)] |
| 22 | #[serde(deny_unknown_fields)] |
| 23 | struct Child<T> { |
| 24 | c_a: T, |
| 25 | c_b: T, |
| 26 | } |
| 27 | |
| 28 | #[derive(Debug, PartialEq)] |
| 29 | enum CasedString { |
| 30 | Lowercase(String), |
| 31 | Uppercase(String), |
| 32 | } |
| 33 | |
| 34 | impl<'de> de::Deserialize<'de> for CasedString { |
| 35 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| 36 | where |
| 37 | D: de::Deserializer<'de>, |
| 38 | { |
| 39 | struct CasedStringVisitor; |
| 40 | |
| 41 | impl<'de> de::Visitor<'de> for CasedStringVisitor { |
| 42 | type Value = CasedString; |
| 43 | |
| 44 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { |
| 45 | formatter.write_str("a string" ) |
| 46 | } |
| 47 | |
| 48 | fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> |
| 49 | where |
| 50 | E: de::Error, |
| 51 | { |
| 52 | if s.is_empty() { |
| 53 | Err(de::Error::invalid_length(0, &"a non-empty string" )) |
| 54 | } else if s.chars().all(|x| x.is_ascii_lowercase()) { |
| 55 | Ok(CasedString::Lowercase(s.to_string())) |
| 56 | } else if s.chars().all(|x| x.is_ascii_uppercase()) { |
| 57 | Ok(CasedString::Uppercase(s.to_string())) |
| 58 | } else { |
| 59 | Err(de::Error::invalid_value( |
| 60 | de::Unexpected::Str(s), |
| 61 | &"all lowercase or all uppercase" , |
| 62 | )) |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | deserializer.deserialize_any(CasedStringVisitor) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | #[test] |
| 72 | fn custom_errors() { |
| 73 | basic_toml::from_str::<Parent<CasedString>>( |
| 74 | " |
| 75 | p_a = 'a' |
| 76 | p_b = [{c_a = 'a', c_b = 'c'}] |
| 77 | " , |
| 78 | ) |
| 79 | .unwrap(); |
| 80 | |
| 81 | // Custom error at p_b value. |
| 82 | bad!( |
| 83 | " |
| 84 | p_a = '' |
| 85 | # ^ |
| 86 | " , |
| 87 | Parent<CasedString>, |
| 88 | "invalid length 0, expected a non-empty string for key `p_a` at line 2 column 19" |
| 89 | ); |
| 90 | |
| 91 | // Missing field in table. |
| 92 | bad!( |
| 93 | " |
| 94 | p_a = 'a' |
| 95 | # ^ |
| 96 | " , |
| 97 | Parent<CasedString>, |
| 98 | "missing field `p_b` at line 1 column 1" |
| 99 | ); |
| 100 | |
| 101 | // Invalid type in p_b. |
| 102 | bad!( |
| 103 | " |
| 104 | p_a = 'a' |
| 105 | p_b = 1 |
| 106 | # ^ |
| 107 | " , |
| 108 | Parent<CasedString>, |
| 109 | "invalid type: integer `1`, expected a sequence for key `p_b` at line 3 column 19" |
| 110 | ); |
| 111 | |
| 112 | // Sub-table in Vec is missing a field. |
| 113 | bad!( |
| 114 | " |
| 115 | p_a = 'a' |
| 116 | p_b = [ |
| 117 | {c_a = 'a'} |
| 118 | # ^ |
| 119 | ] |
| 120 | " , |
| 121 | Parent<CasedString>, |
| 122 | "missing field `c_b` for key `p_b` at line 4 column 17" |
| 123 | ); |
| 124 | |
| 125 | // Sub-table in Vec has a field with a bad value. |
| 126 | bad!( |
| 127 | " |
| 128 | p_a = 'a' |
| 129 | p_b = [ |
| 130 | {c_a = 'a', c_b = '*'} |
| 131 | # ^ |
| 132 | ] |
| 133 | " , |
| 134 | Parent<CasedString>, |
| 135 | "invalid value: string \"* \", expected all lowercase or all uppercase for key `p_b` at line 4 column 35" |
| 136 | ); |
| 137 | |
| 138 | // Sub-table in Vec is missing a field. |
| 139 | bad!( |
| 140 | " |
| 141 | p_a = 'a' |
| 142 | p_b = [ |
| 143 | {c_a = 'a', c_b = 'b'}, |
| 144 | {c_a = 'aa'} |
| 145 | # ^ |
| 146 | ] |
| 147 | " , |
| 148 | Parent<CasedString>, |
| 149 | "missing field `c_b` for key `p_b` at line 5 column 17" |
| 150 | ); |
| 151 | |
| 152 | // Sub-table in the middle of a Vec is missing a field. |
| 153 | bad!( |
| 154 | " |
| 155 | p_a = 'a' |
| 156 | p_b = [ |
| 157 | {c_a = 'a', c_b = 'b'}, |
| 158 | {c_a = 'aa'}, |
| 159 | # ^ |
| 160 | {c_a = 'aaa', c_b = 'bbb'}, |
| 161 | ] |
| 162 | " , |
| 163 | Parent<CasedString>, |
| 164 | "missing field `c_b` for key `p_b` at line 5 column 17" |
| 165 | ); |
| 166 | |
| 167 | // Sub-table in the middle of a Vec has a field with a bad value. |
| 168 | bad!( |
| 169 | " |
| 170 | p_a = 'a' |
| 171 | p_b = [ |
| 172 | {c_a = 'a', c_b = 'b'}, |
| 173 | {c_a = 'aa', c_b = 1}, |
| 174 | # ^ |
| 175 | {c_a = 'aaa', c_b = 'bbb'}, |
| 176 | ] |
| 177 | " , |
| 178 | Parent<CasedString>, |
| 179 | "invalid type: integer `1`, expected a string for key `p_b` at line 5 column 36" |
| 180 | ); |
| 181 | |
| 182 | // Sub-table in the middle of a Vec has an extra field. |
| 183 | // FIXME: This location could be better. |
| 184 | bad!( |
| 185 | " |
| 186 | p_a = 'a' |
| 187 | p_b = [ |
| 188 | {c_a = 'a', c_b = 'b'}, |
| 189 | {c_a = 'aa', c_b = 'bb', c_d = 'd'}, |
| 190 | # ^ |
| 191 | {c_a = 'aaa', c_b = 'bbb'}, |
| 192 | {c_a = 'aaaa', c_b = 'bbbb'}, |
| 193 | ] |
| 194 | " , |
| 195 | Parent<CasedString>, |
| 196 | "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 5 column 17" |
| 197 | ); |
| 198 | |
| 199 | // Sub-table in the middle of a Vec is missing a field. |
| 200 | // FIXME: This location is pretty off. |
| 201 | bad!( |
| 202 | " |
| 203 | p_a = 'a' |
| 204 | [[p_b]] |
| 205 | c_a = 'a' |
| 206 | c_b = 'b' |
| 207 | [[p_b]] |
| 208 | c_a = 'aa' |
| 209 | # c_b = 'bb' # <- missing field |
| 210 | [[p_b]] |
| 211 | c_a = 'aaa' |
| 212 | c_b = 'bbb' |
| 213 | [[p_b]] |
| 214 | # ^ |
| 215 | c_a = 'aaaa' |
| 216 | c_b = 'bbbb' |
| 217 | " , |
| 218 | Parent<CasedString>, |
| 219 | "missing field `c_b` for key `p_b` at line 12 column 13" |
| 220 | ); |
| 221 | |
| 222 | // Sub-table in the middle of a Vec has a field with a bad value. |
| 223 | bad!( |
| 224 | " |
| 225 | p_a = 'a' |
| 226 | [[p_b]] |
| 227 | c_a = 'a' |
| 228 | c_b = 'b' |
| 229 | [[p_b]] |
| 230 | c_a = 'aa' |
| 231 | c_b = '*' |
| 232 | # ^ |
| 233 | [[p_b]] |
| 234 | c_a = 'aaa' |
| 235 | c_b = 'bbb' |
| 236 | " , |
| 237 | Parent<CasedString>, |
| 238 | "invalid value: string \"* \", expected all lowercase or all uppercase for key `p_b.c_b` at line 8 column 19" |
| 239 | ); |
| 240 | |
| 241 | // Sub-table in the middle of a Vec has an extra field. |
| 242 | // FIXME: This location is pretty off. |
| 243 | bad!( |
| 244 | " |
| 245 | p_a = 'a' |
| 246 | [[p_b]] |
| 247 | c_a = 'a' |
| 248 | c_b = 'b' |
| 249 | [[p_b]] |
| 250 | c_a = 'aa' |
| 251 | c_d = 'dd' # unknown field |
| 252 | [[p_b]] |
| 253 | c_a = 'aaa' |
| 254 | c_b = 'bbb' |
| 255 | [[p_b]] |
| 256 | # ^ |
| 257 | c_a = 'aaaa' |
| 258 | c_b = 'bbbb' |
| 259 | " , |
| 260 | Parent<CasedString>, |
| 261 | "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 12 column 13" |
| 262 | ); |
| 263 | } |
| 264 | |
| 265 | #[test] |
| 266 | fn serde_derive_deserialize_errors() { |
| 267 | bad!( |
| 268 | " |
| 269 | p_a = '' |
| 270 | # ^ |
| 271 | " , |
| 272 | Parent<String>, |
| 273 | "missing field `p_b` at line 1 column 1" |
| 274 | ); |
| 275 | |
| 276 | bad!( |
| 277 | " |
| 278 | p_a = '' |
| 279 | p_b = [ |
| 280 | {c_a = ''} |
| 281 | # ^ |
| 282 | ] |
| 283 | " , |
| 284 | Parent<String>, |
| 285 | "missing field `c_b` for key `p_b` at line 4 column 17" |
| 286 | ); |
| 287 | |
| 288 | bad!( |
| 289 | " |
| 290 | p_a = '' |
| 291 | p_b = [ |
| 292 | {c_a = '', c_b = 1} |
| 293 | # ^ |
| 294 | ] |
| 295 | " , |
| 296 | Parent<String>, |
| 297 | "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34" |
| 298 | ); |
| 299 | |
| 300 | // FIXME: This location could be better. |
| 301 | bad!( |
| 302 | " |
| 303 | p_a = '' |
| 304 | p_b = [ |
| 305 | {c_a = '', c_b = '', c_d = ''}, |
| 306 | # ^ |
| 307 | ] |
| 308 | " , |
| 309 | Parent<String>, |
| 310 | "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 4 column 17" |
| 311 | ); |
| 312 | |
| 313 | bad!( |
| 314 | " |
| 315 | p_a = 'a' |
| 316 | p_b = [ |
| 317 | {c_a = '', c_b = 1, c_d = ''}, |
| 318 | # ^ |
| 319 | ] |
| 320 | " , |
| 321 | Parent<String>, |
| 322 | "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34" |
| 323 | ); |
| 324 | } |
| 325 | |
| 326 | #[test] |
| 327 | fn error_handles_crlf() { |
| 328 | bad!( |
| 329 | " \r\n\ |
| 330 | [t1] \r\n\ |
| 331 | [t2] \r\n\ |
| 332 | a = 1 \r\n\ |
| 333 | . = 2 \r\n\ |
| 334 | " , |
| 335 | serde_json::Value, |
| 336 | "expected a table key, found a period at line 5 column 1" |
| 337 | ); |
| 338 | |
| 339 | // Should be the same as above. |
| 340 | bad!( |
| 341 | " \n\ |
| 342 | [t1] \n\ |
| 343 | [t2] \n\ |
| 344 | a = 1 \n\ |
| 345 | . = 2 \n\ |
| 346 | " , |
| 347 | serde_json::Value, |
| 348 | "expected a table key, found a period at line 5 column 1" |
| 349 | ); |
| 350 | } |
| 351 | |