derive_convert 0.7.0

Derives 'impl (Try)From for Self' and 'impl (Try)From<Self>'
Documentation
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{DataEnum, Expr, Ident, Type};

use super::AllVariantsOptions;
use crate::{
    FieldNamer, FieldOp, ParseAttrsError, TryFromAttrs, TypeRef,
    convert_enum::VariantContext,
};

enum TryFromVariantOp {
    Into { rename: Option<Ident> },
    Default,
    Skip,
}

impl Default for TryFromVariantOp {
    fn default() -> Self {
        Self::Into { rename: None }
    }
}

impl FieldOp for TryFromVariantOp {
    type FieldContext = VariantContext;

    fn rename(
        mut self,
        rename_to: Option<Ident>,
    ) -> Result<Self, ParseAttrsError> {
        match &mut self {
            Self::Into { rename } => {
                *rename = rename_to;
                Ok(self)
            }
            _ if rename_to.is_none() => Ok(self),
            _ => Err(ParseAttrsError::CantRename),
        }
    }

    fn from_key_expr(_key: &str, _expr: Expr) -> Result<Self, ParseAttrsError> {
        Err(ParseAttrsError::UnsupportedNameValue)
    }

    fn from_key(key: &str) -> Result<Self, ParseAttrsError> {
        Ok(match key {
            "default" => Self::Default,
            "skip" => Self::Skip,
            _ => return Err(ParseAttrsError::UnsupportedPath),
        })
    }

    fn quote<'a>(
        &'a self,
        namer @ &mut FieldNamer {
            from,
            to,
            context: &VariantContext { has_one_field },
            ..
        }: &mut FieldNamer<'a, VariantContext>,
    ) -> TokenStream2 {
        let name = namer.name;
        match self {
            TryFromVariantOp::Into { rename } => {
                let (this, other) = namer.with(rename);
                if has_one_field {
                    quote!(#from::#other(___value) => #to::#this(___value.try_into()?),)
                } else {
                    quote!(#from::#other => #to::#this,)
                }
            }
            TryFromVariantOp::Default => {
                let _ = namer.with(None);
                if has_one_field {
                    quote!(#from::#name(_) => #to::default(),)
                } else {
                    quote!(#from::#name => #to::default(),)
                }
            }
            TryFromVariantOp::Skip => {
                quote!()
            }
        }
    }
}

pub(super) fn derive_try_from_enum(
    TryFromAttrs { types, err_ty }: &TryFromAttrs,
    subject: &Type,
    data: &DataEnum,
    from_self: bool,
) -> TokenStream2 {
    let filter_path = if from_self {
        "try_from_self"
    } else {
        "try_from"
    };
    let variants =
        AllVariantsOptions::<TryFromVariantOp>::parse(data, filter_path, types);
    types
        .iter_with(subject, from_self)
        .map(|type_ref @ TypeRef { from, to, .. }| {
            let (lines, foreign_fields) =
                variants.lines_n_fields(from_self, type_ref);
            quote! {
                impl std::convert::TryFrom<#from> for #to {
                    type Error = #err_ty;

                    fn try_from(value: #from) -> Result<#to, Self::Error> {
                        #foreign_fields
                        Ok(match value {
                            #lines
                        })
                    }
                }
            }
        })
        .collect()
}