diff --git a/src/lib.rs b/src/lib.rs index 404bc85..5c0ee94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ mod patch; mod view; use crate::logic::is_attribute; -use logic::args::AttrArgsDefaults; +use logic::args::ModelAttrArgs; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; @@ -18,21 +18,22 @@ pub fn models(input: TokenStream) -> TokenStream { let model_attr = ast.attrs .iter() .filter(|v| is_attribute(v, "model")) + .cloned() .collect::>(); - let defaults = AttrArgsDefaults::parse(model_attr); + let model_args = ModelAttrArgs::parse(model_attr); let views: Vec = ast .attrs .iter() .filter(|v| is_attribute(v, "view")) - .map(|a| view::impl_view_model(&ast, a, &defaults)) + .map(|a| view::impl_view_model(&ast, a, model_args.clone())) .collect(); let patches: Vec = ast .attrs .iter() .filter(|v| is_attribute(v, "patch")) - .map(|a| patch::impl_patch_model(&ast, a, &defaults)) + .map(|a| patch::impl_patch_model(&ast, a, model_args.clone())) .collect(); let gen = quote::quote!( diff --git a/src/logic/args.rs b/src/logic/args.rs index 96d918f..8dc5f4c 100644 --- a/src/logic/args.rs +++ b/src/logic/args.rs @@ -7,42 +7,89 @@ use proc_macro_error::abort; use syn::{Attribute, Field}; #[derive(Clone)] -pub(crate) struct AttrArgsDefaults { - pub fields: Option, - pub derive: Option>, - pub preset: Option, - pub attributes_with: AttributesWith, // Has it's own None +pub(crate) struct ModelAttrArgs { + pub base: Option, + pub defaults: Option, } -impl AttrArgsDefaults { - /// Parses the attribute and returns the parsed arguments (0) and any remaining (1) - pub(crate) fn parse(attr: Vec<&Attribute>) -> Self { +impl ModelAttrArgs { + /// Conditional aborts on unexpected args to show that they arent valid + pub(crate) fn abort_unexpected(args: &[TokenTree]) { + const EXPECTED: &[&str; 2] = &["base", "defaults"]; + abort_unexpected_args(EXPECTED.to_vec(), args); + } + + pub(crate) fn parse(attr: Vec) -> Self { if attr.is_empty() { return Self { - fields: None, - derive: None, - preset: None, - attributes_with: AttributesWith::None, + base: None, + defaults: None, }; } else if attr.len() > 1 { abort!( attr[1], - "Expected only one `model` attribute to derive defaults from." + "Invalid attribute, expected only one `model` attribute but got `{}`", + attr.len() ) } let attr = attr.first().unwrap(); - let mut tks: Vec = attr + let mut args: Vec = attr .meta .require_list() - .unwrap() + .expect("This attribute must be in a list format") .to_owned() .tokens .into_iter() .collect::>(); - let args = &mut tks; + let args_mr = &mut args; + + let base = take_ident_group("base", args_mr) + .map(|g| BaseAttrArgs::parse(&mut g.stream().into_iter().collect(), attr)); + + let defaults = take_ident_group("defaults", args_mr) + .map(|g| DefaultAttrArgs::parse(&mut g.stream().into_iter().collect(), attr)); + + Self::abort_unexpected(&args); + + Self { base, defaults } + } +} + +#[derive(Clone)] +pub(crate) struct BaseAttrArgs { + pub fields: Option, + pub derive: Option>, +} + +impl BaseAttrArgs { + /// Parses the attribute and returns the parsed arguments (0) and any remaining (1) + pub(crate) fn parse(args: &mut Vec, attr: &Attribute) -> Self { + let fields = { + let fields = FieldsArg::parse(args, attr); + match fields.is_default() { + true => None, + false => Some(fields), + } + }; + let derive = take_path_group("derive", args); + + Self { fields, derive } + } +} + +#[derive(Clone)] +pub(crate) struct DefaultAttrArgs { + pub fields: Option, + pub derive: Option>, + pub preset: Option, + pub attributes_with: AttributesWith, // Has it's own None +} +impl DefaultAttrArgs { + /// Parses the attribute and returns the parsed arguments (0) and any remaining (1) + pub(crate) fn parse(args: &mut Vec, attr: &Attribute) -> Self { let fields = { let fields = FieldsArg::parse(args, attr); match fields.is_default() { @@ -84,16 +131,16 @@ impl AttrArgs { abort_unexpected_args(expect, args) } - /// Parses the attribute and returns the parsed arguments (0) and any remaining (1) + /// Parses the attribute and returns the parsed arguments as `Self` (0) and any arguments remaining unparsed (1) pub(crate) fn parse( attr: &Attribute, - defaults: &AttrArgsDefaults, + model_args: ModelAttrArgs, abort_unexpected: bool, ) -> (Self, Vec) { let tks: Vec = attr .meta .require_list() - .unwrap() + .expect("This attribute must be in a list format") .to_owned() .tokens .into_iter() @@ -113,23 +160,13 @@ impl AttrArgs { true => vec![], false => tks[2..].to_vec(), }; - let args_mr = &mut args; - - // Parse Expected Macro Args - let fields = { - let fields = FieldsArg::parse(args_mr, attr); - match &defaults.fields { - Some(f) if fields.is_default() => f.clone(), - _ => fields, - } - }; - let derive = take_path_group("derive", args_mr).or(defaults.derive.clone()); - let preset = Preset::parse(args_mr).or(defaults.preset); - let attributes_with = AttributesWith::parse(args_mr).unwrap_or_else(|| match &preset { - Some(preset) => preset.attr_with(), - _ => defaults.attributes_with, - }); + let fields = FieldsArg::parse_with_args(&mut args, &model_args, attr); + let derive = parse_derives_wtih_args(&mut args, &model_args); + let preset = Preset::parse_with_args(&mut args, &model_args); + let attributes_with = + AttributesWith::parse_with_args(&mut args, &model_args, preset.as_ref()) + .unwrap_or_default(); if abort_unexpected { Self::abort_unexpected(&args, &[]) @@ -182,6 +219,38 @@ impl FieldsArg { } } + pub(crate) fn parse_with_args( + args: &mut Vec, + model_args: &ModelAttrArgs, + attr: &Attribute, // Just for its span and error highlighting purposes + ) -> Self { + use FieldsArg::*; + let fields = FieldsArg::parse(args, attr); + + let default_fields = model_args.defaults.as_ref().and_then(|v| v.fields.clone()); + let fields = match &default_fields { + Some(f) if fields.is_default() => f.clone(), + _ => fields, + }; + + let base_fields = model_args.base.as_ref().and_then(|v| v.fields.clone()); + if let Some(base) = base_fields { + let final_fields: Vec<_> = match (fields, base) { + (Fields(f), Fields(b)) => f.into_iter().filter(|v| b.contains(v)).collect(), + (Fields(f), Omit(b)) => f.into_iter().filter(|v| !b.contains(v)).collect(), + (Omit(f), Fields(b)) => b.into_iter().filter(|v| !f.contains(v)).collect(), + (Omit(f), Omit(mut b)) => { + let not_in_base = f.into_iter().filter(|v| !b.contains(v)).collect::>(); + b.extend(not_in_base); + b + } + }; + Fields(final_fields) + } else { + fields + } + } + /// Similar to an is_empty function but only checks if omit is empty as thats the default case pub(crate) fn is_default(&self) -> bool { match self { @@ -226,14 +295,25 @@ impl AttributesWith { "all" => Self::All, #[cfg(feature = "openapi")] v => abort!( - ident, - "Invalid value, expected `none`, `oai` (from poem_openapi crate), `deriveless`, or `all` but got `{}`", v - ), + ident, + "Invalid value, expected `none`, `oai` (from poem_openapi crate), `deriveless`, or `all` but got `{}`", v + ), #[cfg(not(feature = "openapi"))] v => abort!( - ident, - "Invalid value, expected `none`, `deriveless`, or `all` but got `{}`", v - ), + ident, + "Invalid value, expected `none`, `deriveless`, or `all` but got `{}`", v + ), + }) + } + + pub(crate) fn parse_with_args( + args: &mut Vec, + model_args: &ModelAttrArgs, + preset: Option<&Preset>, + ) -> Option { + AttributesWith::parse(args).or_else(|| match preset { + Some(preset) => Some(preset.attr_with()), + None => model_args.defaults.as_ref().map(|f| f.attributes_with), }) } @@ -331,16 +411,22 @@ impl Preset { "write" => Self::Write, #[cfg(feature = "openapi")] v => abort!( - ident, - "Invalid value, expected `none` or `read`/`write` (with `openapi` feature) but got `{}`", v - ), + ident, + "Invalid value, expected `none` or `read`/`write` (with `openapi` feature) but got `{}`", v + ), #[cfg(not(feature = "openapi"))] v => abort!( - ident, - "Invalid value, expected `none` but got `{}`", v - ), + ident, + "Invalid value, expected `none` but got `{}`", v + ), }) } + pub(crate) fn parse_with_args( + args: &mut Vec, + model_args: &ModelAttrArgs, + ) -> Option { + Preset::parse(args).or(model_args.defaults.as_ref().and_then(|f| f.preset)) + } pub(crate) fn predicate(&self, field: &Field) -> bool { match self { @@ -368,3 +454,23 @@ impl Preset { } } } + +/// Parses the `derive` attribute and returns the parsed arguments as a Vec of `syn::Path` if the argument was given +fn parse_derives_wtih_args( + args: &mut Vec, + model_args: &ModelAttrArgs, +) -> Option> { + let base_derives = model_args.base.as_ref().and_then(|v| v.derive.clone()); + let default_derives = model_args.defaults.as_ref().and_then(|v| v.derive.clone()); + + let derives = take_path_group("derive", args).or(default_derives.clone()); + match (derives, base_derives) { + (Some(d), Some(mut b)) => { + b.extend(d); + Some(b) + } + (Some(g), None) => Some(g), + (None, Some(b)) => Some(b), + (None, None) => None, + } +} diff --git a/src/patch.rs b/src/patch.rs index d2b22ff..2f12eec 100644 --- a/src/patch.rs +++ b/src/patch.rs @@ -1,5 +1,5 @@ use crate::logic::{ - args::{AttrArgs, AttrArgsDefaults, OptionType}, + args::{AttrArgs, ModelAttrArgs, OptionType}, *, }; use proc_macro2::{Ident, TokenStream}; @@ -10,7 +10,7 @@ use syn::{Attribute, DeriveInput, Type}; pub fn impl_patch_model( ast: &DeriveInput, attr: &Attribute, - defaults: &AttrArgsDefaults, + defaults: ModelAttrArgs, ) -> TokenStream { // Argument and Variable Initialization and Prep let (args, mut remainder) = AttrArgs::parse(attr, defaults, false); diff --git a/src/view.rs b/src/view.rs index 87c7eb3..f25a7f6 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,13 +1,15 @@ -use crate::logic::{args::{AttrArgs, AttrArgsDefaults}, *}; +use crate::logic::{args::AttrArgs, *}; use proc_macro2::{Ident, TokenStream}; use proc_macro_error::abort; use quote::quote; use syn::{self, Attribute, DataEnum, DataStruct, DeriveInput}; +use self::args::ModelAttrArgs; + pub fn impl_view_model( ast: &DeriveInput, attr: &Attribute, - defaults: &AttrArgsDefaults + defaults: ModelAttrArgs ) -> TokenStream { // Argument and Variable Initialization and Prep let (args, _) = AttrArgs::parse(attr, defaults, true); diff --git a/tests/patch.rs b/tests/patch.rs index b36d8d4..6353e1b 100644 --- a/tests/patch.rs +++ b/tests/patch.rs @@ -67,7 +67,7 @@ impl UserAlt { //------------------ Structs -- defaults #[derive(Models)] -#[model(fields(display_name, bio), attributes_with = "none")] +#[model(defaults(fields(display_name, bio), attributes_with = "none"))] #[patch(UserProfileDefaults)] struct UserDefaults{ id: i32, @@ -76,6 +76,28 @@ struct UserDefaults{ password: String, } +//------------------ Structs -- base +#[derive(Models)] +#[model(base(fields(display_name, bio), attributes_with = "none"))] +#[patch(UserProfileBase)] +struct UserBase { + id: i32, + display_name: String, + bio: String, + password: String, +} + +//------------------ Structs -- base & defaults mix +#[derive(Models)] +#[model(base(fields(bio, display_name), attributes_with = "none"), defaults(omit(display_name)))] +#[patch(UserProfileMix)] +struct UserMix { + id: i32, + display_name: String, + bio: String, + password: String, +} + #[test] fn alt_omitted_only() { diff --git a/tests/view.rs b/tests/view.rs index 87e6f22..a113e4a 100644 --- a/tests/view.rs +++ b/tests/view.rs @@ -92,7 +92,7 @@ struct UserAttrNone{ //------------------ Structs -- defaults #[derive(Models)] -#[model(fields(display_name, bio), attributes_with = "none")] +#[model(defaults(fields(display_name, bio), attributes_with = "none"))] #[view(UserProfileDefaults)] struct UserDefaults{ id: i32,