1use proc_macro::TokenStream;
56use proc_macro2::TokenStream as TokenStream2;
57use quote::{quote, ToTokens};
58use syn::{
59 parse_macro_input, Attribute, Ident, ImplItem, ItemImpl, Meta, Token,
60};
61
62#[proc_macro_attribute]
79pub fn aggregate(attr: TokenStream, item: TokenStream) -> TokenStream {
80 let args = parse_macro_input!(attr as AggregateArgs);
81 let input = parse_macro_input!(item as ItemImpl);
82
83 let expanded = expand_aggregate(args, input);
84 TokenStream::from(expanded)
85}
86
87struct AggregateArgs {
88 domain: String,
89 state: Ident,
90}
91
92impl syn::parse::Parse for AggregateArgs {
93 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
94 let mut domain = None;
95 let mut state = None;
96
97 while !input.is_empty() {
98 let ident: Ident = input.parse()?;
99 input.parse::<Token![=]>()?;
100
101 match ident.to_string().as_str() {
102 "domain" => {
103 let value: syn::LitStr = input.parse()?;
104 domain = Some(value.value());
105 }
106 "state" => {
107 let value: Ident = input.parse()?;
108 state = Some(value);
109 }
110 _ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
111 }
112
113 if input.peek(Token![,]) {
114 input.parse::<Token![,]>()?;
115 }
116 }
117
118 Ok(AggregateArgs {
119 domain: domain.ok_or_else(|| {
120 syn::Error::new(proc_macro2::Span::call_site(), "domain is required")
121 })?,
122 state: state.ok_or_else(|| {
123 syn::Error::new(proc_macro2::Span::call_site(), "state is required")
124 })?,
125 })
126 }
127}
128
129fn expand_aggregate(args: AggregateArgs, mut input: ItemImpl) -> TokenStream2 {
130 let domain = &args.domain;
131 let state_ty = &args.state;
132 let self_ty = &input.self_ty;
133
134 let mut handlers = Vec::new();
136 let mut rejection_handlers = Vec::new();
137 let mut appliers = Vec::new();
138
139 for item in &input.items {
140 if let ImplItem::Fn(method) = item {
141 for attr in &method.attrs {
142 if attr.path().is_ident("handles") {
143 if let Ok(command_type) = get_attr_ident(attr) {
144 handlers.push((method.sig.ident.clone(), command_type));
145 }
146 } else if attr.path().is_ident("rejected") {
147 if let Ok((rej_domain, command)) = get_rejected_args(attr) {
148 rejection_handlers.push((method.sig.ident.clone(), rej_domain, command));
149 }
150 } else if attr.path().is_ident("applies") {
151 if let Ok(event_type) = get_attr_ident(attr) {
152 appliers.push((method.sig.ident.clone(), event_type));
153 }
154 }
155 }
156 }
157 }
158
159 let command_types: Vec<_> = handlers
161 .iter()
162 .map(|(_, cmd_type)| {
163 let cmd_str = cmd_type.to_string();
164 quote! { #cmd_str.into() }
165 })
166 .collect();
167
168 let handle_arms: Vec<_> = handlers
170 .iter()
171 .map(|(method, cmd_type)| {
172 let cmd_str = cmd_type.to_string();
173 quote! {
174 if payload.type_url.ends_with(#cmd_str) {
175 let cmd = <#cmd_type as prost::Message>::decode(payload.value.as_slice())
176 .map_err(|e| angzarr_client::CommandRejectedError::new(format!("Failed to decode {}: {}", #cmd_str, e)))?;
177 return self.inner.#method(cmd_book, cmd, state, seq);
178 }
179 }
180 })
181 .collect();
182
183 let rejection_arms: Vec<_> = rejection_handlers
185 .iter()
186 .map(|(method, rej_domain, command)| {
187 quote! {
188 if target_domain == #rej_domain && target_command.ends_with(#command) {
189 return self.inner.#method(notification, state);
190 }
191 }
192 })
193 .collect();
194
195 let state_router_on_calls: Vec<_> = appliers
197 .iter()
198 .map(|(method, event_type)| {
199 quote! {
200 .on::<#event_type>(#self_ty::#method)
201 }
202 })
203 .collect();
204
205 for item in &mut input.items {
207 if let ImplItem::Fn(method) = item {
208 method.attrs.retain(|attr| {
209 !attr.path().is_ident("handles")
210 && !attr.path().is_ident("rejected")
211 && !attr.path().is_ident("applies")
212 });
213 }
214 }
215
216 let handler_name = syn::Ident::new(
218 &format!("{}Handler", self_ty.to_token_stream()),
219 proc_macro2::Span::call_site(),
220 );
221
222 let state_router_static = syn::Ident::new(
224 &format!("{}_STATE_ROUTER", self_ty.to_token_stream()).to_uppercase(),
225 proc_macro2::Span::call_site(),
226 );
227
228 quote! {
229 #input
230
231 static #state_router_static: std::sync::LazyLock<angzarr_client::StateRouter<#state_ty>> =
233 std::sync::LazyLock::new(|| {
234 angzarr_client::StateRouter::new()
235 #(#state_router_on_calls)*
236 });
237
238 pub struct #handler_name {
240 inner: #self_ty,
241 }
242
243 impl #handler_name {
244 pub fn new(inner: #self_ty) -> Self {
245 Self { inner }
246 }
247 }
248
249 impl angzarr_client::CommandHandlerDomainHandler for #handler_name {
250 type State = #state_ty;
251
252 fn command_types(&self) -> Vec<String> {
253 vec![#(#command_types),*]
254 }
255
256 fn state_router(&self) -> &angzarr_client::StateRouter<Self::State> {
257 &#state_router_static
258 }
259
260 fn handle(
261 &self,
262 cmd_book: &angzarr_client::proto::CommandBook,
263 payload: &prost_types::Any,
264 state: &Self::State,
265 seq: u32,
266 ) -> angzarr_client::CommandResult<angzarr_client::proto::EventBook> {
267 #(#handle_arms)*
268 Err(angzarr_client::CommandRejectedError::new(format!("Unknown command type: {}", payload.type_url)))
269 }
270
271 fn on_rejected(
272 &self,
273 notification: &angzarr_client::proto::Notification,
274 state: &Self::State,
275 target_domain: &str,
276 target_command: &str,
277 ) -> angzarr_client::CommandResult<angzarr_client::RejectionHandlerResponse> {
278 #(#rejection_arms)*
279 Ok(angzarr_client::RejectionHandlerResponse::default())
280 }
281 }
282
283 impl #self_ty {
284 pub fn into_router(self) -> angzarr_client::CommandHandlerRouter<#state_ty, #handler_name>
286 where
287 Self: Send + Sync + 'static,
288 {
289 angzarr_client::CommandHandlerRouter::new(#domain, #domain, #handler_name::new(self))
290 }
291 }
292 }
293}
294
295#[proc_macro_attribute]
306pub fn handles(_attr: TokenStream, item: TokenStream) -> TokenStream {
307 item
310}
311
312#[proc_macro_attribute]
327pub fn rejected(_attr: TokenStream, item: TokenStream) -> TokenStream {
328 item
331}
332
333#[proc_macro_attribute]
359pub fn applies(_attr: TokenStream, item: TokenStream) -> TokenStream {
360 item
363}
364
365#[proc_macro_attribute]
387pub fn saga(attr: TokenStream, item: TokenStream) -> TokenStream {
388 let args = parse_macro_input!(attr as SagaArgs);
389 let input = parse_macro_input!(item as ItemImpl);
390
391 let expanded = expand_saga(args, input);
392 TokenStream::from(expanded)
393}
394
395struct SagaArgs {
396 name: String,
397 input: String,
398}
399
400impl syn::parse::Parse for SagaArgs {
401 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
402 let mut name = None;
403 let mut input_domain = None;
404
405 while !input.is_empty() {
406 let ident: Ident = input.parse()?;
407 input.parse::<Token![=]>()?;
408 let value: syn::LitStr = input.parse()?;
409
410 match ident.to_string().as_str() {
411 "name" => name = Some(value.value()),
412 "input" => input_domain = Some(value.value()),
413 _ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
414 }
415
416 if input.peek(Token![,]) {
417 input.parse::<Token![,]>()?;
418 }
419 }
420
421 Ok(SagaArgs {
422 name: name.ok_or_else(|| {
423 syn::Error::new(proc_macro2::Span::call_site(), "name is required")
424 })?,
425 input: input_domain.ok_or_else(|| {
426 syn::Error::new(proc_macro2::Span::call_site(), "input is required")
427 })?,
428 })
429 }
430}
431
432fn expand_saga(args: SagaArgs, mut input: ItemImpl) -> TokenStream2 {
433 let name = &args.name;
434 let input_domain = &args.input;
435 let self_ty = &input.self_ty;
436
437 let mut event_handlers = Vec::new();
439
440 for item in &input.items {
441 if let ImplItem::Fn(method) = item {
442 for attr in &method.attrs {
443 if attr.path().is_ident("handles") {
444 if let Ok(event_type) = get_attr_ident(attr) {
445 event_handlers.push((method.sig.ident.clone(), event_type));
446 }
447 }
448 }
449 }
450 }
451
452 let event_types: Vec<_> = event_handlers
454 .iter()
455 .map(|(_, event_type)| {
456 let event_str = event_type.to_string();
457 quote! { #event_str.into() }
458 })
459 .collect();
460
461 let handle_arms: Vec<_> = event_handlers
463 .iter()
464 .map(|(method, event_type)| {
465 let event_str = event_type.to_string();
466 quote! {
467 if event.type_url.ends_with(#event_str) {
468 let evt = <#event_type as prost::Message>::decode(event.value.as_slice())
469 .map_err(|e| angzarr_client::CommandRejectedError::new(format!("Failed to decode {}: {}", #event_str, e)))?;
470 return self.inner.#method(evt, source);
471 }
472 }
473 })
474 .collect();
475
476 for item in &mut input.items {
478 if let ImplItem::Fn(method) = item {
479 method.attrs.retain(|attr| !attr.path().is_ident("handles"));
480 }
481 }
482
483 let handler_name = syn::Ident::new(
485 &format!("{}Handler", self_ty.to_token_stream()),
486 proc_macro2::Span::call_site(),
487 );
488
489 quote! {
490 #input
491
492 pub struct #handler_name {
494 inner: #self_ty,
495 }
496
497 impl #handler_name {
498 pub fn new(inner: #self_ty) -> Self {
499 Self { inner }
500 }
501 }
502
503 impl angzarr_client::SagaDomainHandler for #handler_name {
504 fn event_types(&self) -> Vec<String> {
505 vec![#(#event_types),*]
506 }
507
508 fn handle(
509 &self,
510 source: &angzarr_client::proto::EventBook,
511 event: &prost_types::Any,
512 ) -> angzarr_client::CommandResult<angzarr_client::SagaHandlerResponse> {
513 #(#handle_arms)*
514 Ok(angzarr_client::SagaHandlerResponse::default())
515 }
516 }
517
518 impl #self_ty {
519 pub fn into_router(self) -> angzarr_client::SagaRouter<#handler_name>
521 where
522 Self: Send + Sync + 'static,
523 {
524 angzarr_client::SagaRouter::new(#name, #input_domain, #handler_name::new(self))
525 }
526 }
527 }
528}
529
530#[proc_macro_attribute]
540pub fn prepares(_attr: TokenStream, item: TokenStream) -> TokenStream {
541 item
542}
543
544#[proc_macro_attribute]
574pub fn process_manager(attr: TokenStream, item: TokenStream) -> TokenStream {
575 let args = parse_macro_input!(attr as ProcessManagerArgs);
576 let input = parse_macro_input!(item as ItemImpl);
577
578 let expanded = expand_process_manager(args, input);
579 TokenStream::from(expanded)
580}
581
582struct ProcessManagerArgs {
583 name: String,
584 domain: String,
585 state: Ident,
586 inputs: Vec<String>,
587}
588
589impl syn::parse::Parse for ProcessManagerArgs {
590 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
591 let mut name = None;
592 let mut domain = None;
593 let mut state = None;
594 let mut inputs = None;
595
596 while !input.is_empty() {
597 let ident: Ident = input.parse()?;
598 input.parse::<Token![=]>()?;
599
600 match ident.to_string().as_str() {
601 "name" => {
602 let value: syn::LitStr = input.parse()?;
603 name = Some(value.value());
604 }
605 "domain" => {
606 let value: syn::LitStr = input.parse()?;
607 domain = Some(value.value());
608 }
609 "state" => {
610 let value: Ident = input.parse()?;
611 state = Some(value);
612 }
613 "inputs" => {
614 let content;
615 syn::bracketed!(content in input);
616 let mut domains = Vec::new();
617 while !content.is_empty() {
618 let lit: syn::LitStr = content.parse()?;
619 domains.push(lit.value());
620 if content.peek(Token![,]) {
621 content.parse::<Token![,]>()?;
622 }
623 }
624 inputs = Some(domains);
625 }
626 _ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
627 }
628
629 if input.peek(Token![,]) {
630 input.parse::<Token![,]>()?;
631 }
632 }
633
634 Ok(ProcessManagerArgs {
635 name: name.ok_or_else(|| {
636 syn::Error::new(proc_macro2::Span::call_site(), "name is required")
637 })?,
638 domain: domain.ok_or_else(|| {
639 syn::Error::new(proc_macro2::Span::call_site(), "domain is required")
640 })?,
641 state: state.ok_or_else(|| {
642 syn::Error::new(proc_macro2::Span::call_site(), "state is required")
643 })?,
644 inputs: inputs.ok_or_else(|| {
645 syn::Error::new(proc_macro2::Span::call_site(), "inputs is required")
646 })?,
647 })
648 }
649}
650
651fn expand_process_manager(args: ProcessManagerArgs, mut input: ItemImpl) -> TokenStream2 {
652 let name = &args.name;
653 let pm_domain = &args.domain;
654 let state_ty = &args.state;
655 let inputs = &args.inputs;
656 let self_ty = &input.self_ty;
657
658 let mut prepare_handlers = Vec::new();
660 let mut event_handlers = Vec::new();
661 let mut appliers = Vec::new();
662
663 for item in &input.items {
664 if let ImplItem::Fn(method) = item {
665 for attr in &method.attrs {
666 if attr.path().is_ident("prepares") {
667 if let Ok(event_type) = get_attr_ident(attr) {
668 prepare_handlers.push((method.sig.ident.clone(), event_type));
669 }
670 } else if attr.path().is_ident("handles") {
671 if let Ok(event_type) = get_attr_ident(attr) {
672 event_handlers.push((method.sig.ident.clone(), event_type));
673 }
674 } else if attr.path().is_ident("applies") {
675 if let Ok(event_type) = get_attr_ident(attr) {
676 appliers.push((method.sig.ident.clone(), event_type));
677 }
678 }
679 }
680 }
681 }
682
683 let event_types: Vec<_> = event_handlers
685 .iter()
686 .map(|(_, event_type)| {
687 let event_str = event_type.to_string();
688 quote! { #event_str.into() }
689 })
690 .collect();
691
692 let prepare_arms: Vec<_> = prepare_handlers
694 .iter()
695 .map(|(method, event_type)| {
696 let event_str = event_type.to_string();
697 quote! {
698 if event.type_url.ends_with(#event_str) {
699 if let Ok(evt) = <#event_type as prost::Message>::decode(event.value.as_slice()) {
700 return self.inner.#method(trigger, state, &evt);
701 }
702 }
703 }
704 })
705 .collect();
706
707 let handle_arms: Vec<_> = event_handlers
709 .iter()
710 .map(|(method, event_type)| {
711 let event_str = event_type.to_string();
712 quote! {
713 if event.type_url.ends_with(#event_str) {
714 let evt = <#event_type as prost::Message>::decode(event.value.as_slice())
715 .map_err(|e| angzarr_client::CommandRejectedError::new(format!("Failed to decode {}: {}", #event_str, e)))?;
716 return self.inner.#method(trigger, state, evt, destinations);
717 }
718 }
719 })
720 .collect();
721
722 let apply_arms: Vec<_> = appliers
724 .iter()
725 .map(|(method, event_type)| {
726 let suffix = event_type.to_string();
727 quote! {
728 if event_any.type_url.ends_with(#suffix) {
729 if let Ok(event) = <#event_type as prost::Message>::decode(event_any.value.as_slice()) {
730 #self_ty::#method(state, event);
731 return;
732 }
733 }
734 }
735 })
736 .collect();
737
738 for item in &mut input.items {
740 if let ImplItem::Fn(method) = item {
741 method.attrs.retain(|attr| {
742 !attr.path().is_ident("prepares")
743 && !attr.path().is_ident("handles")
744 && !attr.path().is_ident("applies")
745 });
746 }
747 }
748
749 let apply_event_fn = if !appliers.is_empty() {
751 quote! {
752 pub fn apply_event(state: &mut #state_ty, event_any: &prost_types::Any) {
754 #(#apply_arms)*
755 }
757
758 pub fn rebuild(events: &angzarr_client::proto::EventBook) -> #state_ty {
760 let mut state = #state_ty::default();
761 for page in &events.pages {
762 if let Some(angzarr_client::proto::event_page::Payload::Event(event)) = &page.payload {
763 Self::apply_event(&mut state, event);
764 }
765 }
766 state
767 }
768 }
769 } else {
770 quote! {
771 pub fn rebuild(_events: &angzarr_client::proto::EventBook) -> #state_ty {
773 #state_ty::default()
774 }
775 }
776 };
777
778 let handler_name = syn::Ident::new(
780 &format!("{}Handler", self_ty.to_token_stream()),
781 proc_macro2::Span::call_site(),
782 );
783
784 let domain_registrations: Vec<_> = inputs
786 .iter()
787 .map(|domain| {
788 quote! {
789 .domain(#domain, #handler_name { inner: inner.clone() })
790 }
791 })
792 .collect();
793
794 quote! {
795 #input
796
797 impl #self_ty {
798 #apply_event_fn
799 }
800
801 pub struct #handler_name {
803 inner: std::sync::Arc<#self_ty>,
804 }
805
806 impl angzarr_client::ProcessManagerDomainHandler<#state_ty> for #handler_name {
807 fn event_types(&self) -> Vec<String> {
808 vec![#(#event_types),*]
809 }
810
811 fn prepare(
812 &self,
813 trigger: &angzarr_client::proto::EventBook,
814 state: &#state_ty,
815 event: &prost_types::Any,
816 ) -> Vec<angzarr_client::proto::Cover> {
817 #(#prepare_arms)*
818 vec![]
819 }
820
821 fn handle(
822 &self,
823 trigger: &angzarr_client::proto::EventBook,
824 state: &#state_ty,
825 event: &prost_types::Any,
826 destinations: &[angzarr_client::proto::EventBook],
827 ) -> angzarr_client::CommandResult<angzarr_client::ProcessManagerResponse> {
828 #(#handle_arms)*
829 Ok(angzarr_client::ProcessManagerResponse::default())
830 }
831 }
832
833 impl #self_ty {
834 pub fn into_router(self) -> angzarr_client::ProcessManagerRouter<#state_ty>
836 where
837 Self: Send + Sync + 'static,
838 {
839 let inner = std::sync::Arc::new(self);
840 angzarr_client::ProcessManagerRouter::new(#name, #pm_domain, Self::rebuild)
841 #(#domain_registrations)*
842 }
843 }
844 }
845}
846
847#[proc_macro_attribute]
857pub fn projects(_attr: TokenStream, item: TokenStream) -> TokenStream {
858 item
859}
860
861#[proc_macro_attribute]
882pub fn projector(attr: TokenStream, item: TokenStream) -> TokenStream {
883 let args = parse_macro_input!(attr as ProjectorArgs);
884 let input = parse_macro_input!(item as ItemImpl);
885
886 let expanded = expand_projector(args, input);
887 TokenStream::from(expanded)
888}
889
890struct ProjectorArgs {
891 name: String,
892}
893
894impl syn::parse::Parse for ProjectorArgs {
895 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
896 let mut name = None;
897
898 while !input.is_empty() {
899 let ident: Ident = input.parse()?;
900 input.parse::<Token![=]>()?;
901
902 match ident.to_string().as_str() {
903 "name" => {
904 let value: syn::LitStr = input.parse()?;
905 name = Some(value.value());
906 }
907 _ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
908 }
909
910 if input.peek(Token![,]) {
911 input.parse::<Token![,]>()?;
912 }
913 }
914
915 Ok(ProjectorArgs {
916 name: name.ok_or_else(|| {
917 syn::Error::new(proc_macro2::Span::call_site(), "name is required")
918 })?,
919 })
920 }
921}
922
923fn expand_projector(args: ProjectorArgs, mut input: ItemImpl) -> TokenStream2 {
924 let name = &args.name;
925 let self_ty = &input.self_ty;
926
927 let mut event_handlers = Vec::new();
929
930 for item in &input.items {
931 if let ImplItem::Fn(method) = item {
932 for attr in &method.attrs {
933 if attr.path().is_ident("projects") {
934 if let Ok(event_type) = get_attr_ident(attr) {
935 event_handlers.push((method.sig.ident.clone(), event_type));
936 }
937 }
938 }
939 }
940 }
941
942 let handler_arms: Vec<_> = event_handlers
944 .iter()
945 .map(|(method, event_type)| {
946 let suffix = event_type.to_string();
947 quote! {
948 if type_url.ends_with(#suffix) {
949 if let Ok(event) = <#event_type as prost::Message>::decode(event_any.value.as_slice()) {
950 return Some(self.#method(event));
951 }
952 }
953 }
954 })
955 .collect();
956
957 let dispatch_fn = if !event_handlers.is_empty() {
959 quote! {
960 fn handle_event(&self, event_any: &prost_types::Any) -> Option<angzarr_client::proto::Projection> {
962 let type_url = &event_any.type_url;
963 #(#handler_arms)*
964 None
965 }
966 }
967 } else {
968 quote! {
969 fn handle_event(&self, _event_any: &prost_types::Any) -> Option<angzarr_client::proto::Projection> {
970 None
971 }
972 }
973 };
974
975 for item in &mut input.items {
977 if let ImplItem::Fn(method) = item {
978 method.attrs.retain(|attr| !attr.path().is_ident("projects"));
979 }
980 }
981
982 quote! {
983 #input
984
985 impl #self_ty {
986 #dispatch_fn
987
988 pub fn handle(&self, events: &angzarr_client::proto::EventBook) -> angzarr_client::proto::Projection {
990 let cover = events.cover.as_ref();
991 let mut last_seq = 0u32;
992
993 for page in &events.pages {
994 if let Some(angzarr_client::proto::event_page::Payload::Event(event_any)) = &page.payload {
995 if let Some(projection) = self.handle_event(event_any) {
996 return projection;
997 }
998 }
999 if let Some(header) = &page.header {
1000 if let Some(angzarr_client::proto::page_header::SequenceType::Sequence(seq)) = &header.sequence_type {
1001 last_seq = *seq;
1002 }
1003 }
1004 }
1005
1006 angzarr_client::proto::Projection {
1008 cover: cover.cloned(),
1009 projector: #name.to_string(),
1010 sequence: last_seq,
1011 projection: None,
1012 }
1013 }
1014
1015 pub fn into_handler(self) -> angzarr_client::ProjectorHandler
1017 where
1018 Self: Send + Sync + 'static,
1019 {
1020 let projector = std::sync::Arc::new(self);
1021 angzarr_client::ProjectorHandler::new(#name).with_handle_fn(move |events| {
1022 Ok(projector.handle(events))
1023 })
1024 }
1025 }
1026 }
1027}
1028
1029fn get_attr_ident(attr: &Attribute) -> syn::Result<Ident> {
1032 let meta = attr.meta.clone();
1033 match meta {
1034 Meta::List(list) => {
1035 let ident: Ident = syn::parse2(list.tokens)?;
1036 Ok(ident)
1037 }
1038 _ => Err(syn::Error::new_spanned(attr, "expected #[attr(Type)]")),
1039 }
1040}
1041
1042fn get_rejected_args(attr: &Attribute) -> syn::Result<(String, String)> {
1043 let meta = attr.meta.clone();
1044 match meta {
1045 Meta::List(list) => {
1046 let args: RejectedArgs = syn::parse2(list.tokens)?;
1047 Ok((args.domain, args.command))
1048 }
1049 _ => Err(syn::Error::new_spanned(
1050 attr,
1051 "expected #[rejected(domain = \"...\", command = \"...\")]",
1052 )),
1053 }
1054}
1055
1056struct RejectedArgs {
1057 domain: String,
1058 command: String,
1059}
1060
1061impl syn::parse::Parse for RejectedArgs {
1062 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1063 let mut domain = None;
1064 let mut command = None;
1065
1066 while !input.is_empty() {
1067 let ident: Ident = input.parse()?;
1068 input.parse::<Token![=]>()?;
1069 let value: syn::LitStr = input.parse()?;
1070
1071 match ident.to_string().as_str() {
1072 "domain" => domain = Some(value.value()),
1073 "command" => command = Some(value.value()),
1074 _ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
1075 }
1076
1077 if input.peek(Token![,]) {
1078 input.parse::<Token![,]>()?;
1079 }
1080 }
1081
1082 Ok(RejectedArgs {
1083 domain: domain.ok_or_else(|| {
1084 syn::Error::new(proc_macro2::Span::call_site(), "domain is required")
1085 })?,
1086 command: command.ok_or_else(|| {
1087 syn::Error::new(proc_macro2::Span::call_site(), "command is required")
1088 })?,
1089 })
1090 }
1091}