int-enum-impl 0.3.0

A procedural macro for conversion between integer and enum types.
Documentation
cfg_if::cfg_if! {
    if #[cfg(feature = "serialize")] {
        pub use inner::serde_impls;
    } else {
        pub fn serde_impls(
            _: &syn::Ident,
            _: &syn::Path,
            _: &syn::Ident,
            _: &syn::Ident,
        ) -> proc_macro2::TokenStream {
            proc_macro2::TokenStream::new()
        }
    }
}

#[cfg(feature = "serialize")]
mod inner {
    use proc_macro2::{Span, TokenStream};
    use quote::quote;
    use syn::parse_quote;
    use syn::{Ident, ItemFn, ItemImpl, Path};

    pub fn serde_impls(crate_: &Ident, core: &Path, enum_: &Ident, int_: &Ident) -> TokenStream {
        let serde = parse_quote!(#crate_::__serde);

        let enum_str = enum_.to_string();
        let int_str = int_.to_string();

        let serialize_impl = serialize_impl(&crate_, &core, &serde, &enum_, &int_, &int_str);
        let deserialize_impl =
            deserialize_impl(&crate_, &core, &serde, &enum_, &int_, &enum_str, &int_str);

        quote! {
            #serialize_impl
            #deserialize_impl
        }
    }

    fn serialize_impl(
        crate_: &Ident,
        core: &Path,
        serde: &Path,
        enum_: &Ident,
        int_: &Ident,
        int_str: &str,
    ) -> ItemImpl {
        let ser_fn = Ident::new(&format!("serialize_{}", &int_str), Span::call_site());
        parse_quote! {
            impl #serde::Serialize for #enum_ {
                fn serialize<S>(&self, serializer: S) -> #core::result::Result<S::Ok, S::Error>
                where
                    S: #serde::Serializer
                {
                    let n: #int_ = #crate_::IntEnum::to_int(self);
                    serializer.#ser_fn(n)
                }
            }
        }
    }

    fn deserialize_impl(
        crate_: &Ident,
        core: &Path,
        serde: &Path,
        enum_: &Ident,
        int_: &Ident,
        enum_str: &str,
        int_str: &str,
    ) -> ItemImpl {
        let expecting = format!("{} integer", &int_str);
        let visit_fn = visit_fn(&crate_, &core, &serde, &int_, &enum_str, &int_str);

        parse_quote! {
            impl<'de> #serde::Deserialize<'de> for #enum_ {
                fn deserialize<D>(deserializer: D) -> #core::result::Result<Self, D::Error>
                where
                    D: #serde::Deserializer<'de>
                {
                    struct Visitor;

                    impl<'de> #serde::de::Visitor<'de> for Visitor {
                        type Value = #enum_;

                        fn expecting(
                            &self,
                            formatter: &mut #core::fmt::Formatter<'_>,
                        ) -> #core::fmt::Result {
                            formatter.write_str(#expecting)
                        }

                        #visit_fn
                    }

                    deserializer.deserialize_any(Visitor)
                }
            }
        }
    }

    fn visit_fn(
        crate_: &Ident,
        core: &Path,
        serde: &Path,
        int_: &Ident,
        enum_str: &str,
        int_str: &str,
    ) -> ItemFn {
        let unknown_value = {
            cfg_if::cfg_if! {
                if #[cfg(any(feature = "std", feature = "alloc"))] {
                    let unknown_value = format!("unknown {} value: {{}}", &enum_str);
                    quote!(custom(#crate_::__format!(#unknown_value, v)))
                } else {
                    let unknown_value = format!("unknown {} value", &enum_str);
                    quote!(custom(#unknown_value))
                }
            }
        };

        let (visit, fn_body) = match &int_str[..] {
            "i128" | "u128" => (
                quote!(#int_),
                quote!(#crate_::IntEnum::from_int(v as #int_).map_err(|_| E::#unknown_value)),
            ),
            s => {
                let (visit_fn, min_max, cond) = match s {
                    "i8" | "i16" | "i32" | "i64" | "isize" => (
                        quote!(i64),
                        quote! {
                            const MIN: i64 = #core::#int_::MIN as i64;
                            const MAX: i64 = #core::#int_::MAX as i64;
                        },
                        quote!(MIN <= v && v <= MAX),
                    ),
                    "u8" | "u16" | "u32" | "u64" | "usize" => (
                        quote!(u64),
                        quote! {
                            const MAX: u64 = #core::#int_::MAX as u64;
                        },
                        quote!(v <= MAX),
                    ),
                    _ => unreachable!(),
                };

                (
                    visit_fn,
                    quote! {
                        #min_max

                        if #cond {
                            let v: #core::result::Result<Self::Value, _> = #crate_::IntEnum::from_int(v as #int_);
                            if let #core::result::Result::Ok(v) = v {
                                return #core::result::Result::Ok(v);
                            }
                        }

                        #core::result::Result::Err(E::#unknown_value)
                    },
                )
            }
        };
        let fn_name = Ident::new(&format!("visit_{}", visit.to_string()), Span::call_site());

        parse_quote! {
            fn #fn_name<E>(self, v: #visit) -> #core::result::Result<Self::Value, E>
            where
                E: #serde::de::Error,
            {
                #fn_body
            }
        }
    }
}