str_enum 0.3.2

Simple declarative macro for an enum with associated strings for each variant
Documentation
use std::{collections::HashMap, hash::DefaultHasher};

use str_enum::str_enum;

#[cfg(not(feature = "strum"))]
str_enum! {
    #[error_type(MyError)]
    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
    #[repr(u8)]
    pub(crate) enum MyEnum {
        Variant1 = 5 => "Variant1"("variant1"),
        Variant2 => "Variant2",
    }
}

#[cfg(feature = "strum")]
str_enum! {
    #[error_type(MyError)]
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    #[repr(u8)]
    pub(crate) enum MyEnum {
        Variant1 = 5 => "Variant1"("variant1"),
        Variant2 => "Variant2",
    }
}

#[test]
fn test_repr() {
    assert_eq!(std::mem::size_of::<MyEnum>(), std::mem::size_of::<u8>());
    assert_eq!(MyEnum::Variant1.into_repr(), 5);
}

#[test]
fn test_from_str_primary() {
    let v1: MyEnum = "Variant1".parse().unwrap();
    let v2: MyEnum = "Variant2".parse().unwrap();
    assert_eq!(v1, MyEnum::Variant1);
    assert_eq!(v2, MyEnum::Variant2);
}

#[test]
fn test_from_str_alternate() {
    let v1: MyEnum = "variant1".parse().unwrap();
    assert_eq!(v1, MyEnum::Variant1);
}

#[test]
fn test_from_str_invalid() {
    let result: Result<MyEnum, _> = "nonexistent".parse();
    assert!(result.is_err());
}

#[test]
fn test_debug() {
    assert_eq!(format!("{:?}", MyEnum::Variant1), "Variant1");
    assert_eq!(format!("{:?}", MyEnum::Variant2), "Variant2");
}

#[test]
fn test_clone() {
    let v1 = MyEnum::Variant1;
    #[allow(clippy::clone_on_copy)]
    let v1_clone = v1.clone();
    assert_eq!(v1, v1_clone);
}

#[test]
fn test_ordering() {
    assert!(MyEnum::Variant1 < MyEnum::Variant2);
    let mut variants = vec![MyEnum::Variant2, MyEnum::Variant1];
    variants.sort();
    assert_eq!(variants, vec![MyEnum::Variant1, MyEnum::Variant2]);
}

#[test]
fn test_all_values_str() {
    assert_eq!(MyEnum::ALL_VALUE_STR, "Variant1,Variant2");
}

#[test]
fn test_error_expected_str() {
    assert_eq!(MyError::EXPECTED_STR, "expected one of [Variant1,Variant2]");
}

#[test]
fn test_display() {
    assert_eq!(format!("{}", MyEnum::Variant1), "Variant1");
    assert_eq!(format!("{}", MyEnum::Variant2), "Variant2");
}

#[test]
fn test_error_display() {
    let err = MyError;
    assert_eq!(format!("{err}"), "expected one of [Variant1,Variant2]");
}

#[test]
fn test_hashmap_str_lookup() {
    let mut map = HashMap::<MyEnum, u32>::new();
    map.insert(MyEnum::Variant1, 1);
    map.insert(MyEnum::Variant2, 2);

    assert_eq!(map.get("Variant1"), Some(&1));
    assert_eq!(map.get("Variant2"), Some(&2));
    assert_eq!(map.get(&MyEnum::Variant1), Some(&1));
    assert_eq!(map.get(&MyEnum::Variant2), Some(&2));
    assert_eq!(map.get("variant1"), None);
}

fn hash_of<T: std::hash::Hash>(t: &T) -> u64 {
    let mut hasher = DefaultHasher::new();
    t.hash(&mut hasher);
    std::hash::Hasher::finish(&hasher)
}

#[test]
fn test_hash_different_variants_differ() {
    assert_ne!(hash_of(&MyEnum::Variant1), hash_of(&MyEnum::Variant2));
}

#[cfg(feature = "serde")]
mod serde {
    use crate::MyEnum;

