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}