vertigo-macro 0.11.4

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::{DataStruct, Ident};

pub(super) fn impl_js_json_newtype(
    name: &Ident,
    data: &DataStruct,
) -> Result<TokenStream, Box<dyn Error>> {
    let mut encodes = Vec::new();
    let mut decodes = Vec::new();

    // Struct(T) <-> T
    if data.fields.len() == 1 {
        let field = data.fields.iter().next().ok_or("Expected one field")?;
        let field_opts = crate::jsjson::attributes::FieldOpts::from_attributes(&field.attrs)?;

        if field_opts.stringify {
            let is_option = match &field.ty {
                syn::Type::Path(ty_path) => ty_path
                    .path
                    .segments
                    .last()
                    .is_some_and(|last| last.ident == "Option"),
                _ => false,
            };

            if is_option {
                // Encode Option
                encodes.push(quote! {
                    match &self.0 {
                        Some(val) => vertigo::JsJson::String(format!("{}", val)),
                        None => vertigo::JsJson::Null,
                    }
                });

                // Decode Option
                decodes.push(quote! {
                    match json {
                        vertigo::JsJson::String(v) => {
                            match v.parse() {
                                Ok(v) => Ok(Self(Some(v))),
                                Err(e) => {
                                    let message = format!("Error parsing string '{}': {}", v, e);
                                    Err(ctx.add(message))
                                }
                            }
                        },
                        vertigo::JsJson::Null => Ok(Self(None)),
                        other => {
                            let message = ["String or null expected, received ", other.typename()].concat();
                            Err(ctx.add(message))
                        }
                    }
                });
            } else {
                // Encode
                encodes.push(quote! {
                    vertigo::JsJson::String(format!("{}", self.0))
                });

                // Decode
                decodes.push(quote! {
                    match json {
                        vertigo::JsJson::String(v) => {
                            match v.parse() {
                                Ok(v) => Ok(Self(v)),
                                Err(e) => {
                                    let message = format!("Error parsing string '{}': {}", v, e);
                                    Err(ctx.add(message))
                                }
                            }
                        },
                        other => {
                            let message = ["String expected, received ", other.typename()].concat();
                            Err(ctx.add(message))
                        }
                    }
                });
            }
        } else {
            // Encode
            encodes.push(quote! {
                self.0.to_json()
            });

            // Decode
            decodes.push(quote! {
                vertigo::JsJsonDeserialize::from_json(ctx, json).map(Self)
            });
        }

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

        encodes.push(quote! {
            let #name (#(#field_idents,)*) = self;
            vertigo::JsJson::List(vec![
                #(#field_encodes)*
            ])
        });

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

        decodes.push(quote! {
            match json {
                vertigo::JsJson::List(fields) => {
                    if fields.len() != #fields_number {
                        return Err(ctx.add(
                            format!("Wrong number of fields in tuple for newtype {}. Expected {}, got {}", #name_str, #fields_number, fields.len())
                        ));
                    }
                    let mut fields_rev = fields.into_iter().rev().collect::<Vec<_>>();
                    return Ok(#name(
                        #(#field_decodes)*
                    ))
                }
                x => return Err(ctx.add(
                    format!("Invalid type {} while decoding newtype tuple, expected list", x.typename())
                )),
            }
        });
    }

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

        impl vertigo::JsJsonDeserialize for #name {
            fn from_json(
                ctx: vertigo::JsJsonContext,
                json: vertigo::JsJson,
            ) -> Result<Self, vertigo::JsJsonContext> {
                #(#decodes)*
            }
        }
    };

    Ok(result.into())
}