1mod transport_factory;
35
36use std::time::Duration;
37
38use a2a_protocol_types::{AgentCard, AgentInterface};
39
40use crate::config::{ClientConfig, TlsConfig};
41use crate::error::{ClientError, ClientResult};
42use crate::interceptor::{CallInterceptor, InterceptorChain};
43use crate::retry::RetryPolicy;
44use crate::transport::Transport;
45
46#[allow(dead_code)]
54pub(crate) const SUPPORTED_PROTOCOL_MAJOR: u32 = 1;
55
56#[allow(dead_code)] pub(crate) fn protocol_version_mismatch(protocol_version: &str) -> Option<&str> {
72 if protocol_version.is_empty() {
73 return None;
74 }
75 let major = protocol_version
76 .split('.')
77 .next()
78 .and_then(|s| s.parse::<u32>().ok());
79 if major == Some(SUPPORTED_PROTOCOL_MAJOR) {
80 None
81 } else {
82 Some(protocol_version)
83 }
84}
85
86pub struct ClientBuilder {
93 pub(super) endpoint: String,
94 pub(super) transport_override: Option<Box<dyn Transport>>,
95 pub(super) interceptors: InterceptorChain,
96 pub(super) config: ClientConfig,
97 pub(super) preferred_binding: Option<String>,
98 pub(super) retry_policy: Option<RetryPolicy>,
99 pub(super) card_interfaces: Vec<AgentInterface>,
106 pub(super) grpc_bare_address_scheme: crate::config::GrpcBareAddressScheme,
115 #[cfg(feature = "grpc-tls")]
120 pub(super) grpc_tls_config: Option<crate::transport::grpc::ClientTlsConfig>,
121}
122
123fn select_interface<'a>(card: &'a AgentCard, preferences: &[String]) -> Option<&'a AgentInterface> {
132 for wanted in preferences {
133 if let Some(iface) = card
134 .supported_interfaces
135 .iter()
136 .find(|i| i.protocol_binding.eq_ignore_ascii_case(wanted))
137 {
138 return Some(iface);
139 }
140 }
141 card.supported_interfaces.first()
142}
143
144impl ClientBuilder {
145 #[must_use]
150 pub fn new(endpoint: impl Into<String>) -> Self {
151 Self {
152 endpoint: endpoint.into(),
153 transport_override: None,
154 interceptors: InterceptorChain::new(),
155 config: ClientConfig::default(),
156 preferred_binding: None,
157 retry_policy: None,
158 grpc_bare_address_scheme: crate::config::GrpcBareAddressScheme::default(),
159 #[cfg(feature = "grpc-tls")]
160 grpc_tls_config: None,
161 card_interfaces: Vec::new(),
162 }
163 }
164
165 pub fn from_card(card: &AgentCard) -> ClientResult<Self> {
172 Self::from_card_preferring(card, &ClientConfig::default().preferred_bindings)
173 }
174
175 pub fn from_card_preferring(card: &AgentCard, preferences: &[String]) -> ClientResult<Self> {
202 let first = select_interface(card, preferences).ok_or_else(|| {
203 ClientError::InvalidEndpoint("agent card has no supported interfaces".into())
204 })?;
205 let (endpoint, binding) = (first.url.clone(), first.protocol_binding.clone());
206
207 #[cfg(feature = "tracing")]
209 if let Some(mismatched) = protocol_version_mismatch(&first.protocol_version) {
210 trace_warn!(
211 agent = %card.name,
212 protocol_version = %mismatched,
213 supported_major = SUPPORTED_PROTOCOL_MAJOR,
214 "agent protocol version may be incompatible with this client"
215 );
216 }
217
218 Ok(Self {
219 endpoint,
220 transport_override: None,
221 interceptors: InterceptorChain::new(),
222 config: ClientConfig {
223 tenant: first.tenant.clone(),
225 preferred_bindings: preferences.to_vec(),
230 ..ClientConfig::default()
231 },
232 preferred_binding: Some(binding),
233 retry_policy: None,
234 grpc_bare_address_scheme: crate::config::GrpcBareAddressScheme::default(),
235 #[cfg(feature = "grpc-tls")]
236 grpc_tls_config: None,
237 card_interfaces: card.supported_interfaces.clone(),
238 })
239 }
240
241 #[must_use]
245 pub const fn with_timeout(mut self, timeout: Duration) -> Self {
246 self.config.request_timeout = timeout;
247 self
248 }
249
250 #[must_use]
255 pub const fn with_stream_connect_timeout(mut self, timeout: Duration) -> Self {
256 self.config.stream_connect_timeout = timeout;
257 self
258 }
259
260 #[must_use]
265 pub const fn with_connection_timeout(mut self, timeout: Duration) -> Self {
266 self.config.connection_timeout = timeout;
267 self
268 }
269
270 #[must_use]
276 pub const fn with_max_response_size(mut self, max_bytes: usize) -> Self {
277 self.config.max_response_size = max_bytes;
278 self
279 }
280
281 #[must_use]
299 pub fn chosen_interface(&self) -> Option<&AgentInterface> {
300 let binding = self.preferred_binding.as_deref()?;
301 self.card_interfaces
302 .iter()
303 .find(|i| i.protocol_binding.eq_ignore_ascii_case(binding) && i.url == self.endpoint)
304 }
305
306 #[must_use]
323 pub fn with_protocol_binding(mut self, binding: impl Into<String>) -> Self {
324 let binding = binding.into();
325 let resolved = self
326 .card_interfaces
327 .iter()
328 .find(|i| i.protocol_binding.eq_ignore_ascii_case(&binding))
329 .map(|i| (i.url.clone(), i.tenant.clone()));
330 if let Some((url, tenant)) = resolved {
331 self.endpoint = url;
332 self.config.tenant = tenant;
333 }
334 self.preferred_binding = Some(binding);
335 self
336 }
337
338 #[must_use]
350 pub const fn with_grpc_bare_address_scheme(
351 mut self,
352 scheme: crate::config::GrpcBareAddressScheme,
353 ) -> Self {
354 self.grpc_bare_address_scheme = scheme;
355 self
356 }
357
358 #[cfg(feature = "grpc-tls")]
365 #[must_use]
366 pub fn with_grpc_tls_config(mut self, tls: crate::transport::grpc::ClientTlsConfig) -> Self {
367 self.grpc_tls_config = Some(tls);
368 self
369 }
370
371 #[must_use]
373 pub fn with_accepted_output_modes(mut self, modes: Vec<String>) -> Self {
374 self.config.accepted_output_modes = modes;
375 self
376 }
377
378 #[must_use]
380 pub const fn with_history_length(mut self, length: u32) -> Self {
381 self.config.history_length = Some(length);
382 self
383 }
384
385 #[must_use]
391 pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
392 self.config.tenant = Some(tenant.into());
393 self
394 }
395
396 #[must_use]
398 pub const fn with_return_immediately(mut self, val: bool) -> Self {
399 self.config.return_immediately = val;
400 self
401 }
402
403 #[must_use]
408 pub fn with_custom_transport(mut self, transport: impl Transport) -> Self {
409 self.transport_override = Some(Box::new(transport));
410 self
411 }
412
413 #[must_use]
415 pub const fn without_tls(mut self) -> Self {
416 self.config.tls = TlsConfig::Disabled;
417 self
418 }
419
420 #[must_use]
439 pub const fn with_retry_policy(mut self, policy: RetryPolicy) -> Self {
440 self.retry_policy = Some(policy);
441 self
442 }
443
444 #[must_use]
448 pub fn with_interceptor<I: CallInterceptor>(mut self, interceptor: I) -> Self {
449 self.interceptors.push(interceptor);
450 self
451 }
452}
453
454impl std::fmt::Debug for ClientBuilder {
455 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
456 f.debug_struct("ClientBuilder")
457 .field("endpoint", &self.endpoint)
458 .field("preferred_binding", &self.preferred_binding)
459 .finish_non_exhaustive()
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use crate::config::{BINDING_GRPC, BINDING_HTTP_JSON, BINDING_JSONRPC, BINDING_REST};
467 use std::time::Duration;
468
469 fn card_with(interfaces: Vec<a2a_protocol_types::AgentInterface>) -> AgentCard {
481 use a2a_protocol_types::AgentCapabilities;
482
483 AgentCard {
484 url: None,
485 name: "prefs".into(),
486 version: "1.0".into(),
487 description: "Binding preference fixture".into(),
488 supported_interfaces: interfaces,
489 provider: None,
490 icon_url: None,
491 documentation_url: None,
492 capabilities: AgentCapabilities::none(),
493 security_schemes: None,
494 security_requirements: None,
495 default_input_modes: vec![],
496 default_output_modes: vec![],
497 skills: vec![],
498 signatures: None,
499 }
500 }
501
502 fn iface(binding: &str, url: &str) -> a2a_protocol_types::AgentInterface {
503 a2a_protocol_types::AgentInterface {
504 url: url.into(),
505 protocol_binding: binding.into(),
506 protocol_version: "1.0.0".into(),
507 tenant: None,
508 }
509 }
510
511 fn jsonrpc_then_grpc() -> AgentCard {
513 card_with(vec![
514 iface(BINDING_JSONRPC, "http://localhost:1111"),
515 iface(BINDING_GRPC, "http://localhost:2222"),
516 ])
517 }
518
519 #[test]
520 fn from_card_prefers_the_callers_binding_order_over_the_cards() {
521 let builder =
522 ClientBuilder::from_card_preferring(&jsonrpc_then_grpc(), &[BINDING_GRPC.into()])
523 .expect("from_card_preferring");
524
525 assert_eq!(
526 builder.endpoint, "http://localhost:2222",
527 "the caller ranked GRPC; the card's first interface is JSONRPC. \
528 Taking the card's order would give :1111"
529 );
530 assert_eq!(builder.preferred_binding.as_deref(), Some(BINDING_GRPC));
531 }
532
533 #[test]
534 fn a_later_preference_wins_when_the_earlier_one_is_not_offered() {
535 let builder = ClientBuilder::from_card_preferring(
536 &jsonrpc_then_grpc(),
537 &[BINDING_HTTP_JSON.into(), BINDING_GRPC.into()],
538 )
539 .expect("from_card_preferring");
540
541 assert_eq!(
542 builder.endpoint, "http://localhost:2222",
543 "HTTP+JSON is unavailable, so the second preference (GRPC) applies"
544 );
545 }
546
547 #[test]
548 fn an_unmatched_preference_falls_back_to_the_cards_first_interface() {
549 let builder =
550 ClientBuilder::from_card_preferring(&jsonrpc_then_grpc(), &[BINDING_HTTP_JSON.into()])
551 .expect("from_card_preferring");
552
553 assert_eq!(
554 builder.endpoint, "http://localhost:1111",
555 "no ranked binding is offered, so the card's own first choice is used"
556 );
557 assert_eq!(builder.preferred_binding.as_deref(), Some(BINDING_JSONRPC));
558 }
559
560 #[test]
561 fn an_empty_preference_list_takes_the_cards_first_interface() {
562 let builder = ClientBuilder::from_card_preferring(&jsonrpc_then_grpc(), &[])
563 .expect("from_card_preferring");
564
565 assert_eq!(builder.endpoint, "http://localhost:1111");
566 }
567
568 #[test]
574 fn chosen_interface_is_the_one_from_card_picked() {
575 let builder = ClientBuilder::from_card(&jsonrpc_then_grpc()).expect("from_card");
576 let chosen = builder.chosen_interface().expect("came from a card");
577 assert_eq!(chosen.protocol_binding, BINDING_JSONRPC);
578 assert_eq!(chosen.url, "http://localhost:1111");
579
580 let moved = builder.with_protocol_binding(BINDING_GRPC);
581 let chosen = moved.chosen_interface().expect("GRPC is on the card");
582 assert_eq!(chosen.url, "http://localhost:2222", "follows the endpoint");
583
584 assert!(
585 moved
586 .with_protocol_binding(BINDING_HTTP_JSON)
587 .chosen_interface()
588 .is_none(),
589 "a binding the card does not advertise leaves no card interface to report"
590 );
591 assert!(
592 ClientBuilder::new("http://localhost:3333")
593 .chosen_interface()
594 .is_none(),
595 "no card, no interface"
596 );
597 }
598
599 #[test]
600 fn binding_preference_matches_case_insensitively() {
601 let builder =
605 ClientBuilder::from_card_preferring(&jsonrpc_then_grpc(), &["gRpC".to_owned()])
606 .expect("from_card_preferring");
607
608 assert_eq!(builder.endpoint, "http://localhost:2222");
609 }
610
611 #[test]
612 fn from_card_applies_the_default_preference_list() {
613 let card = card_with(vec![
616 iface(BINDING_GRPC, "http://localhost:2222"),
617 iface(BINDING_JSONRPC, "http://localhost:1111"),
618 ]);
619
620 let builder = ClientBuilder::from_card(&card).expect("from_card");
621
622 assert_eq!(
623 builder.endpoint, "http://localhost:1111",
624 "from_card must honour ClientConfig's default preference (JSONRPC), \
625 not the card's own first entry (GRPC)"
626 );
627 assert_eq!(builder.preferred_binding.as_deref(), Some(BINDING_JSONRPC));
628 }
629
630 #[test]
631 fn a_single_interface_card_is_used_whatever_the_preference() {
632 let card = card_with(vec![iface(BINDING_JSONRPC, "http://localhost:1111")]);
633
634 let builder = ClientBuilder::from_card_preferring(&card, &[BINDING_GRPC.into()])
635 .expect("from_card_preferring");
636
637 assert_eq!(
638 builder.endpoint, "http://localhost:1111",
639 "an agent that speaks nothing the caller ranked is still worth \
640 talking to; refusing to connect would be a worse answer"
641 );
642 }
643
644 #[test]
645 fn the_applied_preference_is_recorded_in_the_built_config() {
646 let prefs = vec![BINDING_GRPC.to_owned(), BINDING_JSONRPC.to_owned()];
649 let builder = ClientBuilder::from_card_preferring(&jsonrpc_then_grpc(), &prefs)
650 .expect("from_card_preferring");
651
652 assert_eq!(builder.config.preferred_bindings, prefs);
653 }
654
655 #[test]
664 fn switching_binding_on_a_card_builder_moves_the_endpoint_too() {
665 let builder = ClientBuilder::from_card(&jsonrpc_then_grpc())
666 .expect("from_card")
667 .with_protocol_binding(BINDING_GRPC);
668
669 assert_eq!(
670 builder.endpoint, "http://localhost:2222",
671 "the card advertises GRPC at :2222; keeping :1111 would speak gRPC \
672 to the JSON-RPC port"
673 );
674 assert_eq!(builder.preferred_binding.as_deref(), Some(BINDING_GRPC));
675 }
676
677 #[test]
678 fn switching_binding_carries_that_interfaces_tenant() {
679 let card = card_with(vec![
680 iface(BINDING_JSONRPC, "http://localhost:1111"),
681 a2a_protocol_types::AgentInterface {
682 tenant: Some("grpc-tenant".into()),
683 ..iface(BINDING_GRPC, "http://localhost:2222")
684 },
685 ]);
686
687 let builder = ClientBuilder::from_card(&card)
688 .expect("from_card")
689 .with_protocol_binding(BINDING_GRPC);
690
691 assert_eq!(
692 builder.config.tenant.as_deref(),
693 Some("grpc-tenant"),
694 "tenant is per-interface; the old interface's tenant does not \
695 survive a move to a different one"
696 );
697 }
698
699 #[test]
700 fn with_tenant_after_a_binding_switch_wins() {
701 let builder = ClientBuilder::from_card(&jsonrpc_then_grpc())
702 .expect("from_card")
703 .with_protocol_binding(BINDING_GRPC)
704 .with_tenant("explicit");
705
706 assert_eq!(builder.config.tenant.as_deref(), Some("explicit"));
707 }
708
709 #[test]
710 fn switching_to_a_binding_the_card_lacks_leaves_the_endpoint_alone() {
711 let builder = ClientBuilder::from_card(&jsonrpc_then_grpc())
712 .expect("from_card")
713 .with_protocol_binding(BINDING_HTTP_JSON);
714
715 assert_eq!(
716 builder.endpoint, "http://localhost:1111",
717 "nothing to resolve against, so the caller's endpoint stands"
718 );
719 assert_eq!(
720 builder.preferred_binding.as_deref(),
721 Some(BINDING_HTTP_JSON)
722 );
723 }
724
725 #[test]
726 fn a_plain_new_builder_keeps_its_endpoint_across_a_binding_switch() {
727 let builder =
730 ClientBuilder::new("http://localhost:8080").with_protocol_binding(BINDING_REST);
731
732 assert_eq!(builder.endpoint, "http://localhost:8080");
733 assert_eq!(builder.preferred_binding.as_deref(), Some(BINDING_REST));
734 }
735
736 #[test]
737 fn from_card_preferring_rejects_a_card_with_no_interfaces() {
738 let result =
739 ClientBuilder::from_card_preferring(&card_with(vec![]), &[BINDING_GRPC.into()]);
740 assert!(result.is_err(), "empty interfaces should return error");
741 }
742
743 #[test]
744 fn builder_from_card_uses_card_url() {
745 use a2a_protocol_types::{AgentCapabilities, AgentCard, AgentInterface};
746
747 let card = AgentCard {
748 url: None,
749 name: "test".into(),
750 version: "1.0".into(),
751 description: "A test agent".into(),
752 supported_interfaces: vec![AgentInterface {
753 url: "http://localhost:9090".into(),
754 protocol_binding: "JSONRPC".into(),
755 protocol_version: "1.0.0".into(),
756 tenant: None,
757 }],
758 provider: None,
759 icon_url: None,
760 documentation_url: None,
761 capabilities: AgentCapabilities::none(),
762 security_schemes: None,
763 security_requirements: None,
764 default_input_modes: vec![],
765 default_output_modes: vec![],
766 skills: vec![],
767 signatures: None,
768 };
769
770 let client = ClientBuilder::from_card(&card)
771 .unwrap()
772 .build()
773 .expect("build");
774 let _ = client;
775 }
776
777 #[test]
778 fn builder_with_timeout_sets_config() {
779 let client = ClientBuilder::new("http://localhost:8080")
780 .with_timeout(Duration::from_secs(60))
781 .build()
782 .expect("build");
783 assert_eq!(client.config().request_timeout, Duration::from_secs(60));
784 }
785
786 #[test]
787 fn builder_from_card_empty_interfaces_returns_error() {
788 use a2a_protocol_types::{AgentCapabilities, AgentCard};
789
790 let card = AgentCard {
791 url: None,
792 name: "empty".into(),
793 version: "1.0".into(),
794 description: "No interfaces".into(),
795 supported_interfaces: vec![],
796 provider: None,
797 icon_url: None,
798 documentation_url: None,
799 capabilities: AgentCapabilities::none(),
800 security_schemes: None,
801 security_requirements: None,
802 default_input_modes: vec![],
803 default_output_modes: vec![],
804 skills: vec![],
805 signatures: None,
806 };
807
808 let result = ClientBuilder::from_card(&card);
809 assert!(result.is_err(), "empty interfaces should return error");
810 }
811
812 #[test]
813 fn builder_with_return_immediately() {
814 let client = ClientBuilder::new("http://localhost:8080")
815 .with_return_immediately(true)
816 .build()
817 .expect("build");
818 assert!(client.config().return_immediately);
819 }
820
821 #[test]
822 fn builder_with_history_length() {
823 let client = ClientBuilder::new("http://localhost:8080")
824 .with_history_length(10)
825 .build()
826 .expect("build");
827 assert_eq!(client.config().history_length, Some(10));
828 }
829
830 #[test]
831 fn builder_debug_contains_fields() {
832 let builder = ClientBuilder::new("http://localhost:8080");
833 let debug = format!("{builder:?}");
834 assert!(
835 debug.contains("ClientBuilder"),
836 "debug output missing struct name: {debug}"
837 );
838 assert!(
839 debug.contains("http://localhost:8080"),
840 "debug output missing endpoint: {debug}"
841 );
842 }
843
844 #[test]
847 fn builder_from_card_mismatched_version() {
848 use a2a_protocol_types::{AgentCapabilities, AgentCard, AgentInterface};
849
850 let card = AgentCard {
851 url: None,
852 name: "mismatch".into(),
853 version: "1.0".into(),
854 description: "Version mismatch test".into(),
855 supported_interfaces: vec![AgentInterface {
856 url: "http://localhost:9091".into(),
857 protocol_binding: "JSONRPC".into(),
858 protocol_version: "99.0.0".into(), tenant: None,
860 }],
861 provider: None,
862 icon_url: None,
863 documentation_url: None,
864 capabilities: AgentCapabilities::none(),
865 security_schemes: None,
866 security_requirements: None,
867 default_input_modes: vec![],
868 default_output_modes: vec![],
869 skills: vec![],
870 signatures: None,
871 };
872
873 let builder = ClientBuilder::from_card(&card).unwrap();
874 assert_eq!(builder.endpoint, "http://localhost:9091");
875 }
876
877 #[test]
880 fn version_mismatch_matching_major_returns_none() {
881 assert_eq!(protocol_version_mismatch("1.0.0"), None);
882 assert_eq!(protocol_version_mismatch("1.2.3"), None);
883 assert_eq!(protocol_version_mismatch("1"), None);
884 }
885
886 #[test]
887 fn version_mismatch_returns_original_on_mismatch() {
888 assert_eq!(protocol_version_mismatch("0.5.0"), Some("0.5.0"));
889 assert_eq!(protocol_version_mismatch("2.0.0"), Some("2.0.0"));
890 assert_eq!(protocol_version_mismatch("99.0.0"), Some("99.0.0"));
891 }
892
893 #[test]
894 fn version_mismatch_empty_is_compatible() {
895 assert_eq!(protocol_version_mismatch(""), None);
897 }
898
899 #[test]
900 fn version_mismatch_unparseable_is_incompatible() {
901 assert_eq!(
902 protocol_version_mismatch("not-a-version"),
903 Some("not-a-version")
904 );
905 assert_eq!(protocol_version_mismatch("v1.0.0"), Some("v1.0.0"));
906 assert_eq!(protocol_version_mismatch("1-preview"), Some("1-preview"));
907 }
908
909 #[test]
916 fn builder_from_card_preserves_tenant() {
917 use a2a_protocol_types::{AgentCapabilities, AgentCard, AgentInterface};
918
919 let card = AgentCard {
920 url: None,
921 name: "multi-tenant".into(),
922 version: "1.0".into(),
923 description: "Multi-tenant agent".into(),
924 supported_interfaces: vec![AgentInterface {
925 url: "http://localhost:9092".into(),
926 protocol_binding: "JSONRPC".into(),
927 protocol_version: "1.0.0".into(),
928 tenant: Some("tenant-42".into()),
929 }],
930 provider: None,
931 icon_url: None,
932 documentation_url: None,
933 capabilities: AgentCapabilities::none(),
934 security_schemes: None,
935 security_requirements: None,
936 default_input_modes: vec![],
937 default_output_modes: vec![],
938 skills: vec![],
939 signatures: None,
940 };
941
942 let builder = ClientBuilder::from_card(&card).expect("from_card");
943 assert_eq!(
944 builder.config.tenant.as_deref(),
945 Some("tenant-42"),
946 "tenant from AgentInterface must be propagated to ClientConfig"
947 );
948 }
949
950 #[test]
951 fn builder_from_card_none_tenant_stays_none() {
952 use a2a_protocol_types::{AgentCapabilities, AgentCard, AgentInterface};
953
954 let card = AgentCard {
955 url: None,
956 name: "no-tenant".into(),
957 version: "1.0".into(),
958 description: String::new(),
959 supported_interfaces: vec![AgentInterface {
960 url: "http://localhost:9093".into(),
961 protocol_binding: "JSONRPC".into(),
962 protocol_version: "1.0.0".into(),
963 tenant: None,
964 }],
965 provider: None,
966 icon_url: None,
967 documentation_url: None,
968 capabilities: AgentCapabilities::none(),
969 security_schemes: None,
970 security_requirements: None,
971 default_input_modes: vec![],
972 default_output_modes: vec![],
973 skills: vec![],
974 signatures: None,
975 };
976
977 let builder = ClientBuilder::from_card(&card).expect("from_card");
978 assert!(builder.config.tenant.is_none());
979 }
980
981 #[test]
983 fn builder_with_connection_timeout_and_retry_policy() {
984 use crate::retry::RetryPolicy;
985
986 let client = ClientBuilder::new("http://localhost:8080")
987 .with_connection_timeout(Duration::from_secs(5))
988 .with_retry_policy(RetryPolicy::default())
989 .build()
990 .expect("build");
991 assert_eq!(client.config().connection_timeout, Duration::from_secs(5));
992 }
993
994 #[test]
996 fn builder_with_stream_connect_timeout() {
997 let client = ClientBuilder::new("http://localhost:8080")
998 .with_stream_connect_timeout(Duration::from_secs(15))
999 .build()
1000 .expect("build");
1001 assert_eq!(
1002 client.config().stream_connect_timeout,
1003 Duration::from_secs(15)
1004 );
1005 }
1006}