ormer-derive 0.2.3

A minimalist ORM framework that supports SQLite, PostgreSQL, MySQL, and SqlServer
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Ident};

pub fn derive_model_enum(input: DeriveInput) -> TokenStream {
    derive_field_type(input)
}

pub fn derive_field_type(input: DeriveInput) -> TokenStream {
    match &input.data {
        Data::Enum(data_enum) => derive_enum_field_type(&input, data_enum),
        Data::Struct(data_struct) => derive_tuple_struct_field_type(&input, data_struct),
        _ => panic!("FieldType can only be derived for enums or single-field tuple structs"),
    }
}

fn derive_enum_field_type(input: &DeriveInput, data_enum: &syn::DataEnum) -> TokenStream {
    let name = &input.ident;
    let variants = &data_enum.variants;
    let variant_names: Vec<&Ident> = variants.iter().map(|v| &v.ident).collect();
    let variant_names_str: Vec<String> = variant_names.iter().map(|v| v.to_string()).collect();

    let repr_integer_type = repr_integer_type(input);
    let is_numeric_enum = repr_integer_type.is_some();

    let from_value_impl = if is_numeric_enum {
        quote! {
            impl From<#name> for ::ormer::model::Value {
                fn from(v: #name) -> Self {
                    ::ormer::model::Value::Integer(v as i64)
                }
            }

            impl ::core::convert::From<#name> for i32 {
                fn from(v: #name) -> Self {
                    v as i32
                }
            }
        }
    } else {
        let match_arms = variant_names.iter().map(|v| {
            quote! {
                #name::#v => ::ormer::model::Value::Text(stringify!(#v).to_string()),
            }
        });

        quote! {
            impl From<#name> for ::ormer::model::Value {
                fn from(v: #name) -> Self {
                    match v {
                        #(#match_arms)*
                    }
                }
            }
        }
    };

    let from_impl = if is_numeric_enum {
        let match_arms = variant_names.iter().map(|v| {
            quote! {
                val if val == #name::#v as i64 => Ok(#name::#v),
            }
        });

        quote! {
            impl ::ormer::model::FromValue for #name {
                fn from_value(value: &::ormer::model::Value) -> ::ormer::Result<Self> {
                    match value {
                        ::ormer::model::Value::Integer(val) => {
                            match *val {
                                #(#match_arms)*
                                _ => Err(::ormer::ormer_error!(
                                    "Unknown numeric value '{}' for {}", val, stringify!(#name)
                                )),
                            }
                        }
                        _ => Err(::ormer::ormer_error!(
                            "Expected Integer value for {}", stringify!(#name)
                        )),
                    }
                }
            }
        }
    } else {
        let match_arms = variant_names.iter().map(|v| {
            let v_str = v.to_string();
            quote! {
                #v_str => Ok(#name::#v),
            }
        });

        quote! {
            impl ::ormer::model::FromValue for #name {
                fn from_value(value: &::ormer::model::Value) -> ::ormer::Result<Self> {
                    match value {
                        ::ormer::model::Value::Text(s) => {
                            match s.as_str() {
                                #(#match_arms)*
                                _ => Err(::ormer::ormer_error!(
                                    "Unknown enum variant '{}' for {}", s, stringify!(#name)
                                )),
                            }
                        }
                        _ => Err(::ormer::ormer_error!(
                            "Expected Text value for {}", stringify!(#name)
                        )),
                    }
                }
            }
        }
    };

    let from_row_values_impl = quote! {
        impl ::ormer::model::FromRowValues for #name {
            fn from_row_values(values: &[::ormer::model::Value]) -> ::ormer::Result<Self> {
                if values.is_empty() {
                    return Err(::ormer::ormer_error!(
                        "Expected at least one value for {}", stringify!(#name)
                    ));
                }
                <#name as ::ormer::model::FromValue>::from_value(&values[0])
            }
        }
    };

    let try_from_i32_impl = if let Some(repr_type) = &repr_integer_type {
        let match_arms = variant_names.iter().map(|v| {
            quote! {
                val if val == #name::#v as #repr_type => Ok(#name::#v),
            }
        });

        quote! {
            impl ::core::convert::TryFrom<i32> for #name {
                type Error = ::ormer::OrmerError;

                fn try_from(value: i32) -> ::ormer::Result<Self> {
                    let repr_value = <#repr_type as ::core::convert::TryFrom<i32>>::try_from(value)
                        .map_err(|err| {
                            ::ormer::ormer_error!(
                                "Failed to convert numeric value '{}' to {}: {}",
                                value,
                                stringify!(#name),
                                err
                            )
                        })?;
                    match repr_value {
                        #(#match_arms)*
                        _ => Err(::ormer::ormer_error!(
                            "Unknown numeric value '{}' for {}", value, stringify!(#name)
                        )),
                    }
                }
            }
        }
    } else {
        quote! {}
    };

    let name_method = {
        let match_arms_1 = variant_names.iter().map(|v| {
            let v_str = v.to_string();
            quote! {
                #name::#v => #v_str,
            }
        });

        let match_arms_2 = variant_names.iter().map(|v| {
            let v_str = v.to_string();
            quote! {
                #name::#v => #v_str,
            }
        });

        let from_name_arms = variant_names.iter().map(|v| {
            let v_str = v.to_string();
            quote! {
                #v_str => Ok(#name::#v),
            }
        });

        let from_i64_arms = if is_numeric_enum {
            variant_names
                .iter()
                .map(|v| {
                    quote! {
                        val if val == #name::#v as i64 => Ok(#name::#v),
                    }
                })
                .collect::<Vec<_>>()
        } else {
            vec![]
        };

        let numeric_enum_methods = if is_numeric_enum {
            quote! {
                fn as_i64(&self) -> i64 {
                    *self as i64
                }

                fn from_i64(value: i64) -> ::ormer::Result<Self> {
                    match value {
                        #(#from_i64_arms)*
                        _ => Err(::ormer::ormer_error!(
                            "Unknown numeric value '{}' for {}", value, stringify!(#name)
                        )),
                    }
                }

                fn is_numeric_enum() -> bool {
                    true
                }
            }
        } else {
            quote! {}
        };

        quote! {
            impl #name {
                pub fn name(&self) -> &'static str {
                    match self {
                        #(#match_arms_1)*
                    }
                }

                pub const VARIANTS: &'static [&'static str] = &[#(#variant_names_str),*];
            }

            impl ::ormer::model::FieldType for #name {
                const VARIANTS: &'static [&'static str] = &[#(#variant_names_str),*];

                fn name(&self) -> &'static str {
                    match self {
                        #(#match_arms_2)*
                    }
                }

                fn from_name(name: &str) -> ::ormer::Result<Self> {
                    match name {
                        #(#from_name_arms)*
                        _ => Err(::ormer::ormer_error!(
                            "Unknown enum variant '{}' for {}", name, stringify!(#name)
                        )),
                    }
                }

                #numeric_enum_methods
            }

            impl ::ormer::model::FieldTypeProvider for #name {
                const ENUM_VARIANTS: Option<&'static [&'static str]> = Some(#name::VARIANTS);
                const DB_VALUE_TYPE: Option<fn(::ormer::DbType) -> &'static str> = None;
            }
        }
    };

    quote! {
        #try_from_i32_impl
        #from_impl
        #from_value_impl
        #from_row_values_impl
        #name_method
    }
}

