From 8011d85ca4354b833b22816000ab5cdf55d5ffd8 Mon Sep 17 00:00:00 2001 From: Logan Mzz Date: Sun, 15 Sep 2024 15:13:59 +0200 Subject: [PATCH] Add `fields(Default=!)` for struct attribute `#[builder]` (#32) --- CHANGELOG.md | 1 + CRATES_IO.md | 3 + macon_derive/src/attributes.rs | 141 +++++++++++++ macon_derive/src/model.rs | 90 ++++++-- src/lib.rs | 3 + .../macro_panic_default_field_all_disabled.rs | 134 ++++++++++++ ...macro_result_default_field_all_disabled.rs | 141 +++++++++++++ ...ro_typestate_default_field_all_disabled.rs | 198 ++++++++++++++++++ 8 files changed, 698 insertions(+), 13 deletions(-) create mode 100644 tests/macro_panic_default_field_all_disabled.rs create mode 100644 tests/macro_result_default_field_all_disabled.rs create mode 100644 tests/macro_typestate_default_field_all_disabled.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f5e2bfe..50a9eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Source: https://keepachangelog.com/ ### Added - Add nested `fields(...)` value for struct attribute `#[builder]` (#32) +- Add `fields(Default=!)` for struct attribute `#[builder]` (#32) ## [1.1.0] - 2024-08-26 diff --git a/CRATES_IO.md b/CRATES_IO.md index 6ef48ab..26a51b7 100644 --- a/CRATES_IO.md +++ b/CRATES_IO.md @@ -51,6 +51,9 @@ Enforce [`Default`][Default] support for **struct**. See ["`Default` struct"](#d * **`fields(Option=!)`**
Disable automatic [`Option`][Option] detection for **fields**. See ["`Option` fields"](#option-fields). +* **`fields(Default=!)`**
+Disable automatic [`Default`][Default] detection for **fields**. See ["`Default` fields"](#default-fields). + * **`fields(Into=!)`**
Disable [`Into`][Into] for **fields**. See ["`Into` argument"](#into-argument). diff --git a/macon_derive/src/attributes.rs b/macon_derive/src/attributes.rs index 9421214..96ab02c 100644 --- a/macon_derive/src/attributes.rs +++ b/macon_derive/src/attributes.rs @@ -27,6 +27,7 @@ pub struct StructBuilder { #[derive(Debug, Default, PartialEq)] pub struct StructBuilderFields { option: Setting<()>, + default: Setting<()>, into: Setting<()>, } @@ -119,6 +120,13 @@ impl StructBuilderFields { &mut self.option } + pub fn default(&self) -> &Setting<()> { + &self.default + } + pub fn default_mut(&mut self) -> &mut Setting<()> { + &mut self.default + } + pub fn into_(&self) -> &Setting<()> { &self.into } @@ -131,6 +139,9 @@ impl StructBuilderFields { if nested.path.is_ident("Option") { self.option = Setting::<()>::from_parse_nested_meta(nested) .map_err_context("Unable to parse Option for fields struct builder attribute")?; + } else if nested.path.is_ident("Default") { + self.default = Setting::<()>::from_parse_nested_meta(nested) + .map_err_context("Unable to parse Default for fields struct builder attribute")?; } else if nested.path.is_ident("Into") { self.into = Setting::<()>::from_parse_nested_meta(nested) .map_err_context("Unable to parse Into for fields struct builder attribute")?; @@ -293,6 +304,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -323,6 +339,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -353,6 +374,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -384,6 +410,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -414,6 +445,11 @@ pub mod tests { Setting::enable((), span()), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -444,6 +480,11 @@ pub mod tests { Setting::disable(span()), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -474,6 +515,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::enable((), span()), @@ -504,6 +550,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::disable(span()), @@ -534,6 +585,11 @@ pub mod tests { Setting::enable((), span()), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -564,6 +620,81 @@ pub mod tests { Setting::disable(span()), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); + assert_eq!( + builder.fields.option, + Setting::undefined(), + "fields.option", + ); + } + + #[test] + fn struct_builder_attribute_fields_default_enabled() { + let derive_input: DeriveInput = parse_quote! { + #[builder(fields(Default))] + struct Foobar; + }; + let builder = StructBuilder::from_input(&derive_input) + .expect("StructBuilder::from_input"); + assert_eq!( + builder.mode, + Setting::undefined(), + "mode", + ); + assert_eq!( + builder.default, + Setting::undefined(), + "default", + ); + assert_eq!( + builder.fields.into, + Setting::undefined(), + "fields.into", + ); + assert_eq!( + builder.fields.default, + Setting::enable((), span()), + "fields.default", + ); + assert_eq!( + builder.fields.option, + Setting::undefined(), + "fields.option", + ); + } + + #[test] + fn struct_builder_attribute_fields_default_disabled() { + let derive_input: DeriveInput = parse_quote! { + #[builder(fields(Default=!))] + struct Foobar; + }; + let builder = StructBuilder::from_input(&derive_input) + .expect("StructBuilder::from_input"); + assert_eq!( + builder.mode, + Setting::undefined(), + "mode", + ); + assert_eq!( + builder.default, + Setting::undefined(), + "default", + ); + assert_eq!( + builder.fields.into, + Setting::undefined(), + "fields.into", + ); + assert_eq!( + builder.fields.default, + Setting::disable(span()), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::undefined(), @@ -594,6 +725,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::enable((), span()), @@ -624,6 +760,11 @@ pub mod tests { Setting::undefined(), "fields.into", ); + assert_eq!( + builder.fields.default, + Setting::undefined(), + "fields.default", + ); assert_eq!( builder.fields.option, Setting::disable(span()), diff --git a/macon_derive/src/model.rs b/macon_derive/src/model.rs index d5110e2..1dbe11d 100644 --- a/macon_derive/src/model.rs +++ b/macon_derive/src/model.rs @@ -77,10 +77,10 @@ pub struct PropertySettings { pub struct Properties { /// Is Tuple struct `(a,b,c)` or Named one `{ a:A, b:B, c:C }` pub is_tuple: bool, - /// Is Default supported for struct - pub default: Setting<()>, /// Is Into supported for fields pub into: Setting<()>, + /// Is Default supported for fields + pub default: Setting<()>, /// Is Option supported for fields pub option: Setting<()>, /// Struct fields @@ -183,8 +183,9 @@ impl Builder { self.mode = builder.mode().try_into()?; self.set_default(builder.default().clone()); - self.properties.option = builder.fields().option().clone(); - self.properties.into = builder.fields().into_().clone(); + self.properties.option = builder.fields().option().clone(); + self.properties.default = builder.fields().default().clone(); + self.properties.into = builder.fields().into_().clone(); if ! self.default.is_defined() { if let Some(span) = derives.get_type("Default") { @@ -235,7 +236,6 @@ impl Builder { pub fn set_default(&mut self, default: Setting<()>) { self.default = default; - self.properties.default = default; } } @@ -258,11 +258,15 @@ impl Property { builder_attribute.option().clone() }; let default = if builder_attribute.default().is_undefined() { - let default_types = crate::config::get().default_types(); - if default_types.match_type(&field.ty) { - Setting::enable((), span) - } else { + if builder.properties.default.is_disabled() { Setting::disable(span) + } else { + let default_types = crate::config::get().default_types(); + if default_types.match_type(&field.ty) { + Setting::enable((), span) + } else { + Setting::disable(span) + } } } else { builder_attribute.default().clone() @@ -700,6 +704,10 @@ pub mod tests { use super::*; + fn span() -> Span { + Span::call_site() + } + fn newbuilder(derive: DeriveInput) -> Builder { Builder::from_input(derive).expect("Builder::from_input") } @@ -754,7 +762,7 @@ pub mod tests { }); assert_eq!( builder.properties.into, - Setting::disable(Span::call_site()), + Setting::disable(span()), ); } @@ -857,7 +865,7 @@ pub mod tests { struct Demo; }); - assert_eq!(builder.default, Setting::enable((), Span::call_site()), "builder.default"); + assert_eq!(builder.default, Setting::enable((), span()), "builder.default"); } #[test] @@ -868,7 +876,7 @@ pub mod tests { struct Demo; }); - assert_eq!(builder.default, Setting::enable((), Span::call_site()), "builder.default") + assert_eq!(builder.default, Setting::enable((), span()), "builder.default") } #[test] @@ -879,6 +887,62 @@ pub mod tests { struct Demo; }); - assert_eq!(builder.default, Setting::disable(Span::call_site()), "builder.default") + assert_eq!(builder.default, Setting::disable(span()), "builder.default") + } + + #[test] + fn builder_derive_fields_default_disabled() { + let builder = newbuilder(parse_quote! { + #[builder(fields(Default=!))] + struct Foobar { + foobar: usize, + } + }); + + assert_eq!(builder.properties.default, Setting::disable(span()), "builder.properties.default"); + + let mut properties = builder.properties.items.iter(); + let mut property_opt = properties.next(); + assert!(property_opt.is_some(), "builder.properties.items[0]"); + let property = property_opt.unwrap(); + assert_eq!(property.default, Setting::disable(span()), "builder.properties.items[0].default"); + + property_opt = properties.next(); + assert!(property_opt.is_none(), "builder.properties.items[1]"); + } + + #[test] + fn builder_derive_fields_default_disabled_with_customized_fields() { + let builder = newbuilder(parse_quote! { + #[builder(fields(Default=!))] + struct Foobar { + foo: usize, + #[builder(Default)] + bar: usize, + baz: usize, + } + }); + + assert_eq!(builder.properties.default, Setting::disable(span()), "builder.properties.default"); + + let mut properties = builder.properties.items.iter(); + + let mut property_opt = properties.next(); + assert!(property_opt.is_some(), "builder.properties.items[0]"); + let mut property = property_opt.unwrap(); + assert_eq!(property.default, Setting::disable(span()), "builder.properties.items[0].default"); + + property_opt = properties.next(); + assert!(property_opt.is_some(), "builder.properties.items[1]"); + property = property_opt.unwrap(); + assert_eq!(property.default, Setting::enable((), span()), "builder.properties.items[1].default"); + + property_opt = properties.next(); + assert!(property_opt.is_some(), "builder.properties.items[2]"); + property = property_opt.unwrap(); + assert_eq!(property.default, Setting::disable(span()), "builder.properties.items[2].default"); + + property_opt = properties.next(); + assert!(property_opt.is_none(), "builder.properties.items[3]"); } } diff --git a/src/lib.rs b/src/lib.rs index cc51b9a..29ca457 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,9 @@ //! * **`fields(Option=!)`**
//! Disable automatic [`Option`] detection for **fields**. See ["`Option` fields"](#option-fields). //! +//! * **`fields(Default=!)`**
+//! Disable automatic [`Default`] detection for **fields**. See ["`Default` fields"](#default-fields). +//! //! * **`fields(Into=!)`**
//! Disable [`Into`] for **fields**. See ["`Into` argument"](#into-argument). //! diff --git a/tests/macro_panic_default_field_all_disabled.rs b/tests/macro_panic_default_field_all_disabled.rs new file mode 100644 index 0000000..792d2af --- /dev/null +++ b/tests/macro_panic_default_field_all_disabled.rs @@ -0,0 +1,134 @@ +use macon::Builder; + +// ############################################################################# +// ################################### INPUT ################################### +// ############################################################################# +use ::std::path::PathBuf; + +#[derive(Builder)] +#[builder(mode=Panic,fields(Default=!),)] +#[derive(PartialEq,Debug,)] +struct StructNamed { + id: i32, + value: String, + optional: Option, + #[builder(Default)] + exception: String, + mandatory: PathBuf, +} + +#[derive(Builder)] +#[builder(mode=Panic,fields(Default=!),)] +#[derive(PartialEq,Debug,)] +struct StructTuple( + i32, + String, + Option, + #[builder(Default)] + String, + PathBuf, +); + +// ############################################################################# +// ################################### TESTS ################################### +// ############################################################################# + +#[test] +#[should_panic(expected="Field id is missing\nField value is missing")] +fn named_build_default_implicit() { + StructNamed::builder() + .mandatory("/dev/null") + .build(); +} + +#[test] +fn named_build_default_explicit_none() { + let built = StructNamed::builder() + .id(43) + .value("another value") + .optional_none() + .exception_default() + .mandatory("/dev/null") + .build(); + assert_eq!( + StructNamed { + id: 43, + value: String::from("another value"), + optional: None, + exception: String::from(""), + mandatory: PathBuf::from("/dev/null"), + }, + built, + ) +} + +#[test] +fn named_build_full() { + let built = StructNamed::builder() + .id(44) + .value("any value") + .optional("optional") + .exception("exception") + .mandatory("/dev/null") + .build(); + assert_eq!( + StructNamed { + id: 44, + value: String::from("any value"), + optional: Some(String::from("optional")), + exception: String::from("exception"), + mandatory: PathBuf::from("/dev/null"), + }, + built, + ) +} + +#[test] +#[should_panic(expected="Field 0 is missing\nField 1 is missing")] +fn tuple_build_default_implicit() { + StructTuple::builder() + .set3("/dev/null") + .build(); +} + +#[test] +fn tuple_build_unordered_default_explicit_none() { + let built = StructTuple::builder() + .set0(43) + .set1("another value") + .set2_none() + .set3_default() + .set4("/dev/null") + .build(); + assert_eq!( + StructTuple( + 43, + String::from("another value"), + None, + String::from(""), + PathBuf::from("/dev/null"), + ), + built, + ) +} + +#[test] +fn tuple_build_unordered_full() { + let built = StructTuple::builder() + .set0(44) + .set1("any value") + .set2("optional") + .set3("exception") + .set4("/dev/null") + .build(); + assert_eq!( + StructTuple( + 44, + String::from("any value"), + Some(String::from("optional")), + String::from("exception"), + PathBuf::from("/dev/null"), + ), + built, + ) +} diff --git a/tests/macro_result_default_field_all_disabled.rs b/tests/macro_result_default_field_all_disabled.rs new file mode 100644 index 0000000..5c7f19b --- /dev/null +++ b/tests/macro_result_default_field_all_disabled.rs @@ -0,0 +1,141 @@ +use macon::Builder; + +// ############################################################################# +// ################################### INPUT ################################### +// ############################################################################# +use ::std::path::PathBuf; + +#[derive(Builder)] +#[builder(mode=Result,fields(Default=!),)] +#[derive(PartialEq,Debug,)] +struct StructNamed { + id: i32, + value: String, + optional: Option, + #[builder(Default)] + exception: String, + mandatory: PathBuf, +} + +#[derive(Builder)] +#[builder(mode=Result,fields(Default=!),)] +#[derive(PartialEq,Debug,)] +struct StructTuple( + i32, + String, + Option, + #[builder(Default)] + String, + PathBuf, +); + + +// ############################################################################# +// ################################### TESTS ################################### +// ############################################################################# + +#[test] +fn named_build_default_implicit() { + let built = StructNamed::builder() + .mandatory("/dev/null") + .build(); + assert_eq!( + Err(String::from("Field id is missing\nField value is missing")), + built, + ) +} + +#[test] +fn named_build_default_explicit_none() { + let built = StructNamed::builder() + .id(43) + .value("another value") + .optional_none() + .exception_default() + .mandatory("/dev/null") + .build(); + assert_eq!( + Ok(StructNamed { + id: 43, + value: String::from("another value"), + optional: None, + exception: String::from(""), + mandatory: PathBuf::from("/dev/null"), + }), + built, + ) +} + +#[test] +fn named_build_full() { + let built = StructNamed::builder() + .id(44) + .value("any value") + .optional("optional") + .exception("exception") + .mandatory("/dev/null") + .build(); + assert_eq!( + Ok(StructNamed { + id: 44, + value: String::from("any value"), + optional: Some(String::from("optional")), + exception: String::from("exception"), + mandatory: PathBuf::from("/dev/null"), + }), + built, + ) +} + +#[test] +fn tuple_build_default_implicit() { + let built = StructTuple::builder() + .set4("/dev/null") + .build(); + assert_eq!( + Err(String::from("Field 0 is missing\nField 1 is missing")), + built, + ) +} + +#[test] +fn tuple_build_unordered_default_explicit_none() { + let built = StructTuple::builder() + .set0(43) + .set1("another value") + .set2_none() + .set3_default() + .set4("/dev/null") + .build(); + assert_eq!( + Ok(StructTuple( + 43, + String::from("another value"), + None, + String::from(""), + PathBuf::from("/dev/null"), + )), + built, + ) +} + +#[test] +fn tuple_build_unordered_full() { + let built = StructTuple::builder() + .set0(44) + .set1("any value") + .set2("optional") + .set3("exception") + .set4("/dev/null") + .build(); + assert_eq!( + Ok(StructTuple( + 44, + String::from("any value"), + Some(String::from("optional")), + String::from("exception"), + PathBuf::from("/dev/null"), + )), + built, + ) +} diff --git a/tests/macro_typestate_default_field_all_disabled.rs b/tests/macro_typestate_default_field_all_disabled.rs new file mode 100644 index 0000000..a2a6a09 --- /dev/null +++ b/tests/macro_typestate_default_field_all_disabled.rs @@ -0,0 +1,198 @@ +use macon::Builder; + +// ############################################################################# +// ################################### INPUT ################################### +// ############################################################################# +use ::std::path::PathBuf; + +#[derive(Builder)] +#[builder(mode=Typestate,fields(Default=!),)] +#[derive(PartialEq,Debug,)] +struct StructNamed { + id: i32, + value: String, + optional: Option, + #[builder(Default)] + exception: String, + mandatory: PathBuf, +} + +#[derive(Builder)] +#[builder(mode=Typestate,fields(Default=!),)] +#[derive(PartialEq,Debug,)] +struct StructTuple( + i32, + String, + Option, + #[builder(Default)] + String, + PathBuf, +); + +// ############################################################################# +// ################################### TESTS ################################### +// ############################################################################# + +#[test] +fn named_build_minimum() { + let built = StructNamed::builder() + .id(42) + .value("a value") + .mandatory("/dev/null") + .build(); + assert_eq!( + StructNamed { + id: 42, + value: String::from("a value"), + optional: None, + exception: String::from(""), + mandatory: PathBuf::from("/dev/null"), + }, + built, + ) +} + +#[test] +fn named_build_default_explicit_none() { + let built = StructNamed::builder() + .id(43) + .value("another value") + .optional_none() + .exception_default() + .mandatory("/dev/null") + .build(); + assert_eq!( + StructNamed { + id: 43, + value: String::from("another value"), + optional: None, + exception: String::from(""), + mandatory: PathBuf::from("/dev/null"), + }, + built, + ) +} + +#[test] +fn named_build_full() { + let built = StructNamed::builder() + .id(44) + .value("any value") + .optional("optional") + .exception("exception") + .mandatory("/dev/null") + .build(); + assert_eq!( + StructNamed { + id: 44, + value: String::from("any value"), + optional: Some(String::from("optional")), + exception: String::from("exception"), + mandatory: PathBuf::from("/dev/null"), + }, + built, + ) +} + +#[test] +fn tuple_build_minimum() { + let built = StructTuple::builder() + .set0(42) + .set1("a value") + .set4("/dev/null") + .build(); + assert_eq!( + StructTuple( + 42, + String::from("a value"), + None, + String::from(""), + PathBuf::from("/dev/null"), + ), + built, + ) +} + +#[test] +fn tuple_build_unordered_default_explicit_none() { + let built = StructTuple::builder() + .set0(43) + .set1("another value") + .set2_none() + .set3_default() + .set4("/dev/null") + .build(); + assert_eq!( + StructTuple( + 43, + String::from("another value"), + None, + String::from(""), + PathBuf::from("/dev/null"), + ), + built, + ) +} + +#[test] +fn tuple_build_unordered_full() { + let built = StructTuple::builder() + .set0(44) + .set1("any value") + .set2("optional") + .set3("exception") + .set4("/dev/null") + .build(); + assert_eq!( + StructTuple( + 44, + String::from("any value"), + Some(String::from("optional")), + String::from("exception"), + PathBuf::from("/dev/null"), + ), + built, + ) +} + +#[test] +fn tuple_build_ordered_default_explicit_none() { + let built = StructTuple::builder() + .set(43) + .set("another value") + .none() + .default() + .set("/dev/null") + .build(); + assert_eq!( + StructTuple( + 43, + String::from("another value"), + None, + String::from(""), + PathBuf::from("/dev/null"), + ), + built, + ) +} + +#[test] +fn tuple_ordered_build_full() { + let built = StructTuple::builder() + .set(44) + .set("any value") + .set("optional") + .set("exception") + .set("/dev/null") + .build(); + assert_eq!( + StructTuple( + 44, + String::from("any value"), + Some(String::from("optional")), + String::from("exception"), + PathBuf::from("/dev/null"), + ), + built, + ) +}