bitwarden_error_macro/flat/
attribute.rs

1use quote::quote;
2use syn::Data;
3
4pub(crate) fn bitwarden_error_flat(
5    input: &syn::DeriveInput,
6    type_identifier: &proc_macro2::Ident,
7    export_as_identifier: &proc_macro2::Ident,
8) -> proc_macro::TokenStream {
9    match &input.data {
10        Data::Enum(data) => {
11            let variant_names = data.variants.iter().map(|variant| &variant.ident);
12            let match_arms = data.variants.iter().map(|variant| {
13                let variant_ident = &variant.ident;
14                let variant_str = variant_ident.to_string();
15
16                match variant.fields {
17                    syn::Fields::Unit => {
18                        quote! {
19                            #type_identifier::#variant_ident => #variant_str
20                        }
21                    }
22                    syn::Fields::Named(_) => {
23                        quote! {
24                            #type_identifier::#variant_ident { .. } => #variant_str
25                        }
26                    }
27                    syn::Fields::Unnamed(_) => {
28                        quote! {
29                            #type_identifier::#variant_ident(..) => #variant_str
30                        }
31                    }
32                }
33            });
34
35            let wasm = cfg!(feature = "wasm").then(|| {
36                flat_error_wasm(
37                    type_identifier,
38                    export_as_identifier,
39                    &variant_names.collect::<Vec<_>>(),
40                )
41            });
42
43            quote! {
44                #input
45                #wasm
46
47                #[automatically_derived]
48                impl ::bitwarden_error::flat_error::FlatError for #type_identifier {
49                    fn error_variant(&self) -> &'static str {
50                        match &self {
51                            #(#match_arms), *
52                        }
53                    }
54                }
55            }
56            .into()
57        }
58        _ => syn::Error::new_spanned(input, "bitwarden_error can only be used with enums")
59            .to_compile_error()
60            .into(),
61    }
62}
63
64fn flat_error_wasm(
65    type_identifier: &proc_macro2::Ident,
66    export_as_identifier: &proc_macro2::Ident,
67    variant_names: &[&proc_macro2::Ident],
68) -> proc_macro2::TokenStream {
69    let export_as_identifier_str = export_as_identifier.to_string();
70    let is_error_function_name = format!("is{}", export_as_identifier);
71    let ts_variant_names = variant_names
72        .iter()
73        .map(|vn| format!(r#""{vn}""#))
74        .collect::<Vec<String>>()
75        .join("|");
76    let ts_code_str = format!(
77        r##"r#"
78            export interface {export_as_identifier_str} extends Error {{
79                name: "{export_as_identifier_str}";
80                variant: {ts_variant_names};
81            }};
82
83            export function {is_error_function_name}(error: any): error is {export_as_identifier_str};
84        "#"##,
85    );
86    let ts_code: proc_macro2::TokenStream = ts_code_str
87        .parse()
88        .expect("Could not generate TypeScript code");
89
90    quote! {
91        const _: () = {
92            use bitwarden_error::wasm_bindgen::prelude::*;
93            use bitwarden_error::wasm_bindgen as wasm_bindgen;
94
95            #[wasm_bindgen(typescript_custom_section)]
96            const TS_APPEND_CONTENT: &'static str = #ts_code;
97
98            #[wasm_bindgen(js_name = #is_error_function_name, skip_typescript)]
99            pub fn is_error(error: &JsValue) -> bool {
100                let name_js_value = ::bitwarden_error::js_sys::Reflect::get(&error, &JsValue::from_str("name")).unwrap_or(JsValue::NULL);
101                let name = name_js_value.as_string().unwrap_or_default();
102                name == #export_as_identifier_str
103            }
104
105            #[automatically_derived]
106            impl From<#type_identifier> for JsValue {
107                fn from(error: #type_identifier) -> Self {
108                    let js_error = ::bitwarden_error::wasm::SdkJsError::new(error.to_string());
109                    js_error.set_name(#export_as_identifier_str.to_owned());
110                    js_error.set_variant(::bitwarden_error::flat_error::FlatError::error_variant(&error).to_owned());
111                    js_error.into()
112                }
113            }
114        };
115    }
116}