errlanes-derive 0.16.1

Derive macros for errlanes (Rejection, Lift, Failure)
Documentation
use darling::{FromDeriveInput, ast, util::PathList};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Field, Ident, Type};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(failure), supports(struct_newtype))]
struct FailureInput {
    ident: Ident,
    data: ast::Data<(), Field>,
    #[darling(default)]
    from: PathList,
    #[darling(default)]
    lift: PathList,
    #[darling(default)]
    sqlx: darling::util::Flag,
}

fn rejection_ty(field_ty: &Type) -> darling::Result<&Type> {
    if let Type::Path(p) = field_ty
        && let Some(seg) = p.path.segments.last()
        && seg.ident == "Fail"
        && let syn::PathArguments::AngleBracketed(args) = &seg.arguments
        && let Some(syn::GenericArgument::Type(t)) = args.args.first()
    {
        return Ok(t);
    }
    Err(darling::Error::custom(
        "#[derive(errlanes::Failure)] requires a single-field newtype wrapping `errlanes::Fail<D>`",
    ))
}

pub fn derive(ast: &syn::DeriveInput) -> darling::Result<TokenStream> {
    let input = FailureInput::from_derive_input(ast)?;
    let ident = &input.ident;
    let fields = match &input.data {
        ast::Data::Struct(f) => f,
        ast::Data::Enum(_) => {
            return Err(
                darling::Error::custom("Failure can only be derived for a newtype struct")
                    .with_span(&input.ident),
            );
        }
    };
    let field = fields.fields.first().ok_or_else(|| {
        darling::Error::custom("Failure requires exactly one field").with_span(&input.ident)
    })?;
    let d_ty = rejection_ty(&field.ty)?;

    let mut tokens = quote! {
        impl std::fmt::Display for #ident {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                std::fmt::Display::fmt(&self.0, f)
            }
        }

        impl std::error::Error for #ident {
            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
                Some(&self.0)
            }
        }

        impl errlanes::Failure for #ident {
            type Rejection = #d_ty;
            type Lanes = errlanes::AllLanes;

            fn into_fail(self) -> errlanes::Fail<Self::Rejection> {
                self.0
            }

            fn from_fail(f: errlanes::Fail<Self::Rejection>) -> Self {
                Self(f)
            }

            fn as_fail(&self) -> &errlanes::Fail<Self::Rejection> {
                &self.0
            }
        }

        impl From<#d_ty> for #ident {
            fn from(d: #d_ty) -> Self {
                Self(errlanes::Fail::Rejected(d))
            }
        }

        impl From<errlanes::Fail<#d_ty>> for #ident {
            fn from(f: errlanes::Fail<#d_ty>) -> Self {
                Self(f)
            }
        }

        impl From<errlanes::Transient> for #ident {
            fn from(t: errlanes::Transient) -> Self {
                Self(errlanes::Fail::Transient(t))
            }
        }

        impl From<errlanes::Fatal> for #ident {
            fn from(x: errlanes::Fatal) -> Self {
                Self(errlanes::Fail::Fatal(x))
            }
        }

        impl From<errlanes::Denied> for #ident {
            fn from(d: errlanes::Denied) -> Self {
                Self(errlanes::Fail::Denied(d))
            }
        }

        impl From<errlanes::Exhausted> for #ident {
            fn from(e: errlanes::Exhausted) -> Self {
                Self(errlanes::Fail::from(e))
            }
        }

        impl From<errlanes::Fault> for #ident {
            fn from(f: errlanes::Fault) -> Self {
                Self(f.into())
            }
        }
    };

    if input.sqlx.is_present() {
        tokens.extend(quote! {
            impl From<sqlx::Error> for #ident {
                fn from(e: sqlx::Error) -> Self {
                    Self(errlanes::Fail::from(e))
                }
            }
        });
    }

    for upstream in input.from.iter() {
        tokens.extend(quote! {
            impl From<#upstream> for #ident {
                fn from(e: #upstream) -> Self {
                    Self(<#upstream as errlanes::Failure>::into_fail(e).widen())
                }
            }
        });
    }

    for lift in input.lift.iter() {
        tokens.extend(quote! {
            impl From<errlanes::Fail<#lift>> for #ident {
                fn from(f: errlanes::Fail<#lift>) -> Self {
                    Self(f.widen())
                }
            }

            impl From<#lift> for #ident {
                fn from(x: #lift) -> Self {
                    errlanes::Fail::Rejected(x).into()
                }
            }
        });
    }

    Ok(tokens)
}