hodgepodge 0.4.1

Ready-made enum datasets and optional animal taxonomy for prototyping, teaching, and experimentation
Documentation
use hodgepodge::*;
use std::{fmt::Debug, hash::Hash, str::FromStr};

macro_rules! all_datasets {
    ($check:ident) => {
        $check::<ROYGBIV>();
        $check::<CMYK>();
        $check::<RGB>();
        $check::<CSS>();
        $check::<Unit>();
        $check::<TaxonomicRank>();
        $check::<PrefixLarge>();
        $check::<PrefixSmall>();
        $check::<Planet>();
        $check::<Element>();
        $check::<Direction>();
        $check::<Continent>();
        $check::<EU>();
        $check::<States>();
        $check::<Ocean>();
        $check::<CanadianProvince>();
        $check::<Country>();
        $check::<Day>();
        $check::<Month>();
        $check::<Season>();
        $check::<Quarter>();
        $check::<Suit>();
        $check::<Rank>();
        $check::<DiceFace>();
        $check::<ChessPiece>();
        $check::<BetterThanRust>();
        $check::<Medal>();
        $check::<Ordinal>();
        $check::<CoinSide>();
        $check::<RockPaperScissors>();
        $check::<RoundOutcome>();
        $check::<DnaBase>();
        $check::<AminoAcid>();
        $check::<BodySide>();
        $check::<SkeletalDivision>();
        $check::<BoneRegion>();
        $check::<Bone>();
        $check::<MuscleRegion>();
        $check::<Muscle>();
        $check::<Biome>();
        $check::<RockClass>();
        $check::<Rock>();
        $check::<EarthLayer>();
        $check::<AtmosphereLayer>();
        $check::<GeologicEon>();
        $check::<GeologicEra>();
        $check::<GeologicPeriod>();
        $check::<GeologicEpoch>();
        $check::<InfernoCircle>();
        $check::<Hemisphere>();
        $check::<SiPrefix>();
        $check::<SiBaseQuantity>();
        $check::<SiBaseUnit>();
        $check::<RnaBase>();
        $check::<Codon>();
        $check::<IsoCountry>();
    };
}

#[test]
fn every_dataset_supports_the_default_api() {
    fn check<
        T: Dataset
            + Copy
            + Clone
            + Debug
            + Eq
            + Hash
            + std::fmt::Display
            + FromStr<Err = ParseEnumError>,
    >() {
    }
    all_datasets!(check);
}

#[test]
fn names_are_available_without_optional_features() {
    const SEPTEMBER: &str = Month::September.as_str();
    assert_eq!(SEPTEMBER, "September");
    assert_eq!("sEpTeMbEr".parse::<Month>(), Ok(Month::September));
    assert_eq!(Month::September.to_string(), SEPTEMBER);
    let mut counts = std::collections::HashMap::new();
    counts.insert(Month::September, 3);
    assert_eq!(counts.get(&"september".parse::<Month>().unwrap()), Some(&3));
}

#[test]
fn invalid_names_return_a_typed_error() {
    for input in ["", " September", "September ", "Sep", "9", "Séptember"] {
        let error = input.parse::<Month>().unwrap_err();
        assert_eq!(error.enum_name(), "Month");
        assert_eq!(error.to_string(), "unknown Month variant");
        assert!(std::error::Error::source(&error).is_none());
    }
    assert!("North America".parse::<Continent>().is_err());
    assert_eq!(
        "northamerica".parse::<Continent>(),
        Ok(Continent::NorthAmerica)
    );
}

#[test]
fn every_variant_round_trips_through_its_name() {
    fn check<T>()
    where
        T: Dataset + Debug + Eq + Hash + std::fmt::Display + FromStr<Err = ParseEnumError>,
    {
        let mut seen = std::collections::HashSet::new();
        for &value in T::ALL {
            let name = value.to_string();
            assert_eq!(name, value.as_str());
            assert!(!value.label().is_empty());
            assert_eq!(name.parse::<T>(), Ok(value));
            assert_eq!(name.to_ascii_lowercase().parse::<T>(), Ok(value));
            assert_eq!(name.to_ascii_uppercase().parse::<T>(), Ok(value));
            assert!(seen.insert(value), "duplicate variant: {name}");
        }
        assert_eq!(seen.len(), T::COUNT);
    }
    all_datasets!(check);
}

#[cfg(feature = "serde")]
#[test]
fn every_variant_round_trips_through_json() {
    fn check<T>()
    where
        T: Dataset
            + Debug
            + Eq
            + std::fmt::Display
            + serde::Serialize
            + serde::de::DeserializeOwned,
    {
        for &value in T::ALL {
            let json = serde_json::to_string(&value).unwrap();
            assert_eq!(json, format!("\"{value}\""));
            assert_eq!(serde_json::from_str::<T>(&json).unwrap(), value);
        }
    }
    all_datasets!(check);
}

#[cfg(feature = "strum")]
#[test]
fn strum_metadata_agrees_with_default_metadata() {
    fn check<T: Dataset + IntoEnumIterator + EnumCount + Eq + Debug>() {
        assert_eq!(<T as Dataset>::COUNT, <T as EnumCount>::COUNT);
        assert_eq!(T::iter().collect::<Vec<_>>(), T::ALL);
    }
    all_datasets!(check);
}

#[cfg(feature = "rand")]
#[test]
fn every_dataset_supports_caller_seeded_sampling() {
    use rand::{Rng, SeedableRng};
    use rand_chacha::ChaCha8Rng;

    fn check<T: Dataset + Eq + Debug>()
    where
        rand::distr::StandardUniform: rand::distr::Distribution<T>,
    {
        let mut first = ChaCha8Rng::seed_from_u64(42);
        let mut second = ChaCha8Rng::seed_from_u64(42);
        for _ in 0..100 {
            let value: T = first.random();
            assert!(T::ALL.contains(&value));
            assert_eq!(value, second.random());
        }
    }
    all_datasets!(check);
}

#[test]
fn every_dataset_has_explicit_provenance_and_unique_identity() {
    fn check<T: Dataset>() {
        assert!(!T::INFO.id.is_empty());
        assert!(!T::INFO.scope.is_empty());
        assert!(!T::INFO.license.is_empty());
        assert!(!T::INFO.sources.is_empty());
        assert!(DATASETS.contains(&T::INFO));
        for source in T::INFO.sources {
            assert!(source.url.starts_with("https://"));
        }
    }
    all_datasets!(check);
    let ids: std::collections::HashSet<_> = DATASETS.iter().map(|info| info.id).collect();
    assert_eq!(ids.len(), DATASETS.len());
    assert_eq!(DATASETS.len(), 56 + usize::from(cfg!(feature = "taxonomy")));
    assert_eq!(Country::INFO.coverage, DatasetCoverage::LegacyIllustration);
    assert_eq!(Muscle::INFO.coverage, DatasetCoverage::CuratedSelection);
}