1#![deny(missing_docs)]
7
8mod stable_enum;
9mod utils;
10
11use proc_macro::TokenStream;
12use proc_macro2::{Ident, Span};
13use syn::{
14 __private::{quote::quote, TokenStream2},
15 parse_macro_input, Fields, ItemEnum,
16};
17
18use crate::utils::{concat, snakify};
19
20#[proc_macro_derive(GraphQLMutationRoot)]
24pub fn derive_mutation_root(input: TokenStream) -> TokenStream {
25 let input = parse_macro_input!(input as ItemEnum);
26 generate_mutation_root_code(input, "linera_sdk").into()
27}
28
29#[proc_macro_derive(GraphQLMutationRootInCrate)]
32pub fn derive_mutation_root_in_crate(input: TokenStream) -> TokenStream {
33 let input = parse_macro_input!(input as ItemEnum);
34 generate_mutation_root_code(input, "crate").into()
35}
36
37#[proc_macro_derive(StableEnum)]
47pub fn derive_stable_enum(input: TokenStream) -> TokenStream {
48 let input = parse_macro_input!(input as ItemEnum);
49 stable_enum::generate_all(&input, stable_enum::CrateRoot::LineraSdk)
50 .unwrap_or_else(|err| err.to_compile_error())
51 .into()
52}
53
54#[proc_macro_derive(StableEnumInCrate)]
58pub fn derive_stable_enum_in_crate(input: TokenStream) -> TokenStream {
59 let input = parse_macro_input!(input as ItemEnum);
60 stable_enum::generate_all(&input, stable_enum::CrateRoot::Crate)
61 .unwrap_or_else(|err| err.to_compile_error())
62 .into()
63}
64
65fn generate_mutation_root_code(input: ItemEnum, crate_root: &str) -> TokenStream2 {
66 let crate_root = Ident::new(crate_root, Span::call_site());
67 let enum_name = input.ident;
68 let mutation_root_name = concat(&enum_name, "MutationRoot");
69 let mut methods = vec![];
70
71 for variant in input.variants {
72 let variant_name = &variant.ident;
73 let function_name = snakify(variant_name);
74 match variant.fields {
75 Fields::Named(named) => {
76 let mut fields = vec![];
77 let mut field_names = vec![];
78 for field in named.named {
79 let name = field.ident.expect("named fields always have names");
80 let ty = field.ty;
81 fields.push(quote! {#name: #ty});
82 field_names.push(name);
83 }
84 methods.push(quote! {
85 async fn #function_name(&self, #(#fields,)*) -> [u8; 0] {
86 let operation = #enum_name::#variant_name {
87 #(#field_names,)*
88 };
89
90 self.runtime.schedule_operation(&operation);
91
92 []
93 }
94 });
95 }
96 Fields::Unnamed(unnamed) => {
97 let mut fields = vec![];
98 let mut field_names = vec![];
99 for (i, field) in unnamed.unnamed.iter().enumerate() {
100 let name = concat(&syn::parse_str::<Ident>("field").unwrap(), &i.to_string());
101 let ty = &field.ty;
102 fields.push(quote! {#name: #ty});
103 field_names.push(name);
104 }
105 methods.push(quote! {
106 async fn #function_name(&self, #(#fields,)*) -> [u8; 0] {
107 let operation = #enum_name::#variant_name(
108 #(#field_names,)*
109 );
110
111 self.runtime.schedule_operation(&operation);
112
113 []
114 }
115 });
116 }
117 Fields::Unit => {
118 methods.push(quote! {
119 async fn #function_name(&self) -> [u8; 0] {
120 let operation = #enum_name::#variant_name;
121
122 self.runtime.schedule_operation(&operation);
123
124 []
125 }
126 });
127 }
128 };
129 }
130
131 quote! {
132 pub struct #mutation_root_name<Application>
134 where
135 Application: #crate_root::Service,
136 Application::Abi: #crate_root::abi::ContractAbi<Operation = #enum_name>,
137 #crate_root::ServiceRuntime<Application>: Send + Sync,
138 {
139 runtime: ::std::sync::Arc<#crate_root::ServiceRuntime<Application>>,
140 }
141
142 #[async_graphql::Object]
143 impl<Application> #mutation_root_name<Application>
144 where
145 Application: #crate_root::Service,
146 Application::Abi: #crate_root::abi::ContractAbi<Operation = #enum_name>,
147 #crate_root::ServiceRuntime<Application>: Send + Sync,
148 {
149 #(#methods)*
150 }
151
152 impl<Application> #crate_root::graphql::GraphQLMutationRoot<Application> for #enum_name
153 where
154 Application: #crate_root::Service,
155 Application::Abi: #crate_root::abi::ContractAbi<Operation = #enum_name>,
156 #crate_root::ServiceRuntime<Application>: Send + Sync,
157 {
158 type MutationRoot = #mutation_root_name<Application>;
159
160 fn mutation_root(
161 runtime: ::std::sync::Arc<#crate_root::ServiceRuntime<Application>>,
162 ) -> Self::MutationRoot {
163 #mutation_root_name { runtime }
164 }
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use syn::{__private::quote::quote, parse_quote, ItemEnum};
172
173 use crate::generate_mutation_root_code;
174
175 fn assert_eq_no_whitespace(mut actual: String, mut expected: String) {
176 println!("{actual}");
178
179 actual.retain(|c| !c.is_whitespace());
180 expected.retain(|c| !c.is_whitespace());
181
182 assert_eq!(actual, expected);
183 }
184
185 #[test]
186 fn test_derive_mutation_root() {
187 let operation: ItemEnum = parse_quote! {
188 enum SomeOperation {
189 TupleVariant(String),
190 StructVariant {
191 a: u32,
192 b: u64
193 },
194 EmptyVariant
195 }
196 };
197
198 let output = generate_mutation_root_code(operation, "linera_sdk");
199
200 let expected = quote! {
201 pub struct SomeOperationMutationRoot<Application>
203 where
204 Application: linera_sdk::Service,
205 Application::Abi: linera_sdk::abi::ContractAbi<Operation = SomeOperation>,
206 linera_sdk::ServiceRuntime<Application>: Send + Sync,
207 {
208 runtime: ::std::sync::Arc<linera_sdk::ServiceRuntime<Application>>,
209 }
210
211 #[async_graphql::Object]
212 impl<Application> SomeOperationMutationRoot<Application>
213 where
214 Application: linera_sdk::Service,
215 Application::Abi: linera_sdk::abi::ContractAbi<Operation = SomeOperation>,
216 linera_sdk::ServiceRuntime<Application>: Send + Sync,
217 {
218 async fn tuple_variant(&self, field0: String,) -> [u8; 0] {
219 let operation = SomeOperation::TupleVariant(field0,);
220 self.runtime.schedule_operation(&operation);
221 []
222 }
223
224 async fn struct_variant(&self, a: u32, b: u64,) -> [u8; 0] {
225 let operation = SomeOperation::StructVariant { a, b, };
226 self.runtime.schedule_operation(&operation);
227 []
228 }
229
230 async fn empty_variant(&self) -> [u8; 0] {
231 let operation = SomeOperation::EmptyVariant;
232 self.runtime.schedule_operation(&operation);
233 []
234 }
235 }
236
237 impl<Application> linera_sdk::graphql::GraphQLMutationRoot<Application>
238 for SomeOperation
239 where
240 Application: linera_sdk::Service,
241 Application::Abi: linera_sdk::abi::ContractAbi<Operation = SomeOperation>,
242 linera_sdk::ServiceRuntime<Application>: Send + Sync,
243 {
244 type MutationRoot = SomeOperationMutationRoot<Application>;
245
246 fn mutation_root(
247 runtime: ::std::sync::Arc<linera_sdk::ServiceRuntime<Application>>,
248 ) -> Self::MutationRoot {
249 SomeOperationMutationRoot { runtime }
250 }
251 }
252 };
253
254 assert_eq_no_whitespace(output.to_string(), expected.to_string());
255 }
256}