derive-into 0.2.4

A Rust derive macro for easily creating conversions between structs and enums
Documentation
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{DataStruct, spanned::Spanned};

use crate::{
    attribute_parsing::{
        conversion_field::extract_convertible_fields, conversion_meta::ConversionMeta,
    },
    derive_into::build_field_conversions,
};

pub(super) fn implement_all_struct_conversions(
    data_struct: &DataStruct,
    conversions: Vec<ConversionMeta>,
) -> syn::Result<TokenStream2> {
    let named_struct = match &data_struct.fields {
        syn::Fields::Named(_) => true,
        syn::Fields::Unnamed(_) => false,
        syn::Fields::Unit => panic!("Unit structs are not supported for conversion"),
    };

    let conversion_impls: Vec<_> = conversions
        .into_iter()
        .map(|conversion| {
            implement_struct_conversion(
                conversion.clone(),
                named_struct,
                build_field_conversions(
                    &conversion,
                    named_struct,
                    true,
                    &extract_convertible_fields(
                        &data_struct.fields,
                        conversion.method,
                        &conversion.other_type(),
                    )?,
                )?,
            )
        })
        .collect::<Result<_, _>>()?;

    Ok(quote! {
        #(#conversion_impls)*
    })
}

fn implement_struct_conversion(
    meta: ConversionMeta,
    named_struct: bool,
    fields: Vec<TokenStream2>,
) -> syn::Result<TokenStream2> {
    let ConversionMeta {
        source_name,
        target_name,
        method,
        default_allowed,
        validate,
    } = meta;

    if !named_struct && default_allowed {
        return Err(syn::Error::new(
            source_name.span(),
            "Default values are not supported for unnamed structs",
        ));
    }

    let default_fields = if default_allowed {
        quote! { ..Default::default() }
    } else {
        quote! {}
    };

    let inner = if named_struct {
        quote! { #target_name { #(#fields)* #default_fields } }
    } else {
        quote! { #target_name(#(#fields)* #default_fields) }
    };

    let error_type = if cfg!(feature = "anyhow") {
        quote! { anyhow::Error }
    } else {
        quote! { String }
    };

    let validate_call = validate.map(|func| quote! {
        #func(&source).map_err(|e| format!("Failed trying to convert {} to {}: {}",
            stringify!(#source_name), stringify!(#target_name), e))?;
    });

    Ok(if method.is_falliable() {
        quote! {
            impl TryFrom<#source_name> for #target_name {
                type Error = #error_type;
                fn try_from(source: #source_name) -> Result<#target_name, Self::Error> {
                    #validate_call
                    Ok(#inner)
                }
            }
        }
    } else {
        quote! {
            impl From<#source_name> for #target_name {
                fn from(source: #source_name) -> #target_name {
                    #inner
                }
            }
        }
    })
}