From 81e51cc22a87258936cb842e28c1be4c3cefd74c Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Tue, 9 Dec 2025 09:47:32 -0800 Subject: [PATCH 1/8] Move `LooseBool` into an example. --- .github/workflows/rust.yml | 5 + Cargo.lock | 489 ------------------------------------- Cargo.toml | 11 +- examples/loose_bool.rs | 105 ++++++++ src/lib.rs | 6 - src/loose_bool.rs | 142 ----------- 6 files changed, 115 insertions(+), 643 deletions(-) create mode 100644 examples/loose_bool.rs delete mode 100644 src/loose_bool.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ae3ae42..807fbad 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -26,3 +26,8 @@ jobs: run: cargo build --verbose --all-features - name: Run tests (All features) run: cargo test --verbose --all-features + + - name: Build (Examples) + run: cargo build --verbose --all-features --examples + - name: Run tests (Examples) + run: cargo test --verbose --all-features --examples \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 296f01a..11ad85a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,305 +2,13 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "assert_type_match" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "bevy_macro_utils" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d984f9f8bd0f0d9fb020492a955e641e30e7a425f3588bf346cb3e61fec3c3" -dependencies = [ - "parking_lot", - "proc-macro2", - "quote", - "syn", - "toml_edit", -] - -[[package]] -name = "bevy_platform" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4691af6d7cfd1b5deb2fc926a43a180a546cdc3fe1e5a013fcee60db9bb2c81f" -dependencies = [ - "foldhash", - "futures-channel", - "hashbrown", - "portable-atomic", - "portable-atomic-util", - "serde", - "spin", -] - -[[package]] -name = "bevy_ptr" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d24d7906c7de556033168b3485de36c59049fbaef0c2c44c715a23e0329b10" - -[[package]] -name = "bevy_reflect" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5472b91928c0f3e4e3988c0d036b00719f19520f53a0c3f8c2af72f00e693c5" -dependencies = [ - "assert_type_match", - "bevy_platform", - "bevy_ptr", - "bevy_reflect_derive", - "bevy_utils", - "derive_more", - "disqualified", - "downcast-rs", - "erased-serde", - "foldhash", - "serde", - "thiserror", - "variadics_please", -] - -[[package]] -name = "bevy_reflect_derive" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "083784255162fa39960aa3cf3c23af0e515db2daa7f2e796ae34df993f4d3f6c" -dependencies = [ - "bevy_macro_utils", - "indexmap", - "proc-macro2", - "quote", - "syn", - "uuid", -] - -[[package]] -name = "bevy_utils" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "789d04f88c764877a4552e07745b402dbc45f5d0545e6d102558f2f1752a1d89" -dependencies = [ - "bevy_platform", - "disqualified", -] - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "derive_more" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "disqualified" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" - -[[package]] -name = "downcast-rs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "equivalent", - "serde", - "serde_core", -] - -[[package]] -name = "indexmap" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "js-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.178" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "loose_enum" version = "0.1.0-beta.1" dependencies = [ - "bevy_reflect", - "num-traits", "serde_core", ] -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - [[package]] name = "proc-macro2" version = "1.0.103" @@ -319,51 +27,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -384,21 +47,6 @@ dependencies = [ "syn", ] -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" -dependencies = [ - "portable-atomic", -] - [[package]] name = "syn" version = "2.0.111" @@ -410,145 +58,8 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml_datetime" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.23.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" -dependencies = [ - "winnow", -] - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "uuid" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "variadics_please" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] diff --git a/Cargo.toml b/Cargo.toml index de18ae9..37c7383 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,16 +10,15 @@ license = "MIT OR Apache-2.0" authors = ["AlephCubed"] [features] -default = ["loose_bool", "std"] +default = ["std"] std = [] -loose_bool = ["num-traits"] - serde = ["dep:serde_core"] -bevy_reflect = ["dep:bevy_reflect", "loose_bool"] [dependencies] -bevy_reflect = { version = "0.17.3", optional = true, default-features = false } -num-traits = { version = "0.2", optional = true, default-features = false } serde_core = { version = "1.0", optional = true, default-features = false, features = [ "std", ] } + +[[example]] +name = "loose_bool" +crate-type = ["lib"] diff --git a/examples/loose_bool.rs b/examples/loose_bool.rs new file mode 100644 index 0000000..5ef772a --- /dev/null +++ b/examples/loose_bool.rs @@ -0,0 +1,105 @@ +use core::error::Error; +use core::fmt::{Display, Formatter}; +use loose_enum::loose_enum; + +loose_enum! { + /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as `Unknown`. + #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] + pub enum LooseBool: i32 { + #[default] + False = 0, + True = 1, + } +} + +impl LooseBool { + pub fn is_true(&self) -> bool { + matches!(self, Self::True) + } + + pub fn is_false(&self) -> bool { + matches!(self, Self::False) + } +} + +impl From for LooseBool { + fn from(value: bool) -> Self { + match value { + true => Self::True, + false => Self::False, + } + } +} + +impl TryFrom for bool { + type Error = UnknownBoolError; + + fn try_from(value: LooseBool) -> Result { + match value { + LooseBool::False => Ok(false), + LooseBool::True => Ok(true), + LooseBool::Unknown(_) => Err(UnknownBoolError), + } + } +} + +/// Error returned when attempting to convert a [`LooseBool::Unknown`] into a `bool`. +#[derive(Debug, Eq, PartialEq, Hash)] +pub struct UnknownBoolError; + +impl Display for UnknownBoolError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "Cannot convert `LooseBool::Unknown` into `bool`.") + } +} + +impl Error for UnknownBoolError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn int_to_loose() { + assert_eq!(LooseBool::from(0), LooseBool::False); + assert_eq!(LooseBool::from(1), LooseBool::True); + + for i in 2..256 { + assert_eq!( + LooseBool::from(i), + LooseBool::Unknown(i), + "Failed for i={i}" + ); + } + } + + #[test] + fn loose_to_int() { + assert_eq!(i32::from(LooseBool::False), 0); + assert_eq!(i32::from(LooseBool::True), 1); + + for i in 2..256 { + assert_eq!(i32::from(LooseBool::Unknown(i)), i, "Failed for i={i}"); + } + } + + #[test] + fn loose_to_bool() { + assert_eq!(bool::try_from(LooseBool::True), Ok(true)); + assert_eq!(bool::try_from(LooseBool::False), Ok(false)); + + for i in 2..256 { + assert_eq!( + bool::try_from(LooseBool::Unknown(i)), + Err(UnknownBoolError), + "Failed for i={i}" + ); + } + } + + #[test] + fn bool_to_loose() { + assert_eq!(LooseBool::from(false), LooseBool::False); + assert_eq!(LooseBool::from(true), LooseBool::True); + } +} diff --git a/src/lib.rs b/src/lib.rs index 44ae481..d54350e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(feature = "loose_bool")] -mod loose_bool; - -#[cfg(feature = "loose_bool")] -pub use loose_bool::LooseBool; - #[doc(hidden)] #[cfg(feature = "serde")] pub use serde_core as __serde; diff --git a/src/loose_bool.rs b/src/loose_bool.rs deleted file mode 100644 index 53a10c6..0000000 --- a/src/loose_bool.rs +++ /dev/null @@ -1,142 +0,0 @@ -use crate::loose_enum; -use core::error::Error; -use core::fmt::{Display, Formatter}; -use num_traits::{ConstOne, ConstZero, PrimInt}; - -loose_enum! { - /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as `Unknown`. - #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] - #[cfg_attr( - feature = "bevy_reflect", - derive(bevy_reflect::Reflect), - )] - pub enum LooseBool { - #[default] - False = T::ZERO, - True = T::ONE, - } -} - -impl LooseBool { - pub fn is_true(&self) -> bool { - matches!(self, Self::True) - } - - pub fn is_false(&self) -> bool { - matches!(self, Self::False) - } - - // Todo Orphan rule forbids `From` impl. - pub fn from_bool(value: bool) -> Self { - match value { - true => Self::True, - false => Self::False, - } - } -} - -impl TryFrom> for bool { - type Error = UnknownBoolError; - - fn try_from(value: LooseBool) -> Result { - match value { - LooseBool::False => Ok(false), - LooseBool::True => Ok(true), - LooseBool::Unknown(_) => Err(UnknownBoolError), - } - } -} - -/// Error returned when attempting to convert a [`LooseBool::Unknown`] into a `bool`. -#[derive(Debug, Eq, PartialEq, Hash)] -pub struct UnknownBoolError; - -impl Display for UnknownBoolError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "Cannot convert `LooseBool::Unknown` into `bool`.") - } -} - -impl Error for UnknownBoolError {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn u8_to_loose() { - assert_eq!(LooseBool::from(0u8), LooseBool::False); - assert_eq!(LooseBool::from(1u8), LooseBool::True); - - for i in 2..u8::MAX { - assert_eq!( - LooseBool::from(i), - LooseBool::Unknown(i), - "Failed for i={i}" - ); - } - } - - #[test] - fn i8_to_loose() { - assert_eq!(LooseBool::from(0i8), LooseBool::False); - assert_eq!(LooseBool::from(1i8), LooseBool::True); - - for i in 2..i8::MAX { - assert_eq!( - LooseBool::from(i), - LooseBool::Unknown(i), - "Failed for i={i}" - ); - } - - for i in i8::MIN..0 { - assert_eq!( - LooseBool::from(i), - LooseBool::Unknown(i), - "Failed for i={i}" - ); - } - } - - #[test] - fn loose_to_u8() { - assert_eq!(LooseBool::::False.to_repr(), 0); - assert_eq!(LooseBool::::True.to_repr(), 1); - - for i in 2..u8::MAX { - assert_eq!(LooseBool::::Unknown(i).to_repr(), i, "Failed for i={i}"); - } - } - - #[test] - fn loose_to_i8() { - assert_eq!(LooseBool::::False.to_repr(), 0); - assert_eq!(LooseBool::::True.to_repr(), 1); - - for i in 2..i8::MAX { - assert_eq!(LooseBool::::Unknown(i).to_repr(), i, "Failed for i={i}"); - } - - for i in i8::MIN..0 { - assert_eq!(LooseBool::::Unknown(i).to_repr(), i, "Failed for i={i}"); - } - } - - #[test] - fn loose_to_bool() { - assert_eq!(LooseBool::::True.try_into(), Ok(true)); - assert_eq!(LooseBool::::False.try_into(), Ok(false)); - - for i in 2..u8::MAX { - let b: Result = LooseBool::::Unknown(i as i32).try_into(); - assert_eq!(b, Err(UnknownBoolError), "Failed for i={i}"); - } - } - - #[test] - fn bool_to_loose() { - assert_eq!(LooseBool::::from_bool(false), LooseBool::False); - assert_eq!(LooseBool::::from_bool(true), LooseBool::True); - } -} From 0eb3391b7eb2d7f2fc0225324c3d7ae4aeef901f Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Tue, 9 Dec 2025 09:53:50 -0800 Subject: [PATCH 2/8] Remove non-specific tests in example. --- examples/loose_bool.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/examples/loose_bool.rs b/examples/loose_bool.rs index 5ef772a..c74dcf6 100644 --- a/examples/loose_bool.rs +++ b/examples/loose_bool.rs @@ -59,30 +59,6 @@ impl Error for UnknownBoolError {} mod tests { use super::*; - #[test] - fn int_to_loose() { - assert_eq!(LooseBool::from(0), LooseBool::False); - assert_eq!(LooseBool::from(1), LooseBool::True); - - for i in 2..256 { - assert_eq!( - LooseBool::from(i), - LooseBool::Unknown(i), - "Failed for i={i}" - ); - } - } - - #[test] - fn loose_to_int() { - assert_eq!(i32::from(LooseBool::False), 0); - assert_eq!(i32::from(LooseBool::True), 1); - - for i in 2..256 { - assert_eq!(i32::from(LooseBool::Unknown(i)), i, "Failed for i={i}"); - } - } - #[test] fn loose_to_bool() { assert_eq!(bool::try_from(LooseBool::True), Ok(true)); From 25e68faa69ee91b96908f0e8531ec447499ff710 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Tue, 9 Dec 2025 10:07:33 -0800 Subject: [PATCH 3/8] Add generic example. --- Cargo.lock | 16 +++++++ Cargo.toml | 7 +++ examples/loose_bool.rs | 7 ++- examples/loose_bool_generic.rs | 83 ++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 examples/loose_bool_generic.rs diff --git a/Cargo.lock b/Cargo.lock index 11ad85a..2d81396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,13 +2,29 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "loose_enum" version = "0.1.0-beta.1" dependencies = [ + "num-traits", "serde_core", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "proc-macro2" version = "1.0.103" diff --git a/Cargo.toml b/Cargo.toml index 37c7383..eef5774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,13 @@ serde_core = { version = "1.0", optional = true, default-features = false, featu "std", ] } +[dev-dependencies] +num-traits = { version = "0.2", default-features = false } + [[example]] name = "loose_bool" crate-type = ["lib"] + +[[example]] +name = "loose_bool_generic" +crate-type = ["lib"] diff --git a/examples/loose_bool.rs b/examples/loose_bool.rs index c74dcf6..f03d0f0 100644 --- a/examples/loose_bool.rs +++ b/examples/loose_bool.rs @@ -1,9 +1,14 @@ +//! This example creates a repr bool with 0 as False, 1 aa True, and anything else as Unknown. +//! Also included are `From`/`TryFrom` implementations to convert to/from a standard bool. +//! +//! A generic version of this example is also available in `loose_bool_generic.rs`. + use core::error::Error; use core::fmt::{Display, Formatter}; use loose_enum::loose_enum; loose_enum! { - /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as `Unknown`. + /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Unknown. #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum LooseBool: i32 { #[default] diff --git a/examples/loose_bool_generic.rs b/examples/loose_bool_generic.rs new file mode 100644 index 0000000..80f8811 --- /dev/null +++ b/examples/loose_bool_generic.rs @@ -0,0 +1,83 @@ +//! This example creates a repr bool with 0 as False, 1 aa True, and anything else as Unknown. +//! Also included are `From`/`TryFrom` implementations to convert to/from a standard bool. +//! +//! A non-generic version of this example is also available in `loose_bool.rs`. + +use core::error::Error; +use core::fmt::{Display, Formatter}; +use loose_enum::loose_enum; +use num_traits::{ConstOne, ConstZero, PrimInt}; + +loose_enum! { + /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Unknown. + #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] + pub enum LooseBool { + #[default] + False = T::ZERO, + True = T::ONE, + } +} + +impl LooseBool { + pub fn is_true(&self) -> bool { + matches!(self, Self::True) + } + + pub fn is_false(&self) -> bool { + matches!(self, Self::False) + } + + // Orphan rule forbids `From` implementation, so we create our own method. + pub fn from_bool(value: bool) -> Self { + match value { + true => Self::True, + false => Self::False, + } + } +} + +impl TryFrom> for bool { + type Error = UnknownBoolError; + + fn try_from(value: LooseBool) -> Result { + match value { + LooseBool::False => Ok(false), + LooseBool::True => Ok(true), + LooseBool::Unknown(_) => Err(UnknownBoolError), + } + } +} + +/// Error returned when attempting to convert a [`LooseBool::Unknown`] into a `bool`. +#[derive(Debug, Eq, PartialEq, Hash)] +pub struct UnknownBoolError; + +impl Display for UnknownBoolError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "Cannot convert `LooseBool::Unknown` into `bool`.") + } +} + +impl Error for UnknownBoolError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn loose_to_bool() { + assert_eq!(LooseBool::::True.try_into(), Ok(true)); + assert_eq!(LooseBool::::False.try_into(), Ok(false)); + + for i in 2..u8::MAX { + let b: Result = LooseBool::Unknown(i).try_into(); + assert_eq!(b, Err(UnknownBoolError), "Failed for i={i}"); + } + } + + #[test] + fn bool_to_loose() { + assert_eq!(LooseBool::::from_bool(false), LooseBool::False); + assert_eq!(LooseBool::::from_bool(true), LooseBool::True); + } +} From 22f5dc18ecaaee0d42839fa038fd2b57513cddad Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Tue, 9 Dec 2025 10:16:38 -0800 Subject: [PATCH 4/8] Started on documentation. --- Cargo.toml | 10 ++++++++++ examples/loose_bool.rs | 4 ++++ examples/loose_bool_generic.rs | 6 +++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index eef5774..f334b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ repository = "https://github.com/AlephCubed/loose_enum" license = "MIT OR Apache-2.0" authors = ["AlephCubed"] +[package.metadata.docs.rs] +all-features = true + [features] default = ["std"] std = [] @@ -22,6 +25,13 @@ serde_core = { version = "1.0", optional = true, default-features = false, featu [dev-dependencies] num-traits = { version = "0.2", default-features = false } +[lints.rust] +missing_docs = "warn" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } +unsafe_code = "deny" +unsafe_op_in_unsafe_fn = "warn" +unused_qualifications = "warn" + [[example]] name = "loose_bool" crate-type = ["lib"] diff --git a/examples/loose_bool.rs b/examples/loose_bool.rs index f03d0f0..f343157 100644 --- a/examples/loose_bool.rs +++ b/examples/loose_bool.rs @@ -11,17 +11,21 @@ loose_enum! { /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Unknown. #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum LooseBool: i32 { + /// A falsy value of zero. #[default] False = 0, + /// A truthy value of one. True = 1, } } impl LooseBool { + /// Returns true if the value is [`True`](Self::True). pub fn is_true(&self) -> bool { matches!(self, Self::True) } + /// Returns true if the value is [`False`](Self::False). pub fn is_false(&self) -> bool { matches!(self, Self::False) } diff --git a/examples/loose_bool_generic.rs b/examples/loose_bool_generic.rs index 80f8811..59c8db9 100644 --- a/examples/loose_bool_generic.rs +++ b/examples/loose_bool_generic.rs @@ -12,22 +12,26 @@ loose_enum! { /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Unknown. #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum LooseBool { + /// A falsy value of zero. #[default] False = T::ZERO, + /// A truthy value of one. True = T::ONE, } } impl LooseBool { + /// Returns true if the value is [`True`](Self::True). pub fn is_true(&self) -> bool { matches!(self, Self::True) } + /// Returns true if the value is [`False`](Self::False). pub fn is_false(&self) -> bool { matches!(self, Self::False) } - // Orphan rule forbids `From` implementation, so we create our own method. + /// Orphan rule forbids `From` implementation, so we create our own method. pub fn from_bool(value: bool) -> Self { match value { true => Self::True, From 19cc6ed582ec7f1d2f0f5a808c6aad07b7748d58 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Tue, 9 Dec 2025 10:31:29 -0800 Subject: [PATCH 5/8] Rename `Unknown` to `Undefined` and added some more docs. --- examples/loose_bool.rs | 22 +++++----- examples/loose_bool_generic.rs | 22 +++++----- src/lib.rs | 78 +++++++++++++++++++--------------- 3 files changed, 66 insertions(+), 56 deletions(-) diff --git a/examples/loose_bool.rs b/examples/loose_bool.rs index f343157..0a6c5ce 100644 --- a/examples/loose_bool.rs +++ b/examples/loose_bool.rs @@ -1,4 +1,4 @@ -//! This example creates a repr bool with 0 as False, 1 aa True, and anything else as Unknown. +//! This example creates a repr bool with 0 as False, 1 aa True, and anything else as Undefined. //! Also included are `From`/`TryFrom` implementations to convert to/from a standard bool. //! //! A generic version of this example is also available in `loose_bool_generic.rs`. @@ -8,7 +8,7 @@ use core::fmt::{Display, Formatter}; use loose_enum::loose_enum; loose_enum! { - /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Unknown. + /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Undefined. #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum LooseBool: i32 { /// A falsy value of zero. @@ -41,28 +41,28 @@ impl From for LooseBool { } impl TryFrom for bool { - type Error = UnknownBoolError; + type Error = UndefinedBoolError; fn try_from(value: LooseBool) -> Result { match value { LooseBool::False => Ok(false), LooseBool::True => Ok(true), - LooseBool::Unknown(_) => Err(UnknownBoolError), + LooseBool::Undefined(_) => Err(UndefinedBoolError), } } } -/// Error returned when attempting to convert a [`LooseBool::Unknown`] into a `bool`. +/// Error returned when attempting to convert a [`LooseBool::Undefined`] into a `bool`. #[derive(Debug, Eq, PartialEq, Hash)] -pub struct UnknownBoolError; +pub struct UndefinedBoolError; -impl Display for UnknownBoolError { +impl Display for UndefinedBoolError { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "Cannot convert `LooseBool::Unknown` into `bool`.") + write!(f, "Cannot convert `LooseBool::Undefined` into `bool`.") } } -impl Error for UnknownBoolError {} +impl Error for UndefinedBoolError {} #[cfg(test)] mod tests { @@ -75,8 +75,8 @@ mod tests { for i in 2..256 { assert_eq!( - bool::try_from(LooseBool::Unknown(i)), - Err(UnknownBoolError), + bool::try_from(LooseBool::Undefined(i)), + Err(UndefinedBoolError), "Failed for i={i}" ); } diff --git a/examples/loose_bool_generic.rs b/examples/loose_bool_generic.rs index 59c8db9..22d80e8 100644 --- a/examples/loose_bool_generic.rs +++ b/examples/loose_bool_generic.rs @@ -1,4 +1,4 @@ -//! This example creates a repr bool with 0 as False, 1 aa True, and anything else as Unknown. +//! This example creates a repr bool with 0 as False, 1 aa True, and anything else as Undefined. //! Also included are `From`/`TryFrom` implementations to convert to/from a standard bool. //! //! A non-generic version of this example is also available in `loose_bool.rs`. @@ -9,7 +9,7 @@ use loose_enum::loose_enum; use num_traits::{ConstOne, ConstZero, PrimInt}; loose_enum! { - /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Unknown. + /// An integer repr bool, with 0 being false and 1 being true. Any other value will be saved as Undefined. #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum LooseBool { /// A falsy value of zero. @@ -41,28 +41,28 @@ impl LooseBool { } impl TryFrom> for bool { - type Error = UnknownBoolError; + type Error = UndefinedBoolError; fn try_from(value: LooseBool) -> Result { match value { LooseBool::False => Ok(false), LooseBool::True => Ok(true), - LooseBool::Unknown(_) => Err(UnknownBoolError), + LooseBool::Undefined(_) => Err(UndefinedBoolError), } } } -/// Error returned when attempting to convert a [`LooseBool::Unknown`] into a `bool`. +/// Error returned when attempting to convert a [`LooseBool::Undefined`] into a `bool`. #[derive(Debug, Eq, PartialEq, Hash)] -pub struct UnknownBoolError; +pub struct UndefinedBoolError; -impl Display for UnknownBoolError { +impl Display for UndefinedBoolError { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "Cannot convert `LooseBool::Unknown` into `bool`.") + write!(f, "Cannot convert `LooseBool::Undefined` into `bool`.") } } -impl Error for UnknownBoolError {} +impl Error for UndefinedBoolError {} #[cfg(test)] mod tests { @@ -74,8 +74,8 @@ mod tests { assert_eq!(LooseBool::::False.try_into(), Ok(false)); for i in 2..u8::MAX { - let b: Result = LooseBool::Unknown(i).try_into(); - assert_eq!(b, Err(UnknownBoolError), "Failed for i={i}"); + let b: Result = LooseBool::Undefined(i).try_into(); + assert_eq!(b, Err(UndefinedBoolError), "Failed for i={i}"); } } diff --git a/src/lib.rs b/src/lib.rs index d54350e..f75e7b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ #[cfg(feature = "serde")] pub use serde_core as __serde; -/// Defines a repr enum that supports any value. If a value does not match any case, it will be parsed as `Unknown`. +/// Defines a repr enum that supports any value. If a value does not match any case, it will be parsed as `Undefined`. #[cfg(not(feature = "serde"))] #[macro_export] macro_rules! loose_enum { @@ -25,14 +25,15 @@ macro_rules! loose_enum { $(#[$meta])* $variant ),+, - Unknown(String), + /// Any value that doesn't match another case. + Undefined(String), } impl From for $name { fn from(value: String) -> Self { match value.as_str() { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other.to_string()), + other => $name::Undefined(other.to_string()), } } } @@ -41,7 +42,7 @@ macro_rules! loose_enum { fn from(value: &'a str) -> Self { match value { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other.to_string()), + other => $name::Undefined(other.to_string()), } } } @@ -50,7 +51,7 @@ macro_rules! loose_enum { fn from(value: $name) -> Self { match value { $( $name::$variant => $value.to_string(), )+ - $name::Unknown(val) => val, + $name::Undefined(val) => val, } } } @@ -75,14 +76,15 @@ macro_rules! loose_enum { $(#[$meta])* $variant ),+, - Unknown($ty), + /// Any value that doesn't match another case. + Undefined($ty), } impl From<$ty> for $name { fn from(value: $ty) -> Self { match value { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other), + other => $name::Undefined(other), } } } @@ -91,7 +93,7 @@ macro_rules! loose_enum { fn from(value: $name) -> Self { match value { $( $name::$variant => $value, )+ - $name::Unknown(val) => val, + $name::Undefined(val) => val, } } } @@ -116,31 +118,34 @@ macro_rules! loose_enum { $(#[$meta])* $variant ),+, - Unknown($ty), + /// Any value that doesn't match another case. + Undefined($ty), } impl<$ty$(: $first_bound $(+ $other_bounds)+)?> From<$ty> for $name<$ty> { fn from(value: $ty) -> Self { match value { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other), + other => $name::Undefined(other), } } } // Todo Orphan rule forbids `From` impl. impl<$ty$(: $first_bound $(+ $other_bounds)+)?> $name<$ty> { + /// Converts the case into its representation. + /// Orphan rule forbids `From` implementation, so we create our own method. pub fn to_repr(self) -> $ty { match self { $( $name::$variant => $value, )+ - $name::Unknown(val) => val, + $name::Undefined(val) => val, } } } }; } -/// Defines a repr enum that supports any value. If a value does not match any case, it will be parsed as `Unknown`. +/// Defines a repr enum that supports any value. If a value does not match any case, it will be parsed as `Undefined`. #[cfg(feature = "serde")] #[macro_export] macro_rules! loose_enum { @@ -161,14 +166,15 @@ macro_rules! loose_enum { $(#[$meta])* $variant ),+, - Unknown(String), + /// Any value that doesn't match another case. + Undefined(String), } impl From for $name { fn from(value: String) -> Self { match value.as_str() { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other.to_string()), + other => $name::Undefined(other.to_string()), } } } @@ -177,7 +183,7 @@ macro_rules! loose_enum { fn from(value: &'a str) -> Self { match value { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other.to_string()), + other => $name::Undefined(other.to_string()), } } } @@ -186,7 +192,7 @@ macro_rules! loose_enum { fn from(value: $name) -> Self { match value { $( $name::$variant => $value.to_string(), )+ - $name::Unknown(val) => val, + $name::Undefined(val) => val, } } } @@ -199,7 +205,7 @@ macro_rules! loose_enum { let val = String::deserialize(deserializer)?; Ok(match val.as_str() { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other.to_string()), + other => $name::Undefined(other.to_string()), }) } } @@ -211,7 +217,7 @@ macro_rules! loose_enum { { match self { $( $name::$variant => str::serialize($value, serializer), )+ - $name::Unknown(val) => str::serialize(val, serializer), + $name::Undefined(val) => str::serialize(val, serializer), } } } @@ -236,14 +242,15 @@ macro_rules! loose_enum { $(#[$meta])* $variant ),+, - Unknown($ty), + /// Any value that doesn't match another case. + Undefined($ty), } impl From<$ty> for $name { fn from(value: $ty) -> Self { match value { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other), + other => $name::Undefined(other), } } } @@ -252,7 +259,7 @@ macro_rules! loose_enum { fn from(value: $name) -> Self { match value { $( $name::$variant => $value, )+ - $name::Unknown(val) => val, + $name::Undefined(val) => val, } } } @@ -265,7 +272,7 @@ macro_rules! loose_enum { let val = $ty::deserialize(deserializer)?; Ok(match val { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other), + other => $name::Undefined(other), }) } } @@ -277,7 +284,7 @@ macro_rules! loose_enum { { match self { $( $name::$variant => $ty::serialize(&$value, serializer), )+ - $name::Unknown(val) => $ty::serialize(val, serializer), + $name::Undefined(val) => $ty::serialize(val, serializer), } } } @@ -302,24 +309,27 @@ macro_rules! loose_enum { $(#[$meta])* $variant ),+, - Unknown($ty), + /// Any value that doesn't match another case. + Undefined($ty), } impl<$ty$(: $first_bound $(+ $other_bounds)+)?> From<$ty> for $name<$ty> { fn from(value: $ty) -> Self { match value { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other), + other => $name::Undefined(other), } } } // Todo Orphan rule forbids `From` impl. impl<$ty$(: $first_bound $(+ $other_bounds)+)?> $name<$ty> { + /// Converts the case into its representation. + /// Orphan rule forbids `From` implementation, so we create our own method. pub fn to_repr(self) -> $ty { match self { $( $name::$variant => $value, )+ - $name::Unknown(val) => val, + $name::Undefined(val) => val, } } } @@ -332,7 +342,7 @@ macro_rules! loose_enum { let val = $ty::deserialize(deserializer)?; Ok(match val { $( c if c == $value => $name::$variant, )+ - other => $name::Unknown(other), + other => $name::Undefined(other), }) } } @@ -344,7 +354,7 @@ macro_rules! loose_enum { { match self { $( $name::$variant => $ty::serialize(&$value, serializer), )+ - $name::Unknown(val) => $ty::serialize(val, serializer), + $name::Undefined(val) => $ty::serialize(val, serializer), } } } @@ -389,7 +399,7 @@ mod tests { assert_eq!( StringEnum::from("Other"), - StringEnum::Unknown("Other".to_string()) + StringEnum::Undefined("Other".to_string()) ); } @@ -401,7 +411,7 @@ mod tests { assert_eq!(String::from(StringEnum::None), "".to_string()); assert_eq!( - String::from(StringEnum::Unknown("Other".to_string())), + String::from(StringEnum::Undefined("Other".to_string())), "Other".to_string() ); } @@ -410,14 +420,14 @@ mod tests { fn int_to_enum() { assert_eq!(IntEnum::from(0), IntEnum::Zero); - assert_eq!(IntEnum::from(123), IntEnum::Unknown(123)); + assert_eq!(IntEnum::from(123), IntEnum::Undefined(123)); } #[test] fn enum_to_int() { assert_eq!(u8::from(IntEnum::Zero), 0); - assert_eq!(u8::from(IntEnum::Unknown(123)), 123); + assert_eq!(u8::from(IntEnum::Undefined(123)), 123); } #[test] @@ -425,7 +435,7 @@ mod tests { assert_eq!(FloatEnum::from(0.0), FloatEnum::Default); assert_eq!(FloatEnum::from(3.14), FloatEnum::Pi); - assert_eq!(FloatEnum::from(123.0), FloatEnum::Unknown(123.0)); + assert_eq!(FloatEnum::from(123.0), FloatEnum::Undefined(123.0)); } #[test] @@ -433,6 +443,6 @@ mod tests { assert_eq!(f32::from(FloatEnum::Default), 0.0); assert_eq!(f32::from(FloatEnum::Pi), 3.14); - assert_eq!(f32::from(FloatEnum::Unknown(123.0)), 123.0); + assert_eq!(f32::from(FloatEnum::Undefined(123.0)), 123.0); } } From 18607d7093cef893e215bfe498d7a3a5093b144c Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 11 Dec 2025 11:34:44 -0800 Subject: [PATCH 6/8] Abstract into multiple macros, slightly reducing duplication. --- src/__internal.rs | 275 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 334 +++++++++++----------------------------------- 2 files changed, 350 insertions(+), 259 deletions(-) create mode 100644 src/__internal.rs diff --git a/src/__internal.rs b/src/__internal.rs new file mode 100644 index 0000000..28e1890 --- /dev/null +++ b/src/__internal.rs @@ -0,0 +1,275 @@ +#[macro_export] +macro_rules! loose_enum_type { + // All types (including String): + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident: $ty:ident { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + $(#[$outer])* + $vis enum $name { + $( + $(#[$meta])* + $variant + ),+, + /// Any value that doesn't match another case. + Undefined($ty), + } + }; + + + + // Generic: + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident<$ty:ident $( : $first_bound:tt $(+ $other_bounds:tt)* )?> { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + $(#[$outer])* + $vis enum $name<$ty$(: $first_bound $(+ $other_bounds)+)?> { + $( + $(#[$meta])* + $variant + ),+, + /// Any value that doesn't match another case. + Undefined($ty), + } + }; +} + +#[macro_export] +macro_rules! loose_enum_impl { + // Special case for strings: + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident: String { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + impl From for $name { + fn from(value: String) -> Self { + match value.as_str() { + $( c if c == $value => $name::$variant, )+ + other => $name::Undefined(other.to_string()), + } + } + } + + impl<'a> From<&'a str> for $name { + fn from(value: &'a str) -> Self { + match value { + $( c if c == $value => $name::$variant, )+ + other => $name::Undefined(other.to_string()), + } + } + } + + impl From<$name> for String { + fn from(value: $name) -> Self { + match value { + $( $name::$variant => $value.to_string(), )+ + $name::Undefined(val) => val, + } + } + } + }; + + + + // All other types: + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident: $ty:ident + { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + impl From<$ty> for $name { + fn from(value: $ty) -> Self { + match value { + $( c if c == $value => $name::$variant, )+ + other => $name::Undefined(other), + } + } + } + + impl From<$name> for $ty { + fn from(value: $name) -> Self { + match value { + $( $name::$variant => $value, )+ + $name::Undefined(val) => val, + } + } + } + }; + + + + // Generic: + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident<$ty:ident $( : $first_bound:tt $(+ $other_bounds:tt)* )?> { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + impl<$ty$(: $first_bound $(+ $other_bounds)+)?> From<$ty> for $name<$ty> { + fn from(value: $ty) -> Self { + match value { + $( c if c == $value => $name::$variant, )+ + other => $name::Undefined(other), + } + } + } + + // Todo Orphan rule forbids `From` impl. + impl<$ty$(: $first_bound $(+ $other_bounds)+)?> $name<$ty> { + /// Converts the case into its representation. + /// Orphan rule forbids `From` implementation, so we create our own method. + pub fn to_repr(self) -> $ty { + match self { + $( $name::$variant => $value, )+ + $name::Undefined(val) => val, + } + } + } + }; +} + +#[cfg(feature = "serde")] +pub use serde_core as serde; + +#[cfg(feature = "serde")] +#[macro_export] +macro_rules! loose_enum_serde { + // Special case for strings: + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident: String + { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + impl<'de> $crate::__internal::serde::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: $crate::__internal::serde::Deserializer<'de>, + { + let val = String::deserialize(deserializer)?; + Ok(match val.as_str() { + $( c if c == $value => $name::$variant, )+ + other => $name::Undefined(other.to_string()), + }) + } + } + + impl $crate::__internal::serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: $crate::__internal::serde::Serializer, + { + match self { + $( $name::$variant => str::serialize($value, serializer), )+ + $name::Undefined(val) => str::serialize(val, serializer), + } + } + } + }; + + + + // All other types: + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident: $ty:ident + { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + impl<'de> $crate::__internal::serde::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: $crate::__internal::serde::Deserializer<'de>, + { + let val = $ty::deserialize(deserializer)?; + Ok(match val { + $( c if c == $value => $name::$variant, )+ + other => $name::Undefined(other), + }) + } + } + + impl $crate::__internal::serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: $crate::__internal::serde::Serializer, + { + match self { + $( $name::$variant => $ty::serialize(&$value, serializer), )+ + $name::Undefined(val) => $ty::serialize(val, serializer), + } + } + } + }; + + + + // Generic: + ( + $(#[$outer:meta])* + $vis:vis enum $name:ident<$ty:ident $( : $first_bound:tt $(+ $other_bounds:tt)* )?> + { + $( + $(#[$meta:meta])* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + impl<'de, $ty$(: $first_bound $(+ $other_bounds)+)? + $crate::__internal::serde::Deserialize<'de>> $crate::__internal::serde::Deserialize<'de> for $name<$ty> { + fn deserialize(deserializer: D) -> Result + where + D: $crate::__internal::serde::Deserializer<'de>, + { + let val = $ty::deserialize(deserializer)?; + Ok(match val { + $( c if c == $value => $name::$variant, )+ + other => $name::Undefined(other), + }) + } + } + + impl<$ty$(: $first_bound $(+ $other_bounds)+)? + $crate::__internal::serde::Serialize> $crate::__internal::serde::Serialize for $name<$ty> { + fn serialize(&self, serializer: S) -> Result + where + S: $crate::__internal::serde::Serializer, + { + match self { + $( $name::$variant => $ty::serialize(&$value, serializer), )+ + $name::Undefined(val) => $ty::serialize(val, serializer), + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f75e7b2..e9a82bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #[doc(hidden)] -#[cfg(feature = "serde")] -pub use serde_core as __serde; +pub mod __internal; /// Defines a repr enum that supports any value. If a value does not match any case, it will be parsed as `Undefined`. #[cfg(not(feature = "serde"))] @@ -11,48 +10,21 @@ macro_rules! loose_enum { // Special case for strings: ( $(#[$outer:meta])* - $vis:vis enum $name:ident: String - { - $( - $(#[$meta:meta])* - $variant:ident = $value:expr - ),+ $(,)? + $vis:vis enum $name:ident: String { + $($body:tt)* } ) => { - $(#[$outer])* - $vis enum $name { - $( - $(#[$meta])* - $variant - ),+, - /// Any value that doesn't match another case. - Undefined(String), - } - - impl From for $name { - fn from(value: String) -> Self { - match value.as_str() { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other.to_string()), - } + $crate::loose_enum_type! { + $(#[$outer])* + $vis enum $name: String { + $($body)* } } - impl<'a> From<&'a str> for $name { - fn from(value: &'a str) -> Self { - match value { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other.to_string()), - } - } - } - - impl From<$name> for String { - fn from(value: $name) -> Self { - match value { - $( $name::$variant => $value.to_string(), )+ - $name::Undefined(val) => val, - } + $crate::loose_enum_impl! { + $(#[$outer])* + $vis enum $name: String { + $($body)* } } }; @@ -62,39 +34,21 @@ macro_rules! loose_enum { // All other types: ( $(#[$outer:meta])* - $vis:vis enum $name:ident: $ty:ident - { - $( - $(#[$meta:meta])* - $variant:ident = $value:expr - ),+ $(,)? + $vis:vis enum $name:ident: $ty:ident { + $($body:tt)* } ) => { - $(#[$outer])* - $vis enum $name { - $( - $(#[$meta])* - $variant - ),+, - /// Any value that doesn't match another case. - Undefined($ty), - } - - impl From<$ty> for $name { - fn from(value: $ty) -> Self { - match value { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other), - } + $crate::loose_enum_type! { + $(#[$outer])* + $vis enum $name: $ty { + $($body)* } } - impl From<$name> for $ty { - fn from(value: $name) -> Self { - match value { - $( $name::$variant => $value, )+ - $name::Undefined(val) => val, - } + $crate::loose_enum_impl! { + $(#[$outer])* + $vis enum $name: $ty { + $($body)* } } }; @@ -106,45 +60,26 @@ macro_rules! loose_enum { $(#[$outer:meta])* $vis:vis enum $name:ident<$ty:ident $( : $first_bound:tt $(+ $other_bounds:tt)* )?> { - $( - $(#[$meta:meta])* - $variant:ident = $value:expr - ),+ $(,)? + $($body:tt)* } ) => { - $(#[$outer])* - $vis enum $name<$ty$(: $first_bound $(+ $other_bounds)+)?> { - $( - $(#[$meta])* - $variant - ),+, - /// Any value that doesn't match another case. - Undefined($ty), - } - - impl<$ty$(: $first_bound $(+ $other_bounds)+)?> From<$ty> for $name<$ty> { - fn from(value: $ty) -> Self { - match value { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other), - } + $crate::loose_enum_type! { + $(#[$outer])* + $vis enum $name<$ty $( : $first_bound $(+ $other_bounds)* )?> { + $($body)* } } - // Todo Orphan rule forbids `From` impl. - impl<$ty$(: $first_bound $(+ $other_bounds)+)?> $name<$ty> { - /// Converts the case into its representation. - /// Orphan rule forbids `From` implementation, so we create our own method. - pub fn to_repr(self) -> $ty { - match self { - $( $name::$variant => $value, )+ - $name::Undefined(val) => val, - } + $crate::loose_enum_impl! { + $(#[$outer])* + $vis enum $name<$ty $( : $first_bound $(+ $other_bounds)* )?> { + $($body)* } } }; } + /// Defines a repr enum that supports any value. If a value does not match any case, it will be parsed as `Undefined`. #[cfg(feature = "serde")] #[macro_export] @@ -152,73 +87,28 @@ macro_rules! loose_enum { // Special case for strings: ( $(#[$outer:meta])* - $vis:vis enum $name:ident: String - { - $( - $(#[$meta:meta])* - $variant:ident = $value:expr - ),+ $(,)? + $vis:vis enum $name:ident: String { + $($body:tt)* } ) => { - $(#[$outer])* - $vis enum $name { - $( - $(#[$meta])* - $variant - ),+, - /// Any value that doesn't match another case. - Undefined(String), - } - - impl From for $name { - fn from(value: String) -> Self { - match value.as_str() { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other.to_string()), - } - } - } - - impl<'a> From<&'a str> for $name { - fn from(value: &'a str) -> Self { - match value { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other.to_string()), - } + $crate::loose_enum_type! { + $(#[$outer])* + $vis enum $name: String { + $($body)* } } - impl From<$name> for String { - fn from(value: $name) -> Self { - match value { - $( $name::$variant => $value.to_string(), )+ - $name::Undefined(val) => val, - } + $crate::loose_enum_impl! { + $(#[$outer])* + $vis enum $name: String { + $($body)* } } - - impl<'de> $crate::__serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: $crate::__serde::Deserializer<'de>, - { - let val = String::deserialize(deserializer)?; - Ok(match val.as_str() { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other.to_string()), - }) - } - } - - impl $crate::__serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: $crate::__serde::Serializer, - { - match self { - $( $name::$variant => str::serialize($value, serializer), )+ - $name::Undefined(val) => str::serialize(val, serializer), - } + + $crate::loose_enum_serde! { + $(#[$outer])* + $vis enum $name: String { + $($body)* } } }; @@ -228,64 +118,28 @@ macro_rules! loose_enum { // All other types: ( $(#[$outer:meta])* - $vis:vis enum $name:ident: $ty:ident - { - $( - $(#[$meta:meta])* - $variant:ident = $value:expr - ),+ $(,)? + $vis:vis enum $name:ident: $ty:ident { + $($body:tt)* } ) => { - $(#[$outer])* - $vis enum $name { - $( - $(#[$meta])* - $variant - ),+, - /// Any value that doesn't match another case. - Undefined($ty), - } - - impl From<$ty> for $name { - fn from(value: $ty) -> Self { - match value { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other), - } - } - } - - impl From<$name> for $ty { - fn from(value: $name) -> Self { - match value { - $( $name::$variant => $value, )+ - $name::Undefined(val) => val, - } + $crate::loose_enum_type! { + $(#[$outer])* + $vis enum $name: $ty { + $($body)* } } - impl<'de> $crate::__serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: $crate::__serde::Deserializer<'de>, - { - let val = $ty::deserialize(deserializer)?; - Ok(match val { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other), - }) + $crate::loose_enum_impl! { + $(#[$outer])* + $vis enum $name: $ty { + $($body)* } } - - impl $crate::__serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: $crate::__serde::Serializer, - { - match self { - $( $name::$variant => $ty::serialize(&$value, serializer), )+ - $name::Undefined(val) => $ty::serialize(val, serializer), - } + + $crate::loose_enum_serde! { + $(#[$outer])* + $vis enum $name: $ty { + $($body)* } } }; @@ -297,65 +151,27 @@ macro_rules! loose_enum { $(#[$outer:meta])* $vis:vis enum $name:ident<$ty:ident $( : $first_bound:tt $(+ $other_bounds:tt)* )?> { - $( - $(#[$meta:meta])* - $variant:ident = $value:expr - ),+ $(,)? + $($body:tt)* } ) => { - $(#[$outer])* - $vis enum $name<$ty$(: $first_bound $(+ $other_bounds)+)?> { - $( - $(#[$meta])* - $variant - ),+, - /// Any value that doesn't match another case. - Undefined($ty), - } - - impl<$ty$(: $first_bound $(+ $other_bounds)+)?> From<$ty> for $name<$ty> { - fn from(value: $ty) -> Self { - match value { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other), - } + $crate::loose_enum_type! { + $(#[$outer])* + $vis enum $name<$ty $( : $first_bound $(+ $other_bounds)* )?> { + $($body)* } } - // Todo Orphan rule forbids `From` impl. - impl<$ty$(: $first_bound $(+ $other_bounds)+)?> $name<$ty> { - /// Converts the case into its representation. - /// Orphan rule forbids `From` implementation, so we create our own method. - pub fn to_repr(self) -> $ty { - match self { - $( $name::$variant => $value, )+ - $name::Undefined(val) => val, - } + $crate::loose_enum_impl! { + $(#[$outer])* + $vis enum $name<$ty $( : $first_bound $(+ $other_bounds)* )?> { + $($body)* } } - - impl<'de, $ty$(: $first_bound $(+ $other_bounds)+)? + $crate::__serde::Deserialize<'de>> $crate::__serde::Deserialize<'de> for $name<$ty> { - fn deserialize(deserializer: D) -> Result - where - D: $crate::__serde::Deserializer<'de>, - { - let val = $ty::deserialize(deserializer)?; - Ok(match val { - $( c if c == $value => $name::$variant, )+ - other => $name::Undefined(other), - }) - } - } - - impl<$ty$(: $first_bound $(+ $other_bounds)+)? + $crate::__serde::Serialize> $crate::__serde::Serialize for $name<$ty> { - fn serialize(&self, serializer: S) -> Result - where - S: $crate::__serde::Serializer, - { - match self { - $( $name::$variant => $ty::serialize(&$value, serializer), )+ - $name::Undefined(val) => $ty::serialize(val, serializer), - } + + $crate::loose_enum_serde! { + $(#[$outer])* + $vis enum $name<$ty $( : $first_bound $(+ $other_bounds)* )?> { + $($body)* } } }; From bff1a052ba6d63607b79abe4dbb8c922d5a5b7f1 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 11 Dec 2025 11:55:50 -0800 Subject: [PATCH 7/8] Added basic readme. --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 8 ++++---- 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4262b8d --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Loose Enum +[![Version](https://img.shields.io/crates/v/loose_enum)](https://crates.io/crates/loose_enum) +[![Docs](https://img.shields.io/docsrs/loose_enum)](https://docs.rs/loose_enum) +![License](https://img.shields.io/crates/l/loose_enum) + +A macro for defining loose repr enums. + +When parsing userdata, you often have known/supported cases; however, users don't always follow the rules. +One way to solve this is having a backup `Undefined` case that supports any value. This crate hopes to simplify this process. + +### Example: +For example, an integer repr bool, with 0 being false and 1 being true would look something like this: +```rust +loose_enum::loose_enum! { + #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] + pub enum LooseBool: i32 { + #[default] + False = 0, + True = 1, + } +} +``` +Which expands into the following: +```rust ignore +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum LooseBool { + #[default] + False, + True, + /// Any value that doesn't match another case. + Undefined(i32), +} + +impl From for LooseBool { /* ... */ } +impl From for i32 { /* ... */ } + +// If feature flag `serde` is enabled: +impl Serialize for LooseBool { /* ... */ } +impl Deserialize for LooseBool { /* ... */ } +``` \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e9a82bd..454505f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] #[doc(hidden)] @@ -79,7 +80,6 @@ macro_rules! loose_enum { }; } - /// Defines a repr enum that supports any value. If a value does not match any case, it will be parsed as `Undefined`. #[cfg(feature = "serde")] #[macro_export] @@ -104,7 +104,7 @@ macro_rules! loose_enum { $($body)* } } - + $crate::loose_enum_serde! { $(#[$outer])* $vis enum $name: String { @@ -135,7 +135,7 @@ macro_rules! loose_enum { $($body)* } } - + $crate::loose_enum_serde! { $(#[$outer])* $vis enum $name: $ty { @@ -167,7 +167,7 @@ macro_rules! loose_enum { $($body)* } } - + $crate::loose_enum_serde! { $(#[$outer])* $vis enum $name<$ty $( : $first_bound $(+ $other_bounds)* )?> { From b1d89a0ee2d18cd49c196ac4153da842578a52ea Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 11 Dec 2025 11:56:09 -0800 Subject: [PATCH 8/8] Update version number to beta 2. --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d81396..87fd0b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "loose_enum" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" dependencies = [ "num-traits", "serde_core", diff --git a/Cargo.toml b/Cargo.toml index f334b6b..6a23d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "loose_enum" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" edition = "2024" description = "A macro for defining loose repr enums." categories = ["no-std", "no-std::no-alloc", "rust-patterns"]