postcard-bindgen-core 0.5.2

A crate to generate bindings for the postcard binary format for other languages than Rust - Core Crate
Documentation
use genco::quote;

use crate::{code_gen::js::Tokens, registry::EnumType};

use super::BindingTypeGenerateable;

impl BindingTypeGenerateable for EnumType {
    fn gen_ser_body(&self) -> Tokens {
        quote!($(ser::gen_function(&self.variants)))
    }

    fn gen_des_body(&self) -> Tokens {
        quote!($(des::gen_function(&self.variants)))
    }

    fn gen_ty_check_body(&self) -> Tokens {
        ty_check::gen_check_func(&self.variants)
    }

    fn gen_ts_typings_body(&self) -> Tokens {
        ts::gen_typings(&self.variants)
    }
}

pub mod ser {

    use genco::{
        lang::js::Tokens,
        prelude::JavaScript,
        quote, quote_in,
        tokens::{quoted, FormatInto},
    };

    use crate::{
        code_gen::{
            js::{
                generateable::{container::ser, types::JsTypeGenerateable},
                Case, SwitchCase, VariableAccess, VariablePath, JS_ENUM_VARIANT_KEY,
                JS_ENUM_VARIANT_VALUE,
            },
            switch_case::DefaultCase,
        },
        registry::{EnumVariant, EnumVariantType},
    };

    pub fn gen_function(variants: impl AsRef<[EnumVariant]>) -> impl FormatInto<JavaScript> {
        let enumerated_variants = variants.as_ref().iter().enumerate();

        let mut switch_case = SwitchCase::new(quote!(v.$JS_ENUM_VARIANT_KEY));
        switch_case.extend_cases(
            enumerated_variants.map(|(index, variant)| gen_case_for_variant(index, variant)),
        );
        switch_case.default_case(DefaultCase::new_without_break(
            quote!(throw "variant not implemented"),
        ));

        switch_case
    }

    enum CaseBody {
        Body(Tokens),
        None,
    }

    impl FormatInto<JavaScript> for CaseBody {
        fn format_into(self, tokens: &mut genco::Tokens<JavaScript>) {
            quote_in! { *tokens =>
                $(match self {
                    CaseBody::Body(b) => $b,
                    CaseBody::None => ()
                })
            }
        }
    }

    fn gen_case_for_variant(index: usize, variant: &EnumVariant) -> Case {
        let variant_name = quoted(variant.name);
        let variable_path = VariablePath::default()
            .modify_push(VariableAccess::Field(JS_ENUM_VARIANT_VALUE.into()));
        let body = match &variant.inner_type {
            EnumVariantType::Empty => CaseBody::None,
            EnumVariantType::Tuple(fields) => CaseBody::Body(match fields.len() {
                1 => quote!($(fields[0].gen_ser_accessor(variable_path));),
                _ => ser::gen_accessors_indexed(fields, variable_path),
            }),
            EnumVariantType::NewType(fields) => {
                CaseBody::Body(ser::gen_accessors_fields(fields, variable_path))
            }
        };

        Case::new(
            variant_name,
            quote! {
                s.serialize_number(U32_BYTES, false, $index);
                $body
            },
        )
    }
}

pub mod des {
    use genco::{
        lang::js::Tokens,
        prelude::JavaScript,
        quote, quote_in,
        tokens::{quoted, FormatInto},
    };

    use crate::{
        code_gen::{
            js::{
                generateable::{container::des, types::JsTypeGenerateable},
                Case, DefaultCase, FieldAccessor, SwitchCase, JS_ENUM_VARIANT_KEY,
                JS_ENUM_VARIANT_VALUE,
            },
            utils::{JoinType, TokensIterExt},
        },
        registry::{EnumVariant, EnumVariantType},
    };

    pub fn gen_function(variants: impl AsRef<[EnumVariant]>) -> impl FormatInto<JavaScript> {
        let enumerated_variants = variants.as_ref().iter().enumerate();

        let mut switch_case = SwitchCase::new(quote!(d.deserialize_number(U32_BYTES, false)));
        switch_case.extend_cases(
            enumerated_variants.map(|(index, variant)| gen_case_for_variant(index, variant)),
        );
        switch_case.default_case(DefaultCase::new_without_break(
            quote!(throw "variant not implemented"),
        ));

        switch_case
    }

    enum CaseBody {
        Body(Tokens),
        None,
    }

    impl FormatInto<JavaScript> for CaseBody {
        fn format_into(self, tokens: &mut genco::Tokens<JavaScript>) {
            quote_in! { *tokens =>
                $(match self {
                    CaseBody::Body(b) => {$JS_ENUM_VARIANT_VALUE: $b},
                    CaseBody::None => ()
                })
            }
        }
    }

    fn gen_case_for_variant(index: usize, variant: &EnumVariant) -> Case {
        let variant_name = quoted(variant.name);
        let body = match &variant.inner_type {
            EnumVariantType::Empty => CaseBody::None,
            EnumVariantType::NewType(fields) => CaseBody::Body(des::gen_accessors_fields(fields)),
            EnumVariantType::Tuple(fields) => CaseBody::Body(match fields.len() {
                1 => fields[0].gen_des_accessor(FieldAccessor::None),
                _ => des::gen_accessors_indexed(fields),
            }),
        };

        let body = [quote!($JS_ENUM_VARIANT_KEY: $variant_name), quote!($body)]
            .into_iter()
            .join_with([JoinType::Comma, JoinType::LineBreak]);

        Case::new_without_break(
            index,
            quote! {
                return {
                    $body
                };
            },
        )
    }
}

