1use proc_macro::TokenStream;
4use proc_macro_crate::{FoundCrate, crate_name};
5use quote::{format_ident, quote};
6use syn::spanned::Spanned;
7use syn::{Token, parse::Parser, punctuated::Punctuated};
8
9fn engine_crate() -> proc_macro2::TokenStream {
12 match crate_name("galeon-engine").expect("galeon-engine must be in Cargo.toml") {
13 FoundCrate::Itself => quote!(crate),
14 FoundCrate::Name(name) => {
15 let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
16 quote!(#ident)
17 }
18 }
19}
20
21#[proc_macro_derive(Component)]
26pub fn derive_component(input: TokenStream) -> TokenStream {
27 let input = syn::parse_macro_input!(input as syn::DeriveInput);
28 let name = &input.ident;
29 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
30
31 let expanded = quote! {
32 impl #impl_generics galeon_engine::component::Component for #name #ty_generics #where_clause {}
33 };
34
35 TokenStream::from(expanded)
36}
37
38fn validate_struct(item: &syn::Item) -> Result<&syn::ItemStruct, syn::Error> {
44 match item {
45 syn::Item::Struct(s) => {
46 if let syn::Fields::Unnamed(_) = &s.fields {
47 Err(syn::Error::new(
48 s.fields.span(),
49 "galeon protocol macros do not support tuple structs",
50 ))
51 } else {
52 Ok(s)
53 }
54 }
55 syn::Item::Enum(e) => Err(syn::Error::new(
56 e.enum_token.span(),
57 "galeon protocol macros do not support enums",
58 )),
59 _ => Err(syn::Error::new(
60 proc_macro2::Span::call_site(),
61 "galeon protocol macros can only be applied to structs",
62 )),
63 }
64}
65
66fn extract_doc(attrs: &[syn::Attribute]) -> String {
68 for attr in attrs {
69 if attr.path().is_ident("doc")
70 && let syn::Meta::NameValue(nv) = &attr.meta
71 && let syn::Expr::Lit(syn::ExprLit {
72 lit: syn::Lit::Str(s),
73 ..
74 }) = &nv.value
75 {
76 return s.value().trim().to_string();
77 }
78 }
79 String::new()
80}
81
82fn type_to_string(ty: &syn::Type) -> String {
87 let raw = quote!(#ty).to_string();
88 raw.replace(" < ", "<")
89 .replace("< ", "<")
90 .replace(" >", ">")
91 .replace(" ,", ",")
92 .replace(" ::", "::")
93 .replace(":: ", "::")
94}
95
96fn parse_surfaces(attr: TokenStream) -> Result<Vec<String>, syn::Error> {
98 if attr.is_empty() {
99 return Ok(Vec::new());
100 }
101
102 let parser = Punctuated::<syn::MetaNameValue, Token![,]>::parse_terminated;
103 let args = parser.parse(attr)?;
104 let mut surfaces = Vec::new();
105 let mut saw_surface = false;
106 let mut saw_surfaces = false;
107
108 for arg in args {
109 if arg.path.is_ident("surface") {
110 if saw_surfaces {
111 return Err(syn::Error::new(
112 arg.path.span(),
113 "use either `surface = \"...\"` or `surfaces = [..]`, not both",
114 ));
115 }
116 if saw_surface {
117 return Err(syn::Error::new(
118 arg.path.span(),
119 "`surface` may only be specified once",
120 ));
121 }
122 let syn::Expr::Lit(syn::ExprLit {
123 lit: syn::Lit::Str(surface),
124 ..
125 }) = arg.value
126 else {
127 return Err(syn::Error::new(
128 arg.path.span(),
129 "`surface` must be a string literal",
130 ));
131 };
132 let val = surface.value();
133 if val.is_empty() {
134 return Err(syn::Error::new(
135 surface.span(),
136 "surface name must not be empty",
137 ));
138 }
139 saw_surface = true;
140 surfaces.push(val);
141 continue;
142 }
143
144 if arg.path.is_ident("surfaces") {
145 if saw_surface {
146 return Err(syn::Error::new(
147 arg.path.span(),
148 "use either `surface = \"...\"` or `surfaces = [..]`, not both",
149 ));
150 }
151 if saw_surfaces {
152 return Err(syn::Error::new(
153 arg.path.span(),
154 "`surfaces` may only be specified once",
155 ));
156 }
157 let syn::Expr::Array(array) = arg.value else {
158 return Err(syn::Error::new(
159 arg.path.span(),
160 "`surfaces` must be an array of string literals",
161 ));
162 };
163 for elem in array.elems {
164 let syn::Expr::Lit(syn::ExprLit {
165 lit: syn::Lit::Str(surface),
166 ..
167 }) = elem
168 else {
169 return Err(syn::Error::new(
170 arg.path.span(),
171 "`surfaces` must be an array of string literals",
172 ));
173 };
174 let surface_val = surface.value();
175 if surface_val.is_empty() {
176 return Err(syn::Error::new(
177 surface.span(),
178 "surface name must not be empty",
179 ));
180 }
181 surfaces.push(surface_val);
182 }
183 saw_surfaces = true;
184 continue;
185 }
186
187 return Err(syn::Error::new(
188 arg.path.span(),
189 "unsupported protocol attribute argument; expected `surface` or `surfaces`",
190 ));
191 }
192
193 surfaces.sort();
194 surfaces.dedup();
195 Ok(surfaces)
196}
197
198fn protocol_attr(
200 attr: TokenStream,
201 input: TokenStream,
202 marker_trait: &str,
203 kind_variant: &str,
204 extra_derives: &[&str],
205) -> TokenStream {
206 let item: syn::Item = match syn::parse(input) {
207 Ok(item) => item,
208 Err(e) => return e.to_compile_error().into(),
209 };
210
211 let s = match validate_struct(&item) {
212 Ok(s) => s,
213 Err(e) => return e.to_compile_error().into(),
214 };
215
216 let name = &s.ident;
217 let name_str = name.to_string();
218 let (impl_generics, ty_generics, where_clause) = s.generics.split_for_impl();
219
220 let krate = engine_crate();
222
223 let marker_trait_ident = syn::Ident::new(marker_trait, proc_macro2::Span::call_site());
224 let kind_variant_ident = syn::Ident::new(kind_variant, proc_macro2::Span::call_site());
225 let surfaces = match parse_surfaces(attr) {
226 Ok(surfaces) => surfaces,
227 Err(e) => return e.to_compile_error().into(),
228 };
229 let surface_literals: Vec<syn::LitStr> = surfaces
230 .iter()
231 .map(|surface| syn::LitStr::new(surface, proc_macro2::Span::call_site()))
232 .collect();
233
234 let extra: Vec<syn::Path> = extra_derives
235 .iter()
236 .map(|d| syn::parse_str(d).expect("valid derive path"))
237 .collect();
238
239 let serde_crate_path = format!("{}::serde", krate);
241
242 let doc_str = extract_doc(&s.attrs);
244 let field_entries: Vec<proc_macro2::TokenStream> = match &s.fields {
245 syn::Fields::Named(fields) => fields
246 .named
247 .iter()
248 .map(|f| {
249 let fname = f.ident.as_ref().unwrap().to_string();
250 let ftype = type_to_string(&f.ty);
251 quote! {
252 #krate::manifest::FieldEntry {
253 name: #fname,
254 ty: #ftype,
255 }
256 }
257 })
258 .collect(),
259 _ => Vec::new(), };
261
262 let expanded = quote! {
263 #[derive(#krate::serde::Serialize, #krate::serde::Deserialize, #(#extra),*)]
264 #[serde(crate = #serde_crate_path)]
265 #item
266
267 impl #impl_generics #krate::protocol::#marker_trait_ident for #name #ty_generics #where_clause {}
268
269 impl #impl_generics #krate::protocol::ProtocolMeta for #name #ty_generics #where_clause {
270 fn name() -> &'static str {
271 #name_str
272 }
273 fn kind() -> #krate::protocol::ProtocolKind {
274 #krate::protocol::ProtocolKind::#kind_variant_ident
275 }
276 }
277
278 #krate::inventory::submit! {
279 #krate::manifest::ProtocolRegistration {
280 name: #name_str,
281 kind: #krate::protocol::ProtocolKind::#kind_variant_ident,
282 fields: &[#(#field_entries),*],
283 doc: #doc_str,
284 surfaces: &[#(#surface_literals),*],
285 }
286 }
287 };
288
289 expanded.into()
290}
291
292#[proc_macro_attribute]
306pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
307 protocol_attr(attr, input, "Command", "Command", &[])
308}
309
310#[proc_macro_attribute]
321pub fn query(attr: TokenStream, input: TokenStream) -> TokenStream {
322 protocol_attr(attr, input, "ProtocolQuery", "Query", &[])
323}
324
325#[proc_macro_attribute]
339pub fn event(attr: TokenStream, input: TokenStream) -> TokenStream {
340 protocol_attr(attr, input, "Event", "Event", &[])
341}
342
343#[proc_macro_attribute]
356pub fn dto(attr: TokenStream, input: TokenStream) -> TokenStream {
357 protocol_attr(attr, input, "Dto", "Dto", &["Clone"])
358}
359
360fn extract_result_pair(ty: &syn::Type) -> Result<(syn::Type, syn::Type), syn::Error> {
366 if let syn::Type::Path(type_path) = ty
367 && let Some(segment) = type_path.path.segments.last()
368 && segment.ident == "Result"
369 && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
370 {
371 let type_args: Vec<_> = args.args.iter().collect();
372 if type_args.len() == 2 {
373 let ok_type = match type_args[0] {
374 syn::GenericArgument::Type(t) => t.clone(),
375 _ => {
376 return Err(syn::Error::new_spanned(
377 type_args[0],
378 "expected type parameter for Result Ok type",
379 ));
380 }
381 };
382 let err_type = match type_args[1] {
383 syn::GenericArgument::Type(t) => t.clone(),
384 _ => {
385 return Err(syn::Error::new_spanned(
386 type_args[1],
387 "expected type parameter for Result Err type",
388 ));
389 }
390 };
391 return Ok((ok_type, err_type));
392 }
393 }
394
395 Err(syn::Error::new_spanned(
396 ty,
397 "#[handler] functions must return `Result<R, E>`",
398 ))
399}
400
401fn handler_attr(_attr: TokenStream, input: TokenStream) -> TokenStream {
403 let func: syn::ItemFn = match syn::parse(input) {
404 Ok(f) => f,
405 Err(e) => return e.to_compile_error().into(),
406 };
407
408 if !matches!(func.vis, syn::Visibility::Public(_)) {
412 return syn::Error::new(func.sig.ident.span(), "#[handler] functions must be `pub`")
413 .to_compile_error()
414 .into();
415 }
416
417 if !func.sig.generics.params.is_empty() {
419 return syn::Error::new(
420 func.sig.generics.params.first().unwrap().span(),
421 "#[handler] functions must not be generic — each handler must have concrete request/response types",
422 )
423 .to_compile_error()
424 .into();
425 }
426
427 if let Some(async_token) = func.sig.asyncness {
429 return syn::Error::new(
430 async_token.span(),
431 "#[handler] functions must be synchronous (not `async`)",
432 )
433 .to_compile_error()
434 .into();
435 }
436
437 if func.sig.inputs.is_empty() {
439 return syn::Error::new(
440 func.sig.paren_token.span.join(),
441 "#[handler] functions must have at least one parameter (the request type)",
442 )
443 .to_compile_error()
444 .into();
445 }
446
447 let first_param = func.sig.inputs.first().unwrap();
449 let (request_type, request_type_str) = match first_param {
450 syn::FnArg::Typed(pat_type) => (pat_type.ty.clone(), type_to_string(&pat_type.ty)),
451 syn::FnArg::Receiver(r) => {
452 return syn::Error::new(
453 r.self_token.span(),
454 "#[handler] functions must not have `self` as the first parameter",
455 )
456 .to_compile_error()
457 .into();
458 }
459 };
460
461 let extra_param_types: Vec<Box<syn::Type>> = func
463 .sig
464 .inputs
465 .iter()
466 .skip(1)
467 .filter_map(|arg| match arg {
468 syn::FnArg::Typed(pat_type) => Some(pat_type.ty.clone()),
469 _ => None,
470 })
471 .collect();
472
473 let return_type = match &func.sig.output {
475 syn::ReturnType::Default => {
476 return syn::Error::new(
477 func.sig.paren_token.span.close(),
478 "#[handler] functions must return `Result<R, E>`",
479 )
480 .to_compile_error()
481 .into();
482 }
483 syn::ReturnType::Type(_, ty) => ty,
484 };
485
486 let (response_type, err_type) = match extract_result_pair(return_type) {
487 Ok(types) => types,
488 Err(e) => return e.to_compile_error().into(),
489 };
490 let response_type_str = type_to_string(&response_type);
491 let error_type_str = type_to_string(&err_type);
492
493 let krate = engine_crate();
496 let name_str = func.sig.ident.to_string();
497 let fn_ident = &func.sig.ident;
498
499 let params_tuple = if extra_param_types.is_empty() {
502 quote!(())
503 } else {
504 let types = &extra_param_types;
505 quote!((#(#types,)*))
506 };
507
508 let json_shim_ident = format_ident!("{}__galeon_axum_json", fn_ident);
509
510 let expanded = quote! {
511 #func
512
513 #[doc(hidden)]
515 #[allow(non_snake_case)]
516 pub fn #json_shim_ident(
517 json: &str,
518 world: &mut #krate::World,
519 ) -> Result<#krate::serde_json::Value, String> {
520 let mut h = #krate::handler_function::IntoHandler::<
521 #request_type,
522 #response_type,
523 #params_tuple,
524 >::into_handler(#fn_ident, #name_str);
525 #krate::run_json_handler_value(&mut *h, json, world)
526 }
527
528 #krate::inventory::submit! {
529 #krate::manifest::HandlerRegistration {
530 name: #name_str,
531 module_path: module_path!(),
532 request_type: #request_type_str,
533 response_type: #response_type_str,
534 error_type: #error_type_str,
535 }
536 }
537
538 const _: () = {
542 fn _assert_into_handler<F, Req, Resp, Params>(
543 _f: F,
544 ) where
545 F: #krate::handler_function::IntoHandler<Req, Resp, Params>,
546 {}
547
548 fn _check() {
549 _assert_into_handler::<_, #request_type, _, #params_tuple>(#fn_ident);
550 }
551 };
552 };
553
554 expanded.into()
555}
556
557#[proc_macro_attribute]
578pub fn handler(attr: TokenStream, input: TokenStream) -> TokenStream {
579 handler_attr(attr, input)
580}