1use std::sync::Arc;
13use std::time::Duration;
14
15use a2a_protocol_types::agent_card::AgentCard;
16use a2a_protocol_types::extensions::AgentExtension;
17use a2a_protocol_types::idempotency;
18
19use crate::error::ServerResult;
20use crate::executor::AgentExecutor;
21use crate::handler::{HandlerLimits, RequestHandler};
22use crate::interceptor::{ServerInterceptor, ServerInterceptorChain};
23use crate::metrics::{Metrics, NoopMetrics};
24use crate::push::{InMemoryPushConfigStore, PushConfigStore, PushSender};
25use crate::store::{InMemoryTaskStore, TaskStore, TaskStoreConfig};
26use crate::streaming::EventQueueManager;
27use crate::tenant_config::PerTenantConfig;
28use crate::tenant_resolver::TenantResolver;
29
30pub const DEFAULT_MAX_CONCURRENT_STREAMS: usize = 1024;
39
40pub const DEFAULT_EXECUTOR_TIMEOUT: Duration = Duration::from_secs(3600);
72
73pub struct RequestHandlerBuilder {
97 executor: Arc<dyn AgentExecutor>,
98 task_store: Option<Arc<dyn TaskStore>>,
99 task_store_config: TaskStoreConfig,
100 push_config_store: Option<Arc<dyn PushConfigStore>>,
101 push_sender: Option<Arc<dyn PushSender>>,
102 interceptors: ServerInterceptorChain,
103 agent_card: Option<AgentCard>,
104 executor_timeout: Option<Duration>,
105 event_queue_capacity: Option<usize>,
106 max_event_size: Option<usize>,
107 max_concurrent_streams: Option<usize>,
108 metrics: Arc<dyn Metrics>,
109 handler_limits: HandlerLimits,
110 tenant_resolver: Option<Arc<dyn TenantResolver>>,
111 tenant_config: Option<PerTenantConfig>,
112 require_resolved_tenant: bool,
113 inbound_trace_policy: crate::handler::InboundTracePolicy,
114 allow_unauthenticated_extended_card: bool,
115}
116
117impl RequestHandlerBuilder {
118 #[must_use]
122 pub fn new(executor: impl AgentExecutor) -> Self {
123 Self {
124 executor: Arc::new(executor),
125 task_store: None,
126 task_store_config: TaskStoreConfig::default(),
127 push_config_store: None,
128 push_sender: None,
129 interceptors: ServerInterceptorChain::new(),
130 agent_card: None,
131 executor_timeout: Some(DEFAULT_EXECUTOR_TIMEOUT),
132 event_queue_capacity: None,
133 max_event_size: None,
134 max_concurrent_streams: None,
135 metrics: Arc::new(NoopMetrics),
136 handler_limits: HandlerLimits::default(),
137 tenant_resolver: None,
138 tenant_config: None,
139 require_resolved_tenant: false,
140 inbound_trace_policy: crate::handler::InboundTracePolicy::Continue,
141 allow_unauthenticated_extended_card: false,
142 }
143 }
144
145 #[must_use]
147 pub fn with_task_store(mut self, store: impl TaskStore + 'static) -> Self {
148 self.task_store = Some(Arc::new(store));
149 self
150 }
151
152 #[must_use]
157 pub fn with_task_store_arc(mut self, store: Arc<dyn TaskStore>) -> Self {
158 self.task_store = Some(store);
159 self
160 }
161
162 #[must_use]
170 pub fn with_task_store_config(mut self, config: TaskStoreConfig) -> Self {
171 debug_assert!(
172 self.task_store.is_none(),
173 "with_task_store_config() called after with_task_store(); \
174 the config will be ignored because a custom store was already set"
175 );
176 self.task_store_config = config;
177 self
178 }
179
180 #[must_use]
182 pub fn with_push_config_store(mut self, store: impl PushConfigStore + 'static) -> Self {
183 self.push_config_store = Some(Arc::new(store));
184 self
185 }
186
187 #[must_use]
189 pub fn with_push_sender(mut self, sender: impl PushSender + 'static) -> Self {
190 self.push_sender = Some(Arc::new(sender));
191 self
192 }
193
194 #[must_use]
196 pub fn with_interceptor(mut self, interceptor: impl ServerInterceptor + 'static) -> Self {
197 self.interceptors.push(Arc::new(interceptor));
198 self
199 }
200
201 #[must_use]
211 pub const fn with_executor_timeout(mut self, timeout: Duration) -> Self {
212 self.executor_timeout = Some(timeout);
213 self
214 }
215
216 #[must_use]
227 pub const fn without_executor_timeout(mut self) -> Self {
228 self.executor_timeout = None;
229 self
230 }
231
232 #[must_use]
234 pub fn with_agent_card(mut self, card: AgentCard) -> Self {
235 self.agent_card = Some(card);
236 self
237 }
238
239 #[must_use]
247 pub const fn with_event_queue_capacity(mut self, capacity: usize) -> Self {
248 self.event_queue_capacity = Some(capacity);
249 self
250 }
251
252 #[must_use]
257 pub const fn with_max_event_size(mut self, max_event_size: usize) -> Self {
258 self.max_event_size = Some(max_event_size);
259 self
260 }
261
262 #[must_use]
269 pub const fn with_max_concurrent_streams(mut self, max: usize) -> Self {
270 self.max_concurrent_streams = Some(max);
271 self
272 }
273
274 #[must_use]
278 pub const fn with_handler_limits(mut self, limits: HandlerLimits) -> Self {
279 self.handler_limits = limits;
280 self
281 }
282
283 #[must_use]
287 pub fn with_metrics(mut self, metrics: impl Metrics + 'static) -> Self {
288 self.metrics = Arc::new(metrics);
289 self
290 }
291
292 #[must_use]
308 pub fn with_tenant_resolver(mut self, resolver: impl TenantResolver) -> Self {
309 self.tenant_resolver = Some(Arc::new(resolver));
310 self
311 }
312
313 #[must_use]
325 pub const fn require_resolved_tenant(mut self) -> Self {
326 self.require_resolved_tenant = true;
327 self
328 }
329
330 #[must_use]
350 pub const fn with_inbound_trace_policy(
351 mut self,
352 policy: crate::handler::InboundTracePolicy,
353 ) -> Self {
354 self.inbound_trace_policy = policy;
355 self
356 }
357
358 #[must_use]
371 pub const fn allow_unauthenticated_extended_card(mut self) -> Self {
372 self.allow_unauthenticated_extended_card = true;
373 self
374 }
375
376 #[must_use]
390 pub fn with_tenant_config(mut self, config: PerTenantConfig) -> Self {
391 self.tenant_config = Some(config);
392 self
393 }
394
395 #[allow(clippy::too_many_lines)]
403 pub fn build(self) -> ServerResult<RequestHandler> {
404 if let Some(ref card) = self.agent_card
406 && card.supported_interfaces.is_empty()
407 {
408 return Err(crate::error::ServerError::InvalidParams(
409 "agent card must have at least one supported interface".into(),
410 ));
411 }
412
413 if let Some(timeout) = self.executor_timeout
415 && timeout.is_zero()
416 {
417 return Err(crate::error::ServerError::InvalidParams(
418 "executor timeout must be greater than zero".into(),
419 ));
420 }
421
422 if self.handler_limits.max_id_length == 0 {
424 return Err(crate::error::ServerError::InvalidParams(
425 "max_id_length must be greater than zero".into(),
426 ));
427 }
428 if self.handler_limits.max_metadata_size == 0 {
429 return Err(crate::error::ServerError::InvalidParams(
430 "max_metadata_size must be greater than zero".into(),
431 ));
432 }
433 if self.handler_limits.push_delivery_timeout.is_zero() {
434 return Err(crate::error::ServerError::InvalidParams(
435 "push_delivery_timeout must be greater than zero".into(),
436 ));
437 }
438 let task_store = self
441 .task_store
442 .unwrap_or_else(|| Arc::new(InMemoryTaskStore::with_config(self.task_store_config)));
443
444 let mut agent_card = self.agent_card;
460 if let Some(card) = agent_card.as_mut() {
461 let mut wanted: Vec<AgentExtension> = Vec::new();
462 if task_store.supports_idempotency() {
463 wanted.push(AgentExtension {
464 uri: idempotency::IDEMPOTENCY_EXTENSION_URI.to_owned(),
465 description: Some(
466 "Client-supplied idempotency keys on message/send: a \
467 retried send returns the task the first one created \
468 instead of starting a second."
469 .to_owned(),
470 ),
471 required: Some(false),
472 params: None,
473 });
474 }
475 wanted.push(AgentExtension {
476 uri: a2a_protocol_types::failure::FAILURE_EXTENSION_URI.to_owned(),
477 description: Some(
478 "A failed task says why in a form a caller can match on, \
479 rather than only in prose."
480 .to_owned(),
481 ),
482 required: Some(false),
483 params: None,
484 });
485
486 let declared = card.capabilities.extensions.as_deref().unwrap_or(&[]);
487 let missing: Vec<AgentExtension> = wanted
488 .into_iter()
489 .filter(|w| !declared.iter().any(|have| have.uri == w.uri))
490 .collect();
491
492 if !missing.is_empty()
501 && card
502 .signatures
503 .as_ref()
504 .is_some_and(|sigs| !sigs.is_empty())
505 {
506 let uris: Vec<&str> = missing.iter().map(|e| e.uri.as_str()).collect();
507 return Err(crate::error::ServerError::InvalidParams(format!(
508 "the agent card carries a signature but does not declare {}. \
509 Advertising an extension edits `capabilities.extensions`, \
510 which is inside the signed bytes, so the card this server \
511 would serve no longer verifies against its own signature. \
512 Declare the extension on the card before signing it, or \
513 supply the card unsigned.",
514 uris.join(", ")
515 )));
516 }
517
518 if !missing.is_empty() {
519 card.capabilities
520 .extensions
521 .get_or_insert_with(Vec::new)
522 .extend(missing);
523 }
524 }
525
526 let (required_extensions, declared_extensions) = agent_card
529 .as_ref()
530 .and_then(|c| c.capabilities.extensions.as_ref())
531 .map(|exts| {
532 let declared: Vec<String> = exts.iter().map(|e| e.uri.clone()).collect();
533 let required: Vec<String> = exts
534 .iter()
535 .filter(|e| e.required == Some(true))
536 .map(|e| e.uri.clone())
537 .collect();
538 (required, declared)
539 })
540 .unwrap_or_default();
541
542 Ok(RequestHandler {
543 executor: self.executor,
544 task_store,
545 push_config_store: self
546 .push_config_store
547 .unwrap_or_else(|| Arc::new(InMemoryPushConfigStore::new())),
548 push_sender: self.push_sender,
549 event_queue_manager: {
550 let mut mgr = self
551 .event_queue_capacity
552 .map_or_else(EventQueueManager::new, EventQueueManager::with_capacity);
553 if let Some(max_size) = self.max_event_size {
554 mgr = mgr.with_max_event_size(max_size);
555 }
556 mgr = mgr.with_max_concurrent_queues(
557 self.max_concurrent_streams
558 .unwrap_or(DEFAULT_MAX_CONCURRENT_STREAMS),
559 );
560 mgr = mgr.with_metrics(Arc::clone(&self.metrics));
561 mgr
562 },
563 interceptors: self.interceptors,
564 agent_card,
565 executor_timeout: self.executor_timeout,
566 metrics: self.metrics,
567 limits: self.handler_limits,
568 tenant_resolver: self.tenant_resolver,
569 require_resolved_tenant: self.require_resolved_tenant,
570 inbound_trace_policy: self.inbound_trace_policy,
571 allow_unauthenticated_extended_card: self.allow_unauthenticated_extended_card,
572 required_extensions,
573 declared_extensions,
574 tenant_config: self.tenant_config,
575 cancellation_tokens: Arc::new(tokio::sync::RwLock::new(
576 std::collections::HashMap::new(),
577 )),
578 context_locks: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
579 tenant_slots: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
580 })
581 }
582}
583
584impl std::fmt::Debug for RequestHandlerBuilder {
585 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586 f.debug_struct("RequestHandlerBuilder")
587 .field("executor", &"<dyn AgentExecutor>")
588 .field("task_store", &self.task_store.is_some())
589 .field("task_store_config", &self.task_store_config)
590 .field("push_config_store", &self.push_config_store.is_some())
591 .field("push_sender", &self.push_sender.is_some())
592 .field("interceptors", &self.interceptors)
593 .field("agent_card", &self.agent_card.is_some())
594 .field("executor_timeout", &self.executor_timeout)
595 .field("event_queue_capacity", &self.event_queue_capacity)
596 .field("max_event_size", &self.max_event_size)
597 .field("max_concurrent_streams", &self.max_concurrent_streams)
598 .field("metrics", &"<dyn Metrics>")
599 .field("handler_limits", &self.handler_limits)
600 .field("tenant_resolver", &self.tenant_resolver.is_some())
601 .field("tenant_config", &self.tenant_config)
602 .field("require_resolved_tenant", &self.require_resolved_tenant)
603 .field("inbound_trace_policy", &self.inbound_trace_policy)
604 .field(
605 "allow_unauthenticated_extended_card",
606 &self.allow_unauthenticated_extended_card,
607 )
608 .finish()
609 }
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615 use crate::agent_executor;
616
617 struct TestExecutor;
618
619 agent_executor!(TestExecutor, |_ctx, _queue| async { Ok(()) });
620
621 #[test]
622 fn builder_defaults_build_ok() {
623 let handler = RequestHandlerBuilder::new(TestExecutor).build();
624 let h = handler.expect("default builder should succeed");
625 assert!(
626 h.tenant_resolver().is_none(),
627 "default builder should have no tenant resolver"
628 );
629 assert!(
630 h.tenant_config().is_none(),
631 "default builder should have no tenant config"
632 );
633 }
634
635 #[test]
636 fn builder_zero_executor_timeout_errors() {
637 let result = RequestHandlerBuilder::new(TestExecutor)
638 .with_executor_timeout(Duration::ZERO)
639 .build();
640 assert!(result.is_err());
641 }
642
643 #[test]
644 fn builder_empty_agent_card_interfaces_errors() {
645 use a2a_protocol_types::{AgentCapabilities, AgentCard};
646
647 let card = AgentCard {
648 url: None,
649 name: "empty".into(),
650 version: "1.0".into(),
651 description: "No interfaces".into(),
652 supported_interfaces: vec![],
653 provider: None,
654 icon_url: None,
655 documentation_url: None,
656 capabilities: AgentCapabilities::none(),
657 security_schemes: None,
658 security_requirements: None,
659 default_input_modes: vec![],
660 default_output_modes: vec![],
661 skills: vec![],
662 signatures: None,
663 };
664
665 let result = RequestHandlerBuilder::new(TestExecutor)
666 .with_agent_card(card)
667 .build();
668 assert!(result.is_err());
669 }
670
671 #[test]
672 fn builder_with_all_options() {
673 use a2a_protocol_types::{AgentCapabilities, AgentCard, AgentInterface};
674
675 let card = AgentCard {
676 url: None,
677 name: "test".into(),
678 version: "1.0".into(),
679 description: "Test agent".into(),
680 supported_interfaces: vec![AgentInterface {
681 url: "http://localhost:8080".into(),
682 protocol_binding: "JSONRPC".into(),
683 protocol_version: "1.0.0".into(),
684 tenant: None,
685 }],
686 provider: None,
687 icon_url: None,
688 documentation_url: None,
689 capabilities: AgentCapabilities::none(),
690 security_schemes: None,
691 security_requirements: None,
692 default_input_modes: vec![],
693 default_output_modes: vec![],
694 skills: vec![],
695 signatures: None,
696 };
697
698 let result = RequestHandlerBuilder::new(TestExecutor)
699 .with_agent_card(card)
700 .with_executor_timeout(Duration::from_secs(30))
701 .with_event_queue_capacity(128)
702 .with_max_event_size(1024 * 1024)
703 .with_max_concurrent_streams(10)
704 .with_handler_limits(HandlerLimits::default().with_max_id_length(2048))
705 .build();
706 let h = result.expect("builder with all options should succeed");
707 assert!(h.tenant_resolver().is_none(), "no tenant resolver set");
708 }
709
710 #[test]
711 fn builder_with_tenant_resolver_and_config() {
712 use crate::tenant_config::{PerTenantConfig, TenantLimits};
713 use crate::tenant_resolver::HeaderTenantResolver;
714
715 let handler = RequestHandlerBuilder::new(TestExecutor)
716 .with_tenant_resolver(HeaderTenantResolver::default())
717 .with_tenant_config(
718 PerTenantConfig::builder()
719 .default_limits(TenantLimits::builder().rate_limit_rps(100).build())
720 .with_override(
721 "premium",
722 TenantLimits::builder().rate_limit_rps(1000).build(),
723 )
724 .build(),
725 )
726 .build();
727 let handler = handler.expect("builder with tenant resolver and config should succeed");
728 assert!(handler.tenant_resolver().is_some());
729 assert!(handler.tenant_config().is_some());
730 assert_eq!(
731 handler
732 .tenant_config()
733 .unwrap()
734 .get("premium")
735 .rate_limit_rps,
736 Some(1000)
737 );
738 assert_eq!(
739 handler
740 .tenant_config()
741 .unwrap()
742 .get("unknown")
743 .rate_limit_rps,
744 Some(100)
745 );
746 }
747
748 #[test]
749 fn builder_without_tenant_fields() {
750 let handler = RequestHandlerBuilder::new(TestExecutor).build().unwrap();
751 assert!(handler.tenant_resolver().is_none());
752 assert!(handler.tenant_config().is_none());
753 }
754
755 #[test]
759 fn builder_default_caps_concurrent_streams() {
760 let handler = RequestHandlerBuilder::new(TestExecutor).build().unwrap();
761 let debug = format!("{:?}", handler.event_queue_manager);
762 assert!(
763 debug.contains(&format!(
764 "max_concurrent_queues: Some({DEFAULT_MAX_CONCURRENT_STREAMS})"
765 )),
766 "default builder should cap concurrent streams at \
767 {DEFAULT_MAX_CONCURRENT_STREAMS}, got: {debug}"
768 );
769 }
770
771 #[test]
773 fn builder_max_concurrent_streams_override_wins() {
774 let handler = RequestHandlerBuilder::new(TestExecutor)
775 .with_max_concurrent_streams(7)
776 .build()
777 .unwrap();
778 let debug = format!("{:?}", handler.event_queue_manager);
779 assert!(
780 debug.contains("max_concurrent_queues: Some(7)"),
781 "explicit cap should override the default, got: {debug}"
782 );
783 }
784
785 #[test]
786 fn builder_debug_does_not_panic() {
787 let builder = RequestHandlerBuilder::new(TestExecutor);
788 let debug = format!("{builder:?}");
789 assert!(debug.contains("RequestHandlerBuilder"));
790 }
791
792 #[test]
793 fn builder_with_push_config_store_builds_ok() {
794 use crate::push::InMemoryPushConfigStore;
795 let result = RequestHandlerBuilder::new(TestExecutor)
796 .with_push_config_store(InMemoryPushConfigStore::new())
797 .build();
798 let _h = result.expect("builder with push config store should succeed");
799 }
800
801 #[test]
802 fn builder_zero_max_id_length_errors() {
803 let result = RequestHandlerBuilder::new(TestExecutor)
804 .with_handler_limits(HandlerLimits::default().with_max_id_length(0))
805 .build();
806 assert!(result.is_err(), "zero max_id_length should be rejected");
807 }
808
809 #[test]
810 fn builder_zero_max_metadata_size_errors() {
811 let result = RequestHandlerBuilder::new(TestExecutor)
812 .with_handler_limits(HandlerLimits::default().with_max_metadata_size(0))
813 .build();
814 assert!(result.is_err(), "zero max_metadata_size should be rejected");
815 }
816
817 #[test]
818 fn builder_zero_push_delivery_timeout_errors() {
819 let result = RequestHandlerBuilder::new(TestExecutor)
820 .with_handler_limits(
821 HandlerLimits::default().with_push_delivery_timeout(Duration::ZERO),
822 )
823 .build();
824 assert!(
825 result.is_err(),
826 "zero push_delivery_timeout should be rejected"
827 );
828 }
829
830 #[test]
835 fn the_failure_extension_is_advertised_and_never_required() {
836 use a2a_protocol_types::failure::FAILURE_EXTENSION_URI;
837
838 let card = a2a_protocol_types::agent_card::AgentCard::new(
839 "t",
840 "0.0.0",
841 a2a_protocol_types::agent_card::AgentInterface::jsonrpc("http://127.0.0.1:1"),
842 );
843 let handler = RequestHandlerBuilder::new(TestExecutor)
844 .with_agent_card(card)
845 .build()
846 .expect("handler");
847
848 let entry = handler
849 .agent_card
850 .as_ref()
851 .expect("a card was configured")
852 .capabilities
853 .extensions
854 .as_ref()
855 .expect("extensions")
856 .iter()
857 .find(|e| e.uri == FAILURE_EXTENSION_URI)
858 .expect("the failure extension must be advertised");
859 assert_eq!(entry.required, Some(false));
860 assert!(
861 entry.description.is_some(),
862 "a bare URI tells a reader nothing"
863 );
864 }
865
866 #[test]
869 fn an_operators_own_failure_declaration_is_left_alone() {
870 use a2a_protocol_types::extensions::AgentExtension;
871 use a2a_protocol_types::failure::FAILURE_EXTENSION_URI;
872
873 let mut card = a2a_protocol_types::agent_card::AgentCard::new(
874 "t",
875 "0.0.0",
876 a2a_protocol_types::agent_card::AgentInterface::jsonrpc("http://127.0.0.1:1"),
877 );
878 let mut mine = AgentExtension::new(FAILURE_EXTENSION_URI);
879 mine.required = Some(true);
880 mine.description = Some("mine".to_owned());
881 card.capabilities.extensions = Some(vec![mine]);
882
883 let handler = RequestHandlerBuilder::new(TestExecutor)
884 .with_agent_card(card)
885 .build()
886 .expect("handler");
887
888 let entries: Vec<_> = handler
889 .agent_card
890 .as_ref()
891 .expect("card")
892 .capabilities
893 .extensions
894 .as_ref()
895 .expect("extensions")
896 .iter()
897 .filter(|e| e.uri == FAILURE_EXTENSION_URI)
898 .collect();
899 assert_eq!(entries.len(), 1, "the entry must not be duplicated");
900 assert_eq!(entries[0].required, Some(true));
901 assert_eq!(entries[0].description.as_deref(), Some("mine"));
902 }
903
904 fn unsigned_card() -> a2a_protocol_types::agent_card::AgentCard {
906 a2a_protocol_types::agent_card::AgentCard::new(
907 "t",
908 "0.0.0",
909 a2a_protocol_types::agent_card::AgentInterface::jsonrpc("http://127.0.0.1:1"),
910 )
911 }
912
913 fn a_signature() -> a2a_protocol_types::extensions::AgentCardSignature {
914 a2a_protocol_types::extensions::AgentCardSignature {
915 protected: "eyJhbGciOiJFUzI1NiJ9".to_owned(),
916 signature: "c2ln".to_owned(),
917 header: None,
918 }
919 }
920
921 #[test]
927 fn a_signed_card_missing_an_advertisement_is_refused_not_edited() {
928 let mut card = unsigned_card();
929 card.signatures = Some(vec![a_signature()]);
930
931 let err = RequestHandlerBuilder::new(TestExecutor)
932 .with_agent_card(card)
933 .build()
934 .expect_err("a signed card that would be edited must not build");
935
936 let msg = err.to_string();
937 assert!(
938 msg.contains(a2a_protocol_types::failure::FAILURE_EXTENSION_URI),
939 "the error must name the extension to declare: {msg}"
940 );
941 assert!(
942 msg.contains("signature"),
943 "the error must say why refusing is right: {msg}"
944 );
945 }
946
947 #[test]
951 fn a_signed_card_that_declares_everything_is_served_unchanged() {
952 use a2a_protocol_types::extensions::AgentExtension;
953
954 let mut card = unsigned_card();
955 card.capabilities.extensions = Some(vec![
956 AgentExtension::new(idempotency::IDEMPOTENCY_EXTENSION_URI),
957 AgentExtension::new(a2a_protocol_types::failure::FAILURE_EXTENSION_URI),
958 ]);
959 card.signatures = Some(vec![a_signature()]);
960
961 let signed = serde_json::to_value(&card).expect("serialize");
962
963 let handler = RequestHandlerBuilder::new(TestExecutor)
964 .with_agent_card(card)
965 .build()
966 .expect("a fully-declared signed card must build");
967
968 let served =
969 serde_json::to_value(handler.agent_card.as_ref().expect("card")).expect("serialize");
970
971 assert_eq!(
972 signed, served,
973 "the served card must be what was signed, field for field"
974 );
975 }
976
977 #[test]
980 fn an_empty_signature_list_does_not_count_as_signed() {
981 let mut card = unsigned_card();
982 card.signatures = Some(Vec::new());
983
984 let handler = RequestHandlerBuilder::new(TestExecutor)
985 .with_agent_card(card)
986 .build()
987 .expect("an unsigned card still gets its advertisements");
988
989 assert!(
990 handler
991 .agent_card
992 .as_ref()
993 .expect("card")
994 .capabilities
995 .extensions
996 .as_ref()
997 .is_some_and(|e| e
998 .iter()
999 .any(|x| x.uri == a2a_protocol_types::failure::FAILURE_EXTENSION_URI)),
1000 "the failure extension must still be advertised"
1001 );
1002 }
1003}