1pub use assert_order_derive::VariantOrder;
27
28pub trait VariantOrder {
31 fn order() -> &'static [&'static str];
34}
35
36pub fn assert_order<E, O, V>(order: O)
43where
44 E: VariantOrder,
45 O: IntoIterator<Item = V>,
46 V: AsRef<str>,
47{
48 let variants = E::order();
49 let iter = order.into_iter();
50
51 let mut n: usize = 0;
52 for variant in iter {
53 let variant = variant.as_ref();
54 let canonical = variants.get(n).map(|canonical| *canonical);
55
56 let Some(canonical) = canonical else {
57 panic!("Expected more canonical variants.");
58 };
59
60 assert!(
61 variant == canonical,
62 "Variant name mismatch: Expected \"{}\", got \"{}\"",
63 variant,
64 canonical,
65 );
66
67 n += 1;
68 }
69
70 assert!(
71 variants.len() == n,
72 "unexpected length: expected {}, got {}",
73 n,
74 variants.len()
75 );
76}
77
78#[cfg(test)]
79mod tests {
80 use std::thread::spawn;
81
82 use crate::{assert_order, VariantOrder};
83
84 #[test]
85 fn assert_proper_ordering() {
86 #[derive(VariantOrder)]
87 #[expect(unused)]
88 enum TestEnum {
89 A,
90 B(),
91 C {},
92 }
93
94 assert_eq!(["A", "B", "C"], TestEnum::order());
95 }
96
97 #[test]
98 fn assert_proper_ordering_lifetime() {
99 #[derive(VariantOrder)]
100 #[expect(unused)]
101 enum TestEnum<'a> {
102 A,
103 B(&'a str),
104 C {},
105 }
106
107 assert_eq!(["A", "B", "C"], TestEnum::order());
108 }
109
110 #[test]
111 fn expect_nonpanic() {
112 #[derive(VariantOrder)]
113 #[expect(unused)]
114 enum TestEnum {
115 A,
116 B(),
117 C {},
118 }
119
120 assert_order::<TestEnum, _, _>(["A", "B", "C"]);
121 }
122
123 #[test]
124 fn expect_nonpanic_lifetime() {
125 #[derive(VariantOrder)]
126 #[expect(unused)]
127 enum TestEnum<'a, T> {
128 A,
129 B(&'a str),
130 C { inner: T },
131 }
132
133 assert_order::<TestEnum::<'_, ()>, _, _>(["A", "B", "C"]);
134 }
135
136 #[test]
137 fn expect_nonpanic_generic() {
138 #[derive(VariantOrder)]
139 #[expect(unused)]
140 enum TestEnum<T> {
141 A,
142 B(T),
143 C {},
144 }
145
146 assert_order::<TestEnum::<()>, _, _>(["A", "B", "C"]);
147 }
148
149 #[test]
150 fn expect_order_panic() {
151 let thread = spawn(|| {
152 #[derive(VariantOrder)]
153 #[expect(unused)]
154 enum TestEnum {
155 A,
156 B(),
157 C {},
158 }
159
160 assert_order::<TestEnum, _, _>(["A", "C", "B"]);
161 });
162
163 let panic = thread.join().expect_err("expected panic");
164 let panic = panic
165 .downcast_ref::<String>()
166 .expect("panic as string")
167 .as_str();
168
169 assert_eq!(panic, "Variant name mismatch: Expected \"C\", got \"B\"");
170 }
171
172 #[test]
173 fn expect_too_long_panic() {
174 let thread = spawn(|| {
175 #[derive(VariantOrder)]
176 #[expect(unused)]
177 enum TestEnum {
178 A,
179 B(),
180 C {},
181 }
182
183 assert_order::<TestEnum, _, _>(["A", "B"]);
184 });
185
186 let panic = thread.join().expect_err("expected panic");
187 let panic = panic
188 .downcast_ref::<String>()
189 .expect("panic as string")
190 .as_str();
191
192 assert_eq!(panic, "unexpected length: expected 2, got 3");
193 }
194
195 #[test]
196 fn expect_too_short_panic() {
197 let thread = spawn(|| {
198 #[derive(VariantOrder)]
199 #[expect(unused)]
200 enum TestEnum {
201 A,
202 B(),
203 C {},
204 }
205
206 assert_order::<TestEnum, _, _>(["A", "B", "C", "D"]);
207 });
208
209 let panic = thread.join().expect_err("expected panic");
210 let panic = *(panic
211 .downcast_ref::<&'static str>()
212 .expect("panic as string"));
213
214 assert_eq!(panic, "Expected more canonical variants.");
215 }
216}