Skip to main content

bity_ic_candid_gen/
lib.rs

1//! Procedural macros for generating Candid method implementations.
2//!
3//! This module provides procedural macros to generate boilerplate code for Candid
4//! method implementations in Internet Computer canisters. It supports both query
5//! and update methods, with and without arguments.
6//!
7//! # Features
8//! - Generation of Candid method implementations
9//! - Support for query and update methods
10//! - Support for methods with and without arguments
11//! - Automatic type generation for Args and Response
12//!
13//! # Examples
14//! ```
15//! use bity_ic_candid_gen::*;
16//!
17//! // Generate a method with arguments
18//! generate_candid_method!(my_canister, transfer, update);
19//!
20//! // Generate a method without arguments
21//! generate_candid_method_no_args!(my_canister, get_balance, query);
22//! ```
23
24use proc_macro::TokenStream;
25use quote::{format_ident, quote};
26use syn::punctuated::Punctuated;
27use syn::{parse_macro_input, Ident, Token};
28
29/// Represents the attributes needed to generate a Candid method.
30///
31/// This struct contains the information required to generate a method implementation,
32/// including the canister name, method name, and method type (query/update).
33struct MethodAttribute {
34    /// The name of the canister (without the "_canister" suffix)
35    canister_name: String,
36    /// The name of the method to generate
37    method_name: String,
38    /// The type of method ("query" or "update")
39    method_type: String,
40}
41
42/// Generates a Candid method implementation with arguments.
43///
44/// This procedural macro generates a method implementation with the specified
45/// arguments and response types. The generated method will be marked with the
46/// appropriate Candid method attribute.
47///
48/// # Arguments
49/// The macro takes three comma-separated identifiers:
50/// * `canister_name` - The name of the canister (without "_canister" suffix)
51/// * `method_name` - The name of the method to generate
52/// * `method_type` - The type of method ("query" or "update")
53///
54/// # Returns
55/// A TokenStream containing the generated method implementation.
56///
57/// # Example
58/// ```
59/// use bity_ic_candid_gen::generate_candid_method;
60///
61/// generate_candid_method!(my_canister, transfer, update);
62/// ```
63#[proc_macro]
64pub fn generate_candid_method(input: TokenStream) -> TokenStream {
65    let inputs = parse_macro_input!(input with Punctuated::<Ident, Token![,]>::parse_terminated)
66        .into_iter()
67        .map(|i| i.to_string())
68        .collect();
69
70    let attribute = get_method_attribute(inputs);
71
72    let canister_name = format_ident!("{}", attribute.canister_name);
73    let method_name = format_ident!("{}", attribute.method_name);
74    let method_type = format_ident!("{}", attribute.method_type);
75
76    let args_name = quote! { #canister_name::#method_name::Args };
77    let response_name = quote! { #canister_name::#method_name::Response };
78
79    let tokens = quote! {
80        #[candid::candid_method(#method_type)]
81        fn #method_name(_: #args_name) -> #response_name {
82            unimplemented!();
83        }
84    };
85
86    TokenStream::from(tokens)
87}
88
89/// Generates a Candid method implementation without arguments.
90///
91/// This procedural macro generates a method implementation without arguments,
92/// only returning a response type. The generated method will be marked with the
93/// appropriate Candid method attribute.
94///
95/// # Arguments
96/// The macro takes three comma-separated identifiers:
97/// * `canister_name` - The name of the canister (without "_canister" suffix)
98/// * `method_name` - The name of the method to generate
99/// * `method_type` - The type of method ("query" or "update")
100///
101/// # Returns
102/// A TokenStream containing the generated method implementation.
103///
104/// # Example
105/// ```
106/// use bity_ic_candid_gen::generate_candid_method_no_args;
107///
108/// generate_candid_method_no_args!(my_canister, get_balance, query);
109/// ```
110#[proc_macro]
111pub fn generate_candid_method_no_args(input: TokenStream) -> TokenStream {
112    let inputs = parse_macro_input!(input with Punctuated::<Ident, Token![,]>::parse_terminated)
113        .into_iter()
114        .map(|i| i.to_string())
115        .collect();
116
117    let attribute = get_method_attribute(inputs);
118
119    let canister_name = format_ident!("{}", attribute.canister_name);
120    let method_name = format_ident!("{}", attribute.method_name);
121    let method_type = format_ident!("{}", attribute.method_type);
122
123    let response_name = quote! { #canister_name::#method_name::Response };
124
125    let tokens = quote! {
126        #[candid::candid_method(#method_type)]
127        fn #method_name() -> #response_name {
128            unimplemented!();
129        }
130    };
131
132    TokenStream::from(tokens)
133}
134
135/// Extracts method attributes from the input tokens.
136///
137/// This function processes the input tokens to create a `MethodAttribute` struct
138/// containing the canister name, method name, and method type.
139///
140/// # Arguments
141/// * `inputs` - A vector of strings containing the input tokens
142///
143/// # Returns
144/// A `MethodAttribute` struct with the processed information.
145///
146/// # Panics
147/// Panics if:
148/// - The input vector has fewer than 3 elements
149/// - The method type is not "query" or "update"
150fn get_method_attribute(inputs: Vec<String>) -> MethodAttribute {
151    let first_arg = inputs.get(0).unwrap();
152    let second_arg = inputs.get(1).unwrap();
153    let third_arg = inputs.get(2).unwrap();
154
155    let canister_name = format!("{first_arg}_canister");
156
157    let method_name = second_arg.to_string();
158
159    let method_type = match third_arg.as_str() {
160        "query" | "update" => third_arg.to_string(),
161        _ => panic!("Unrecognised 'method_type' value: {third_arg}"),
162    };
163
164    MethodAttribute {
165        canister_name,
166        method_name,
167        method_type,
168    }
169}