zod-derive 0.1.0

Rust integraions with the `zod` typescript library.
Documentation
mod field;
mod variant;

use crate::{docs::RustDocs, expand_type_registration, impl_inventory};
use variant::Variant;

use super::args;
use proc_macro2::TokenStream;
use proc_macro_error::abort;
use quote::quote;
use serde_derive_internals::{
    ast::{self, Data},
    attr::TagType,
};

pub fn expand(
    input: args::Input,
    variants: &[args::EnumVariant],
    serde_ast: ast::Container,
    docs: RustDocs,
) -> TokenStream {
    let variant_ast = match serde_ast.data {
        Data::Enum(ref variants) => variants,
        Data::Struct(_, _) => unreachable!(),
    };

    let name = serde_ast.attrs.name().deserialize_name();
    let tag = serde_ast.attrs.tag();

    let variants = variants
        .iter()
        .zip(variant_ast.iter())
        .filter(|(_, ast)| !ast.attrs.skip_deserializing())
        .map(|(v, ast)| Variant::new(v, &serde_ast, ast))
        .collect();

    Enum {
        input,
        variants,
        name,
        tag,
        docs,
    }
    .expand()
}

struct Enum<'a> {
    input: args::Input,
    variants: Vec<Variant<'a>>,
    name: String,
    tag: &'a TagType,
    docs: RustDocs,
}

impl<'a> Enum<'a> {
    pub fn expand(&self) -> TokenStream {
        let ident = &self.input.ident;
        let name = &self.name;
        let ns_path = &self.input.namespace;
        let docs = &self.docs;
        let schema = self.expand_schema();
        let type_def = self.expand_typ_defs();

        let type_register = expand_type_registration(ident, ns_path);
        let inventory = impl_inventory::expand(ident, ns_path, name);
        let zod = crate::get_zod();

        quote! {
            impl #zod::ZodType for #ident {
                fn schema() -> String {
                    #schema
                }

                fn type_def() -> #zod::TsTypeDef {
                    #zod::TsTypeDef::Type({ #type_def })
                }

                fn inline() -> #zod::InlinedType {
                    #zod::InlinedType::Ref {
                        ns_name: <#ns_path as #zod::Namespace>::NAME,
                        name: #name
                    }
                }

                fn docs() -> Option<&'static str> {
                    Some(#docs)
                }
            }

            #inventory

            #type_register

        }
    }

    fn abort_empty(&self) -> ! {
        abort!(
            self.input.ident.span(),
            "deriving zod on empty enums is not supported."
        )
    }

    fn expand_schema(&self) -> TokenStream {
        match self.variants.len() {
            0 => self.abort_empty(),
            1 => self.variants.first().expect("one variant").expand_schema(),
            _ => {
                let expanded_variant_schemas = self.variants.iter().map(|v| v.expand_schema());

                match self.tag {
                    TagType::External => {
                        quote! {
                            let variants: ::std::vec::Vec<String> = vec![#(#expanded_variant_schemas),*];
                            format!("z.union([{}])", variants.join(", "))
                        }
                    }
                    TagType::Internal { tag } | TagType::Adjacent { tag, .. } => {
                        quote! {
                            let variants: ::std::vec::Vec<String> = vec![#(#expanded_variant_schemas),*];
                            format!("z.discriminatedUnion(\"{}\", [{}])", #tag, variants.join(", "))
                        }
                    }
                    TagType::None => {
                        quote! {
                            let variants: ::std::vec::Vec<String> = vec![#(#expanded_variant_schemas),*];
                            format!("z.union([{}])", variants.join(", "))
                        }
                    }
                }
            }
        }
    }

    fn expand_typ_defs(&self) -> TokenStream {
        match self.variants.len() {
            0 => self.abort_empty(),
            1 => self
                .variants
                .first()
                .expect("one variant")
                .expand_type_def(),
            _ => {
                let expanded_variant_type_defs = self.variants.iter().map(|v| v.expand_type_def());

                quote! {
                    let type_defs: std::vec::Vec<String> = vec![#(#expanded_variant_type_defs),*];
                    type_defs.join(" | ")
                }
            }
        }
    }
}