aboutsummaryrefslogtreecommitdiff
path: root/urpc-macro/src
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-07-16 10:46:41 +0100
committerdiogo464 <[email protected]>2025-07-16 10:46:41 +0100
commitf319d7ab5278a3cfb43d38875d81c28cc2dce1e1 (patch)
treecb161fd990643e267bbc373fb09ccd7b689a23b5 /urpc-macro/src
Initial commit - extracted urpc from monorepo
Diffstat (limited to 'urpc-macro/src')
-rw-r--r--urpc-macro/src/lib.rs282
1 files changed, 282 insertions, 0 deletions
diff --git a/urpc-macro/src/lib.rs b/urpc-macro/src/lib.rs
new file mode 100644
index 0000000..4facc60
--- /dev/null
+++ b/urpc-macro/src/lib.rs
@@ -0,0 +1,282 @@
1use proc_macro2::{Punct, Spacing, Span, TokenStream};
2use quote::{ToTokens, TokenStreamExt};
3use syn::parse::Parse;
4
5struct Service {
6 vis: syn::Visibility,
7 ident: syn::Ident,
8 error: syn::Type,
9 methods: Vec<Method>,
10}
11
12impl Service {
13 fn server_trait_ident(&self) -> syn::Ident {
14 self.ident.clone()
15 }
16
17 fn client_struct_ident(&self) -> syn::Ident {
18 syn::Ident::new(&format!("{}Client", self.ident), Span::call_site())
19 }
20}
21
22impl Parse for Service {
23 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
24 let vis = input.parse()?;
25 input.parse::<syn::Token![trait]>()?;
26 let ident = input.parse()?;
27
28 let content;
29 syn::braced!(content in input);
30
31 content.parse::<syn::Token![type]>()?;
32 let error_ident = content.parse::<syn::Ident>()?;
33 if error_ident != "Error" {
34 return Err(syn::Error::new_spanned(
35 error_ident,
36 "expected Error associated type",
37 ));
38 }
39 content.parse::<syn::Token![=]>()?;
40 let error = content.parse()?;
41 content.parse::<syn::Token![;]>()?;
42
43 let mut methods = Vec::default();
44 while !content.is_empty() {
45 let method = content.parse()?;
46 methods.push(method);
47 }
48
49 Ok(Self {
50 vis,
51 ident,
52 error,
53 methods,
54 })
55 }
56}
57
58struct MethodArg {
59 ident: syn::Ident,
60 ty: syn::Type,
61}
62
63impl ToTokens for MethodArg {
64 fn to_tokens(&self, tokens: &mut TokenStream) {
65 self.ident.to_tokens(tokens);
66 tokens.append(Punct::new(':', Spacing::Alone));
67 self.ty.to_tokens(tokens);
68 }
69}
70
71impl Parse for MethodArg {
72 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
73 let ident = input.parse()?;
74 input.parse::<syn::Token![:]>()?;
75 let ty = input.parse()?;
76 Ok(Self { ident, ty })
77 }
78}
79
80struct Method {
81 ident: syn::Ident,
82 arguments: Vec<MethodArg>,
83 return_ty: Option<syn::Type>,
84}
85
86impl Parse for Method {
87 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
88 input.parse::<syn::Token![async]>()?;
89 input.parse::<syn::Token![fn]>()?;
90 let ident = input.parse::<syn::Ident>()?;
91 let arguments_content;
92 syn::parenthesized!(arguments_content in input);
93
94 let mut arguments = Vec::default();
95 while !arguments_content.is_empty() {
96 if !arguments.is_empty() {
97 arguments_content.parse::<syn::Token![,]>()?;
98 }
99 let argument = arguments_content.parse::<MethodArg>()?;
100 arguments.push(argument);
101 }
102
103 let return_ty = match input.peek(syn::Token![->]) {
104 true => {
105 input.parse::<syn::Token![->]>()?;
106 Some(input.parse()?)
107 }
108 false => None,
109 };
110
111 input.parse::<syn::Token![;]>()?;
112
113 Ok(Self {
114 ident,
115 arguments,
116 return_ty,
117 })
118 }
119}
120
121fn generate_service_into_service_match_arm(service: &Service, method: &Method) -> TokenStream {
122 let method_ident = &method.ident;
123 let method_name = method.ident.to_string();
124 let server_trait = service.server_trait_ident();
125 let arg_idents = method
126 .arguments
127 .iter()
128 .map(|arg| &arg.ident)
129 .collect::<Vec<_>>();
130
131 quote::quote! {
132 #method_name => {
133 let ((#(#arg_idents),*), _) =
134 urpc::internal::bincode::serde::decode_from_slice(&arguments, urpc::internal::bincode::config::standard()).map_err(std::io::Error::other)?;
135 let ctx = Default::default();
136 let ret = <T as #server_trait>::#method_ident(&self.0, ctx, #(#arg_idents),*).await;
137 let value = urpc::internal::bincode::serde::encode_to_vec(&ret, urpc::internal::bincode::config::standard()).map_err(std::io::Error::other)?;
138 Ok(From::from(value))
139 }
140 }
141}
142
143fn generate_service_into_service(service: &Service) -> TokenStream {
144 let server_trait = service.server_trait_ident();
145 let service_name = service.ident.to_string();
146 let match_arms = service
147 .methods
148 .iter()
149 .map(|m| generate_service_into_service_match_arm(service, m))
150 .collect::<Vec<_>>();
151 quote::quote! {
152 fn into_service(self) -> impl urpc::Service {
153 struct Adapter<T>(T);
154
155 impl<T> urpc::Service for Adapter<T>
156 where
157 T: #server_trait,
158 {
159 fn name() -> &'static str {
160 #service_name
161 }
162
163 fn call(
164 &self,
165 method: String,
166 arguments: urpc::internal::bytes::Bytes,
167 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<urpc::internal::bytes::Bytes>> + Send + '_>> {
168 Box::pin(async move {
169 match method.as_str() {
170 #(#match_arms)*
171 _ => panic!("unknown method for service {}", "Router"),
172 }
173 })
174 }
175 }
176
177 Adapter(self)
178 }
179 }
180}
181
182fn generate_service_server_method(service: &Service, method: &Method) -> TokenStream {
183 let ident = &method.ident;
184 let service_error = &service.error;
185 let args = &method.arguments;
186 let return_ty = match &method.return_ty {
187 Some(t) => quote::quote! { std::result::Result<#t, #service_error> },
188 None => quote::quote! { std::result::Result<(), #service_error> },
189 };
190 quote::quote! {
191 fn #ident(&self, ctx: urpc::Context, #(#args),*) -> impl std::future::Future<Output = #return_ty> + Send;
192 }
193}
194
195fn generate_service_server(service: &Service) -> TokenStream {
196 let vis = &service.vis;
197 let ident = service.server_trait_ident();
198 let methods = service
199 .methods
200 .iter()
201 .map(|m| generate_service_server_method(service, m));
202 let into_service = generate_service_into_service(service);
203
204 quote::quote! {
205 #vis trait #ident : Sized + Send + Sync + 'static {
206 #(#methods)*
207 #into_service
208 }
209 }
210}
211
212fn generate_service_client_method(service: &Service, method: &Method) -> TokenStream {
213 let service_name = service.ident.to_string();
214 let service_error = &service.error;
215 let method_ident = &method.ident;
216 let method_string = method.ident.to_string();
217 let method_arg_idents = method
218 .arguments
219 .iter()
220 .map(|arg| &arg.ident)
221 .collect::<Vec<_>>();
222 let method_arg_types = method.arguments.iter().map(|arg| &arg.ty);
223 let method_return_type = match &method.return_ty {
224 Some(ty) => quote::quote! { #ty },
225 None => quote::quote! { () },
226 };
227 quote::quote! {
228 pub async fn #method_ident(&self, #(#method_arg_idents: #method_arg_types),*) -> std::result::Result<#method_return_type, urpc::protocol::RpcError<#service_error>> {
229 let service = String::from(#service_name);
230 let method = String::from(#method_string);
231 let arguments = (#(#method_arg_idents),*);
232 let arguments = match urpc::internal::bincode::serde::encode_to_vec(&arguments, urpc::internal::bincode::config::standard()) {
233 Ok(arguments) => From::from(arguments),
234 Err(err) => return Err(urpc::protocol::RpcError::Transport(std::io::Error::other(err))),
235 };
236 let response = match self.channel.call(service, method, arguments).await {
237 Ok(response) => response,
238 Err(err) => return Err(urpc::protocol::RpcError::Transport(err)),
239 };
240 match urpc::internal::bincode::serde::decode_from_slice::<std::result::Result<#method_return_type, #service_error>, _>(&response, urpc::internal::bincode::config::standard()) {
241 Ok((result, _)) => result.map_err(urpc::protocol::RpcError::Remote),
242 Err(err) => Err(urpc::protocol::RpcError::Transport(std::io::Error::other(err))),
243 }
244 }
245 }
246}
247
248fn generate_service_client(service: &Service) -> TokenStream {
249 let vis = &service.vis;
250 let client_ident = service.client_struct_ident();
251 let methods = service
252 .methods
253 .iter()
254 .map(|m| generate_service_client_method(service, m));
255 quote::quote! {
256 #vis struct #client_ident {
257 channel: urpc::ClientChannel
258 }
259
260 impl #client_ident {
261 pub fn new(channel: urpc::ClientChannel) -> Self {
262 Self { channel }
263 }
264
265 #(#methods)*
266 }
267 }
268}
269
270#[proc_macro_attribute]
271pub fn service(
272 _attr: proc_macro::TokenStream,
273 item: proc_macro::TokenStream,
274) -> proc_macro::TokenStream {
275 let service = syn::parse_macro_input!(item as Service);
276 let service_server = generate_service_server(&service);
277 let service_client = generate_service_client(&service);
278 From::from(quote::quote! {
279 #service_server
280 #service_client
281 })
282}