1#[cfg(feature = "fuzzing")]
6#[doc(hidden)]
7pub mod fuzz_support {
8 use a2a::{A2AError, StreamResponse};
9
10 pub fn find_event_boundary(buf: &[u8]) -> Option<(usize, usize)> {
11 crate::jsonrpc::find_event_boundary(buf)
12 }
13
14 pub fn parse_stream_tail(
15 buf: &[u8],
16 parse_event: &dyn Fn(&str) -> Option<Result<StreamResponse, A2AError>>,
17 ) -> Option<Result<StreamResponse, A2AError>> {
18 crate::jsonrpc::parse_stream_tail(buf, parse_event)
19 }
20}
21
22pub mod agent_card;
23pub mod auth;
24pub mod client;
25pub mod factory;
26pub mod jsonrpc;
27pub mod middleware;
28mod push_config_compat;
29pub mod rest;
30pub mod transport;
31
32pub use client::A2AClient;
33pub use factory::A2AClientFactory;
34pub use futures::stream::BoxStream;
35pub use transport::{ServiceParams, Transport, TransportFactory};
36
37#[cfg(any(feature = "rustls-tls", feature = "rustls-no-provider"))]
38pub use rustls;
39
40pub fn default_reqwest_client(
43 #[allow(unused_variables)] extra_root_pem: Option<&[u8]>,
44) -> Result<reqwest::Client, a2a::A2AError> {
45 let builder = reqwest::Client::builder();
46
47 #[cfg(any(
48 feature = "rustls-tls",
49 feature = "rustls-no-provider",
50 feature = "native-tls"
51 ))]
52 let builder = match extra_root_pem {
53 Some(pem) => {
54 let certs = reqwest::Certificate::from_pem_bundle(pem)
55 .map_err(|e| a2a::A2AError::internal(format!("invalid PEM certificate: {e}")))?;
56 builder.tls_certs_merge(certs)
57 }
58 None => builder,
59 };
60
61 builder
62 .build()
63 .map_err(|e| a2a::A2AError::internal(format!("failed to build HTTP client: {e}")))
64}
65
66pub(crate) fn a2a_error_from_details(
67 code: i32,
68 message: String,
69 details: Vec<a2a::TypedDetail>,
70) -> a2a::A2AError {
71 use a2a::{error_code, errordetails, reason_to_error_code};
72 use serde_json::Value;
73
74 let mut code = code;
75 let mut message = message;
76
77 for detail in &details {
78 match detail.type_url.as_str() {
79 errordetails::BAD_REQUEST_TYPE => {
80 if let Some(Value::Array(violations)) = detail.value.get("fieldViolations") {
81 let violation_strs: Vec<String> = violations
82 .iter()
83 .filter_map(|v| {
84 let field = v.get("field")?.as_str()?;
85 let desc = v.get("description")?.as_str()?;
86 if field.is_empty() {
87 Some(desc.to_string())
88 } else {
89 Some(format!("{field}: {desc}"))
90 }
91 })
92 .collect();
93 if !violation_strs.is_empty() {
94 message = format!("{}: {}", message, violation_strs.join("; "));
95 }
96 }
97 if code == error_code::INTERNAL_ERROR {
98 code = error_code::INVALID_PARAMS;
99 }
100 }
101 errordetails::ERROR_INFO_TYPE => {
102 if let Some(Value::String(domain)) = detail.value.get("domain") {
103 if domain == errordetails::PROTOCOL_DOMAIN {
104 if let Some(Value::String(reason)) = detail.value.get("reason") {
105 if let Some(c) = reason_to_error_code(reason) {
106 code = c;
107 }
108 }
109 }
110 }
111 }
112 _ => {}
113 }
114 }
115
116 a2a::A2AError {
117 code,
118 message,
119 details: (!details.is_empty()).then_some(details),
120 }
121}
122
123#[cfg(all(test, any(feature = "rustls-tls", feature = "native-tls")))]
124pub(crate) mod test_utils {
125 pub fn rcgen_self_signed_ca_pem() -> Vec<u8> {
126 let mut params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
127 params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
128 params
129 .distinguished_name
130 .push(rcgen::DnType::CommonName, "Test CA");
131 let key = rcgen::KeyPair::generate().unwrap();
132 let cert = params.self_signed(&key).unwrap();
133 cert.pem().into_bytes()
134 }
135}