lib.rs
raw
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<Ident>,
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<T>
// 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<T>
// 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<Variant, Field>,
#[darling(default)]
pub(crate) patch_name: Option<Ident>,
#[darling(default)]
pub(crate) serde: Serde,
#[darling(default)]
pub(crate) sqlx: Sqlx,
}
pub(crate) fn make_patch_type(target: &Target, input: &Data<Variant, Field>) -> 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
});
}
}