extern crate proc_macro; use darling::{ast::Data, FromDeriveInput}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::{parse_macro_input, Attribute, DeriveInput, Generics, Type, Visibility}; #[derive(Default, Debug, Clone, darling::FromMeta)] #[darling(default)] pub(crate) struct Serde { pub(crate) serialize: bool, pub(crate) deserialize: bool, } #[derive(Default, Debug, Clone, darling::FromMeta)] #[darling(default)] pub(crate) struct Sqlx { pub(crate) from_row: bool, } #[derive(Debug, Clone, Copy, darling::FromMeta)] #[darling(default)] enum UpdateMode { Set, Remove, Merge, Discard, } impl Default for UpdateMode { fn default() -> Self { UpdateMode::Set } } #[derive(Debug, Clone, darling::FromField)] #[darling(attributes(patchable))] struct Field { vis: Visibility, ident: Option, ty: Type, // Determine the default update mode for the field during // construction of the patch from a partial source. #[darling(default)] mode: UpdateMode, // Whether to allow explicit operations in the form of Update // structure representations in the source. A field with // mode="merge", explicit_set and explicit_discard set to true would // default to merging the field, but allow construction of patches // that set or discard instead. #[darling(default)] explicit_set: bool, #[darling(default)] explicit_remove: bool, #[darling(default)] explicit_merge: bool, #[darling(default)] explicit_discard: bool, } #[derive(Debug, Clone, darling::FromVariant)] #[darling(attributes(patchable))] struct Variant { ident: Ident, // Determine the default update mode for the field during // construction of the patch from a partial source. #[darling(default)] mode: UpdateMode, // Whether to allow explicit operations in the form of Update // structure representations in the source. A field with // mode="Merge", explicit_set and explicit_discard set to true would // default to merging the field, but allow construction of patches // that set or discard instead. #[darling(default)] explicit_set: bool, #[darling(default)] explicit_remove: bool, #[darling(default)] explicit_merge: bool, #[darling(default)] explicit_discard: bool, } #[derive(Debug, Clone, darling::FromDeriveInput)] #[darling(attributes(patchable))] pub(crate) struct Target { pub(crate) vis: Visibility, pub(crate) ident: Ident, pub(crate) generics: Generics, pub(crate) data: Data, #[darling(default)] pub(crate) patch_name: Option, #[darling(default)] pub(crate) serde: Serde, #[darling(default)] pub(crate) sqlx: Sqlx, } pub(crate) fn make_patch_type(target: &Target, input: &Data) -> TokenStream { todo!() } pub(crate) fn make_patchable(input: DeriveInput) -> TokenStream { let target = Target::from_derive_input(&input).unwrap(); quote!( #[automatically_derived] ) } #[proc_macro_derive(Patchable, attributes(patchable))] pub fn derive_patchable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { make_patchable(parse_macro_input!(input as DeriveInput)).into() } #[cfg(test)] mod tests { use proc_macro2::Span; use super::*; #[test] fn parse_default_target() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test {} "###, ) .unwrap(), ) .unwrap(); assert_eq!(target.patch_name, None); assert_eq!(target.serde.serialize, false); assert_eq!(target.serde.deserialize, false); assert_eq!(target.sqlx.from_row, false); } #[test] fn parse_patch_name() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] #[patchable(patch_name = "Herberschlatz")] struct Test {} "###, ) .unwrap(), ) .unwrap(); assert_eq!( target.patch_name, Some(Ident::new("Herberschlatz", Span::call_site())) ); assert_eq!(target.serde.serialize, false); assert_eq!(target.serde.deserialize, false); assert_eq!(target.sqlx.from_row, false); } #[test] fn parse_serde_serialize() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] #[patchable(serde(serialize))] struct Test {} "###, ) .unwrap(), ) .unwrap(); assert_eq!(target.patch_name, None); assert_eq!(target.serde.serialize, true); assert_eq!(target.serde.deserialize, false); assert_eq!(target.sqlx.from_row, false); } #[test] fn parse_serde_deserialize() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] #[patchable(serde(deserialize))] struct Test {} "###, ) .unwrap(), ) .unwrap(); assert_eq!(target.patch_name, None); assert_eq!(target.serde.serialize, false); assert_eq!(target.serde.deserialize, true); assert_eq!(target.sqlx.from_row, false); } #[test] fn parse_serde_serialize_and_deserialize() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] #[patchable(serde(serialize, deserialize))] struct Test {} "###, ) .unwrap(), ) .unwrap(); assert_eq!(target.patch_name, None); assert_eq!(target.serde.serialize, true); assert_eq!(target.serde.deserialize, true); assert_eq!(target.sqlx.from_row, false); } #[test] fn parse_sqlx_from_row() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] #[patchable(sqlx(from_row))] struct Test {} "###, ) .unwrap(), ) .unwrap(); assert_eq!(target.patch_name, None); assert_eq!(target.serde.serialize, false); assert_eq!(target.serde.deserialize, false); assert_eq!(target.sqlx.from_row, true); } #[test] fn parse_sqlx_from_row_and_serde_serialize_deserialize() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] #[patchable(sqlx(from_row), serde(serialize, deserialize))] struct Test {} "###, ) .unwrap(), ) .unwrap(); assert_eq!(target.patch_name, None); assert_eq!(target.serde.serialize, true); assert_eq!(target.serde.deserialize, true); assert_eq!(target.sqlx.from_row, true); } #[test] #[should_panic] fn parse_field_option_invalid() { Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test { pub foo: String, #[patchable(this_is_wrong)] pub bar: String, } "###, ) .unwrap(), ) .unwrap(); } #[test] fn parse_field_option_mode() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test { pub foo: String, #[patchable(mode = "merge")] pub bar: String, } "###, ) .unwrap(), ) .unwrap(); assert!(if let Some(fields) = target.data.take_struct() { if let UpdateMode::Merge = fields.fields[1].mode { true } else { false } } else { false }); } #[test] fn parse_field_options_default() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test { pub foo: String, pub bar: String, } "###, ) .unwrap(), ) .unwrap(); if let Some(fields) = target.data.take_struct() { assert!(if let UpdateMode::Set = fields.fields[1].mode { true } else { false }); assert_eq!(fields.fields[1].explicit_set, false); assert_eq!(fields.fields[1].explicit_remove, false); assert_eq!(fields.fields[1].explicit_merge, false); assert_eq!(fields.fields[1].explicit_discard, false); } else { panic!("Field record does not exist"); } } #[test] fn parse_field_option_explicit_set() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test { pub foo: String, #[patchable(explicit_set)] pub bar: String, } "###, ) .unwrap(), ) .unwrap(); assert!(if let Some(fields) = target.data.take_struct() { fields.fields[1].explicit_set } else { false }); } #[test] fn parse_field_option_explicit_remove() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test { pub foo: String, #[patchable(explicit_remove)] pub bar: String, } "###, ) .unwrap(), ) .unwrap(); assert!(if let Some(fields) = target.data.take_struct() { fields.fields[1].explicit_remove } else { false }); } #[test] fn parse_field_option_explicit_merge() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test { pub foo: String, #[patchable(explicit_merge)] pub bar: String, } "###, ) .unwrap(), ) .unwrap(); assert!(if let Some(fields) = target.data.take_struct() { fields.fields[1].explicit_merge } else { false }); } #[test] fn parse_field_option_explicit_discard() { let target = Target::from_derive_input( &syn::parse_str( r###" #[derive(Patchable)] struct Test { pub foo: String, #[patchable(explicit_discard)] pub bar: String, } "###, ) .unwrap(), ) .unwrap(); assert!(if let Some(fields) = target.data.take_struct() { fields.fields[1].explicit_discard } else { false }); } }