1use rquickjs::{
4 class::{Trace, Tracer},
5 Ctx, FromJs, IntoJs, JsLifetime, Result, Value,
6};
7
8macro_rules! define_any_of {
9 ($name:ident, $($variant:ident),+) => {
10 #[derive(Debug, Clone)]
11 pub enum $name<$($variant),+> {
12 $(
13 $variant($variant),
14 )+
15 }
16
17 define_any_of_from_js!($name, $($variant),+);
18
19 impl<'js, $($variant: IntoJs<'js>),+> IntoJs<'js> for $name<$($variant),+> {
20 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
21 match self {
22 $(
23 Self::$variant(val) => val.into_js(ctx),
24 )+
25 }
26 }
27 }
28
29 unsafe impl<'js, $($variant: JsLifetime<'js>),+> JsLifetime<'js> for $name<$($variant),+> {
30 type Changed<'to> = $name<$($variant::Changed<'to>),+>;
31 }
32
33 impl<'js, $($variant: Trace<'js>),+> Trace<'js> for $name<$($variant),+> {
34 fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
35 match self {
36 $(
37 Self::$variant(val) => val.trace(tracer),
38 )+
39 }
40 }
41 }
42
43 define_any_of_methods!($name, $($variant),+);
44 };
45}
46
47macro_rules! define_any_of_from_js {
48 ($name:ident, $first:ident, $($rest:ident),+) => {
49 impl<'js, $first: FromJs<'js>, $($rest: FromJs<'js>),+> FromJs<'js> for $name<$first, $($rest),+> {
50 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
51 define_any_of_from_js_impl!($name, ctx, value, $first, $($rest),+)
52 }
53 }
54 };
55}
56
57macro_rules! define_any_of_from_js_impl {
58 ($name:ident, $ctx:ident, $value:ident, $first:ident) => {
59 $first::from_js($ctx, $value).map($name::$first)
60 };
61
62 ($name:ident, $ctx:ident, $value:ident, $first:ident, $($rest:ident),+) => {
63 $first::from_js($ctx, $value.clone()).map($name::$first).or_else(|error| {
64 if error.is_from_js() {
65 define_any_of_from_js_impl!($name, $ctx, $value, $($rest),+)
66 } else {
67 Err(error)
68 }
69 })
70 };
71}
72
73macro_rules! define_any_of_variant_methods {
74 ($variant:ident, $is_fn:ident, $as_fn:ident, $as_mut_fn:ident, $into_fn:ident) => {
75 #[allow(dead_code)]
76 pub fn $is_fn(&self) -> bool {
77 matches!(self, Self::$variant(_))
78 }
79
80 #[allow(dead_code)]
81 pub fn $as_fn(&self) -> Option<&$variant> {
82 match self {
83 Self::$variant(val) => Some(val),
84 _ => None,
85 }
86 }
87
88 #[allow(dead_code)]
89 pub fn $as_mut_fn(&mut self) -> Option<&mut $variant> {
90 match self {
91 Self::$variant(val) => Some(val),
92 _ => None,
93 }
94 }
95
96 #[allow(dead_code)]
97 pub fn $into_fn(self) -> std::result::Result<$variant, Self> {
98 match self {
99 Self::$variant(val) => Ok(val),
100 other => Err(other),
101 }
102 }
103 };
104
105 (A) => {
106 define_any_of_variant_methods!(A, is_a, as_a, as_a_mut, into_a);
107 };
108 (B) => {
109 define_any_of_variant_methods!(B, is_b, as_b, as_b_mut, into_b);
110 };
111 (C) => {
112 define_any_of_variant_methods!(C, is_c, as_c, as_c_mut, into_c);
113 };
114 (D) => {
115 define_any_of_variant_methods!(D, is_d, as_d, as_d_mut, into_d);
116 };
117 (E) => {
118 define_any_of_variant_methods!(E, is_e, as_e, as_e_mut, into_e);
119 };
120 (F) => {
121 define_any_of_variant_methods!(F, is_f, as_f, as_f_mut, into_f);
122 };
123 (G) => {
124 define_any_of_variant_methods!(G, is_g, as_g, as_g_mut, into_g);
125 };
126 (H) => {
127 define_any_of_variant_methods!(H, is_h, as_h, as_h_mut, into_h);
128 };
129}
130
131macro_rules! define_any_of_methods {
132 ($name:ident, $($variant:ident),+) => {
133 impl<$($variant),+> $name<$($variant),+> {
134 $(
135 define_any_of_variant_methods!($variant);
136 )+
137 }
138 };
139}
140
141define_any_of!(AnyOf2, A, B);
142define_any_of!(AnyOf3, A, B, C);
143define_any_of!(AnyOf4, A, B, C, D);
144define_any_of!(AnyOf5, A, B, C, D, E);
145define_any_of!(AnyOf6, A, B, C, D, E, F);
146define_any_of!(AnyOf7, A, B, C, D, E, F, G);
147define_any_of!(AnyOf8, A, B, C, D, E, F, G, H);
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use rquickjs::{Context, Runtime};
153
154 #[test]
155 fn test_any_of_string_number() {
156 let rt = Runtime::new().unwrap();
157 let ctx = Context::full(&rt).unwrap();
158
159 ctx.with(|ctx| {
160 let val: Value = ctx.eval("'hello'").unwrap();
162 let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
163 assert!(any.is_a());
164 assert_eq!(any.as_a().unwrap(), "hello");
165 assert!(!any.is_b());
166 assert!(any.as_b().is_none());
167
168 let val: Value = ctx.eval("42").unwrap();
170 let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
171 assert!(!any.is_a());
172 assert!(any.is_b());
173 assert_eq!(*any.as_b().unwrap(), 42);
174 });
175 }
176
177 #[test]
178 fn test_any_of_fallback() {
179 let rt = Runtime::new().unwrap();
180 let ctx = Context::full(&rt).unwrap();
181
182 ctx.with(|ctx| {
183 let val: Value = ctx.eval("true").unwrap();
185 let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
186 assert!(any.is_c());
187 assert!(*any.as_c().unwrap());
188 assert!(!any.is_a());
189 assert!(!any.is_b());
190 });
191 }
192
193 #[test]
194 fn test_any_of_into_js() {
195 let rt = Runtime::new().unwrap();
196 let ctx = Context::full(&rt).unwrap();
197
198 ctx.with(|ctx| {
199 let any: AnyOf2<String, i32> = AnyOf2::A("test".to_string());
200 let val: Value = any.into_js(&ctx).unwrap();
201 let result: String = val.get().unwrap();
202 assert_eq!(result, "test");
203
204 let any: AnyOf2<String, i32> = AnyOf2::B(99);
205 let val: Value = any.into_js(&ctx).unwrap();
206 let result: i32 = val.get().unwrap();
207 assert_eq!(result, 99);
208 });
209 }
210
211 #[test]
212 fn test_any_of_methods() {
213 let rt = Runtime::new().unwrap();
214 let ctx = Context::full(&rt).unwrap();
215
216 ctx.with(|ctx| {
217 let val: Value = ctx.eval("'test'").unwrap();
219 let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
220 assert!(any.is_a());
221 assert_eq!(any.as_a().unwrap(), "test");
222 assert_eq!(any.into_a().unwrap(), "test");
223
224 let val: Value = ctx.eval("42").unwrap();
226 let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
227 assert!(any.is_b());
228 assert_eq!(*any.as_b().unwrap(), 42);
229 assert_eq!(any.into_b().unwrap(), 42);
230
231 let val: Value = ctx.eval("true").unwrap();
233 let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
234 assert!(any.is_c());
235 assert!(*any.as_c().unwrap());
236 assert!(any.into_c().unwrap());
237 });
238 }
239
240 #[test]
241 fn test_any_of_mutable_methods() {
242 let rt = Runtime::new().unwrap();
243 let ctx = Context::full(&rt).unwrap();
244
245 ctx.with(|ctx| {
246 let val: Value = ctx.eval("42").unwrap();
247 let mut any: AnyOf4<String, i32, bool, f64> = AnyOf4::from_js(&ctx, val).unwrap();
248
249 if let Some(n) = any.as_b_mut() {
250 *n = 100;
251 }
252
253 assert_eq!(any.into_b().unwrap(), 100);
254 });
255 }
256
257 #[test]
258 fn test_any_of_error_propagation() {
259 use rquickjs::{Array, Object};
260
261 let rt = Runtime::new().unwrap();
262 let ctx = Context::full(&rt).unwrap();
263
264 ctx.with(|ctx| {
265 let val: Value = ctx.eval("42").unwrap();
267 let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
268 assert!(any.is_b());
269
270 let val: Value = ctx.eval("null").unwrap();
272 let result: Result<AnyOf2<Object, Array>> = AnyOf2::from_js(&ctx, val);
273 assert!(result.is_err());
274 });
275 }
276
277 #[test]
278 fn test_any_of_conversion_order() {
279 let rt = Runtime::new().unwrap();
280 let ctx = Context::full(&rt).unwrap();
281
282 ctx.with(|ctx| {
283 let val: Value = ctx.eval("42").unwrap();
287
288 let any: AnyOf3<String, i32, f64> = AnyOf3::from_js(&ctx, val).unwrap();
290 assert!(any.is_b());
291
292 let val: Value = ctx.eval("3.14").unwrap();
294 let any: AnyOf3<String, f64, i32> = AnyOf3::from_js(&ctx, val).unwrap();
295 assert!(any.is_b()); });
297 }
298}