kite_sql_serde_macros 0.1.2

Derive macros for KiteSQL
Documentation
use darling::ast::Data;
use darling::{FromDeriveInput, FromField};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{parse_quote, DeriveInput, Error, Generics, Ident, LitStr, Type};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(projection), supports(struct_named))]
struct ProjectionOpts {
    ident: Ident,
    generics: Generics,
    data: Data<(), ProjectionFieldOpts>,
}

#[derive(Debug, FromField)]
#[darling(attributes(projection))]
struct ProjectionFieldOpts {
    ident: Option<Ident>,
    ty: Type,
    rename: Option<String>,
    from: Option<String>,
}

pub(crate) fn handle(ast: DeriveInput) -> Result<TokenStream, Error> {
    let projection_opts = ProjectionOpts::from_derive_input(&ast)?;
    let struct_name = &projection_opts.ident;
    let mut generics = projection_opts.generics.clone();

    let Data::Struct(data_struct) = projection_opts.data else {
        return Err(Error::new_spanned(
            struct_name,
            "Projection only supports structs with named fields",
        ));
    };

    let mut projected_values = Vec::new();
    let mut assignments = Vec::new();

    for field in data_struct.fields {
        let ProjectionFieldOpts {
            ident,
            ty: field_ty,
            rename,
            from,
        } = field;
        let field_name = ident.ok_or_else(|| {
            Error::new_spanned(struct_name, "Projection only supports named struct fields")
        })?;
        let field_name_string = field_name.to_string();
        let source_name = rename.clone().unwrap_or_else(|| field_name_string.clone());
        let source_name_lit = LitStr::new(&source_name, Span::call_site());
        let field_name_lit = LitStr::new(&field_name_string, Span::call_site());
        let relation_expr = if let Some(source_relation) = from {
            let relation_lit = LitStr::new(&source_relation, Span::call_site());
            quote!(#relation_lit)
        } else {
            quote!(relation)
        };

        generics
            .make_where_clause()
            .predicates
            .push(parse_quote!(#field_ty : ::kite_sql::orm::FromDataValue));

        projected_values.push(if rename.is_some() {
            quote! {
                ::kite_sql::orm::projection_value(#source_name_lit, #relation_expr, #field_name_lit)
            }
        } else {
            quote! {
                ::kite_sql::orm::projection_column(#source_name_lit, #relation_expr)
            }
        });
        assignments.push(quote! {
            if let Some(value) = ::kite_sql::orm::try_get::<#field_ty>(&mut tuple, schema, #field_name_lit) {
                struct_instance.#field_name = value;
            }
        });
    }

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    Ok(quote! {
        impl #impl_generics ::kite_sql::orm::Projection for #struct_name #ty_generics
        #where_clause
        {
            fn projected_values<M: ::kite_sql::orm::Model>(relation: &str) -> ::std::vec::Vec<::kite_sql::orm::ProjectedValue> {
                vec![#(#projected_values),*]
            }
        }

        impl #impl_generics From<(&::kite_sql::types::tuple::SchemaRef, ::kite_sql::types::tuple::Tuple)> for #struct_name #ty_generics
        #where_clause
        {
            fn from((schema, mut tuple): (&::kite_sql::types::tuple::SchemaRef, ::kite_sql::types::tuple::Tuple)) -> Self {
                let mut struct_instance = <Self as ::std::default::Default>::default();
                #(#assignments)*
                struct_instance
            }
        }
    })
}