fn repr_integer_type(input: &DeriveInput) -> Option<syn::Type> {
    for attr in &input.attrs {
        if !attr.path().is_ident("repr") {
            continue;
        }
        let syn::Meta::List(list) = &attr.meta else {
            continue;
        };
        let tokens = list.tokens.to_string().replace(' ', "");
        for repr in tokens.split(',') {
            if matches!(
                repr,
                "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize"
            ) {
                return syn::parse_str::<syn::Type>(repr).ok();
            }
        }
    }
    None
}

fn derive_tuple_struct_field_type(
    input: &DeriveInput,
    data_struct: &syn::DataStruct,
) -> TokenStream {
    let name = &input.ident;
    let inner_type = match &data_struct.fields {
        Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
        _ => panic!("FieldType can only be derived for enums or single-field tuple structs"),
    };
    let inner_type_str = normalize_type_string(quote! { #inner_type }.to_string());

    quote! {
        impl From<#name> for ::ormer::model::Value {
            fn from(value: #name) -> Self {
                ::ormer::model::Value::from(value.0)
            }
        }

        impl ::ormer::model::FromValue for #name {
            fn from_value(value: &::ormer::model::Value) -> ::ormer::Result<Self> {
                <#inner_type as ::ormer::model::FromValue>::from_value(value).map(#name)
            }
        }

        impl ::ormer::model::FromRowValues for #name {
            fn from_row_values(values: &[::ormer::model::Value]) -> ::ormer::Result<Self> {
                let value = values.first().ok_or_else(|| {
                    ::ormer::ormer_error!("Expected at least one value for {}", stringify!(#name))
                })?;
                <#name as ::ormer::model::FromValue>::from_value(value)
            }
        }

        impl ::ormer::model::FieldTypeProvider for #name {
            const ENUM_VARIANTS: Option<&'static [&'static str]> = None;
            const DB_VALUE_TYPE: Option<fn(::ormer::DbType) -> &'static str> = None;
            const RUST_TYPE: Option<&'static str> = Some(#inner_type_str);
        }

        impl ::ormer::model::FieldType for #name {
            const VARIANTS: &'static [&'static str] = &[];

            fn name(&self) -> &'static str {
                stringify!(#name)
            }

            fn from_name(_name: &str) -> ::ormer::Result<Self> {
                Err(::ormer::ormer_error!(
                    "{} is not an enum field type", stringify!(#name)
                ))
            }
        }
    }
}

fn normalize_type_string(type_str: String) -> String {
    type_str
        .replace(" :: ", "::")
        .replace(" < ", "<")
        .replace(" >", ">")
        .replace(" , ", ",")
}