linera_sdk_derive/
lib.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! The procedural macros for the crate `linera-sdk`.
5
6mod utils;
7
8use proc_macro::TokenStream;
9use proc_macro2::{Ident, Span};
10use syn::{
11    parse_macro_input, Fields, ItemEnum,
12    __private::{quote::quote, TokenStream2},
13};
14
15use crate::utils::{concat, snakify};
16
17#[proc_macro_derive(GraphQLMutationRoot)]
18pub fn derive_mutation_root(input: TokenStream) -> TokenStream {
19    let input = parse_macro_input!(input as ItemEnum);
20    generate_mutation_root_code(input, "linera_sdk").into()
21}
22
23#[proc_macro_derive(GraphQLMutationRootInCrate)]
24pub fn derive_mutation_root_in_crate(input: TokenStream) -> TokenStream {
25    let input = parse_macro_input!(input as ItemEnum);
26    generate_mutation_root_code(input, "crate").into()
27}
28
29fn generate_mutation_root_code(input: ItemEnum, crate_root: &str) -> TokenStream2 {
30    let crate_root = Ident::new(crate_root, Span::call_site());
31    let enum_name = input.ident;
32    let mutation_root_name = concat(&enum_name, "MutationRoot");
33    let mut methods = vec![];
34
35    for variant in input.variants {
36        let variant_name = &variant.ident;
37        let function_name = snakify(variant_name);
38        match variant.fields {
39            Fields::Named(named) => {
40                let mut fields = vec![];
41                let mut field_names = vec![];
42                for field in named.named {
43                    let name = field.ident.expect("named fields always have names");
44                    let ty = field.ty;
45                    fields.push(quote! {#name: #ty});
46                    field_names.push(name);
47                }
48                methods.push(quote! {
49                    async fn #function_name(&self, #(#fields,)*) -> [u8; 0] {
50                        let operation = #enum_name::#variant_name {
51                            #(#field_names,)*
52                        };
53
54                        self.runtime.schedule_operation(&operation);
55
56                        []
57                    }
58                });
59            }
60            Fields::Unnamed(unnamed) => {
61                let mut fields = vec![];
62                let mut field_names = vec![];
63                for (i, field) in unnamed.unnamed.iter().enumerate() {
64                    let name = concat(&syn::parse_str::<Ident>("field").unwrap(), &i.to_string());
65                    let ty = &field.ty;
66                    fields.push(quote! {#name: #ty});
67                    field_names.push(name);
68                }
69                methods.push(quote! {
70                    async fn #function_name(&self, #(#fields,)*) -> [u8; 0] {
71                        let operation = #enum_name::#variant_name(
72                            #(#field_names,)*
73                        );
74
75                        self.runtime.schedule_operation(&operation);
76
77                        []
78                    }
79                });
80            }
81            Fields::Unit => {
82                methods.push(quote! {
83                    async fn #function_name(&self) -> [u8; 0] {
84                        let operation = #enum_name::#variant_name;
85
86                        self.runtime.schedule_operation(&operation);
87
88                        []
89                    }
90                });
91            }
92        };
93    }
94
95    quote! {
96        /// Mutation root
97        pub struct #mutation_root_name<Application>
98        where
99            Application: #crate_root::Service,
100            Application::Abi: #crate_root::abi::ContractAbi<Operation = #enum_name>,
101            #crate_root::ServiceRuntime<Application>: Send + Sync,
102        {
103            runtime: ::std::sync::Arc<#crate_root::ServiceRuntime<Application>>,
104        }
105
106        #[async_graphql::Object]
107        impl<Application> #mutation_root_name<Application>
108        where
109            Application: #crate_root::Service,
110            Application::Abi: #crate_root::abi::ContractAbi<Operation = #enum_name>,
111            #crate_root::ServiceRuntime<Application>: Send + Sync,
112        {
113            #(#methods)*
114        }
115
116        impl<Application> #crate_root::graphql::GraphQLMutationRoot<Application> for #enum_name
117        where
118            Application: #crate_root::Service,
119            Application::Abi: #crate_root::abi::ContractAbi<Operation = #enum_name>,
120            #crate_root::ServiceRuntime<Application>: Send + Sync,
121        {
122            type MutationRoot = #mutation_root_name<Application>;
123
124            fn mutation_root(
125                runtime: ::std::sync::Arc<#crate_root::ServiceRuntime<Application>>,
126            ) -> Self::MutationRoot {
127                #mutation_root_name { runtime }
128            }
129        }
130    }
131}
132
133#[cfg(test)]
134pub mod tests {
135    use syn::{parse_quote, ItemEnum, __private::quote::quote};
136
137    use crate::generate_mutation_root_code;
138
139    fn assert_eq_no_whitespace(mut actual: String, mut expected: String) {
140        // Intentionally left here for debugging purposes
141        println!("{}", actual);
142
143        actual.retain(|c| !c.is_whitespace());
144        expected.retain(|c| !c.is_whitespace());
145
146        assert_eq!(actual, expected);
147    }
148
149    #[test]
150    fn test_derive_mutation_root() {
151        let operation: ItemEnum = parse_quote! {
152            enum SomeOperation {
153                TupleVariant(String),
154                StructVariant {
155                    a: u32,
156                    b: u64
157                },
158                EmptyVariant
159            }
160        };
161
162        let output = generate_mutation_root_code(operation, "linera_sdk");
163
164        let expected = quote! {
165            /// Mutation root
166            pub struct SomeOperationMutationRoot<Application>
167            where
168                Application: linera_sdk::Service,
169                Application::Abi: linera_sdk::abi::ContractAbi<Operation = SomeOperation>,
170                linera_sdk::ServiceRuntime<Application>: Send + Sync,
171            {
172                runtime: ::std::sync::Arc<linera_sdk::ServiceRuntime<Application>>,
173            }
174
175            #[async_graphql::Object]
176            impl<Application> SomeOperationMutationRoot<Application>
177            where
178                Application: linera_sdk::Service,
179                Application::Abi: linera_sdk::abi::ContractAbi<Operation = SomeOperation>,
180                linera_sdk::ServiceRuntime<Application>: Send + Sync,
181            {
182                async fn tuple_variant(&self, field0: String,) -> [u8; 0] {
183                    let operation = SomeOperation::TupleVariant(field0,);
184                    self.runtime.schedule_operation(&operation);
185                    []
186                }
187
188                async fn struct_variant(&self, a: u32, b: u64,) -> [u8; 0] {
189                    let operation = SomeOperation::StructVariant { a, b, };
190                    self.runtime.schedule_operation(&operation);
191                    []
192                }
193
194                async fn empty_variant(&self) -> [u8; 0] {
195                    let operation = SomeOperation::EmptyVariant;
196                    self.runtime.schedule_operation(&operation);
197                    []
198                }
199            }
200
201            impl<Application> linera_sdk::graphql::GraphQLMutationRoot<Application>
202                for SomeOperation
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                type MutationRoot = SomeOperationMutationRoot<Application>;
209
210                fn mutation_root(
211                    runtime: ::std::sync::Arc<linera_sdk::ServiceRuntime<Application>>,
212                ) -> Self::MutationRoot {
213                    SomeOperationMutationRoot { runtime }
214                }
215            }
216        };
217
218        assert_eq_no_whitespace(output.to_string(), expected.to_string());
219    }
220}