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