|
1 | 1 | //! Implementations of OpenAPI `oneOf` and `anyOf` types, assuming rules are just types |
2 | 2 | #[cfg(feature = "conversion")] |
3 | 3 | use frunk_enum_derive::LabelledGenericEnum; |
4 | | -use serde::{ |
5 | | - de::Error, |
6 | | - Deserialize, Deserializer, Serialize, Serializer, |
7 | | - __private::de::{Content, ContentRefDeserializer}, |
8 | | -}; |
| 4 | +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; |
9 | 5 | #[cfg(feature = "serdevalid")] |
10 | 6 | use serde_valid::Validate; |
11 | 7 | use std::fmt; |
12 | 8 | use std::str::FromStr; |
13 | 9 | use std::string::ToString; |
14 | 10 |
|
| 11 | +use serde_value::Value as SerdeValue; |
| 12 | + |
15 | 13 | // Define a macro to define the common parts between `OneOf` and `AnyOf` enums for a specific |
16 | 14 | // number of inner types. |
17 | 15 | macro_rules! common_one_any_of { |
@@ -67,10 +65,11 @@ macro_rules! one_of { |
67 | 65 | $($i: PartialEq + for<'a> Deserialize<'a>,)* |
68 | 66 | { |
69 | 67 | fn deserialize<De: Deserializer<'b>>(deserializer: De) -> Result<Self, De::Error> { |
70 | | - let content = Content::deserialize(deserializer)?; |
| 68 | + // Capture once into a generic value (serde_value supports all JSON-like data) |
| 69 | + let captured: SerdeValue = SerdeValue::deserialize(deserializer)?; |
71 | 70 | let mut result = Err(De::Error::custom("data did not match any within oneOf")); |
72 | 71 | $( |
73 | | - if let Ok(inner) = $i::deserialize(ContentRefDeserializer::<De::Error>::new(&content)) { |
| 72 | + if let Ok(inner) = <$i as Deserialize>::deserialize(captured.clone()) { |
74 | 73 | if result.is_err() { |
75 | 74 | result = Ok(Self::$i(inner)); |
76 | 75 | } else { |
@@ -133,9 +132,9 @@ macro_rules! any_of { |
133 | 132 | $($i: PartialEq + for<'a> Deserialize<'a>,)* |
134 | 133 | { |
135 | 134 | fn deserialize<De: Deserializer<'b>>(deserializer: De) -> Result<Self, De::Error> { |
136 | | - let content = Content::deserialize(deserializer)?; |
| 135 | + let captured: SerdeValue = SerdeValue::deserialize(deserializer)?; |
137 | 136 | $( |
138 | | - if let Ok(inner) = $i::deserialize(ContentRefDeserializer::<De::Error>::new(&content)) { |
| 137 | + if let Ok(inner) = <$i as Deserialize>::deserialize(captured.clone()) { |
139 | 138 | return Ok(Self::$i(inner)); |
140 | 139 | } |
141 | 140 | )* |
@@ -176,3 +175,274 @@ any_of!(AnyOf13, A, B, C, D, E, F, G, H, I, J, K, L, M); |
176 | 175 | any_of!(AnyOf14, A, B, C, D, E, F, G, H, I, J, K, L, M, N); |
177 | 176 | any_of!(AnyOf15, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); |
178 | 177 | any_of!(AnyOf16, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); |
| 178 | + |
| 179 | +#[cfg(test)] |
| 180 | +mod tests { |
| 181 | + use super::*; |
| 182 | + |
| 183 | + #[test] |
| 184 | + fn anyof_prefers_first_matching_deserialize_number() { |
| 185 | + let json = "123"; |
| 186 | + let v: AnyOf2<u32, String> = serde_json::from_str(json).unwrap(); |
| 187 | + match v { |
| 188 | + AnyOf2::A(n) => assert_eq!(n, 123), |
| 189 | + AnyOf2::B(_) => panic!("expected A variant"), |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + #[test] |
| 194 | + fn anyof_prefers_first_matching_fromstr_number() { |
| 195 | + let v = AnyOf2::<u8, String>::from_str("123").unwrap(); |
| 196 | + match v { |
| 197 | + AnyOf2::A(n) => assert_eq!(n, 123), |
| 198 | + AnyOf2::B(_) => panic!("expected A variant"), |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + #[test] |
| 203 | + fn anyof_deserialize_string_to_second_variant() { |
| 204 | + let json = "\"hello\""; |
| 205 | + let v: AnyOf2<u32, String> = serde_json::from_str(json).unwrap(); |
| 206 | + match v { |
| 207 | + AnyOf2::B(s) => assert_eq!(s, "hello"), |
| 208 | + AnyOf2::A(_) => panic!("expected B variant"), |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + #[test] |
| 213 | + fn oneof_deserialize_single_match() { |
| 214 | + let json = "\"hi\""; |
| 215 | + let v: OneOf2<u32, String> = serde_json::from_str(json).unwrap(); |
| 216 | + match v { |
| 217 | + OneOf2::B(s) => assert_eq!(s, "hi"), |
| 218 | + OneOf2::A(_) => panic!("expected B variant"), |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + #[test] |
| 223 | + fn oneof_deserialize_error_on_multiple_matches() { |
| 224 | + // both u32 and u64 will deserialize from "123" -> ambiguity -> error |
| 225 | + let json = "123"; |
| 226 | + let res: Result<OneOf2<u32, u64>, _> = serde_json::from_str(json); |
| 227 | + assert!(res.is_err(), "expected error when multiple variants match"); |
| 228 | + } |
| 229 | + |
| 230 | + #[test] |
| 231 | + fn oneof_fromstr_error_on_multiple_matches() { |
| 232 | + // String::from_str always succeeds and u8::from_str also succeeds here -> multiple matches |
| 233 | + let res = OneOf2::<u8, String>::from_str("123"); |
| 234 | + assert!(res.is_err(), "expected error when multiple FromStr matches"); |
| 235 | + } |
| 236 | + |
| 237 | + #[test] |
| 238 | + fn display_and_serialize_roundtrip() { |
| 239 | + let a: OneOf2<u32, String> = OneOf2::A(7u32); |
| 240 | + assert_eq!(a.to_string(), "7"); |
| 241 | + let ser = serde_json::to_string(&a).unwrap(); |
| 242 | + assert_eq!(ser, "7"); |
| 243 | + |
| 244 | + let b: OneOf2<u32, String> = OneOf2::B(String::from("abc")); |
| 245 | + assert_eq!(b.to_string(), "abc"); |
| 246 | + let ser_b = serde_json::to_string(&b).unwrap(); |
| 247 | + assert_eq!(ser_b, "\"abc\""); |
| 248 | + } |
| 249 | + |
| 250 | + #[test] |
| 251 | + fn map_ambiguity_oneof() { |
| 252 | + #[derive(Debug, PartialEq, Deserialize)] |
| 253 | + struct S1 { |
| 254 | + x: u32, |
| 255 | + } |
| 256 | + #[derive(Debug, PartialEq, Deserialize)] |
| 257 | + struct S2 { |
| 258 | + x: u64, |
| 259 | + } |
| 260 | + let json = "{\"x\":123}"; |
| 261 | + let res: Result<OneOf2<S1, S2>, _> = serde_json::from_str(json); |
| 262 | + assert!( |
| 263 | + res.is_err(), |
| 264 | + "expected ambiguity error for map matching two structs" |
| 265 | + ); |
| 266 | + } |
| 267 | + |
| 268 | + #[test] |
| 269 | + fn map_first_match_anyof() { |
| 270 | + #[derive(Debug, PartialEq, Deserialize)] |
| 271 | + struct S1 { |
| 272 | + x: u32, |
| 273 | + } |
| 274 | + #[derive(Debug, PartialEq, Deserialize)] |
| 275 | + struct S2 { |
| 276 | + x: u64, |
| 277 | + } |
| 278 | + let json = "{\"x\":123}"; |
| 279 | + let v: AnyOf2<S1, S2> = serde_json::from_str(json).unwrap(); |
| 280 | + match v { |
| 281 | + AnyOf2::A(s) => assert_eq!(s.x, 123), |
| 282 | + AnyOf2::B(_) => panic!("expected first struct"), |
| 283 | + } |
| 284 | + } |
| 285 | + |
| 286 | + #[test] |
| 287 | + fn null_ambiguity_oneof() { |
| 288 | + let json = "null"; |
| 289 | + // Option<Unit> deserializes to None from null; Unit also deserializes from null? (No, unit struct expects object usually) -> To create ambiguity, use Option<String> and Option<u32> both None |
| 290 | + let res: Result<OneOf2<Option<u32>, Option<String>>, _> = serde_json::from_str(json); |
| 291 | + assert!( |
| 292 | + res.is_err(), |
| 293 | + "expected ambiguity with null across two Option types" |
| 294 | + ); |
| 295 | + } |
| 296 | + |
| 297 | + #[test] |
| 298 | + fn null_preference_anyof() { |
| 299 | + let json = "null"; |
| 300 | + let v: AnyOf2<Option<u32>, Option<String>> = serde_json::from_str(json).unwrap(); |
| 301 | + match v { |
| 302 | + AnyOf2::A(opt) => assert!(opt.is_none()), |
| 303 | + AnyOf2::B(_) => panic!("expected first Option variant"), |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + #[test] |
| 308 | + fn sequence_ambiguity_oneof() { |
| 309 | + let json = "[1,2]"; |
| 310 | + let res: Result<OneOf2<Vec<u8>, Vec<u16>>, _> = serde_json::from_str(json); |
| 311 | + assert!( |
| 312 | + res.is_err(), |
| 313 | + "expected ambiguity for two vector numeric element types fitting both" |
| 314 | + ); |
| 315 | + } |
| 316 | + |
| 317 | + #[test] |
| 318 | + fn sequence_first_match_anyof() { |
| 319 | + let json = "[1,2]"; |
| 320 | + let v: AnyOf2<Vec<u8>, Vec<u16>> = serde_json::from_str(json).unwrap(); |
| 321 | + match v { |
| 322 | + AnyOf2::A(v1) => assert_eq!(v1, vec![1, 2]), |
| 323 | + AnyOf2::B(_) => panic!("expected first vector variant"), |
| 324 | + } |
| 325 | + } |
| 326 | + |
| 327 | + #[test] |
| 328 | + fn large_number_anyof_prefers_second() { |
| 329 | + let json = "5000000000"; // > u32::MAX |
| 330 | + let v: AnyOf2<u32, u64> = serde_json::from_str(json).unwrap(); |
| 331 | + match v { |
| 332 | + AnyOf2::B(n) => assert_eq!(n, 5_000_000_000u64), |
| 333 | + AnyOf2::A(_) => panic!("expected second variant since first fails"), |
| 334 | + } |
| 335 | + } |
| 336 | + |
| 337 | + #[test] |
| 338 | + fn oneof_deserialize_no_match_error() { |
| 339 | + // bool doesn't match u32 or String (needs quotes for string) |
| 340 | + let json = "true"; |
| 341 | + let res: Result<OneOf2<u32, String>, _> = serde_json::from_str(json); |
| 342 | + assert!(res.is_err()); |
| 343 | + let msg = format!("{}", res.unwrap_err()); |
| 344 | + assert!( |
| 345 | + msg.contains("did not match any within oneOf"), |
| 346 | + "unexpected error message: {msg}" |
| 347 | + ); |
| 348 | + } |
| 349 | + |
| 350 | + #[test] |
| 351 | + fn anyof_deserialize_no_match_error() { |
| 352 | + let json = "false"; // neither u32 nor String |
| 353 | + let res: Result<AnyOf2<u32, String>, _> = serde_json::from_str(json); |
| 354 | + assert!(res.is_err()); |
| 355 | + let msg = format!("{}", res.unwrap_err()); |
| 356 | + assert!( |
| 357 | + msg.contains("did not match any within anyOf"), |
| 358 | + "unexpected error message: {msg}" |
| 359 | + ); |
| 360 | + } |
| 361 | + |
| 362 | + #[test] |
| 363 | + fn oneof_fromstr_single_match() { |
| 364 | + let v = OneOf2::<bool, u8>::from_str("true").expect("bool should parse"); |
| 365 | + match v { |
| 366 | + OneOf2::A(b) => assert!(b), |
| 367 | + _ => panic!("expected bool variant"), |
| 368 | + } |
| 369 | + } |
| 370 | + |
| 371 | + #[test] |
| 372 | + fn oneof_fromstr_no_match() { |
| 373 | + let res = OneOf2::<u32, u16>::from_str("abc"); |
| 374 | + assert!(res.is_err()); |
| 375 | + } |
| 376 | + |
| 377 | + #[test] |
| 378 | + fn anyof_fromstr_later_match() { |
| 379 | + let v = AnyOf2::<u32, bool>::from_str("true").expect("bool should parse"); |
| 380 | + match v { |
| 381 | + AnyOf2::B(b) => assert!(b), |
| 382 | + _ => panic!("expected second bool variant"), |
| 383 | + } |
| 384 | + } |
| 385 | + |
| 386 | + #[test] |
| 387 | + fn anyof_fromstr_no_match() { |
| 388 | + let res = AnyOf2::<u32, u16>::from_str("abc"); |
| 389 | + assert!(res.is_err()); |
| 390 | + } |
| 391 | + |
| 392 | + #[test] |
| 393 | + fn oneof_higher_arity_ambiguity() { |
| 394 | + // "5" (number) matches u8, u16, u32 -> ambiguity error |
| 395 | + let json = "5"; |
| 396 | + let res: Result<OneOf3<u8, u16, u32>, _> = serde_json::from_str(json); |
| 397 | + assert!(res.is_err(), "expected ambiguity for three numeric matches"); |
| 398 | + } |
| 399 | + |
| 400 | + #[test] |
| 401 | + fn anyof_higher_arity_middle_match() { |
| 402 | + // true fails u32, matches bool (middle), should select variant B |
| 403 | + let json = "true"; |
| 404 | + let v: AnyOf3<u32, bool, String> = serde_json::from_str(json).unwrap(); |
| 405 | + match v { |
| 406 | + AnyOf3::B(b) => assert!(b), |
| 407 | + _ => panic!("expected middle bool variant"), |
| 408 | + } |
| 409 | + } |
| 410 | + |
| 411 | + #[test] |
| 412 | + fn oneof_display_serialize_variant_c() { |
| 413 | + let c: OneOf3<String, u32, bool> = OneOf3::C(true); |
| 414 | + assert_eq!(c.to_string(), "true"); |
| 415 | + let ser = serde_json::to_string(&c).unwrap(); |
| 416 | + assert_eq!(ser, "true"); |
| 417 | + } |
| 418 | + |
| 419 | + #[test] |
| 420 | + fn anyof_nested_structure_second_variant() { |
| 421 | + #[derive(Debug, PartialEq, Deserialize)] |
| 422 | + struct A { |
| 423 | + x: u32, |
| 424 | + } |
| 425 | + #[derive(Debug, PartialEq, Deserialize)] |
| 426 | + struct B { |
| 427 | + y: u32, |
| 428 | + } |
| 429 | + let json = "{\"y\":5}"; |
| 430 | + let v: AnyOf3<A, B, String> = serde_json::from_str(json).unwrap(); |
| 431 | + match v { |
| 432 | + AnyOf3::B(b) => assert_eq!(b.y, 5), |
| 433 | + _ => panic!("expected struct B variant"), |
| 434 | + } |
| 435 | + } |
| 436 | + |
| 437 | + #[test] |
| 438 | + fn oneof_ambiguity_error_message() { |
| 439 | + let json = "123"; // matches multiple integer widths |
| 440 | + let res: Result<OneOf2<u32, u64>, _> = serde_json::from_str(json); |
| 441 | + let err = res.unwrap_err(); |
| 442 | + let msg = format!("{}", err); |
| 443 | + assert!( |
| 444 | + msg.contains("data matched multiple within oneOf"), |
| 445 | + "missing ambiguity message: {msg}" |
| 446 | + ); |
| 447 | + } |
| 448 | +} |
0 commit comments