vertigo-macro 0.13.1

Reactive Real-DOM library with SSR for Rust - macros
Documentation
use darling::FromAttributes;
use proc_macro::TokenStream;
use quote::quote;
use std::error::Error;
use syn::{DataEnum, Fields, Ident, ext::IdentExt};

use crate::jsjson::{
    attributes::{ContainerOpts, FieldOpts},
    is_vec_u8, js_json_object, object_ident,
};

// {
//   "Somestring": "foobar"
// }
//
// {
//   "Point": { "x": 10, "y": "one" }
// }
//
// {
//   "Tuple": ["two", 20]
// }
//
// "Nothing"
pub(super) fn impl_js_json_enum(
    name: &Ident,
    data: &DataEnum,
    container_opts: ContainerOpts,
) -> Result<TokenStream, Box<dyn Error>> {
    let object = object_ident();

    // Encoding code for every variant
    let mut variant_encodes = vec![];

    // Encoding code for every simple variant (data-less)
    let mut variant_string_decodes = vec![];

    // Envoding code for every compound variant (with data)
    let mut variant_object_decodes = vec![];

    for variant in &data.variants {
        let field_opts = FieldOpts::from_attributes(&variant.attrs)?;
        let variant_ident = &variant.ident;
        let variant_name = variant.ident.unraw().to_string();

        let json_key = match field_opts.rename {
            Some(json_key) => json_key,
            None => match container_opts.rename_all {
                Some(rule) => rule.rename(&variant_name),
                None => variant_name.clone(),
            },
        };

        match &variant.fields {
            // Simple variant
            // Enum::Variant <-> "Variant"
            Fields::Unit => {
                variant_encodes.push(quote! { Self::#variant_ident => #json_key.to_json(), });
                variant_string_decodes.push(quote! { #json_key => Ok(Self::#variant_ident), });
            }

            // Compound variant with unnamed field(s) (tuple)
            // Enum::Variant(...) <-> "Variant": ...
            Fields::Unnamed(fields) => {
                // Enum::Variant(T) <-> "Variant": T
                if fields.unnamed.len() == 1 {
                    // Encode
                    let encoded = js_json_object(&[quote! {
                        vertigo::object_insert(&mut #object, #json_key, value.to_json());
                    }]);

                    variant_encodes.push(quote! {
                        Self::#variant_ident(value) => #encoded,
                    });

                    // Decode
                    variant_object_decodes.push(quote! {
                        if let Some(value) = compound_variant.get_mut(#json_key) {
                            return Ok(Self::#variant_ident(
                                vertigo::JsJsonDeserialize::from_json(ctx.clone(), value.to_owned())?
                            ))
                        }
                    });

                // Enum::Variant(T1, T2...) <-> "Variant": [T1, T2, ...]
                } else {
                    // Encode
                    let (field_idents, field_encodes) =
                        super::tuple_fields::get_encodes(fields.unnamed.iter());

                    let encoded = js_json_object(&[quote! {
                        vertigo::object_insert(
                            &mut #object,
                            #json_key,
                            vertigo::JsJson::List(::std::vec![#(#field_encodes)*]),
                        );
                    }]);

                    variant_encodes.push(quote! {
                        Self::#variant_ident(#(#field_idents,)*) => #encoded,
                    });

                    // Decode
                    let fields_number = field_idents.len();
                    let field_decodes = super::tuple_fields::get_decodes(field_idents);

                    variant_object_decodes.push(quote! {
                        if let Some(value) = compound_variant.get_mut(#json_key) {
                            match value.to_owned() {
                                vertigo::JsJson::List(fields) => {
                                    if fields.len() != #fields_number {
                                        return Err(ctx.add(
                                            format!("Wrong unmber of fields in tuple for variant {}. Expected {}, got {}", #variant_name, #fields_number, fields.len())
                                        ));
                                    }
                                    let mut fields_rev = fields.into_iter().rev().collect::<Vec<_>>();
                                    return Ok(Self::#variant_ident (
                                        #(#field_decodes)*
                                    ))
                                },
                                x => return Err(ctx.add(
                                    format!("Invalid type {} while decoding enum tuple, expected list", x.typename())
                                )),
                            }
                        }
                    });
                }
            }

            // Compound variant with named field(s) (anonymous struct)
            // Enum::Variant { x: X, y: Y, ...) <-> "Variant": { x: X, y: Y, ... }
            Fields::Named(fields) => {
                // Encode
                let field_idents = fields
                    .named
                    .iter()
                    .filter_map(|field| field.ident.clone())
                    .collect::<Vec<_>>();

                let mut field_encodes = fields
                    .named
                    .iter()
                    .filter_map(|field| Some((field.ident.clone()?, &field.ty)))
                    .map(|(field_ident, field_ty)| {
                        let field_name = field_ident.unraw().to_string();

                        // Same treatment a struct field of this type gets - see `is_vec_u8`.
                        let insert = if is_vec_u8(field_ty) {
                            quote! {
                                vertigo::object_insert(&mut #object, #field_name, vertigo::JsJson::Vec(#field_ident));
                            }
                        } else {
                            quote! {
                                vertigo::object_insert(&mut #object, #field_name, #field_ident.to_json());
                            }
                        };

                        (field_name, insert)
                    })
                    .collect::<Vec<_>>();

                // Ascending key order, for the same reason the struct encoder sorts: an
                // insert that lands past everything already in the map shifts nothing.
                field_encodes.sort_by(|(left, _), (right, _)| left.cmp(right));
                let field_encodes = field_encodes
                    .into_iter()
                    .map(|(_, tokens)| tokens)
                    .collect::<Vec<_>>();

                let inner = js_json_object(&field_encodes);
                let encoded = js_json_object(&[quote! {
                    vertigo::object_insert(&mut #object, #json_key, #inner);
                }]);

                variant_encodes.push(quote! {
                    Self::#variant_ident {#(#field_idents,)*} => #encoded,
                });

                // Decode
                let field_decodes = fields
                    .named
                    .iter()
                    .filter_map(|field| Some((field.ident.clone()?, &field.ty)))
                    .map(|(field_ident, field_ty)| {
                        let field_name = field_ident.unraw().to_string();

                        if is_vec_u8(field_ty) {
                            return quote! {
                                #field_ident: value
                                    .get_property_jsjson(&ctx, #field_name)
                                    .and_then(|item| match item {
                                        vertigo::JsJson::Vec(data) => Ok(data),
                                        other => {
                                            let message = [
                                                "Vec<u8> expected, received ",
                                                other.typename(),
                                            ].concat();
                                            Err(ctx.add(message))
                                        }
                                    })?,
                            };
                        }

                        quote! {
                            #field_ident: value.get_property(&ctx, #field_name)?,
                        }
                    })
                    .collect::<Vec<_>>();

                variant_object_decodes.push(quote! {
                    if let Some(value) = compound_variant.get_mut(#json_key) {
                        return Ok(Self::#variant_ident {
                            #(#field_decodes)*
                        })
                    }
                });
            }
        }
    }

    let result = quote! {
        impl vertigo::JsJsonSerialize for #name {
            fn to_json(self) -> vertigo::JsJson {
                match self {
                    #(#variant_encodes)*
                }
            }
        }

        impl vertigo::JsJsonDeserialize for #name {
            fn from_json(
                ctx: vertigo::JsJsonContext,
                json: vertigo::JsJson,
            ) -> Result<Self, vertigo::JsJsonContext> {
                match json {
                    vertigo::JsJson::String(simple_variant) => {
                        match simple_variant.as_str() {
                            #(#variant_string_decodes)*
                            x => Err(ctx.add(format!("Invalid simple variant {x}"))),
                        }
                    }
                    vertigo::JsJson::Object(mut compound_variant) => {
                        #(#variant_object_decodes)*
                        Err(ctx.add("Value not matched with any variant".to_string()))
                    }
                    x => Err(ctx.add(
                        format!("Invalid type {} while decoding enum, expected string or object", x.typename())
                    )),
                }
            }
        }
    };

    Ok(result.into())
}