#[cfg(test)]
mod tests;
use a2a_lf::{ERROR_INFO_TYPE, PROTOCOL_DOMAIN, reason_to_error_code};
use reqwest::StatusCode;
use reqwest::header::ACCEPT;
use serde::Deserialize;
use serde_json::Value;
use super::Transport;
use super::binding::{Operation, is_dot_segment};
use crate::a2a::config::A2aUrlError;
use crate::a2a::error::{A2aError, A2aRpcError};
impl Transport {
pub(super) fn rest_request(
&self,
operation: &Operation,
accept: &'static str,
) -> Result<reqwest::RequestBuilder, A2aError> {
let url = self.rest_url(operation)?;
let request = self
.http
.request(operation.verb.clone(), url)
.headers(self.operation_headers(operation))
.header(ACCEPT, accept);
Ok(if operation.has_body() {
request.json(&operation.params)
} else {
request
})
}
fn rest_url(&self, operation: &Operation) -> Result<url::Url, A2aError> {
let dropped = self
.tenant
.iter()
.chain(operation.path.iter())
.any(|segment| is_dot_segment(segment));
if dropped {
return Err(A2aError::InvalidId {
method: operation.method,
});
}
let mut url = self.endpoint.clone();
{
let mut segments = url
.path_segments_mut()
.map_err(|_| A2aError::InvalidEndpoint(A2aUrlError::Malformed))?;
segments.pop_if_empty();
segments.extend(self.tenant.iter());
segments.extend(operation.path.iter());
}
if !operation.query.is_empty() {
url.query_pairs_mut().extend_pairs(operation.query.iter());
}
Ok(url)
}
}
pub(super) fn decode_reply(
method: &'static str,
status: StatusCode,
body: &[u8],
) -> Result<Value, A2aError> {
if !status.is_success() {
return Err(match a2a_error(body) {
Some(error) => A2aError::Rpc { method, error },
None => A2aError::HttpStatus {
operation: method,
status,
},
});
}
if body.iter().all(u8::is_ascii_whitespace) {
return Ok(Value::Null);
}
serde_json::from_slice(body).map_err(|_| A2aError::Decode { method })
}
pub(super) fn a2a_error(body: &[u8]) -> Option<A2aRpcError> {
let envelope: StatusEnvelope = serde_json::from_slice(body).ok()?;
envelope.error.a2a_error()
}
#[derive(Deserialize)]
pub(super) struct StatusEnvelope {
pub(super) error: Status,
}
#[derive(Deserialize)]
pub(super) struct Status {
#[serde(default)]
status: Option<String>,
#[serde(default)]
details: Option<Vec<Value>>,
}
impl Status {
pub(super) fn is_status(&self) -> bool {
self.status.is_some() || self.details.is_some()
}
pub(super) fn a2a_error(&self) -> Option<A2aRpcError> {
self.details.iter().flatten().find_map(|detail| {
let field = |name: &str| detail.get(name).and_then(Value::as_str);
if field("@type") != Some(ERROR_INFO_TYPE) || field("domain") != Some(PROTOCOL_DOMAIN) {
return None;
}
reason_to_error_code(field("reason")?).map(A2aRpcError::from_code)
})
}
}