You can probably get pretty far with ordinary declarative macros, as well. The first step would be to transform the code into something like this, which has as much repetition as you can come up with:
(NB: This is all untested, as the playground doesn't have access to the js_sys
crate)
impl TryFrom<js_sys::Array> for JsMessageType {
type Error = JsValue;
fn try_from(array: js_sys::Array) -> Result<Self, Self::Error> {
let mut iter = array.into_iter();
let discriminant: String = iter.next()?
.dyn_into::<js_sys::JsString>()?
.into();
match discriminant.as_str() {
"MessageA" => {
Ok(JsMessageType::MessageA(
iter.next()?.dyn_into()?,
iter.next()?.dyn_into()?,
iter.next()?.dyn_into()?,
))
},
"MessageB" => {
Ok(JsMessageType::MessageB(
iter.next()?.dyn_into()?,
iter.next()?.dyn_into()?,
))
}
_ => Err(JsValue::from_str("Unknown variant"))
}
}
}
Then, you can write a macro to simplify the construction of individual variants:
impl TryFrom<js_sys::Array> for JsMessageType {
type Error = JsValue;
fn try_from(array: js_sys::Array) -> Result<Self, Self::Error> {
let mut iter = array.into_iter();
let discriminant: String = iter.next()?
.dyn_into::<js_sys::JsString>()?
.into();
macro_rules! parse_msg! {
$(variant::ident, 0) => { JsMessageType::$variant() };
$(variant::ident, 1) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
) };
$(variant::ident, 2) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
) };
$(variant::ident, 3) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
) };
$(variant::ident, 4) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
) };
}
match discriminant.as_str() {
"MessageA" => Ok(parse_msg!(MessageA, 3)),
"MessageB" => Ok(parse_msg!(MessageB, 2)),
_ => Err(JsValue::from_str("Unknown variant"))
}
}
}
From there, you can write another to implement the match statement and flag a compile error if you forget to add a new variant:
impl TryFrom<js_sys::Array> for JsMessageType {
type Error = JsValue;
fn try_from(array: js_sys::Array) -> Result<Self, Self::Error> {
let mut iter = array.into_iter();
let discriminant: String = iter.next()?
.dyn_into::<js_sys::JsString>()?
.into();
macro_rules! parse_msg! {
$(variant::ident, 0) => { JsMessageType::$variant() };
$(variant::ident, 1) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
) };
$(variant::ident, 2) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
) };
$(variant::ident, 3) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
) };
$(variant::ident, 4) => { JsMessageType::$variant(
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
iter.next()?.dyn_into()?
) };
}
macro_rules! parse_enum {
{$($variant:ident: $nargs:literal),*} => {{
/// This will trigger a compiler error if any variant is missing
let _ = |x:JsMessageType| match x {
$($variant(..) => ()),
};
match discriminant.as_str() {
$(stringify!($variant) => Ok(parse_msg!($variant, $nargs)),)
_ => Err(JsValue::from_str("Unknown variant"))
}
}}
}
parse_enum! {
MessageA: 3,
MessageB: 2
}
}
}