1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::meta::ParseNestedMeta;
4use syn::{parse_macro_input, Attribute, FnArg, ItemFn, Lifetime, Pat};
5
6use syn::fold::{self, Fold};
7use syn::{AngleBracketedGenericArguments, GenericArgument, Type, TypeReference};
8
9const OUTPUT_ID_ARG: &str = "output_id";
12
13const BIG_TYPES_NAMES: &[&str] = &[
15 "EventLog",
16 "OCEL",
17 "EventLogActivityProjection",
18 "SlimLinkedOCEL",
19 "IndexLinkedOCEL",
20 "TabularSource",
21];
22
23struct LifetimeStripper;
25
26impl Fold for LifetimeStripper {
27 fn fold_type_reference(&mut self, mut node: TypeReference) -> TypeReference {
29 node.lifetime = Some(syn::Lifetime::new("'_", proc_macro2::Span::call_site()));
31 fold::fold_type_reference(self, node)
33 }
34
35 fn fold_angle_bracketed_generic_arguments(
37 &mut self,
38 mut node: AngleBracketedGenericArguments,
39 ) -> AngleBracketedGenericArguments {
40 node.args = node
42 .args
43 .into_iter()
44 .map(|arg| {
45 if matches!(arg, GenericArgument::Lifetime(_)) {
46 GenericArgument::Lifetime(Lifetime::new("'_", proc_macro2::Span::call_site()))
47 } else {
48 arg
49 }
50 })
51 .collect();
52
53 fold::fold_angle_bracketed_generic_arguments(self, node)
55 }
56 fn fold_type(&mut self, ty: Type) -> Type {
58 if let Type::ImplTrait(it) = &ty {
59 if it.bounds.len() != 1 {
60 return fold::fold_type(self, ty);
61 }
62 if let Some(syn::TypeParamBound::Trait(really_it)) = it.bounds.first() {
63 let really_it_str = quote::quote!(#really_it).to_string();
64 let ret = match really_it_str.as_str() {
65 "AsRef < Path >"
66 | "AsRef < std :: path :: Path >"
67 | "AsRef < path :: Path >" => {
68 syn::parse_quote!(std::path::PathBuf)
69 }
70 "AsRef < str >" => syn::parse_quote!(String),
71 "LinkedOCELAccess < 'a >" => syn::parse_quote!(
72 crate::core::event_data::object_centric::linked_ocel::SlimLinkedOCEL
73 ),
74 _ => {
75 return fold::fold_type(self, ty);
76 }
77 };
78 return ret;
79 };
80 }
81 fold::fold_type(self, ty)
82 }
83}
84
85fn strip_lifetimes(ty: Type) -> Type {
87 let mut stripper = LifetimeStripper;
88 stripper.fold_type(ty)
89}
90
91fn longest_big_type_match(s: &str) -> Option<String> {
96 BIG_TYPES_NAMES
97 .iter()
98 .filter(|tn| s.ends_with(**tn))
99 .max_by_key(|tn| tn.len())
100 .map(|s| s.to_string())
101}
102
103fn is_big_type_ref(ty: &Type) -> bool {
104 if matches!(ty, Type::Reference(_)) {
105 let ty_str = quote::quote!(#ty).to_string();
106 longest_big_type_match(&ty_str).is_some()
107 } else {
108 false
109 }
110}
111
112fn is_big_type(ty: &Type) -> Option<String> {
113 let ty_str = quote::quote!(#ty).to_string();
114 longest_big_type_match(&ty_str)
115}
116
117fn is_mut_big_type_ref(ty: &Type) -> Option<String> {
120 if let Type::Reference(TypeReference {
121 mutability: Some(_),
122 elem,
123 ..
124 }) = ty
125 {
126 let elem_str = quote::quote!(#elem).to_string();
127 longest_big_type_match(&elem_str)
128 } else {
129 None
130 }
131}
132
133#[derive(Default)]
134struct RegisterBindingAttrs {
135 stringify_error: bool,
136 debug_output: bool,
137 custom_name: Option<String>,
138 returns_handle: bool,
139}
140
141impl RegisterBindingAttrs {
142 fn parse(&mut self, meta: ParseNestedMeta) -> syn::parse::Result<()> {
143 if meta.path.is_ident("debug_output") {
144 self.debug_output = true;
145 } else if meta.path.is_ident("stringify_error") {
146 self.stringify_error = true;
147 } else if meta.path.is_ident("returns_handle") {
148 self.returns_handle = true;
149 } else if meta.path.is_ident("name") {
150 let value: syn::LitStr = meta.value()?.parse()?;
151 self.custom_name = Some(value.value());
152 } else {
153 return Err(meta.error(
154 "unknown #[register_binding] option, expected one of `debug_output`, \
155 `stringify_error`, `returns_handle`, `name = \"..\"`",
156 ));
157 }
158 Ok(())
159 }
160}
161
162struct ArgOptions {
163 default_value: Option<syn::Expr>,
164 is_state: bool,
166 is_state_mut: bool,
169 is_handle: bool,
171}
172
173fn parse_arg_attributes(attrs: &[Attribute]) -> syn::parse::Result<ArgOptions> {
174 let mut opts = ArgOptions {
175 default_value: None,
176 is_state: false,
177 is_state_mut: false,
178 is_handle: false,
179 };
180 for attr in attrs {
181 if attr.path().is_ident("bind") {
182 attr.parse_nested_meta(|meta| {
183 if meta.path.is_ident("state") {
184 opts.is_state = true;
185 } else if meta.path.is_ident("state_mut") {
186 opts.is_state_mut = true;
187 } else if meta.path.is_ident("handle") {
188 opts.is_handle = true;
189 } else if meta.path.is_ident("default") {
190 if meta.input.peek(syn::Token![=]) {
191 let expr: syn::Expr = meta.value()?.parse()?;
192 opts.default_value = Some(expr);
193 } else {
194 opts.default_value = Some(syn::parse_quote!(Default::default()));
195 }
196 } else {
197 return Err(meta.error(
198 "unknown #[bind] option, expected one of `state`, `state_mut`, \
199 `handle`, `default`, `default = <expr>`",
200 ));
201 }
202 Ok(())
203 })?;
204 }
205 }
206 Ok(opts)
207}
208
209struct ArgInfo {
211 name: String,
212 change_from_ref: bool,
213 ty_without_ref: Type,
214 opts: ArgOptions,
215 mut_big_type_name: Option<String>,
217 handle_elem: Option<(Type, bool)>,
219}
220
221impl ArgInfo {
222 fn is_mut_handle(&self) -> bool {
225 matches!(self.handle_elem, Some((_, true)))
226 }
227}
228
229#[proc_macro_attribute]
230pub fn register_binding(args: TokenStream, item: TokenStream) -> TokenStream {
231 let mut input_fn = parse_macro_input!(item as ItemFn);
232 let fn_ident = &input_fn.sig.ident;
233
234 let mut attrs = RegisterBindingAttrs::default();
235 let attr_parser = syn::meta::parser(|meta| attrs.parse(meta));
236 parse_macro_input!(args with attr_parser);
237
238 let binding_name_str = attrs.custom_name.unwrap_or_else(|| fn_ident.to_string());
239 let wrapper_name = format_ident!("{}_wrapper", fn_ident);
240
241 let docs: Vec<String> = input_fn
242 .attrs
243 .iter()
244 .filter(|attr| attr.path().is_ident("doc"))
245 .filter_map(|attr| match &attr.meta {
246 syn::Meta::NameValue(syn::MetaNameValue {
247 value:
248 syn::Expr::Lit(syn::ExprLit {
249 lit: syn::Lit::Str(s),
250 ..
251 }),
252 ..
253 }) => Some(s.value()),
254 _ => None,
255 })
256 .flat_map(|s| {
257 s.lines()
258 .map(|l| l.strip_prefix(' ').unwrap_or(l).to_string())
259 .collect::<Vec<_>>()
260 })
261 .collect();
262
263 let args_info: syn::parse::Result<Vec<ArgInfo>> = input_fn
264 .sig
265 .inputs
266 .iter()
267 .map(|arg| match arg {
268 FnArg::Typed(pat_type) => {
269 let pat = &pat_type.pat;
270 let ty = &pat_type.ty;
271
272 let arg_opts = parse_arg_attributes(&pat_type.attrs)?;
273
274 let arg_name =
275 match &**pat {
276 Pat::Ident(p) => p.ident.to_string(),
277 _ => return Err(syn::Error::new_spanned(
278 pat,
279 "#[register_binding] needs a plain `name: Type` argument, because the \
280 name is what a caller sends the argument under.",
281 )),
282 };
283
284 let ty_no_life = strip_lifetimes(*ty.clone());
285 let ty_as_str = quote::quote!(#ty_no_life).to_string();
286 let handle_elem = match (&ty_no_life, arg_opts.is_handle) {
288 (Type::Reference(r), true) => Some((*r.elem.clone(), r.mutability.is_some())),
289 (_, true) => {
290 return Err(syn::Error::new_spanned(
291 ty,
292 "#[bind(handle)] is only valid on a reference argument \
293 (`&T` or `&mut T`), where `T: CustomRegistryValue`.",
294 ))
295 }
296 (_, false) => None,
297 };
298 let change_from_ref = matches!(ty_no_life, Type::Reference(_))
299 && !arg_opts.is_handle
300 && !(BIG_TYPES_NAMES.iter().any(|tn| ty_as_str.ends_with(tn)));
301 let type_without_ref = match &ty_no_life {
302 Type::Reference(type_reference) if change_from_ref => {
303 match &*type_reference.elem {
304 Type::Slice(slice) => {
307 let elem = &slice.elem;
308 syn::parse_quote!(Vec<#elem>)
309 }
310 _ => *type_reference.elem.clone(),
311 }
312 }
313 x => x.clone(),
314 };
315 let mut_big_type_name = if handle_elem.is_some() {
316 None
317 } else {
318 is_mut_big_type_ref(&ty_no_life)
319 };
320 Ok(ArgInfo {
321 name: arg_name,
322 change_from_ref,
323 ty_without_ref: type_without_ref,
324 opts: arg_opts,
325 mut_big_type_name,
326 handle_elem,
327 })
328 }
329 FnArg::Receiver(receiver) => Err(syn::Error::new_spanned(
330 receiver,
331 "#[register_binding] is for free functions. A method's `self` has no argument \
332 name a caller could send it under.",
333 )),
334 })
335 .collect();
336
337 for input in &mut input_fn.sig.inputs {
339 if let FnArg::Typed(pat_type) = input {
340 pat_type.attrs.retain(|attr| !attr.path().is_ident("bind"));
341 }
342 }
343
344 let args_info = match args_info {
345 Ok(args_info) => args_info,
346 Err(e) => {
347 let err = e.to_compile_error();
348 return TokenStream::from(quote! { #input_fn #err });
349 }
350 };
351
352 let needs_write_lock = args_info
356 .iter()
357 .any(|a| a.mut_big_type_name.is_some() || a.is_mut_handle() || a.opts.is_state_mut);
358
359 if needs_write_lock && args_info.iter().any(|a| a.opts.is_state) {
362 let err = syn::Error::new_spanned(
363 &input_fn.sig,
364 "#[bind(state)] cannot be combined with a `&mut` big-type, `#[bind(handle)]`, or \
365 `#[bind(state_mut)]` argument. Return the value instead of taking it by `&mut`.",
366 )
367 .to_compile_error();
368 return TokenStream::from(quote! { #input_fn #err });
369 }
370
371 let state_mut_count = args_info.iter().filter(|a| a.opts.is_state_mut).count();
375 if state_mut_count > 1 {
376 let err = syn::Error::new_spanned(
377 &input_fn.sig,
378 "#[bind(state_mut)] may only be used once per binding.",
379 )
380 .to_compile_error();
381 return TokenStream::from(quote! { #input_fn #err });
382 }
383 if state_mut_count == 1
384 && args_info
385 .iter()
386 .any(|a| !a.opts.is_state_mut && (a.mut_big_type_name.is_some() || a.is_mut_handle()))
387 {
388 let err = syn::Error::new_spanned(
389 &input_fn.sig,
390 "#[bind(state_mut)] cannot be combined with a `&mut` big-type or `#[bind(handle)]` \
391 argument; it already has full mutable access to the registry, so reach that item \
392 through `state_mut` instead.",
393 )
394 .to_compile_error();
395 return TokenStream::from(quote! { #input_fn #err });
396 }
397
398 if needs_write_lock {
399 if let Some(shared) = args_info.iter().find(|a| {
400 a.mut_big_type_name.is_none()
401 && !a.is_mut_handle()
402 && !a.opts.is_state_mut
403 && (a.handle_elem.is_some() || is_big_type_ref(&a.ty_without_ref))
404 }) {
405 let name = &shared.name;
406 let err = syn::Error::new_spanned(
407 &input_fn.sig,
408 format!(
409 "#[register_binding] on a function taking a `&mut` big-type, \
410 `#[bind(handle)]`, or `#[bind(state_mut)]` argument cannot also take \
411 `{name}` by shared reference. Take it by `&mut` too, or take it by value."
412 ),
413 )
414 .to_compile_error();
415 return TokenStream::from(quote! { #input_fn #err });
416 }
417 }
418
419 let extractions = args_info.iter().map(|a| {
423 let (name, is_ref, ty_without_ref, opts) = (&a.name, a.change_from_ref, &a.ty_without_ref, &a.opts);
424 if opts.is_state {
425 return quote! { ::process_mining::bindings::StateRef::new(state) };
426 }
427 let maybe_ref = if is_ref {
428 quote! {&}
429 } else {
430 quote! {}
431 };
432 if let Some(default_expr) = &opts.default_value {
433 quote! {
434 #maybe_ref ::process_mining::bindings::extract_param::<#ty_without_ref>(arg_map, #name, state, || Some(#default_expr))?
435 }
436 } else {
437 quote! {
438 #maybe_ref ::process_mining::bindings::extract_param::<#ty_without_ref>(arg_map, #name, state, || None)?
439 }
440 }
441 });
442
443 let schema_gens = args_info.iter().map(|a| {
445 let (name, ty_without_ref, opts) = (&a.name, &a.ty_without_ref, &a.opts);
446 if opts.is_state || opts.is_state_mut {
447 return quote! {};
448 }
449 if let Some((elem, _)) = &a.handle_elem {
450 quote! {
451 args_schema.push((#name.to_string(), ::process_mining::__private::serde_json::json!({
452 "type": "string",
453 "title": <#elem as ::process_mining::bindings::CustomRegistryValue>::kind_name(),
454 "x-registry-ref": <#elem as ::process_mining::bindings::CustomRegistryValue>::kind_name(),
455 "x-widget": "entity-selector"
456 })));
457 }
458 } else if is_big_type_ref(ty_without_ref) {
459 let ty_str = quote::quote!(#ty_without_ref).to_string();
460 let type_name = longest_big_type_match(&ty_str).unwrap();
461 quote! {
462 args_schema.push((#name.to_string(), ::process_mining::__private::serde_json::json!({
463 "type": "string",
464 "title": #type_name,
465 "x-registry-ref": #type_name,
466 "x-widget": "entity-selector"
467 })));
468 }
469 } else {
470 quote! { args_schema.push((#name.to_string(), ::process_mining::__private::serde_json::to_value(::process_mining::__private::schemars::schema_for!(#ty_without_ref)).unwrap())); }
471 }
472 });
473
474 let raw_ret_type = match &input_fn.sig.output {
476 syn::ReturnType::Default => syn::parse_quote!(()), syn::ReturnType::Type(_, ty) => *ty.clone(),
478 };
479
480 let mut ret_type = strip_lifetimes(raw_ret_type);
482
483 if attrs.returns_handle && attrs.debug_output {
484 let err = syn::Error::new_spanned(
485 &input_fn.sig,
486 "#[register_binding(returns_handle)] cannot be combined with `debug_output`: \
487 one stores the value and returns its id, the other formats it away.",
488 )
489 .to_compile_error();
490 return TokenStream::from(quote! { #input_fn #err });
491 }
492
493 if attrs.debug_output {
495 ret_type = syn::parse_quote!(String);
496 } else if attrs.stringify_error {
497 if let Type::Path(tp) = &ret_type {
501 if let Some(segment) = tp.path.segments.last() {
502 if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
504 if let Some(ok_type) = args.args.first() {
505 ret_type = syn::parse_quote!(#ok_type);
506 }
507 }
508 }
509 }
510 }
511
512 let returns_registry_handle = attrs.returns_handle || is_big_type(&ret_type).is_some();
514
515 let output_id_schema = if returns_registry_handle {
516 quote! {
517 args_schema.push((#OUTPUT_ID_ARG.to_string(), ::process_mining::__private::serde_json::json!({
518 "type": ["string", "null"],
519 "title": #OUTPUT_ID_ARG,
520 "description": "Store the result under this id instead of a generated one."
521 })));
522 }
523 } else {
524 quote! {}
525 };
526
527 let output_id_extraction = if returns_registry_handle {
528 quote! {
529 let __output_id = ::process_mining::bindings::extract_param_json::<Option<String>>(arg_map, #OUTPUT_ID_ARG, || Some(None))?;
530 }
531 } else {
532 quote! {}
533 };
534
535 let stored_id = quote! {
536 let id = __output_id
537 .unwrap_or_else(|| format!("res_{}", ::process_mining::__private::uuid::Uuid::new_v4()));
538 };
539
540 let required_arg_names = args_info
543 .iter()
544 .filter(|a| a.opts.default_value.is_none() && !a.opts.is_state && !a.opts.is_state_mut)
545 .map(|a| &a.name);
546
547 let extractions: Vec<_> = extractions.collect();
549
550 let error_handling = if attrs.stringify_error && !attrs.debug_output {
552 quote! { let result = result.map_err(|e| e.to_string())?; }
553 } else {
554 quote! {}
555 };
556
557 let serialization_logic = if attrs.debug_output {
558 quote! {
559 let final_result = format!("{:?}", result);
560 ::process_mining::__private::serde_json::to_vec(&final_result).map_err(|e| e.to_string())
561 }
562 } else {
563 quote! {
564 ::process_mining::__private::serde_json::to_vec(&result).map_err(|e| e.to_string())
565 }
566 };
567
568 let execution_block = if needs_write_lock {
569 let json_extractions: Vec<_> = args_info
572 .iter()
573 .filter(|a| a.mut_big_type_name.is_none() && !a.is_mut_handle() && !a.opts.is_state_mut)
574 .map(|a| {
575 let (name, is_ref, ty_without_ref, opts) = (&a.name, a.change_from_ref, &a.ty_without_ref, &a.opts);
576 let param_ident = format_ident!("__param_{}", name);
577 let maybe_ref = if is_ref {
578 quote! { & }
579 } else {
580 quote! {}
581 };
582 if let Some(default_expr) = &opts.default_value {
583 quote! {
584 let #param_ident = #maybe_ref ::process_mining::bindings::extract_param_json::<#ty_without_ref>(arg_map, #name, || Some(#default_expr))?;
585 }
586 } else {
587 quote! {
588 let #param_ident = #maybe_ref ::process_mining::bindings::extract_param_json::<#ty_without_ref>(arg_map, #name, || None)?;
589 }
590 }
591 })
592 .collect();
593
594 let mut_extractions: Vec<_> = args_info
596 .iter()
597 .filter_map(|a| {
598 let name = &a.name;
599 let param_ident = format_ident!("__param_{}", name);
600 if a.opts.is_state_mut {
601 return Some(quote! {
602 let #param_ident = ::process_mining::bindings::StateRefMut::new(&mut __state_guard);
603 });
604 }
605 if let Some((elem, true)) = &a.handle_elem {
606 return Some(quote! {
607 let #param_ident = {
608 let __id = arg_map.get(#name).and_then(|v| v.as_str())
609 .ok_or_else(|| format!("Missing required argument {}", #name))?;
610 __state_guard.get_mut(__id)
611 .ok_or_else(|| format!("Item '{}' not found", __id))?
612 .as_custom_mut::<#elem>()
613 .ok_or_else(|| format!("ID '{}' is not a {}", __id,
614 <#elem as ::process_mining::bindings::CustomRegistryValue>::kind_name()))?
615 };
616 });
617 }
618 let type_name = a.mut_big_type_name.as_ref()?;
619 let variant_ident = format_ident!("{}", type_name);
620 Some(quote! {
621 let #param_ident = {
622 let __id = arg_map.get(#name).and_then(|v| v.as_str())
623 .ok_or_else(|| format!("Missing required argument {}", #name))?;
624 match __state_guard.get_mut(__id)
625 .ok_or_else(|| format!("Item '{}' not found", __id))? {
626 ::process_mining::bindings::RegistryItem::#variant_ident(inner) => inner,
627 _ => return Err(format!("ID '{}' is not a {}", __id, #type_name)),
628 }
629 };
630 })
631 })
632 .collect();
633
634 let call_args: Vec<_> = args_info
636 .iter()
637 .map(|a| {
638 let param_ident = format_ident!("__param_{}", a.name);
639 quote! { #param_ident }
640 })
641 .collect();
642
643 let mut_serialization = if attrs.returns_handle {
647 quote! {
648 #stored_id
649 __state_guard.insert(id.clone(), ::process_mining::bindings::RegistryItem::custom(result));
650 ::process_mining::__private::serde_json::to_vec(&id).map_err(|e| e.to_string())
651 }
652 } else if let Some(type_name) = is_big_type(&ret_type) {
653 let variant_ident = format_ident!("{}", type_name);
654 quote! {
655 #stored_id
656 __state_guard.insert(id.clone(), ::process_mining::bindings::RegistryItem::#variant_ident(result));
657 ::process_mining::__private::serde_json::to_vec(&id).map_err(|e| e.to_string())
658 }
659 } else {
660 serialization_logic.clone()
661 };
662
663 quote! {
664 #(#json_extractions)*
665 let mut __state_guard = state_lock.write();
666 #(#mut_extractions)*
667 let result = #fn_ident( #(#call_args),* );
668 #error_handling
669 #mut_serialization
670 }
671 } else if attrs.returns_handle {
672 quote! {
673 let result = {
674 let state_guard = state_lock.read();
675 let state = &*state_guard;
676 #fn_ident( #(#extractions),* )
677 };
678 #error_handling
679 #stored_id
680 state_lock.add(&id, ::process_mining::bindings::RegistryItem::custom(result));
681 ::process_mining::__private::serde_json::to_vec(&id).map_err(|e| e.to_string())
682 }
683 } else if let Some(type_name) = is_big_type(&ret_type) {
684 let variant_ident = format_ident!("{}", type_name);
685 quote! {
686 let result = {
687 let state_guard = state_lock.read();
688 let state = &*state_guard;
689 #fn_ident( #(#extractions),* )
690 };
691 #error_handling
692 #stored_id
693 state_lock.add(&id, ::process_mining::bindings::RegistryItem::#variant_ident(result));
694 ::process_mining::__private::serde_json::to_vec(&id).map_err(|e| e.to_string())
695 }
696 } else {
697 quote! {
698 let state_guard = state_lock.read();
699 let state = &*state_guard;
700 let result = #fn_ident( #(#extractions),* );
701 #error_handling
702 #serialization_logic
703 }
704 };
705
706 let ret_type_schema = if attrs.returns_handle {
707 quote! {
708 ::process_mining::__private::serde_json::json!({
709 "type": "string",
710 "title": <#ret_type as ::process_mining::bindings::CustomRegistryValue>::kind_name(),
711 "x-registry-ref": <#ret_type as ::process_mining::bindings::CustomRegistryValue>::kind_name(),
712 "x-widget": "entity-selector"
713 })
714 }
715 } else if let Some(type_name) = is_big_type(&ret_type) {
716 quote! {
717 ::process_mining::__private::serde_json::json!({
718 "type": "string",
719 "title": #type_name,
720 "x-registry-ref": #type_name,
721 "x-widget": "entity-selector"
722 })
723 }
724 } else {
725 quote! {
726 ::process_mining::__private::serde_json::to_value(::process_mining::__private::schemars::schema_for!(#ret_type)).unwrap()
727 }
728 };
729
730 if returns_registry_handle && args_info.iter().any(|a| a.name == OUTPUT_ID_ARG) {
733 let msg = format!(
734 "#[register_binding] on `{}`: its result is stored in the registry, so the binding \
735 already takes an `{}` argument. Rename the function's own argument.",
736 fn_ident, OUTPUT_ID_ARG
737 );
738 return TokenStream::from(quote! {
739 #input_fn
740 ::core::compile_error!(#msg);
741 });
742 }
743
744 let docs_fn_name = format_ident!("{}_docs", fn_ident);
745 let args_fn_name = format_ident!("{}_args", fn_ident);
746 let required_args_fn_name = format_ident!("{}_required_args", fn_ident);
747 let return_type_fn_name = format_ident!("{}_return_type", fn_ident);
748
749 let registration_cfg = if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("process_mining") {
752 quote! { #[cfg(feature = "bindings")] }
753 } else {
754 quote! {}
755 };
756
757 let expanded = quote! {
758 #input_fn
759
760 #registration_cfg
761 const _: () = {
762 use ::process_mining::bindings::{Binding, AppState};
763 use ::process_mining::__private::serde_json::Value;
764
765 fn #wrapper_name(args: &Value, state_lock: &AppState) -> Result<Vec<u8>, String> {
766 let arg_map = args.as_object().ok_or("Args must be JSON object")?;
767 #output_id_extraction
768 #execution_block
769 }
770
771 fn #docs_fn_name() -> Vec<String> {
772 vec![#(#docs.to_string(),)*]
773 }
774
775 fn #args_fn_name() -> Vec<(String, Value)> {
776 let mut args_schema = ::std::vec::Vec::new();
777 #(#schema_gens)*
778 #output_id_schema
779 args_schema
780 }
781
782 fn #required_args_fn_name() -> Vec<String> {
783 vec![#(#required_arg_names.to_string(),)*]
784 }
785
786 fn #return_type_fn_name() -> Value {
787 #ret_type_schema
788 }
789
790 ::process_mining::__private::inventory::submit! {
791 Binding {
792 id: concat!(module_path!(), "::", stringify!(#fn_ident)),
793 name: #binding_name_str,
794 handler: #wrapper_name,
795 docs: #docs_fn_name,
796 module: module_path!(),
797 source_path: file!(),
798 source_line: line!(),
799 args: #args_fn_name,
800 required_args: #required_args_fn_name,
801 return_type: #return_type_fn_name,
802 }
803 }
804 };
805 };
806 TokenStream::from(expanded)
807}
808
809#[proc_macro]
812pub fn big_types_list(_item: TokenStream) -> TokenStream {
813 let types = BIG_TYPES_NAMES;
814 let expanded = quote! {
815 &[#(#types),*]
816 };
817 TokenStream::from(expanded)
818}
819
820#[proc_macro_derive(CustomRegistryEntity)]
828pub fn derive_custom_registry_entity(item: TokenStream) -> TokenStream {
829 let input = parse_macro_input!(item as syn::DeriveInput);
830 let name = &input.ident;
831
832 let expanded = quote! {
833 impl<'a> ::process_mining::bindings::FromContext<'a> for &'a #name {
834 fn from_context(value: &::process_mining::__private::serde_json::Value, state: &'a ::process_mining::bindings::InnerAppState) -> Result<Self, String> {
835 let id = value.as_str().ok_or("Expected String ID")?;
836 let item = state.get(id).ok_or_else(|| format!("Item '{}' not found", id))?;
837 item.as_custom::<#name>().ok_or_else(|| {
838 format!(
839 "ID '{}' is not a {}",
840 id,
841 <#name as ::process_mining::bindings::CustomRegistryValue>::kind_name()
842 )
843 })
844 }
845 }
846 };
847 TokenStream::from(expanded)
848}
849
850#[proc_macro_derive(RegistryEntity)]
851pub fn derive_registry_entity(item: TokenStream) -> TokenStream {
852 let input = parse_macro_input!(item as syn::DeriveInput);
853 let name = &input.ident;
854 let name_str = name.to_string();
855
856 let expanded = quote! {
857 #[cfg(feature = "bindings")]
858 impl<'a> ::process_mining::bindings::FromContext<'a> for &'a #name {
859 fn from_context(value: &::process_mining::__private::serde_json::Value, state: &'a ::process_mining::bindings::InnerAppState) -> Result<Self, String> {
860 let id = value.as_str().ok_or("Expected String ID")?;
861 let item = state.get(id).ok_or_else(|| format!("Item '{}' not found", id))?;
862
863 if let ::process_mining::bindings::RegistryItem::#name(inner) = item {
864 Ok(inner)
865 } else {
866 Err(format!("ID '{}' is not a {}", id, #name_str))
867 }
868 }
869 }
870 };
871 TokenStream::from(expanded)
872}