use std::pin::Pin;
use futures_core::Future;
use crate::adapters::secondary::a2a::A2aMethod;
use crate::domain::a2a::version::A2aVersion;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum A2aError {
MethodNotFound,
MethodNotImplemented {
method: A2aMethod,
},
InvalidParams {
reason: String,
},
InternalError {
message: String,
},
AuthenticationRequired,
AuthenticationFailed {
reason: String,
},
ContentTypeNotSupported {
received: String,
},
}
impl A2aError {
#[must_use]
pub const fn code(&self) -> i32 {
match self {
Self::MethodNotFound | Self::MethodNotImplemented { .. } => -32601,
Self::InvalidParams { .. } => -32602,
Self::InternalError { .. } => -32603,
Self::ContentTypeNotSupported { .. } => -32000,
Self::AuthenticationRequired => -32001,
Self::AuthenticationFailed { .. } => -32002,
}
}
#[must_use]
pub const fn message(&self) -> &'static str {
match self {
Self::MethodNotFound => "Method not found",
Self::MethodNotImplemented { .. } => "Method not implemented",
Self::InvalidParams { .. } => "Invalid params",
Self::InternalError { .. } => "Internal error",
Self::ContentTypeNotSupported { .. } => "Content-Type not supported",
Self::AuthenticationRequired => "Authentication required",
Self::AuthenticationFailed { .. } => "Authentication failed",
}
}
}
#[derive(Debug, Clone)]
pub struct A2aRequestContext {
pub protocol_version: A2aVersion,
}
pub trait A2aRequestHandler: Send + Sync + 'static {
fn dispatch(
&self,
method: A2aMethod,
params: Option<serde_json::Value>,
context: &A2aRequestContext,
) -> Pin<Box<dyn Future<Output = Result<serde_json::Value, A2aError>> + Send + '_>>;
}
#[cfg(test)]
mod tests {
use super::A2aError;
use crate::adapters::secondary::a2a::A2aMethod;
#[test]
fn method_not_found_is_minus_32601() {
assert_eq!(A2aError::MethodNotFound.code(), -32601);
}
#[test]
fn method_not_implemented_is_minus_32601() {
let err = A2aError::MethodNotImplemented {
method: A2aMethod::SendMessage,
};
assert_eq!(err.code(), -32601);
assert_eq!(err.message(), "Method not implemented");
}
#[test]
fn invalid_params_is_minus_32602() {
let err = A2aError::InvalidParams {
reason: "expected an object".to_owned(),
};
assert_eq!(err.code(), -32602);
}
#[test]
fn internal_error_is_minus_32603() {
let err = A2aError::InternalError {
message: "database is down".to_owned(),
};
assert_eq!(err.code(), -32603);
}
#[test]
fn content_type_not_supported_is_minus_32000() {
let err = A2aError::ContentTypeNotSupported {
received: "text/plain".to_owned(),
};
assert_eq!(err.code(), -32000);
}
#[test]
fn authentication_required_is_minus_32001() {
assert_eq!(A2aError::AuthenticationRequired.code(), -32001);
}
#[test]
fn authentication_failed_is_minus_32002() {
let err = A2aError::AuthenticationFailed {
reason: "token expired".to_owned(),
};
assert_eq!(err.code(), -32002);
}
#[test]
fn messages_are_stable_strings() {
assert_eq!(A2aError::MethodNotFound.message(), "Method not found");
assert_eq!(
A2aError::InternalError {
message: "x".to_owned()
}
.message(),
"Internal error"
);
assert_eq!(
A2aError::ContentTypeNotSupported {
received: "x".to_owned()
}
.message(),
"Content-Type not supported"
);
assert_eq!(
A2aError::AuthenticationRequired.message(),
"Authentication required"
);
}
}