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