mod codegen_helpers;
mod ffi_gen;
mod manifest;
mod type_mapping;
use ffi_gen::generate_ffi_wrapper;
use manifest::{generate_manifest_json, manifest_const_name, SdkModule};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, ImplItem, ItemImpl, Meta};
#[proc_macro_attribute]
pub fn goud_api(attr: TokenStream, item: TokenStream) -> TokenStream {
let attrs = parse_macro_input!(attr as GoudApiAttrs);
let input = parse_macro_input!(item as ItemImpl);
match expand_goud_api(attrs, input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
struct GoudApiAttrs {
module: String,
feature: Option<String>,
}
impl syn::parse::Parse for GoudApiAttrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut module = None;
let mut feature = None;
while !input.is_empty() {
let ident: syn::Ident = input.parse()?;
let _eq: syn::Token![=] = input.parse()?;
let value: syn::LitStr = input.parse()?;
match ident.to_string().as_str() {
"module" => module = Some(value.value()),
"feature" => feature = Some(value.value()),
other => {
return Err(syn::Error::new(
ident.span(),
format!("Unknown goud_api attribute: {}", other),
));
}
}
if input.peek(syn::Token![,]) {
let _comma: syn::Token![,] = input.parse()?;
}
}
let module = module.ok_or_else(|| {
syn::Error::new(
proc_macro2::Span::call_site(),
"goud_api requires `module = \"...\"` attribute",
)
})?;
Ok(GoudApiAttrs { module, feature })
}
}
fn expand_goud_api(attrs: GoudApiAttrs, input: ItemImpl) -> syn::Result<TokenStream2> {
let module_name = &attrs.module;
let self_type = extract_self_type(&input)?;
let mut ffi_functions = Vec::new();
let mut manifest_methods = Vec::new();
for item in &input.items {
if let ImplItem::Fn(method) = item {
if has_skip_attr(method) {
continue;
}
if !matches!(method.vis, syn::Visibility::Public(_)) {
continue;
}
let name_override = extract_name_attr(method);
if let Some(generated) =
generate_ffi_wrapper(method, module_name, &self_type, name_override.as_deref())
{
ffi_functions.push(generated.tokens);
manifest_methods.push(generated.manifest);
}
}
}
let sdk_module = SdkModule {
name: module_name.clone(),
feature: attrs.feature.clone(),
methods: manifest_methods,
functions: vec![],
};
let manifest_json = generate_manifest_json(&sdk_module);
let const_name = syn::Ident::new(
&manifest_const_name(module_name),
proc_macro2::Span::call_site(),
);
let manifest_const = quote! {
#[doc(hidden)]
#[used]
static #const_name: &str = #manifest_json;
};
let ffi_block = if let Some(ref feature) = attrs.feature {
let feature_ident = syn::LitStr::new(feature, proc_macro2::Span::call_site());
quote! {
#[cfg(feature = #feature_ident)]
#[doc(hidden)]
pub mod __goud_generated_ffi {
use super::*;
#(#ffi_functions)*
}
}
} else {
quote! {
#[doc(hidden)]
pub mod __goud_generated_ffi {
use super::*;
#(#ffi_functions)*
}
}
};
let mut cleaned_input = input.clone();
for item in &mut cleaned_input.items {
if let ImplItem::Fn(method) = item {
method
.attrs
.retain(|attr| !attr.path().is_ident("goud_api"));
}
}
Ok(quote! {
#cleaned_input
#ffi_block
#manifest_const
})
}
fn extract_self_type(input: &ItemImpl) -> syn::Result<String> {
if let syn::Type::Path(type_path) = &*input.self_ty {
if let Some(segment) = type_path.path.segments.last() {
return Ok(segment.ident.to_string());
}
}
Err(syn::Error::new_spanned(
&input.self_ty,
"goud_api: cannot determine self type from impl block",
))
}
fn has_skip_attr(method: &syn::ImplItemFn) -> bool {
for attr in &method.attrs {
if attr.path().is_ident("goud_api") {
if let Ok(Meta::List(meta_list)) = attr.parse_args::<Meta>() {
if meta_list.path.is_ident("skip") {
return true;
}
}
if let Ok(ident) = attr.parse_args::<syn::Ident>() {
if ident == "skip" {
return true;
}
}
}
}
false
}
fn extract_name_attr(method: &syn::ImplItemFn) -> Option<String> {
for attr in &method.attrs {
if attr.path().is_ident("goud_api") {
if let Ok(name_value) = attr.parse_args_with(|input: syn::parse::ParseStream| {
let ident: syn::Ident = input.parse()?;
if ident != "name" {
return Err(syn::Error::new(ident.span(), "expected `name`"));
}
let _eq: syn::Token![=] = input.parse()?;
let value: syn::LitStr = input.parse()?;
Ok(value.value())
}) {
return Some(name_value);
}
}
}
None
}