Skip to main content

a2a_client/
lib.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3pub mod agent_card;
4pub mod auth;
5pub mod client;
6pub mod factory;
7pub mod jsonrpc;
8pub mod middleware;
9mod push_config_compat;
10pub mod rest;
11pub mod transport;
12
13pub use client::A2AClient;
14pub use factory::A2AClientFactory;
15pub use futures::stream::BoxStream;
16pub use transport::{ServiceParams, Transport, TransportFactory};
17
18#[cfg(any(feature = "rustls-tls", feature = "rustls-no-provider"))]
19pub use rustls;
20
21/// Build a `reqwest::Client` whose TLS layer matches this crate's feature
22/// selection, optionally adding extra root certificates from a PEM bundle.
23pub fn default_reqwest_client(
24    #[allow(unused_variables)] extra_root_pem: Option<&[u8]>,
25) -> Result<reqwest::Client, a2a::A2AError> {
26    let builder = reqwest::Client::builder();
27
28    #[cfg(any(
29        feature = "rustls-tls",
30        feature = "rustls-no-provider",
31        feature = "native-tls"
32    ))]
33    let builder = match extra_root_pem {
34        Some(pem) => {
35            let certs = reqwest::Certificate::from_pem_bundle(pem)
36                .map_err(|e| a2a::A2AError::internal(format!("invalid PEM certificate: {e}")))?;
37            builder.tls_certs_merge(certs)
38        }
39        None => builder,
40    };
41
42    builder
43        .build()
44        .map_err(|e| a2a::A2AError::internal(format!("failed to build HTTP client: {e}")))
45}
46
47pub(crate) fn a2a_error_from_details(
48    code: i32,
49    message: String,
50    details: Vec<a2a::TypedDetail>,
51) -> a2a::A2AError {
52    use a2a::{error_code, errordetails, reason_to_error_code};
53    use serde_json::Value;
54
55    let mut code = code;
56    let mut message = message;
57
58    for detail in &details {
59        match detail.type_url.as_str() {
60            errordetails::BAD_REQUEST_TYPE => {
61                if let Some(Value::Array(violations)) = detail.value.get("fieldViolations") {
62                    let violation_strs: Vec<String> = violations
63                        .iter()
64                        .filter_map(|v| {
65                            let field = v.get("field")?.as_str()?;
66                            let desc = v.get("description")?.as_str()?;
67                            if field.is_empty() {
68                                Some(desc.to_string())
69                            } else {
70                                Some(format!("{field}: {desc}"))
71                            }
72                        })
73                        .collect();
74                    if !violation_strs.is_empty() {
75                        message = format!("{}: {}", message, violation_strs.join("; "));
76                    }
77                }
78                if code == error_code::INTERNAL_ERROR {
79                    code = error_code::INVALID_PARAMS;
80                }
81            }
82            errordetails::ERROR_INFO_TYPE => {
83                if let Some(Value::String(domain)) = detail.value.get("domain") {
84                    if domain == errordetails::PROTOCOL_DOMAIN {
85                        if let Some(Value::String(reason)) = detail.value.get("reason") {
86                            if let Some(c) = reason_to_error_code(reason) {
87                                code = c;
88                            }
89                        }
90                    }
91                }
92            }
93            _ => {}
94        }
95    }
96
97    a2a::A2AError {
98        code,
99        message,
100        details: (!details.is_empty()).then_some(details),
101    }
102}
103
104#[cfg(all(test, any(feature = "rustls-tls", feature = "native-tls")))]
105pub(crate) mod test_utils {
106    pub fn rcgen_self_signed_ca_pem() -> Vec<u8> {
107        let mut params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
108        params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
109        params
110            .distinguished_name
111            .push(rcgen::DnType::CommonName, "Test CA");
112        let key = rcgen::KeyPair::generate().unwrap();
113        let cert = params.self_signed(&key).unwrap();
114        cert.pem().into_bytes()
115    }
116}