bitwarden_ffi_macro/
wasm_record.rs1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use syn::{Item, parse2};
4
5use crate::attrs;
6
7pub(crate) fn wasm_record(attr: TokenStream, item: TokenStream) -> TokenStream {
10 if !attr.is_empty() {
11 return syn::Error::new_spanned(attr, "#[wasm_record] takes no arguments")
12 .to_compile_error();
13 }
14
15 let item = match parse2::<Item>(item) {
16 Ok(item) => item,
17 Err(err) => return err.to_compile_error(),
18 };
19
20 if let Err(err) = attrs::check_exportable_type(&item, "wasm_record") {
21 return err.to_compile_error();
22 }
23
24 let item = item.to_token_stream();
27 quote! {
28 #[cfg_attr(
29 feature = "wasm",
30 derive(::bitwarden_ffi::_macro::Tsify),
31 tsify(into_wasm_abi, from_wasm_abi)
32 )]
33 #item
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 fn expand(item: TokenStream) -> String {
42 wasm_record(TokenStream::new(), item)
43 .to_string()
44 .replace(' ', "")
45 }
46
47 #[test]
48 fn derives_tsify_for_both_abi_directions() {
49 let out = expand(quote! {
50 #[derive(Serialize, Deserialize)]
51 pub struct Point { pub x: i32 }
52 });
53
54 assert!(!out.contains("compile_error!"), "{out}");
55 assert!(
56 out.contains("derive(::bitwarden_ffi::_macro::Tsify)"),
57 "{out}"
58 );
59 assert!(out.contains("tsify(into_wasm_abi,from_wasm_abi)"), "{out}");
60 assert!(out.contains("feature=\"wasm\""), "{out}");
61 }
62
63 #[test]
64 fn leaves_tsify_and_serde_attributes_in_place() {
65 let out = expand(quote! {
67 #[derive(Serialize, Deserialize)]
68 #[serde(rename_all = "camelCase")]
69 pub struct Cipher {
70 #[cfg_attr(feature = "wasm", tsify(optional))]
71 pub name: Option<String>,
72 }
73 });
74
75 assert!(out.contains("#[serde(rename_all=\"camelCase\")]"), "{out}");
76 assert!(
77 out.contains("#[cfg_attr(feature=\"wasm\",tsify(optional))]"),
78 "{out}"
79 );
80 }
81
82 #[test]
83 fn rejects_arguments() {
84 let out = wasm_record(quote!(into_wasm_abi), quote! { pub struct Point; })
85 .to_string()
86 .replace(' ', "");
87
88 assert!(out.contains("compile_error!"), "{out}");
89 assert!(out.contains("takesnoarguments"), "{out}");
90 }
91
92 #[test]
93 fn rejects_generics() {
94 let out = expand(quote! {
95 pub struct Wrapper<T> { pub inner: T }
96 });
97
98 assert!(out.contains("compile_error!"), "{out}");
99 assert!(out.contains("doesnotsupportgenerics"), "{out}");
100 }
101
102 #[test]
103 fn rejects_an_item_that_is_not_a_struct_or_an_enum() {
104 let out = expand(quote! {
105 pub fn point() {}
106 });
107
108 assert!(out.contains("compile_error!"), "{out}");
109 assert!(out.contains("appliestoastructoranenum"), "{out}");
110 }
111}