stefans-utils 0.9.0

A collection of useful Rust utility functions, types, and traits.
Documentation
#[macro_export]
macro_rules! literal {
    ($(#[$attr:meta])* $name:ident($type:ty) = $value:expr) => {
        #[derive(::core::fmt::Debug, ::core::clone::Clone, ::serde::Serialize)]
        $(#[$attr])*
        pub struct $name($type);

        impl<'de> ::serde::Deserialize<'de> for $name {
            fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
            where
                D: ::serde::Deserializer<'de>,
            {
                let value = <$type as ::serde::Deserialize<'de>>::deserialize(deserializer)?;

                Self::try_from(value).map_err(::serde::de::Error::custom)
            }
        }

        impl<T: PartialEq<$type>> PartialEq<T> for $name {
            fn eq(&self, other: &T) -> bool {
                other.eq(&self.0)
            }
        }

        impl ::schemars::JsonSchema for $name {
            fn schema_name() -> ::std::borrow::Cow<'static, str> {
                stringify!($name).into()
            }

            fn json_schema(_: &mut ::schemars::SchemaGenerator) -> ::schemars::Schema {
                ::schemars::json_schema!({ "const": $value })
            }
        }

        impl ::std::fmt::Display for $name {
            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                f.write_fmt(format_args!("{}", &self.0))
            }
        }

        impl AsRef<$type> for $name {
            fn as_ref(&self) -> &$type {
                &self.0
            }
        }

        impl TryFrom<$type> for $name {
            type Error = String;

            fn try_from(value: $type) -> ::std::result::Result<Self, Self::Error> {
                if value == $value {
                    Ok(Self(value))
                } else {
                    Err(format!("Value must be exactly {:?}", $value))
                }
            }
        }

        impl From<$name> for $type {
            fn from(value: $name) -> Self {
                value.0
            }
        }
    };
}

#[macro_export]
macro_rules! literal_string {
    ($name:ident = $value:expr) => {
        $crate::literal!($name(String) = $value);

        impl Default for $name {
            fn default() -> Self {
                Self($value.to_string())
            }
        }
    };
}

macro_rules! literal_scalars {
    ($d:tt $($type:ident)*) => {
        $(
            ::paste::paste! {
                #[macro_export]
                macro_rules! [<literal_ $type>] {
                    ($d name:ident = $d value:expr) => {
                        $crate::literal!($d name($type) = $d value);

                        impl Default for $d name {
                            fn default() -> Self {
                                Self($d value)
                            }
                        }
                    };
                }
            }
        )*
    };
}

literal_scalars!($ u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize f32 f64 bool);

#[cfg(test)]
mod tests {
    use schemars::{json_schema, schema_for};
    use serde_json::json;

    crate::literal_string!(Xbox = "xbox");

    #[test]
    fn literal_string_should_deserialize_ok_from_correct_value() {
        let ok = serde_json::from_value::<Xbox>(json!("xbox")).unwrap();

        assert_eq!(ok, "xbox");
    }

    #[test]
    fn literal_string_should_deserialize_err_from_correct_type_but_wrong_value() {
        let err = serde_json::from_value::<Xbox>(json!("ps4")).unwrap_err();

        assert_eq!(err.to_string(), "Value must be exactly \"xbox\"");
    }

    #[test]
    fn literal_string_should_produce_correct_json_schema() {
        let schema = schema_for!(Xbox);

        assert_eq!(
            schema,
            json_schema!({
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "title": "Xbox",
                "const": "xbox"
            })
        );
    }

    literal_u32!(OneTwoThree = 123);

    #[test]
    fn literal_u32_should_deserialize_ok_from_correct_value() {
        let ok = serde_json::from_value::<OneTwoThree>(json!(123)).unwrap();

        assert_eq!(ok, 123);
    }

    #[test]
    fn literal_u32_should_deserialize_err_from_correct_type_but_wrong_value() {
        let err = serde_json::from_value::<OneTwoThree>(json!(321)).unwrap_err();

        assert_eq!(err.to_string(), "Value must be exactly 123");
    }

    #[test]
    fn literal_u32_should_produce_correct_json_schema() {
        let schema = schema_for!(OneTwoThree);

        assert_eq!(
            schema,
            json_schema!({
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "title": "OneTwoThree",
                "const": 123
            })
        );
    }
}