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
        });
    }
}