safe_decimal_core 0.1.1

This is a Rust fixed-point numeric library targeting blockchain development. Originally created and used as a part of the Invariant Protocol. The current version leverages macros, traits and generics to exchange dozens of lines of error prone code with a single line and generating the rest.
Documentation
use quote::quote;

use crate::utils::string_to_ident;
use crate::DecimalCharacteristics;

pub fn generate_ops(characteristics: DecimalCharacteristics) -> proc_macro::TokenStream {
    let DecimalCharacteristics {
        struct_name,
        underlying_type,
        ..
    } = characteristics;

    let name_str = &struct_name.to_string();
    let underlying_str = &underlying_type.to_string();

    let module_name = string_to_ident("tests_", &name_str);

    proc_macro::TokenStream::from(quote!(
        impl std::ops::Add for #struct_name {
            type Output = Self;
            fn add(self, rhs: Self) -> Self {
                Self::new(self.get()
                    .checked_add(rhs.get())
                    .unwrap_or_else(|| panic!("decimal: overflow in method {}::add()", #name_str))
                )
            }
        }

        impl std::ops::Sub for #struct_name {
            type Output = #struct_name;

            fn sub(self, rhs: Self) -> #struct_name {
                Self::new(self.get()
                    .checked_sub(rhs.get())
                    .unwrap_or_else(|| panic!("decimal: overflow in method {}::sub()", #name_str))
                )
            }
        }

        impl<T: Decimal> std::ops::Mul<T> for #struct_name
        where
            T::U: TryInto<#underlying_type>,
        {
            type Output = #struct_name;

            fn mul(self, rhs: T) -> Self {
                Self::new(
                    self.get()
                        .checked_mul(
                            rhs.get()
                                .try_into()
                                .unwrap_or_else(|_| std::panic!("decimal: rhs value can't fit into `{}` type in {}::mul()", #underlying_str, #name_str))
                        )
                        .unwrap_or_else(|| std::panic!("decimal: overflow in method {}::mul()", #name_str))
                        .checked_div(T::one())
                        .unwrap_or_else(|| std::panic!("decimal: overflow in method {}::mul()", #name_str))
                )
            }
        }

        impl<T: Decimal> std::ops::Div<T> for #struct_name
        where
            T::U: TryInto<#underlying_type>,
        {
            type Output = Self;

            fn div(self, rhs: T) -> Self {
                Self::new(
                    self.get()
                        .checked_mul(T::one())
                        .unwrap_or_else(|| std::panic!("decimal: overflow in method {}::div()", #name_str))
                        .checked_div(
                            rhs.get()
                                .try_into()
                                .unwrap_or_else(|_| std::panic!("decimal: rhs value can't fit into `{}` type in {}::div()", #underlying_str, #name_str))
                        )
                        .unwrap_or_else(|| std::panic!("decimal: overflow in method {}::div()", #name_str))
                )
            }
        }

        impl std::ops::AddAssign for #struct_name {
            fn add_assign(&mut self, rhs: Self)  {
                *self = *self + rhs
            }
        }

        impl std::ops::SubAssign for #struct_name {
            fn sub_assign(&mut self, rhs: Self)  {
                *self = *self - rhs
            }
        }

        impl std::ops::MulAssign for #struct_name {
            fn mul_assign(&mut self, rhs: Self)  {
                *self = *self * rhs
            }
        }

        impl std::ops::DivAssign for #struct_name {
            fn div_assign(&mut self, rhs: Self)  {
                *self = *self / rhs
            }
        }


        #[cfg(test)]
        pub mod #module_name {
            use super::*;

            #[test]
            fn test_add () {
                let mut a = #struct_name::new(1);
                let b = #struct_name::new(1);
                assert_eq!(a + b, #struct_name::new(2));
                a += b;
                assert_eq!(a, #struct_name::new(2));
            }

            #[test]
            fn test_sub () {
                let mut a = #struct_name::new(1);
                let b = #struct_name::new(1);
                assert_eq!(a - b, #struct_name::new(0));
                a -= b;
                assert_eq!(a, #struct_name::new(0));
            }

            #[test]
            fn test_mul () {
                let mut a = #struct_name::new(2);
                let b = #struct_name::new(#struct_name::one());
                assert_eq!(a * b, #struct_name::new(2));
                a *= b;
                assert_eq!(a, #struct_name::new(2));
            }

            #[test]
            fn test_div () {
                let mut a = #struct_name::new(2);
                let b = #struct_name::new(#struct_name::one());
                assert_eq!(a / b, #struct_name::new(2));
                a /= b;
                assert_eq!(a, #struct_name::new(2));
            }
        }
    ))
}