ffi_macros/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#![crate_type = "proc-macro"]

use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Field, Fields, ItemEnum, ItemFn, ItemStruct, Type, TypePath};

#[proc_macro_attribute]
pub fn ffi_func(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let js_name = &input.sig.ident.to_string().to_case(Case::Camel);
    let vis = &input.vis;
    let attrs = &input.attrs;
    let sig = &input.sig;
    let body = &input.block;

    let output = quote! {
        #(#attrs)*
        #[cfg_attr(feature = "ffi_wasm", wasm_bindgen(js_name = #js_name))]
        #[cfg_attr(feature = "ffi_uniffi", uniffi::export)]
        #vis #sig #body
    };

    output.into()
}

#[proc_macro_attribute]
pub fn ffi_record(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut input = parse_macro_input!(item as ItemStruct);

    if let Fields::Named(ref mut fields) = input.fields {
        for field in &mut fields.named {
            if is_option_type(field) {
                add_option_field_attributes(field);
            }
        }
    }

    let struct_attrs = quote! {
        #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
        #[cfg_attr(feature = "ffi_wasm", derive(Tsify))]
        #[cfg_attr(
            feature = "ffi_wasm",
            tsify(into_wasm_abi, from_wasm_abi, large_number_types_as_bigints)
        )]
        #[cfg_attr(feature = "ffi_wasm", serde(rename_all = "camelCase"))]
        #[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))]
    };

    // Combine original attributes with new ones
    let combined = quote! {
        #struct_attrs
        #input
    };

    combined.into()
}

#[proc_macro_attribute]
pub fn ffi_enum(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemEnum);

    let enum_attrs = quote! {
        #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
        #[cfg_attr(feature = "ffi_wasm", derive(Tsify))]
        #[cfg_attr(
            feature = "ffi_wasm",
            tsify(into_wasm_abi, from_wasm_abi, large_number_types_as_bigints)
        )]
        #[cfg_attr(feature = "ffi_wasm", serde(rename_all = "camelCase"))]
        #[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Enum))]
    };

    // Combine original attributes with new ones
    let combined = quote! {
        #enum_attrs
        #input
    };

    combined.into()
}

fn is_option_type(field: &Field) -> bool {
    if let Type::Path(TypePath { path, .. }) = &field.ty {
        if let Some(segment) = path.segments.last() {
            return segment.ident == "Option";
        }
    }
    false
}

fn add_option_field_attributes(field: &mut Field) {
    let wasm_attr: syn::Attribute =
        syn::parse_quote!(#[cfg_attr(feature = "ffi_wasm", tsify(optional))]);
    field.attrs.push(wasm_attr);

    let field_name = field.ident.to_token_stream().to_string();

    if field_name != "genesis_id" && field_name != "genesis_hash" {
        let uniffi_attr: syn::Attribute =
            syn::parse_quote!(#[cfg_attr(feature = "ffi_uniffi", uniffi(default = None))]);
        field.attrs.push(uniffi_attr);
    }
}