pub mod ty_check {
    use genco::{lang::js::Tokens, quote, tokens::quoted};

    use crate::{
        code_gen::{
            js::{
                generateable::{container::ty_check, types::JsTypeGenerateable},
                VariableAccess, VariablePath, JS_ENUM_VARIANT_KEY, JS_ENUM_VARIANT_VALUE,
                JS_OBJECT_VARIABLE,
            },
            utils::TokensIterExt,
        },
        registry::{EnumVariant, EnumVariantType},
    };

    pub fn gen_check_func(variants: impl AsRef<[EnumVariant]>) -> Tokens {
        let enumerated_variants = variants.as_ref().iter().enumerate();
        let simple_variants = enumerated_variants
            .to_owned()
            .filter(|(_, v)| matches!(v.inner_type, EnumVariantType::Empty));
        let complex_variants = enumerated_variants
            .to_owned()
            .filter(|(_, v)| !matches!(v.inner_type, EnumVariantType::Empty));

        let simple_variant_checks = gen_simple_type_checks(simple_variants);
        let complex_variant_checks = gen_complex_type_checks(complex_variants);

        [simple_variant_checks, complex_variant_checks]
            .into_iter()
            .flatten()
            .join_logic_or()
    }

    fn gen_simple_type_checks<'a>(
        variants: impl Iterator<Item = (usize, &'a EnumVariant)> + Clone,
    ) -> Option<Tokens> {
        if variants.to_owned().count() == 0 {
            None
        } else {
            let variant_checks = variants
                .map(|(_, variant)| quote!(v.$JS_ENUM_VARIANT_KEY === $(quoted(variant.name))))
                .join_logic_or();
            let type_check = simple_enum_type_check();
            Some(quote!(($type_check && $variant_checks)))
        }
    }

    fn gen_complex_type_checks<'a>(
        variants: impl Iterator<Item = (usize, &'a EnumVariant)> + Clone,
    ) -> Option<Tokens> {
        if variants.to_owned().count() == 0 {
            None
        } else {
            let variant_checks = variants.map(|(_, variant)| {
                let inner_type_checks = gen_variant_check(variant);
                quote!((v.$JS_ENUM_VARIANT_KEY === $(quoted(variant.name)) && $inner_type_checks))
            }).join_logic_or();
            let type_check = complex_enum_type_check();
            Some(quote!(($type_check && $variant_checks)))
        }
    }

    fn gen_variant_check(variant: &EnumVariant) -> Tokens {
        let variable_path = VariablePath::new("v".into())
            .modify_push(VariableAccess::Field(JS_ENUM_VARIANT_VALUE.into()));
        match &variant.inner_type {
            EnumVariantType::Empty => unreachable!(),
            EnumVariantType::NewType(fields) => ty_check::gen_object_checks(fields, variable_path),
            EnumVariantType::Tuple(fields) => match fields.len() {
                1 => fields[0].gen_ty_check(variable_path),
                _ => ty_check::gen_array_checks(fields, variable_path),
            },
        }
    }

    fn simple_enum_type_check() -> Tokens {
        quote!(typeof $JS_OBJECT_VARIABLE === "object" && $(quoted(JS_ENUM_VARIANT_KEY)) in $JS_OBJECT_VARIABLE)
    }

    fn complex_enum_type_check() -> Tokens {
        quote!(typeof $JS_OBJECT_VARIABLE === "object" && $(quoted(JS_ENUM_VARIANT_KEY)) in $JS_OBJECT_VARIABLE && $(quoted(JS_ENUM_VARIANT_VALUE)) in $JS_OBJECT_VARIABLE)
    }
}

pub mod ts {
    use genco::{prelude::js::Tokens, quote, tokens::quoted};

    use crate::{
        code_gen::{
            js::{
                generateable::{container, types::JsTypeGenerateable},
                JS_ENUM_VARIANT_KEY, JS_ENUM_VARIANT_VALUE,
            },
            utils::TokensIterExt,
        },
        registry::{EnumVariant, EnumVariantType},
    };

    pub fn gen_typings(variants: impl AsRef<[EnumVariant]>) -> Tokens {
        let body = variants
            .as_ref()
            .iter()
            .map(gen_variant_typings)
            .join_with_vertical_line();
        quote!($body)
    }

    fn gen_variant_typings(variant: &EnumVariant) -> Tokens {
        let name = quoted(variant.name);
        match &variant.inner_type {
            EnumVariantType::Empty => quote!({ $JS_ENUM_VARIANT_KEY: $name }),
            t => {
                let body = match t {
                    EnumVariantType::Tuple(t) => match t.len() {
                        1 => t[0].gen_ts_type(),
                        _ => container::ts::gen_typings_indexed(t),
                    },
                    EnumVariantType::NewType(n) => container::ts::gen_typings_fields(n),
                    _ => unreachable!(),
                };
                quote!({ $JS_ENUM_VARIANT_KEY: $name, $JS_ENUM_VARIANT_VALUE: $body })
            }
        }
    }
}