use darling::{util::Flag, FromDeriveInput, FromMeta}; use proc_macro2::Ident; use syn::parse_quote; #[derive(FromMeta)] struct Vis { public: Flag, private: Flag, } #[derive(FromDeriveInput)] #[darling(attributes(sample))] struct Example { ident: Ident, label: String, #[darling(flatten)] visibility: Vis, } #[test] fn happy_path() { let di = Example::from_derive_input(&parse_quote! { #[sample(label = "Hello", public)] struct Demo {} }); let parsed = di.unwrap(); assert_eq!(parsed.ident, "Demo"); assert_eq!(&parsed.label, "Hello"); assert!(parsed.visibility.public.is_present()); assert!(!parsed.visibility.private.is_present()); } #[test] fn unknown_field_errors() { let errors = Example::from_derive_input(&parse_quote! { #[sample(label = "Hello", republic)] struct Demo {} }) .map(|_| "Should have failed") .unwrap_err(); assert_eq!(errors.len(), 1); } /// This test demonstrates flatten being used recursively. /// Fields are expected to be consumed by the outermost matching struct. #[test] fn recursive_flattening() { #[derive(FromMeta)] struct Nested2 { above: isize, below: isize, port: Option, } #[derive(FromMeta)] struct Nested1 { port: isize, starboard: isize, #[darling(flatten)] z_axis: Nested2, } #[derive(FromMeta)] struct Nested0 { fore: isize, aft: isize, #[darling(flatten)] cross_section: Nested1, } #[derive(FromDeriveInput)] #[darling(attributes(boat))] struct BoatPosition { #[darling(flatten)] pos: Nested0, } let parsed = BoatPosition::from_derive_input(&parse_quote! { #[boat(fore = 1, aft = 1, port = 10, starboard = 50, above = 20, below = -3)] struct Demo; }) .unwrap(); assert_eq!(parsed.pos.fore, 1); assert_eq!(parsed.pos.aft, 1); assert_eq!(parsed.pos.cross_section.port, 10); assert_eq!(parsed.pos.cross_section.starboard, 50); assert_eq!(parsed.pos.cross_section.z_axis.above, 20); assert_eq!(parsed.pos.cross_section.z_axis.below, -3); // This should be `None` because the `port` field in `Nested1` consumed // the field before the leftovers were passed to `Nested2::from_list`. assert_eq!(parsed.pos.cross_section.z_axis.port, None); } /// This test confirms that a collection - in this case a HashMap - can /// be used with `flatten`. #[test] fn flattening_into_hashmap() { #[derive(FromDeriveInput)] #[darling(attributes(ca))] struct Catchall { hello: String, volume: usize, #[darling(flatten)] others: std::collections::HashMap, } let parsed = Catchall::from_derive_input(&parse_quote! { #[ca(hello = "World", volume = 10, first_name = "Alice", second_name = "Bob")] struct Demo; }) .unwrap(); assert_eq!(parsed.hello, "World"); assert_eq!(parsed.volume, 10); assert_eq!(parsed.others.len(), 2); } #[derive(FromMeta)] #[allow(dead_code)] struct Person { first: String, last: String, parent: Option>, } #[derive(FromDeriveInput)] #[darling(attributes(v))] #[allow(dead_code)] struct Outer { #[darling(flatten)] owner: Person, #[darling(default)] blast: bool, } /// This test makes sure that field names from parent structs are not inappropriately /// offered as alternates for unknown field errors in child structs. /// /// A naive implementation that tried to offer all the flattened fields for "did you mean" /// could inspect all errors returned by the flattened field's `from_list` call and add the /// parent's field names as alternates to all unknown field errors. /// /// THIS WOULD BE INCORRECT. Those unknown field errors may have already come from /// child fields within the flattened struct, where the parent's field names are not valid. #[test] fn do_not_suggest_invalid_alts() { let errors = Outer::from_derive_input(&parse_quote! { #[v(first = "Hello", last = "World", parent(first = "Hi", last = "Earth", blasts = "off"))] struct Demo; }) .map(|_| "Should have failed") .unwrap_err() .to_string(); assert!( !errors.contains("`blast`"), "Should not contain `blast`: {}", errors ); } #[test] #[cfg(feature = "suggestions")] fn suggest_valid_parent_alts() { let errors = Outer::from_derive_input(&parse_quote! { #[v(first = "Hello", bladt = false, last = "World", parent(first = "Hi", last = "Earth"))] struct Demo; }) .map(|_| "Should have failed") .unwrap_err() .to_string(); assert!( errors.contains("`blast`"), "Should contain `blast` as did-you-mean suggestion: {}", errors ); } /// Make sure that flatten works with smart pointer types, e.g. `Box`. /// /// The generated `flatten` impl directly calls `FromMeta::from_list` /// rather than calling `from_meta`, and the default impl of `from_list` /// will return an unsupported format error; this test ensures that the /// smart pointer type is properly forwarding the `from_list` call. #[test] fn flattening_to_box() { #[derive(FromDeriveInput)] #[darling(attributes(v))] struct Example { #[darling(flatten)] items: Box, } let when_omitted = Example::from_derive_input(&parse_quote! { struct Demo; }) .unwrap(); assert!(!when_omitted.items.public.is_present()); }