use std::any::Any;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use crate::error::{A2AError, Result};
tokio::task_local! {
pub static SERVICE_PARAMS: ServiceParams;
}
#[must_use]
pub fn current_service_params() -> Option<ServiceParams> {
SERVICE_PARAMS.try_with(Clone::clone).ok()
}
#[derive(Debug, Clone, Default)]
pub struct ServiceParams {
inner: HashMap<String, Vec<String>>,
}
impl ServiceParams {
pub fn append(&mut self, key: impl Into<String>, value: impl Into<String>) {
let vals = self.inner.entry(key.into()).or_default();
let v = value.into();
if !vals.contains(&v) {
vals.push(v);
}
}
#[must_use]
pub fn get_first(&self, key: &str) -> Option<&str> {
self.inner
.get(key)
.and_then(|v| v.first().map(String::as_str))
}
#[must_use]
pub fn get_all(&self, key: &str) -> &[String] {
self.inner.get(key).map_or(&[], Vec::as_slice)
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &[String])> {
self.inner.iter().map(|(k, v)| (k.as_str(), v.as_slice()))
}
#[must_use]
pub const fn as_map(&self) -> &HashMap<String, Vec<String>> {
&self.inner
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
pub struct Request {
pub method: String,
pub card: Option<crate::types::AgentCard>,
pub service_params: ServiceParams,
pub payload: Box<dyn Any + Send>,
}
impl std::fmt::Debug for Request {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Request")
.field("method", &self.method)
.field("card", &self.card.as_ref().map(|c| &c.name))
.field("service_params", &self.service_params)
.field("payload", &"<dyn Any>")
.finish()
}
}
pub struct Response {
pub method: String,
pub card: Option<crate::types::AgentCard>,
pub payload: Option<Box<dyn Any + Send>>,
pub err: Option<A2AError>,
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Response")
.field("method", &self.method)
.field("card", &self.card.as_ref().map(|c| &c.name))
.field("payload", &self.payload.as_ref().map(|_| "<dyn Any>"))
.field("err", &self.err)
.finish()
}
}
pub trait CallInterceptor: Send + Sync {
fn before<'a>(
&'a self,
req: &'a mut Request,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
let _ = req;
Box::pin(async { Ok(()) })
}
fn after<'a>(
&'a self,
resp: &'a mut Response,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
let _ = resp;
Box::pin(async { Ok(()) })
}
}
#[derive(Debug, Clone, Copy)]
pub struct PassthroughInterceptor;
impl CallInterceptor for PassthroughInterceptor {}
#[derive(Debug)]
pub struct StaticParamsInjector {
inject: ServiceParams,
}
impl StaticParamsInjector {
#[must_use]
pub const fn new(params: ServiceParams) -> Self {
Self { inject: params }
}
}
impl CallInterceptor for StaticParamsInjector {
fn before<'a>(
&'a self,
req: &'a mut Request,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
Box::pin(async move {
for (key, value) in self
.inject
.iter()
.flat_map(|(k, vs)| vs.iter().map(move |v| (k, v)))
{
req.service_params.append(key, value);
}
Ok(())
})
}
}