icydb-derive 0.6.38

IcyDB — A type-safe, embedded ORM and schema system for the Internet Computer
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Error, Fields, Type};

// derive_field_values
pub fn derive_field_values(input: TokenStream) -> TokenStream {
    let input: DeriveInput = match syn::parse2(input) {
        Ok(input) => input,
        Err(err) => return err.to_compile_error(),
    };

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

    let fields = if let Data::Struct(data) = &input.data {
        if let Fields::Named(named) = &data.fields {
            &named.named
        } else {
            let err = Error::new_spanned(
                &data.fields,
                "FieldValues can only be derived for structs with named fields",
            );
            return err.to_compile_error();
        }
    } else {
        let err = Error::new_spanned(
            &input.ident,
            "FieldValues can only be derived for structs with named fields",
        );
        return err.to_compile_error();
    };

    let match_arms = fields.iter().map(|field| {
        let field_ident = field.ident.as_ref().expect("named field");
        let field_name = field_ident.to_string();
        match classify_field(&field.ty) {
            FieldCardinality::One => quote! {
                #field_name => Some(self.#field_ident.to_value()),
            },
            FieldCardinality::Opt => quote! {
                #field_name => {
                    match self.#field_ident.as_ref() {
                        Some(inner) => Some(FieldValue::to_value(inner)),
                        None => Some(Value::None),
                    }
                }
            },
            FieldCardinality::Many => quote! {
                #field_name => {
                    let list = self.#field_ident
                        .iter()
                        .map(FieldValue::to_value)
                        .collect::<Vec<_>>();

                    Some(Value::List(list))
                }
            },
        }
    });

    quote! {
        impl #impl_generics ::icydb::traits::FieldValues for #ident #ty_generics #where_clause {
            fn get_value(&self, field: &str) -> Option<::icydb::value::Value> {
                use ::icydb::{traits::FieldValue, value::Value};

                match field {
                    #(#match_arms)*
                    _ => None,
                }
            }
        }
    }
}

///
/// FieldCardinality
///

#[derive(Clone, Copy)]
enum FieldCardinality {
    One,
    Opt,
    Many,
}

fn classify_field(ty: &Type) -> FieldCardinality {
    if is_path_ident(ty, "Option") {
        FieldCardinality::Opt
    } else if is_path_ident(ty, "Vec") {
        FieldCardinality::Many
    } else {
        FieldCardinality::One
    }
}

fn is_path_ident(ty: &Type, ident: &str) -> bool {
    let Type::Path(path) = ty else {
        return false;
    };

    path.path
        .segments
        .last()
        .is_some_and(|segment| segment.ident == ident)
}