    #[test]
    fn test_serialize() {
        let v1 = MyEnum::Variant1;
        let v2 = MyEnum::Variant2;
        assert_eq!(serde_json::to_string(&v1).unwrap(), "\"Variant1\"");
        assert_eq!(serde_json::to_string(&v2).unwrap(), "\"Variant2\"");
    }

    #[test]
    fn test_deserialize_primary() {
        let v1: MyEnum = serde_json::from_str("\"Variant1\"").unwrap();
        let v2: MyEnum = serde_json::from_str("\"Variant2\"").unwrap();
        assert_eq!(v1, MyEnum::Variant1);
        assert_eq!(v2, MyEnum::Variant2);
    }

    #[test]
    fn test_deserialize_alternate() {
        let v1: MyEnum = serde_json::from_str("\"variant1\"").unwrap();
        assert_eq!(v1, MyEnum::Variant1);
    }

    #[test]
    fn test_deserialize_invalid() {
        let result: Result<MyEnum, _> = serde_json::from_str("\"nonexistent\"");
        assert!(result.is_err());
    }

    #[test]
    fn test_serde_roundtrip() {
        for variant in MyEnum::ALL_VARIANTS {
            let serialized = serde_json::to_string(variant).unwrap();
            let deserialized: MyEnum = serde_json::from_str(&serialized).unwrap();
            assert_eq!(*variant, deserialized);
        }
    }

    #[test]
    fn test_serde_expected_str() {
        assert_eq!(MyEnum::SERDE_EXPECTED_STR, "one of [Variant1,Variant2]");
    }
}

#[cfg(feature = "strum")]
mod strum {
    use crate::MyEnum;
    use str_enum::strum::{
        EnumCount, IntoEnumIterator, VariantArray, VariantIterator, VariantMetadata, VariantNames,
    };

    #[test]
    fn test_enum_count() {
        assert_eq!(MyEnum::COUNT, 2);
    }

    #[test]
    fn test_into_enum_iterator() {
        let variants: Vec<MyEnum> = <MyEnum as strum::IntoEnumIterator>::iter().collect();
        assert_eq!(variants, vec![MyEnum::Variant1, MyEnum::Variant2]);
    }

    #[test]
    fn test_into_enum_iterator_double_ended() {
        let variants: Vec<MyEnum> = <MyEnum as strum::IntoEnumIterator>::iter().rev().collect();
        assert_eq!(variants, vec![MyEnum::Variant2, MyEnum::Variant1]);
    }

    #[test]
    fn test_into_enum_iterator_exact_size() {
        let iter = <MyEnum as IntoEnumIterator>::iter();
        assert_eq!(iter.len(), 2);
    }

    #[test]
    fn test_variant_iterator() {
        let variants: Vec<MyEnum> = <MyEnum as VariantIterator>::iter().collect();
        assert_eq!(variants, vec![MyEnum::Variant1, MyEnum::Variant2]);
    }

    #[test]
    fn test_variant_array() {
        assert_eq!(
            <MyEnum as VariantArray>::VARIANTS,
            &[MyEnum::Variant1, MyEnum::Variant2]
        );
    }

    #[test]
    fn test_variant_names() {
        assert_eq!(
            <MyEnum as VariantNames>::VARIANTS,
            &["Variant1", "Variant2"]
        );
    }

    #[test]
    fn test_variant_metadata_count() {
        assert_eq!(MyEnum::VARIANT_COUNT, 2);
    }

    #[test]
    fn test_variant_metadata_names() {
        assert_eq!(MyEnum::VARIANT_NAMES, &["Variant1", "Variant2"]);
    }

    #[test]
    fn test_variant_metadata_variant_name() {
        assert_eq!(MyEnum::Variant1.variant_name(), "Variant1");
        assert_eq!(MyEnum::Variant2.variant_name(), "Variant2");
    }

    #[test]
    fn test_into_discriminant() {
        use str_enum::strum::IntoDiscriminant;
        assert_eq!(MyEnum::Variant1.discriminant(), 5u8);
    }
}