enum_table/
lib.rs

1#![doc = include_str!(concat!("../", core::env!("CARGO_PKG_README")))]
2
3#[cfg(test)]
4pub extern crate self as enum_table;
5
6#[cfg(feature = "derive")]
7pub use enum_table_derive::Enumable;
8
9pub mod builder;
10mod intrinsics;
11
12pub mod __private {
13    pub use crate::intrinsics::sort_variants;
14}
15
16mod impls;
17mod macros;
18
19use intrinsics::{copy_from_usize, copy_variant, from_usize, to_usize};
20
21/// A trait for enumerations that can be used with `EnumTable`.
22///
23/// This trait requires that the enumeration provides a static array of its variants
24/// and a constant representing the count of these variants.
25pub trait Enumable: Sized + 'static {
26    const VARIANTS: &'static [Self];
27    const COUNT: usize = Self::VARIANTS.len();
28}
29
30/// Error type for `EnumTable::try_from_vec`.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum EnumTableFromVecError<K> {
33    /// The vector has an invalid size.
34    InvalidSize { expected: usize, found: usize },
35    /// A required enum variant is missing from the vector.
36    /// This error happened meaning that the vector duplicated some variant
37    MissingVariant(K),
38}
39
40impl<K: core::fmt::Debug> core::fmt::Display for EnumTableFromVecError<K> {
41    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42        match self {
43            EnumTableFromVecError::InvalidSize { expected, found } => {
44                write!(f, "Invalid vector size: expected {expected}, found {found}")
45            }
46            EnumTableFromVecError::MissingVariant(variant) => {
47                write!(f, "Missing enum variant: {variant:?}")
48            }
49        }
50    }
51}
52
53impl<K: core::fmt::Debug> core::error::Error for EnumTableFromVecError<K> {}
54
55/// A table that associates each variant of an enumeration with a value.
56///
57/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
58/// associated values. It provides constant-time access to the values based on
59/// the enumeration variant. This is particularly useful when you want to map
60/// enum variants to specific values without the overhead of a `HashMap`.
61///
62/// # Type Parameters
63///
64/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
65///   ensures that the enum provides a static array of its variants and a count
66///   of these variants.
67/// * `V`: The type of values to be associated with each enum variant.
68/// * `N`: The number of variants in the enum, which should match the length of
69///   the static array of variants provided by the `Enumable` trait.
70///
71/// # Note
72/// The `new` method allows for the creation of an `EnumTable` in `const` contexts,
73/// but it does not perform compile-time checks. For enhanced compile-time safety
74/// and convenience, it is advisable to use the [`crate::et`] macro or
75/// [`crate::builder::EnumTableBuilder`], which provide these checks.
76///
77/// # Examples
78///
79/// ```rust
80/// use enum_table::{EnumTable, Enumable};
81///
82/// #[derive(Enumable)]
83/// enum Color {
84///     Red,
85///     Green,
86///     Blue,
87/// }
88///
89/// // Create an EnumTable using the new_with_fn method
90/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
91///     Color::Red => "Red",
92///     Color::Green => "Green",
93///     Color::Blue => "Blue",
94/// });
95///
96/// // Access values associated with enum variants
97/// assert_eq!(table.get(&Color::Red), &"Red");
98/// assert_eq!(table.get(&Color::Green), &"Green");
99/// assert_eq!(table.get(&Color::Blue), &"Blue");
100/// ```
101pub struct EnumTable<K: Enumable, V, const N: usize> {
102    table: [(usize, V); N],
103    _phantom: core::marker::PhantomData<K>,
104}
105
106impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
107    /// Creates a new `EnumTable` with the given table of discriminants and values.
108    /// Typically, you would use the [`crate::et`] macro or [`crate::builder::EnumTableBuilder`] to create an `EnumTable`.
109    pub(crate) const fn new(table: [(usize, V); N]) -> Self {
110        #[cfg(debug_assertions)]
111        const {
112            // Ensure that the variants are sorted by their discriminants.
113            // This is a compile-time check to ensure that the variants are in the correct order.
114            if !intrinsics::is_sorted(K::VARIANTS) {
115                panic!("Enumable: variants are not sorted by discriminant. Use `enum_table::Enumable` derive macro to ensure correct ordering.");
116            }
117        }
118
119        Self {
120            table,
121            _phantom: core::marker::PhantomData,
122        }
123    }
124
125    /// Create a new EnumTable with a function that takes a variant and returns a value.
126    /// If you want to define it in const, use [`crate::et`] macro
127    /// Creates a new `EnumTable` using a function to generate values for each variant.
128    ///
129    /// # Arguments
130    ///
131    /// * `f` - A function that takes a reference to an enumeration variant and returns
132    ///   a value to be associated with that variant.
133    pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
134        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
135
136        for variant in K::VARIANTS {
137            builder.push(variant, f(variant));
138        }
139
140        builder.build_to()
141    }
142
143    /// Creates a new `EnumTable` using a function that returns a `Result` for each variant.
144    ///
145    /// This method applies the provided closure to each variant of the enum. If the closure
146    /// returns `Ok(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
147    /// If the closure returns `Err(e)` for any variant, the construction is aborted and
148    /// `Err((variant, e))` is returned, where `variant` is the enum variant that caused the error.
149    ///
150    /// # Arguments
151    ///
152    /// * `f` - A closure that takes a reference to an enum variant and returns a `Result<V, E>`.
153    ///
154    /// # Returns
155    ///
156    /// * `Ok(Self)` if all variants succeed.
157    /// * `Err((variant, e))` if any variant fails, containing the failing variant and the error.
158    pub fn try_new_with_fn<E>(mut f: impl FnMut(&K) -> Result<V, E>) -> Result<Self, (K, E)> {
159        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
160
161        for variant in K::VARIANTS {
162            match f(variant) {
163                Ok(value) => builder.push(variant, value),
164                Err(e) => return Err((copy_variant(variant), e)),
165            }
166        }
167
168        Ok(builder.build_to())
169    }
170
171    /// Creates a new `EnumTable` using a function that returns an `Option` for each variant.
172    ///
173    /// This method applies the provided closure to each variant of the enum. If the closure
174    /// returns `Some(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
175    /// If the closure returns `None` for any variant, the construction is aborted and
176    /// `Err(variant)` is returned, where `variant` is the enum variant that caused the failure.
177    ///
178    /// # Arguments
179    ///
180    /// * `f` - A closure that takes a reference to an enum variant and returns an `Option<V>`.
181    ///
182    /// # Returns
183    ///
184    /// * `Ok(Self)` if all variants succeed.
185    /// * `Err(variant)` if any variant fails, containing the failing variant.
186    pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, K> {
187        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
188
189        for variant in K::VARIANTS {
190            if let Some(value) = f(variant) {
191                builder.push(variant, value);
192            } else {
193                return Err(copy_variant(variant));
194            }
195        }
196
197        Ok(builder.build_to())
198    }
199
200    pub(crate) const fn binary_search(&self, variant: &K) -> usize {
201        let discriminant = to_usize(variant);
202        let mut low = 0;
203        let mut high = N;
204
205        while low < high {
206            let mid = low + (high - low) / 2;
207            if self.table[mid].0 < discriminant {
208                low = mid + 1;
209            } else {
210                high = mid;
211            }
212        }
213
214        low
215    }
216
217    /// Returns a reference to the value associated with the given enumeration variant.
218    ///
219    /// # Arguments
220    ///
221    /// * `variant` - A reference to an enumeration variant.
222    pub const fn get(&self, variant: &K) -> &V {
223        let idx = self.binary_search(variant);
224        &self.table[idx].1
225    }
226
227    /// Returns a mutable reference to the value associated with the given enumeration variant.
228    ///
229    /// # Arguments
230    ///
231    /// * `variant` - A reference to an enumeration variant.
232    pub const fn get_mut(&mut self, variant: &K) -> &mut V {
233        let idx = self.binary_search(variant);
234        &mut self.table[idx].1
235    }
236
237    /// Sets the value associated with the given enumeration variant.
238    ///
239    /// # Arguments
240    ///
241    /// * `variant` - A reference to an enumeration variant.
242    /// * `value` - The new value to associate with the variant.
243    /// # Returns
244    /// The old value associated with the variant.
245    pub const fn set(&mut self, variant: &K, value: V) -> V {
246        let idx = self.binary_search(variant);
247        core::mem::replace(&mut self.table[idx].1, value)
248    }
249
250    /// Returns the number of generic N
251    pub const fn len(&self) -> usize {
252        N
253    }
254
255    /// Returns `false` since the table is never empty.
256    pub const fn is_empty(&self) -> bool {
257        false
258    }
259
260    /// Returns an iterator over references to the keys in the table.
261    pub fn keys(&self) -> impl Iterator<Item = &K> {
262        self.table
263            .iter()
264            .map(|(discriminant, _)| from_usize(discriminant))
265    }
266
267    /// Returns an iterator over references to the values in the table.
268    pub fn values(&self) -> impl Iterator<Item = &V> {
269        self.table.iter().map(|(_, value)| value)
270    }
271
272    /// Returns an iterator over mutable references to the values in the table.
273    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
274        self.table.iter_mut().map(|(_, value)| value)
275    }
276
277    /// Returns an iterator over mutable references to the values in the table.
278    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
279        self.table
280            .iter()
281            .map(|(discriminant, value)| (from_usize(discriminant), value))
282    }
283
284    /// Returns an iterator over mutable references to the values in the table.
285    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
286        self.table
287            .iter_mut()
288            .map(|(discriminant, value)| (from_usize(discriminant), value))
289    }
290
291    /// Maps the values of the table using a closure, creating a new `EnumTable` with the transformed values.
292    ///
293    /// # Arguments
294    ///
295    /// * `f` - A closure that takes a value and returns a transformed value.
296    ///
297    /// # Examples
298    ///
299    /// ```rust
300    /// use enum_table::{EnumTable, Enumable};
301    ///
302    /// #[derive(Enumable)]
303    /// enum Color {
304    ///     Red,
305    ///     Green,
306    ///     Blue,
307    /// }
308    ///
309    /// let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
310    ///     Color::Red => 1,
311    ///     Color::Green => 2,
312    ///     Color::Blue => 3,
313    /// });
314    ///
315    /// let doubled = table.map(|x| x * 2);
316    /// assert_eq!(doubled.get(&Color::Red), &2);
317    /// assert_eq!(doubled.get(&Color::Green), &4);
318    /// assert_eq!(doubled.get(&Color::Blue), &6);
319    /// ```
320    pub fn map<U, F>(self, mut f: F) -> EnumTable<K, U, N>
321    where
322        F: FnMut(V) -> U,
323    {
324        let mut builder = builder::EnumTableBuilder::<K, U, N>::new();
325
326        for (discriminant, value) in self.table {
327            let key = from_usize(&discriminant);
328            builder.push(key, f(value));
329        }
330
331        builder.build_to()
332    }
333
334    /// Converts the `EnumTable` into a `Vec` of key-value pairs.
335    ///
336    /// # Examples
337    ///
338    /// ```rust
339    /// use enum_table::{EnumTable, Enumable};
340    ///
341    /// #[derive(Enumable, Debug, PartialEq, Copy, Clone)]
342    /// enum Color {
343    ///     Red,
344    ///     Green,
345    ///     Blue,
346    /// }
347    ///
348    /// let table = EnumTable::<Color, &str, { Color::COUNT }>::new_with_fn(|color| match color {
349    ///     Color::Red => "red",
350    ///     Color::Green => "green",
351    ///     Color::Blue => "blue",
352    /// });
353    ///
354    /// let vec = table.into_vec();
355    /// assert!(vec.contains(&(Color::Red, "red")));
356    /// assert!(vec.contains(&(Color::Green, "green")));
357    /// assert!(vec.contains(&(Color::Blue, "blue")));
358    /// assert_eq!(vec.len(), 3);
359    /// ```
360    pub fn into_vec(self) -> Vec<(K, V)> {
361        self.table
362            .into_iter()
363            .map(|(discriminant, value)| (copy_from_usize(&discriminant), value))
364            .collect()
365    }
366
367    /// Creates an `EnumTable` from a `Vec` of key-value pairs.
368    ///
369    /// Returns an error if the vector doesn't contain exactly one entry for each enum variant.
370    ///
371    /// # Arguments
372    ///
373    /// * `vec` - A vector containing key-value pairs for each enum variant.
374    ///
375    /// # Examples
376    ///
377    /// ```rust
378    /// use enum_table::{EnumTable, Enumable};
379    ///
380    /// #[derive(Enumable, Debug, PartialEq, Copy, Clone)]
381    /// enum Color {
382    ///     Red,
383    ///     Green,
384    ///     Blue,
385    /// }
386    ///
387    /// let vec = vec![
388    ///     (Color::Red, "red"),
389    ///     (Color::Green, "green"),
390    ///     (Color::Blue, "blue"),
391    /// ];
392    ///
393    /// let table = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec).unwrap();
394    /// assert_eq!(table.get(&Color::Red), &"red");
395    /// assert_eq!(table.get(&Color::Green), &"green");
396    /// assert_eq!(table.get(&Color::Blue), &"blue");
397    /// ```
398    pub fn try_from_vec(mut vec: Vec<(K, V)>) -> Result<Self, EnumTableFromVecError<K>> {
399        if vec.len() != N {
400            return Err(EnumTableFromVecError::InvalidSize {
401                expected: N,
402                found: vec.len(),
403            });
404        }
405
406        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
407
408        // Check that all variants are present and move values out
409        for variant in K::VARIANTS {
410            if let Some(pos) = vec
411                .iter()
412                .position(|(k, _)| to_usize(k) == to_usize(variant))
413            {
414                let (_, value) = vec.swap_remove(pos);
415                builder.push(variant, value);
416            } else {
417                return Err(EnumTableFromVecError::MissingVariant(copy_variant(variant)));
418            }
419        }
420
421        Ok(builder.build_to())
422    }
423}
424
425impl<K: Enumable, V, const N: usize> EnumTable<K, Option<V>, N> {
426    /// Creates a new `EnumTable` with `None` values for each variant.
427    pub const fn new_fill_with_none() -> Self {
428        let mut builder = builder::EnumTableBuilder::<K, Option<V>, N>::new();
429
430        let mut i = 0;
431        while i < N {
432            let variant = &K::VARIANTS[i];
433            builder.push(variant, None);
434            i += 1;
435        }
436
437        builder.build_to()
438    }
439
440    /// Clears the table, setting each value to `None`.
441    pub fn clear_to_none(&mut self) {
442        for (_, value) in &mut self.table {
443            *value = None;
444        }
445    }
446}
447
448impl<K: Enumable, V: Copy, const N: usize> EnumTable<K, V, N> {
449    pub const fn new_fill_with_copy(value: V) -> Self {
450        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
451
452        let mut i = 0;
453        while i < N {
454            let variant = &K::VARIANTS[i];
455            builder.push(variant, value);
456            i += 1;
457        }
458
459        builder.build_to()
460    }
461}
462
463impl<K: Enumable, V: Default, const N: usize> EnumTable<K, V, N> {
464    /// Creates a new `EnumTable` with default values for each variant.
465    ///
466    /// This method initializes the table with the default value of type `V` for each
467    /// variant of the enumeration.
468    pub fn new_fill_with_default() -> Self {
469        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
470
471        for variant in K::VARIANTS {
472            builder.push(variant, V::default());
473        }
474
475        builder.build_to()
476    }
477
478    /// Clears the table, setting each value to its default.
479    pub fn clear_to_default(&mut self) {
480        for (_, value) in &mut self.table {
481            *value = V::default();
482        }
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489
490    #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
491    enum Color {
492        Red = 33,
493        Green = 11,
494        Blue = 222,
495    }
496
497    const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
498        crate::et!(Color, &'static str, |color| match color {
499            Color::Red => "Red",
500            Color::Green => "Green",
501            Color::Blue => "Blue",
502        });
503
504    #[test]
505    fn new_with_fn() {
506        let table =
507            EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
508                Color::Red => "Red",
509                Color::Green => "Green",
510                Color::Blue => "Blue",
511            });
512
513        assert_eq!(table.get(&Color::Red), &"Red");
514        assert_eq!(table.get(&Color::Green), &"Green");
515        assert_eq!(table.get(&Color::Blue), &"Blue");
516    }
517
518    #[test]
519    fn try_new_with_fn() {
520        let table =
521            EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
522                |color| match color {
523                    Color::Red => Ok::<&'static str, core::convert::Infallible>("Red"),
524                    Color::Green => Ok("Green"),
525                    Color::Blue => Ok("Blue"),
526                },
527            );
528
529        assert!(table.is_ok());
530        let table = table.unwrap();
531
532        assert_eq!(table.get(&Color::Red), &"Red");
533        assert_eq!(table.get(&Color::Green), &"Green");
534        assert_eq!(table.get(&Color::Blue), &"Blue");
535
536        let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
537            |color| match color {
538                Color::Red => Ok("Red"),
539                Color::Green => Err("Error on Green"),
540                Color::Blue => Ok("Blue"),
541            },
542        );
543
544        assert!(error_table.is_err());
545        let (variant, error) = error_table.unwrap_err();
546
547        assert_eq!(variant, Color::Green);
548        assert_eq!(error, "Error on Green");
549    }
550
551    #[test]
552    fn checked_new_with_fn() {
553        let table =
554            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
555                match color {
556                    Color::Red => Some("Red"),
557                    Color::Green => Some("Green"),
558                    Color::Blue => Some("Blue"),
559                }
560            });
561
562        assert!(table.is_ok());
563        let table = table.unwrap();
564
565        assert_eq!(table.get(&Color::Red), &"Red");
566        assert_eq!(table.get(&Color::Green), &"Green");
567        assert_eq!(table.get(&Color::Blue), &"Blue");
568
569        let error_table =
570            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
571                match color {
572                    Color::Red => Some("Red"),
573                    Color::Green => None,
574                    Color::Blue => Some("Blue"),
575                }
576            });
577
578        assert!(error_table.is_err());
579        let variant = error_table.unwrap_err();
580
581        assert_eq!(variant, Color::Green);
582    }
583
584    #[test]
585    fn get() {
586        assert_eq!(TABLES.get(&Color::Red), &"Red");
587        assert_eq!(TABLES.get(&Color::Green), &"Green");
588        assert_eq!(TABLES.get(&Color::Blue), &"Blue");
589    }
590
591    #[test]
592    fn get_mut() {
593        let mut table = TABLES;
594        assert_eq!(table.get_mut(&Color::Red), &mut "Red");
595        assert_eq!(table.get_mut(&Color::Green), &mut "Green");
596        assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
597
598        *table.get_mut(&Color::Red) = "Changed Red";
599        *table.get_mut(&Color::Green) = "Changed Green";
600        *table.get_mut(&Color::Blue) = "Changed Blue";
601
602        assert_eq!(table.get(&Color::Red), &"Changed Red");
603        assert_eq!(table.get(&Color::Green), &"Changed Green");
604        assert_eq!(table.get(&Color::Blue), &"Changed Blue");
605    }
606
607    #[test]
608    fn set() {
609        let mut table = TABLES;
610        assert_eq!(table.set(&Color::Red, "New Red"), "Red");
611        assert_eq!(table.set(&Color::Green, "New Green"), "Green");
612        assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
613
614        assert_eq!(table.get(&Color::Red), &"New Red");
615        assert_eq!(table.get(&Color::Green), &"New Green");
616        assert_eq!(table.get(&Color::Blue), &"New Blue");
617    }
618
619    #[test]
620    fn keys() {
621        let keys: Vec<_> = TABLES.keys().collect();
622        assert_eq!(keys, vec![&Color::Green, &Color::Red, &Color::Blue]);
623    }
624
625    #[test]
626    fn values() {
627        let values: Vec<_> = TABLES.values().collect();
628        assert_eq!(values, vec![&"Green", &"Red", &"Blue"]);
629    }
630
631    #[test]
632    fn iter() {
633        let iter: Vec<_> = TABLES.iter().collect();
634        assert_eq!(
635            iter,
636            vec![
637                (&Color::Green, &"Green"),
638                (&Color::Red, &"Red"),
639                (&Color::Blue, &"Blue")
640            ]
641        );
642    }
643
644    #[test]
645    fn iter_mut() {
646        let mut table = TABLES;
647        for (key, value) in table.iter_mut() {
648            *value = match key {
649                Color::Red => "Changed Red",
650                Color::Green => "Changed Green",
651                Color::Blue => "Changed Blue",
652            };
653        }
654        let iter: Vec<_> = table.iter().collect();
655        assert_eq!(
656            iter,
657            vec![
658                (&Color::Green, &"Changed Green"),
659                (&Color::Red, &"Changed Red"),
660                (&Color::Blue, &"Changed Blue")
661            ]
662        );
663    }
664
665    #[test]
666    fn map() {
667        let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
668            Color::Red => 1,
669            Color::Green => 2,
670            Color::Blue => 3,
671        });
672
673        let doubled = table.map(|x| x * 2);
674        assert_eq!(doubled.get(&Color::Red), &2);
675        assert_eq!(doubled.get(&Color::Green), &4);
676        assert_eq!(doubled.get(&Color::Blue), &6);
677    }
678
679    #[test]
680    fn into_vec() {
681        let table = TABLES;
682        let vec = table.into_vec();
683
684        assert_eq!(vec.len(), 3);
685        assert!(vec.contains(&(Color::Red, "Red")));
686        assert!(vec.contains(&(Color::Green, "Green")));
687        assert!(vec.contains(&(Color::Blue, "Blue")));
688    }
689
690    #[test]
691    fn try_from_vec() {
692        let vec = vec![
693            (Color::Red, "Red"),
694            (Color::Green, "Green"),
695            (Color::Blue, "Blue"),
696        ];
697
698        let table = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec).unwrap();
699        assert_eq!(table.get(&Color::Red), &"Red");
700        assert_eq!(table.get(&Color::Green), &"Green");
701        assert_eq!(table.get(&Color::Blue), &"Blue");
702    }
703
704    #[test]
705    fn try_from_vec_invalid_size() {
706        let vec = vec![
707            (Color::Red, "Red"),
708            (Color::Green, "Green"),
709            // Missing Blue
710        ];
711
712        let result = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec);
713        assert_eq!(
714            result,
715            Err(crate::EnumTableFromVecError::InvalidSize {
716                expected: 3,
717                found: 2
718            })
719        );
720    }
721
722    #[test]
723    fn try_from_vec_missing_variant() {
724        let vec = vec![
725            (Color::Red, "Red"),
726            (Color::Green, "Green"),
727            (Color::Red, "Duplicate Red"), // Duplicate instead of Blue
728        ];
729
730        let result = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec);
731        assert_eq!(
732            result,
733            Err(crate::EnumTableFromVecError::MissingVariant(Color::Blue))
734        );
735    }
736
737    #[test]
738    fn conversion_roundtrip() {
739        let original = TABLES;
740        let vec = original.into_vec();
741        let reconstructed = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec).unwrap();
742
743        assert_eq!(reconstructed.get(&Color::Red), &"Red");
744        assert_eq!(reconstructed.get(&Color::Green), &"Green");
745        assert_eq!(reconstructed.get(&Color::Blue), &"Blue");
746    }
747
748    macro_rules! run_variants_test {
749        ($($variant:ident),+) => {{
750            #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
751            #[repr(u8)]
752            enum Test {
753                $($variant,)*
754            }
755
756            let map = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_fn(|t| match t {
757                $(Test::$variant => stringify!($variant),)*
758            });
759            $(
760                assert_eq!(map.get(&Test::$variant), &stringify!($variant));
761            )*
762        }};
763    }
764
765    #[test]
766    fn binary_search_correct_variants() {
767        run_variants_test!(A);
768        run_variants_test!(A, B);
769        run_variants_test!(A, B, C);
770        run_variants_test!(A, B, C, D);
771        run_variants_test!(A, B, C, D, E);
772    }
773}