diff --git a/macon-config.schema.json b/macon-config.schema.json index 048d383..f3cfbad 100644 --- a/macon-config.schema.json +++ b/macon-config.schema.json @@ -55,6 +55,41 @@ } } } + }, + "extend_types": { + "description": "Override pathes that should implement Extend", + "type": "object", + "properties": { + "includes": { + "description": "Pathes that should implement Extend", + "type": "array", + "items": { + "oneOf": [ + { + "description": "Path that should implement Extend", + "type": "string" + }, + { + "description": "Path and associated wrapped type", + "type": "object", + "properties": { + "path": { + "description": "Path that should implement Extend", + "type": "string" + }, + "wrapped": { + "description": "Contained type", + "type": "string" + }, + "required": [ + "path" + ] + } + } + ] + } + } + } } } } diff --git a/macon_api/src/lib.rs b/macon_api/src/lib.rs index d225329..5870707 100644 --- a/macon_api/src/lib.rs +++ b/macon_api/src/lib.rs @@ -3,7 +3,9 @@ //! See it for all details. //! -use std::fmt::Debug; +use std::{ + fmt::Debug, ops::Deref, vec +}; /// Builder field type when building struct implementing [`Default`]. #[derive(Default,)] @@ -25,6 +27,14 @@ pub enum Defaulting { Set(T), } +/// Builder field type when target implment [`Extend`]. +pub struct Extending { + /// Collecting items + items: Vec, + /// Building value + value: C, +} + /// Builder field type for `Panic` or `Result` mode. #[derive(Default,)] pub enum Building { @@ -82,6 +92,49 @@ impl Defaulting { } } +impl Default for Extending where C: Default { + fn default() -> Self { + Self { + items: Default::default(), + value: Default::default(), + } + } +} + +impl Deref for Extending { + type Target = C; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl Extend for Extending { + /// Store `iter` values into `items` (until container is created) + fn extend>(&mut self, iter: T) { + self.items.extend(iter) + } +} + +impl IntoIterator for Extending { + type Item = I; + type IntoIter = vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.items.into_iter() + } +} + +impl Extending { + pub fn value_mut(&mut self) -> &mut C { + &mut self.value + } + + /// Consume to return `value` and collected `items`. + pub fn unwrap(self) -> (C, Vec) { + (self.value, self.items) + } +} + impl Debug for Building { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/macon_derive/src/common.rs b/macon_derive/src/common.rs new file mode 100644 index 0000000..f2a62d0 --- /dev/null +++ b/macon_derive/src/common.rs @@ -0,0 +1,60 @@ +use std::{ + fmt::{ + Debug, + Display, + }, + ops::Deref, +}; +use quote::ToTokens; +use syn::Type; + +pub struct TokenDisplay(T); + +pub type MType = TokenDisplay; + +impl Debug for TokenDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self)) + } +} + +impl Display for TokenDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self.0.to_token_stream())) + } +} + +impl From for TokenDisplay { + fn from(value: T) -> Self { + Self(value) + } +} + +impl Deref for TokenDisplay { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ToTokens for TokenDisplay { + fn to_token_stream(&self) -> proc_macro2::TokenStream { + self.0.to_token_stream() + } + + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.0.to_tokens(tokens) + } + + fn into_token_stream(self) -> proc_macro2::TokenStream + where + Self: Sized, { + self.0.into_token_stream() + } +} + +impl PartialEq for TokenDisplay { + fn eq(&self, other: &T) -> bool { + self.0.eq(other) + } +} \ No newline at end of file diff --git a/macon_derive/src/config.rs b/macon_derive/src/config.rs index c400a22..11faaa6 100644 --- a/macon_derive/src/config.rs +++ b/macon_derive/src/config.rs @@ -44,7 +44,8 @@ pub fn get() -> &'static anyhow::Result { #[derive(Debug,)] pub struct Configuration { default_types: TypeSet, - option_types: TypeSet, + option_types: TypeSet, + extend_types: TypeSet, } pub trait TypeSetItem { @@ -109,6 +110,7 @@ impl TypeSet { .map(|segment| segment.ident.to_string()) .collect::>() .join("::"); + self.match_str(&str) } @@ -133,7 +135,12 @@ impl Default for Configuration { fn default() -> Self { let default_types = Configuration::default_default_types(); let option_types = Configuration::default_option_types(); - Self { default_types, option_types, } + let extend_types = Configuration::default_extend_types(); + Self { + default_types, + option_types, + extend_types, + } } } @@ -157,10 +164,15 @@ impl Configuration { crate_config.option_types.map_includes(|i| i.into()), Self::default_option_types, ); + let extend_types = TypeSet::create( + crate_config.extend_types.map_includes(|i| i.into()), + Self::default_extend_types, + ); let this = Self { default_types, option_types, + extend_types, }; #[cfg(feature = "debug")] eprintln!("Merge configuration\n{:#?}", this); @@ -197,18 +209,26 @@ impl Configuration { .add_path("std::collections::hash_set::HashSet") } - pub fn default_option_types() -> TypeSet { + pub fn default_option_types() -> TypeSet { TypeSet::default() .add_path("std::option::Option") .add_path("core::option::Option") } + pub fn default_extend_types() -> TypeSet { + TypeSet::default() + .add_path("std::vec::Vec") + } + pub fn default_types(&self) -> &TypeSet { &self.default_types } - pub fn option_types(&self) -> &TypeSet { + pub fn option_types(&self) -> &TypeSet { &self.option_types } + pub fn extend_types(&self) -> &TypeSet { + &self.extend_types + } } #[derive(Debug,Deserialize,)] @@ -218,31 +238,33 @@ struct CrateConfiguration { pub version: String, #[serde(default)] pub default_types: TypeSetConfiguration, - #[serde(default = "default_typesetconfiguration", deserialize_with="deserialize_crateconfiguration_option_types")] - pub option_types: TypeSetConfiguration, + #[serde(default = "default_typesetconfiguration", deserialize_with="deserialize_crateconfiguration_container_types")] + pub option_types: TypeSetConfiguration, + #[serde(default = "default_typesetconfiguration", deserialize_with="deserialize_crateconfiguration_container_types")] + pub extend_types: TypeSetConfiguration, } -fn deserialize_crateconfiguration_option_types<'de, D>(deserializer: D) -> std::result::Result, D::Error> +fn deserialize_crateconfiguration_container_types<'de, D>(deserializer: D) -> std::result::Result, D::Error> where D: Deserializer<'de>, { let decoded: TypeSetConfiguration = Deserialize::deserialize(deserializer)?; - Ok(decoded.map_includes(OptionTypeSetItem::from)) + Ok(decoded.map_includes(ContainerTypeSetItem::from)) } #[derive(Debug,Default,Deserialize,PartialEq,)] -pub struct OptionTypeSetItem { +pub struct ContainerTypeSetItem { pub path: String, pub wrapped: Option, } -impl TypeSetItem for OptionTypeSetItem { +impl TypeSetItem for ContainerTypeSetItem { fn path(&self) -> String { self.path.clone() } } -impl From<&str> for OptionTypeSetItem { +impl From<&str> for ContainerTypeSetItem { fn from(path: &str) -> Self { Self { path: path.to_owned(), @@ -255,7 +277,7 @@ impl From<&str> for OptionTypeSetItem { #[serde(untagged)] pub enum OptionTypeSetItemConfiguration { String(String), - Item(OptionTypeSetItem), + Item(ContainerTypeSetItem), } impl Default for OptionTypeSetItemConfiguration { @@ -264,7 +286,7 @@ impl Default for OptionTypeSetItemConfiguration { } } -impl From for OptionTypeSetItem { +impl From for ContainerTypeSetItem { fn from(value: OptionTypeSetItemConfiguration) -> Self { match value { OptionTypeSetItemConfiguration::String(string) => string.as_str().into(), @@ -402,7 +424,7 @@ mod tests { let mut option_types_includes = option_types.includes.iter(); assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { + Some(&ContainerTypeSetItem { path: "AsString".to_owned(), wrapped: None, }), @@ -411,19 +433,19 @@ mod tests { assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { path: "AsItemWithoutWrapped".to_owned(), wrapped: None, }), + Some(&ContainerTypeSetItem { path: "AsItemWithoutWrapped".to_owned(), wrapped: None, }), "option_types.includes[1]\n{:#?}", config ); assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { path: "AsItemWithShortWrapped".to_owned(), wrapped: Some("ShortWrapped".to_string()), }), + Some(&ContainerTypeSetItem { path: "AsItemWithShortWrapped".to_owned(), wrapped: Some("ShortWrapped".to_string()), }), "option_types.includes[2]\n{:#?}", config ); assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { path: "AsItemWithFullWrapped".to_owned(), wrapped: Some("::full::path::FullWrapped".to_string()), }), + Some(&ContainerTypeSetItem { path: "AsItemWithFullWrapped".to_owned(), wrapped: Some("::full::path::FullWrapped".to_string()), }), "option_types.includes[3]\n{:#?}", config ); @@ -431,5 +453,42 @@ mod tests { let mut option_types_excludes = option_types.excludes.iter(); assert_eq!(option_types_excludes.next(), None, "option_types.excludes[0]\n{:#?}", config); + + + let extend_types = &config.extend_types; + assert_eq!(extend_types.defaults, true, "extend_types.defaults"); + + let mut extend_types_includes = extend_types.includes.iter(); + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { + path: "AsString".to_owned(), + wrapped: None, + }), + "extend_types.includes[0]\n{:#?}", config + ); + + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { path: "AsItemWithoutWrapped".to_owned(), wrapped: None, }), + "extend_types.includes[1]\n{:#?}", config + ); + + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { path: "AsItemWithShortWrapped".to_owned(), wrapped: Some("ShortWrapped".to_string()), }), + "extend_types.includes[2]\n{:#?}", config + ); + + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { path: "AsItemWithFullWrapped".to_owned(), wrapped: Some("::full::path::FullWrapped".to_string()), }), + "extend_types.includes[3]\n{:#?}", config + ); + + assert!(extend_types_includes.next().is_none(), "extend_types.includes[4]\n{:#?}", config); + + let mut extend_types_excludes = extend_types.excludes.iter(); + assert_eq!(extend_types_excludes.next(), None, "extend_types.excludes[0]\n{:#?}", config); } } diff --git a/macon_derive/src/generators/result_panic.rs b/macon_derive/src/generators/result_panic.rs index 38bf072..721214f 100644 --- a/macon_derive/src/generators/result_panic.rs +++ b/macon_derive/src/generators/result_panic.rs @@ -1,4 +1,8 @@ -//! Generator panicing on missing fields and ignore setting many times. +//! Generator panicing on missing fields and ignore setting same field many times. + +use quote::format_ident; + +use crate::model::Setting; use super::*; @@ -102,11 +106,25 @@ impl ResultPanicGenerator { } else { quote!() }; + let setter_extend = if let Setting::Enabled(ref itemtype) = f.extend { + let setter_extend = f.setter_extend(); + let itemvar = format_ident!("{}ITEM", typevar); + let field = f.id(); + quote! { + pub fn #setter_extend<#itemvar: ::core::convert::Into<#itemtype>, #typevar: ::std::iter::IntoIterator>(mut self, #ident: #typevar) -> Self { + self.#field.extend(::core::iter::IntoIterator::into_iter(#ident).map(::core::convert::Into::into)); + self + } + } + } else { + quote!() + }; quote! { #setter_standard #setter_none #setter_keep #setter_default + #setter_extend } }) } diff --git a/macon_derive/src/lib.rs b/macon_derive/src/lib.rs index ece26d4..7aebc24 100644 --- a/macon_derive/src/lib.rs +++ b/macon_derive/src/lib.rs @@ -9,6 +9,7 @@ use syn::{ DeriveInput, }; +mod common; mod config; mod model; mod generators; diff --git a/macon_derive/src/model.rs b/macon_derive/src/model.rs index e9f25de..9908624 100644 --- a/macon_derive/src/model.rs +++ b/macon_derive/src/model.rs @@ -1,4 +1,7 @@ -use std::borrow::Cow; +use std::{ + borrow::Cow, + fmt::Debug, +}; use proc_macro2::{ Delimiter, Group, @@ -7,7 +10,8 @@ use proc_macro2::{ }; use quote::{ format_ident, - quote, ToTokens, + quote, + ToTokens, }; use syn::{ Attribute, @@ -27,6 +31,8 @@ use syn::{ Visibility, }; +use crate::common::{MType, TokenDisplay}; + #[derive(Debug)] pub struct Builder { pub ident: Ident, @@ -133,8 +139,9 @@ impl From for Setting { #[derive(Debug,Default)] pub struct PropertySettings { - pub option: Setting, + pub option: Setting, pub default: Setting<()>, + pub extend: Setting, } #[derive(Debug,Default,)] @@ -169,9 +176,10 @@ pub struct Property { pub ordinal: usize, pub name: String, pub ident: Ident, - pub ty: Type, + pub ty: TokenDisplay, pub is_tuple: bool, - pub option: Setting, + pub option: Setting>, + pub extend: Setting>, pub default: Setting<()>, pub struct_default: Setting<()>, } @@ -391,13 +399,13 @@ impl Property { settings.option = match type_ { Type::Tuple(ref typetuple) => { if typetuple.elems.is_empty() { - Setting::enable(type_) + Setting::enable(type_.into()) } else { Setting::disable() } }, Type::Never(_) => Setting::disable(), - _ => Setting::enable(type_), + _ => Setting::enable(type_.into()), }; } else if nested.path.is_ident("Default") { if settings.default.is_defined() { @@ -429,7 +437,14 @@ impl Property { if let Some(ty) = Self::get_option_arg(&field.ty)? { #[cfg(feature = "debug")] eprintln!("{}.{}: Option<{}>", builder.ident, name, ty.to_token_stream()); - settings.option = Setting::enable(ty); + settings.option = Setting::enable(ty.into()); + } + } + if settings.extend.is_undefined() { + if let Some(ty) = Self::get_extend_arg(&field.ty)? { + #[cfg(feature = "debug")] + eprintln!("{}.{}: Extend<{}>", builder.ident, name, ty.to_token_stream()); + settings.extend = Setting::enable(ty.into()); } } if settings.default.is_undefined() { @@ -445,9 +460,10 @@ impl Property { ordinal, name, ident, - ty: field.ty, + ty: field.ty.into(), is_tuple, - option: settings.option, + option: settings.option.into(), + extend: settings.extend.into(), default: settings.default, struct_default: builder.default.clone(), }) @@ -489,6 +505,43 @@ impl Property { }) } + pub fn get_extend_arg(ty: &Type) -> Result> { + let config = match crate::config::get() { + Ok(config) => config, + Err(error) => return Err(Error::new_spanned(ty, error)), + }; + + Ok(if let Some(item) = config.extend_types().match_type(ty) { + if let Some(ref wrapped) = item.wrapped { + let ty: Type = syn::parse_str(wrapped)?; + Some(ty) + } else { + match ty { + Type::Path(typepath) => typepath + .path + .segments + .last() + .and_then(|path_segment| match path_segment.arguments { + PathArguments::AngleBracketed(ref args) => Some(args), + _ => None, + }) + .and_then(|args| if args.args.len() == 1 { + if let Some(GenericArgument::Type(ty)) = args.args.first() { + Some(ty.to_owned()) + } else { + None + } + } else { + None + }), + _ => None, + } + } + } else { + None + }) + } + pub fn id(&self) -> TokenStream { if self.is_tuple { let literal = Literal::usize_unsuffixed(self.ordinal); @@ -527,6 +580,10 @@ impl Property { format_ident!("{}_default", self.setter()) } + pub fn setter_extend(&self) -> Ident { + format_ident!("{}_extend", self.setter()) + } + pub fn prefix(&self) -> TokenStream { if self.is_tuple { quote!() @@ -536,6 +593,7 @@ impl Property { } } + /// Not option, field default or struct default. pub fn is_required(&self) -> bool { ! self.option.is_enabled() && ! self.default.is_enabled() && @@ -704,15 +762,23 @@ impl Property { } else { ty = quote!(::macon::Building<#ty>); } + if let Setting::Enabled(ref itemty) = self.extend { + ty = quote!(::macon::Extending<#ty, #itemty>); + } quote!(#prefix #ty,) } pub fn result_assign(&self, setter: Setter) -> TokenStream { let ident = &self.ident; let id = self.id(); + let source = if let Setting::Enabled(_) = self.extend { + quote!(*self.#id.value_mut()) + } else { + quote!(self.#id) + }; let mut value = match setter { Setter::Standard => { - let mut value = quote!(#ident.into()); + let mut value = quote!(::core::convert::Into::into(#ident)); if self.option.is_enabled() { value = quote!(::core::option::Option::Some(#value)); } @@ -736,37 +802,68 @@ impl Property { } else { value = quote!(::macon::Building::Set(#value)); } - quote!(self.#id = #value;) + quote!(#source = #value;) } - pub fn result_value(&self) -> TokenStream { + pub fn result_set_value(&self) -> TokenStream { let id = self.id(); - let mut value = quote!(self.#id); + let getvalue = self.result_value(); + if self.extend.is_enabled() { + let ident = &self.ident; + let identitems = format_ident!("{}items", ident); + quote! { + { + let (#ident, #identitems) = self.#id.unwrap(); + let mut #ident = #ident #getvalue; + #ident.extend(#identitems); + #ident + } + } + } else { + quote!(self.#id #getvalue) + } + } + + pub fn result_value(&self) -> TokenStream { if ! self.is_required() { + let mut getvalue = quote!(); if self.struct_default.is_enabled() { - value = quote!(#value.unwrap()); + getvalue = quote!(#getvalue.unwrap()); } if self.default.is_enabled() { - value = quote!(#value.unwrap()); + getvalue = quote!(#getvalue.unwrap()); } + getvalue } else { - value = quote!(#value.unwrap()); + quote!(.unwrap()) } - value } pub fn result_build(&self) -> TokenStream { let prefix = self.prefix(); - let value = self.result_value(); + let value = self.result_set_value(); quote!(#prefix #value,) } pub fn result_override(&self) -> TokenStream { let id = self.id(); - let value = self.result_value(); - quote! { - if self.#id.is_set() { - built.#id = #value; + if ! self.extend.is_enabled() { + let value = self.result_set_value(); + quote! { + if self.#id.is_set() { + built.#id = #value; + } + } + } else { + let ident = &self.ident; + let identitems = format_ident!("{}items", ident); + let value = self.result_value(); + quote! { + let (#ident, #identitems) = self.#id.unwrap(); + if #ident.is_set() { + built.#id = #ident #value; + } + built.#id.extend(#identitems); } } } @@ -871,7 +968,6 @@ pub mod tests { use super::*; fn newbuilder(derive: DeriveInput) -> Builder { - eprintln!("{:#?}", derive); Builder::from_input(derive).expect("Buider::from_input") } diff --git a/macon_derive/tests/config/macon-config-parse.yaml b/macon_derive/tests/config/macon-config-parse.yaml index d2b1ede..41fb1c6 100644 --- a/macon_derive/tests/config/macon-config-parse.yaml +++ b/macon_derive/tests/config/macon-config-parse.yaml @@ -13,3 +13,12 @@ option_types: wrapped: ShortWrapped - path: AsItemWithFullWrapped wrapped: ::full::path::FullWrapped + +extend_types: + includes: + - AsString + - path: AsItemWithoutWrapped + - path: AsItemWithShortWrapped + wrapped: ShortWrapped + - path: AsItemWithFullWrapped + wrapped: ::full::path::FullWrapped diff --git a/tests/blueprint_panic_extend_vec.rs b/tests/blueprint_panic_extend_vec.rs new file mode 100644 index 0000000..8f18028 --- /dev/null +++ b/tests/blueprint_panic_extend_vec.rs @@ -0,0 +1,371 @@ +// ############################################################################# +// ################################### INPUT ################################### +// ############################################################################# +use std::path::PathBuf; + +#[derive(PartialEq,Debug)] +struct DefaultStructNamed { + list: Vec, +} + +impl Default for DefaultStructNamed { + fn default() -> Self { + Self { + list: vec![ + "a", + "b", + "c", + ] + .into_iter() + .map(PathBuf::from) + .collect(), + } + } +} + +#[derive(PartialEq,Debug)] +struct DefaultFieldNamed { + list: Vec, +} + +#[derive(PartialEq,Debug)] +struct MandatoryFieldNamed { + list: Vec, +} + +// ############################################################################# +// ############################## IMPLEMENTATION ############################### +// ############################################################################# + +// impl_target +impl DefaultStructNamed { + pub fn builder() -> DefaultStructNamedBuilder { + ::default() + } +} + +// struct_builder +#[derive(Default,)] +struct DefaultStructNamedBuilder { + list: ::macon::Extending<::macon::Keeping<::macon::Defaulting>>, PathBuf>, +} + +// impl_builder +impl DefaultStructNamedBuilder { + // impl_builder / impl_builder_setters + pub fn list>>(mut self, list: LIST) -> Self { + *self.list.value_mut() = ::macon::Keeping::Set(::macon::Defaulting::Set(::core::convert::Into::into(list))); + self + } + pub fn list_keep(mut self) -> Self { + *self.list.value_mut() = ::macon::Keeping::Keep; + self + } + pub fn list_default(mut self) -> Self { + *self.list.value_mut() = ::macon::Keeping::Set(::macon::Defaulting::Default); + self + } + pub fn list_extend, LIST: ::std::iter::IntoIterator>(mut self, list: LIST) -> Self { + self.list.extend(::core::iter::IntoIterator::into_iter(list).map(::core::convert::Into::into)); + self + } + + // impl_builder / impl_builder_build + pub fn build(self) -> DefaultStructNamed { + let mut built = ::default(); + + let (list, listitems) = self.list.unwrap(); + if list.is_set() { + built.list = list.unwrap().unwrap(); + } + built.list.extend(listitems); + + built + } +} + +// impl_builder / impl_builder_from +impl ::core::convert::From for DefaultStructNamed { + fn from(builder: DefaultStructNamedBuilder) -> Self { + builder.build() + } +} + + +// impl_target +impl DefaultFieldNamed { + pub fn builder() -> DefaultFieldNamedBuilder { + ::default() + } +} + +// struct_builder +#[derive(Default,)] +struct DefaultFieldNamedBuilder { + list: ::macon::Extending<::macon::Defaulting>, PathBuf>, +} + +// impl_builder +impl DefaultFieldNamedBuilder { + // impl_builder / impl_builder_setters + pub fn list>>(mut self, list: LIST) -> DefaultFieldNamedBuilder { + *self.list.value_mut() = ::macon::Defaulting::Set(::core::convert::Into::into(list)); + self + } + pub fn list_default(mut self) -> DefaultFieldNamedBuilder { + *self.list.value_mut() = ::macon::Defaulting::Default; + self + } + pub fn list_extend, LIST: ::std::iter::IntoIterator>(mut self, list: LIST) -> DefaultFieldNamedBuilder { + self.list.extend(::core::iter::IntoIterator::into_iter(list).map(::core::convert::Into::into)); + self + } + + // impl_builder / impl_builder_build + pub fn build(self) -> DefaultFieldNamed { + let mut errors: ::std::vec::Vec<::std::string::String> = ::std::vec![]; + + if !errors.is_empty() { + panic!("{}", errors.join("\n")); + } else { + DefaultFieldNamed { + list: { + let (list, listitems) = self.list.unwrap(); + let mut list = list.unwrap(); + list.extend(listitems); + list + } + } + } + } +} + +// impl_builder / impl_builder_from +impl ::core::convert::From for DefaultFieldNamed { + fn from(builder: DefaultFieldNamedBuilder) -> Self { + builder.build() + } +} + +// impl_target +impl MandatoryFieldNamed { + pub fn builder() -> MandatoryFieldNamedBuilder { + ::default() + } +} + +// struct_builder +#[derive(Default,)] +struct MandatoryFieldNamedBuilder { + list: ::macon::Extending<::macon::Building>, PathBuf>, +} + +// impl_builder +impl MandatoryFieldNamedBuilder { + // impl_builder / impl_builder_setters + pub fn list>>(mut self, list: LIST) -> MandatoryFieldNamedBuilder { + *self.list.value_mut() = ::macon::Building::Set(::core::convert::Into::into(list)); + self + } + pub fn list_extend, LIST: ::std::iter::IntoIterator>(mut self, list: LIST) -> MandatoryFieldNamedBuilder { + self.list.extend(::core::iter::IntoIterator::into_iter(list).map(::core::convert::Into::into)); + self + } + + // impl_builder / impl_builder_build + pub fn build(self) -> MandatoryFieldNamed { + let mut errors: ::std::vec::Vec<::std::string::String> = ::std::vec![]; + + if self.list.is_undefined() { + errors.push("Field list is missing".into()); + } + + if !errors.is_empty() { + panic!("{}", errors.join("\n")); + } else { + MandatoryFieldNamed { + list: { + let (list, listitems) = self.list.unwrap(); + let mut list = list.unwrap(); + list.extend(listitems); + list + } + } + } + } +} + +// impl_builder / impl_builder_from +impl ::core::convert::From for MandatoryFieldNamed { + fn from(builder: MandatoryFieldNamedBuilder) -> Self { + builder.build() + } +} + +// ############################################################################# +// ################################### TESTS ################################### +// ############################################################################# + +#[test] +fn defaultstructnamed_builder_struct_default() { + let built = DefaultStructNamed::builder() + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("a"), + PathBuf::from("b"), + PathBuf::from("c"), + ], + }, + built, + ); +} + +#[test] +fn defaultstructnamed_builder_keep_extend() { + let built = DefaultStructNamed::builder() + .list_keep() + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("a"), + PathBuf::from("b"), + PathBuf::from("c"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn defaultstructnamed_builder_default_extend() { + let built = DefaultStructNamed::builder() + .list_default() + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn defaultstructnamed_builder_set_extend() { + let built = DefaultStructNamed::builder() + .list(vec!["g", "h", "i",].into_iter().map(PathBuf::from).collect::>()) + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("g"), + PathBuf::from("h"), + PathBuf::from("i"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn defaultfieldnamed_builder_field_default() { + let built = DefaultFieldNamed::builder() + .build(); + + assert_eq!( + DefaultFieldNamed { + list: vec![], + }, + built, + ); +} + +#[test] +fn defaultfieldnamed_builder_default_extend() { + let built = DefaultFieldNamed::builder() + .list_default() + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultFieldNamed { + list: vec![ + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn defaultfieldnamed_builder_set_extend() { + let built = DefaultFieldNamed::builder() + .list(vec!["g", "h", "i",].into_iter().map(PathBuf::from).collect::>()) + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultFieldNamed { + list: vec![ + PathBuf::from("g"), + PathBuf::from("h"), + PathBuf::from("i"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn mandatoryfieldnamed_builder_set_extend() { + let built = MandatoryFieldNamed::builder() + .list(vec!["g", "h", "i",].into_iter().map(PathBuf::from).collect::>()) + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + MandatoryFieldNamed { + list: vec![ + PathBuf::from("g"), + PathBuf::from("h"), + PathBuf::from("i"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +#[should_panic(expected = "Field list is missing")] +fn mandatoryfieldnamed_builder_missing() { + let _built = MandatoryFieldNamed::builder() + .list_extend(&["d", "e", "f",]) + .build(); +} diff --git a/tests/macro_panic_extend_vec.rs b/tests/macro_panic_extend_vec.rs new file mode 100644 index 0000000..9ec31b3 --- /dev/null +++ b/tests/macro_panic_extend_vec.rs @@ -0,0 +1,212 @@ +use macon::Builder; + +// ############################################################################# +// ################################### INPUT ################################### +// ############################################################################# +use std::path::PathBuf; + +#[derive(Builder)] +#[derive(PartialEq,Debug)] +#[builder(mode=Panic,)] +#[builder(Default)] +struct DefaultStructNamed { + list: Vec, +} + +impl Default for DefaultStructNamed { + fn default() -> Self { + Self { + list: vec![ + "a", + "b", + "c", + ] + .into_iter() + .map(PathBuf::from) + .collect(), + } + } +} + +#[derive(Builder)] +#[derive(PartialEq,Debug)] +#[builder(mode=Panic,)] +struct DefaultFieldNamed { + list: Vec, +} + +#[derive(Builder)] +#[derive(PartialEq,Debug)] +#[builder(mode=Panic,)] +struct MandatoryFieldNamed { + #[builder(Default=!)] + list: Vec, +} + +// ############################################################################# +// ################################### TESTS ################################### +// ############################################################################# + +#[test] +fn defaultstructnamed_builder_struct_default() { + let built = DefaultStructNamed::builder() + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("a"), + PathBuf::from("b"), + PathBuf::from("c"), + ], + }, + built, + ); +} + +#[test] +fn defaultstructnamed_builder_keep_extend() { + let built = DefaultStructNamed::builder() + .list_keep() + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("a"), + PathBuf::from("b"), + PathBuf::from("c"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +// #[test] +// fn defaultstructnamed_builder_default_extend() { +// let built = DefaultStructNamed::builder() +// .list_default() +// // .list_extend(&["d", "e", "f",]) +// .build(); + +// assert_eq!( +// DefaultStructNamed { +// list: vec![ +// PathBuf::from("d"), +// PathBuf::from("e"), +// PathBuf::from("f"), +// ], +// }, +// built, +// ); +// } + +// #[test] +// fn defaultstructnamed_builder_set_extend() { +// let built = DefaultStructNamed::builder() +// .list(vec!["g", "h", "i",].into_iter().map(PathBuf::from).collect::>()) +// // .list_extend(&["d", "e", "f",]) +// .build(); + +// assert_eq!( +// DefaultStructNamed { +// list: vec![ +// PathBuf::from("g"), +// PathBuf::from("h"), +// PathBuf::from("i"), +// PathBuf::from("d"), +// PathBuf::from("e"), +// PathBuf::from("f"), +// ], +// }, +// built, +// ); +// } + +#[test] +fn defaultfieldnamed_builder_field_default() { + let built = DefaultFieldNamed::builder() + .build(); + + assert_eq!( + DefaultFieldNamed { + list: vec![], + }, + built, + ); +} + +#[test] +fn defaultfieldnamed_builder_default_extend() { + let built = DefaultFieldNamed::builder() + .list_default() + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultFieldNamed { + list: vec![ + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn defaultfieldnamed_builder_set_extend() { + let built = DefaultFieldNamed::builder() + .list(vec!["g", "h", "i",].into_iter().map(PathBuf::from).collect::>()) + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultFieldNamed { + list: vec![ + PathBuf::from("g"), + PathBuf::from("h"), + PathBuf::from("i"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn mandatoryfieldnamed_builder_set_extend() { + let built = MandatoryFieldNamed::builder() + .list(vec!["g", "h", "i",].into_iter().map(PathBuf::from).collect::>()) + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + MandatoryFieldNamed { + list: vec![ + PathBuf::from("g"), + PathBuf::from("h"), + PathBuf::from("i"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +#[should_panic(expected = "Field list is missing")] +fn mandatoryfieldnamed_builder_missing() { + let _built = MandatoryFieldNamed::builder() + .list_extend(&["d", "e", "f",]) + .build(); +}