use derive_more::{Display, From};
#[derive(Debug, Clone, Display, derive_getters::Getters)]
#[display("RMCP error: {}", source)]
pub struct RmcpError {
source: String,
line: u32,
file: &'static str,
}
impl std::error::Error for RmcpError {}
impl RmcpError {
#[track_caller]
pub fn new(source: rmcp::ErrorData) -> Self {
let loc = std::panic::Location::caller();
Self {
source: source.to_string(),
line: loc.line(),
file: loc.file(),
}
}
}
impl From<rmcp::ErrorData> for RmcpError {
#[track_caller]
fn from(source: rmcp::ErrorData) -> Self {
Self::new(source)
}
}
#[derive(Debug, Clone, Display, derive_getters::Getters)]
#[display("JSON error: {}", source)]
pub struct JsonError {
source: String,
line: u32,
file: &'static str,
}
impl std::error::Error for JsonError {}
impl JsonError {
#[track_caller]
pub fn new(source: serde_json::Error) -> Self {
let loc = std::panic::Location::caller();
Self {
source: source.to_string(),
line: loc.line(),
file: loc.file(),
}
}
}
impl From<serde_json::Error> for JsonError {
#[track_caller]
fn from(source: serde_json::Error) -> Self {
Self::new(source)
}
}
#[derive(Debug, Clone, Display, From)]
pub enum ElicitErrorKind {
#[display("{}", _0)]
#[from]
Rmcp(RmcpError),
#[display("{}", _0)]
#[from]
Service(ServiceError),
#[display("{}", _0)]
#[from]
Json(JsonError),
#[display("Invalid format: expected {expected}, received {received}")]
InvalidFormat {
expected: String,
received: String,
},
#[display("Out of range: value must be between {min} and {max}")]
OutOfRange {
min: String,
max: String,
},
#[display("User cancelled elicitation")]
Cancelled,
#[display("Missing required field: {}", _0)]
MissingField(String),
#[display("Invalid option: '{value}' not in [{options}]")]
InvalidOption {
value: String,
options: String,
},
#[display("Invalid selection: {}", _0)]
InvalidSelection(String),
#[display("Parse error: {}", _0)]
ParseError(String),
#[display("Validation failed: {}", _0)]
Validation(String),
#[display("Recursion depth exceeded: maximum depth is {}", _0)]
RecursionDepthExceeded(usize),
}
macro_rules! bridge_error {
($external:ty => $wrapper:ty) => {
impl From<$external> for ElicitErrorKind {
#[track_caller]
fn from(err: $external) -> Self {
<$wrapper>::from(err).into()
}
}
};
}
#[derive(Debug, Clone, Display, derive_getters::Getters)]
#[display("Service error: {}", source)]
pub struct ServiceError {
source: String,
line: u32,
file: &'static str,
}
impl std::error::Error for ServiceError {}
impl ServiceError {
#[track_caller]
pub fn new(source: rmcp::service::ServiceError) -> Self {
let loc = std::panic::Location::caller();
Self {
source: source.to_string(),
line: loc.line(),
file: loc.file(),
}
}
}
impl From<rmcp::service::ServiceError> for ServiceError {
#[track_caller]
fn from(source: rmcp::service::ServiceError) -> Self {
Self::new(source)
}
}
bridge_error!(rmcp::ErrorData => RmcpError);
bridge_error!(rmcp::service::ServiceError => ServiceError);
bridge_error!(serde_json::Error => JsonError);
#[derive(Debug, Clone, Display)]
#[display("Elicit error: {}", _0)]
pub struct ElicitError(Box<ElicitErrorKind>);
impl std::error::Error for ElicitError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &*self.0 {
ElicitErrorKind::Rmcp(e) => Some(e),
ElicitErrorKind::Service(e) => Some(e),
ElicitErrorKind::Json(e) => Some(e),
_ => None,
}
}
}
impl ElicitError {
#[tracing::instrument(skip(self), level = "trace")]
pub fn kind(&self) -> &ElicitErrorKind {
&self.0
}
#[track_caller]
#[tracing::instrument(skip(kind), level = "debug")]
pub fn new(kind: ElicitErrorKind) -> Self {
tracing::error!(error_kind = %kind, "Error created");
Self(Box::new(kind))
}
}
macro_rules! error_from {
($source:ty) => {
impl From<$source> for ElicitError {
#[track_caller]
fn from(err: $source) -> Self {
let kind = ElicitErrorKind::from(err);
tracing::error!(error_kind = %kind, "Error created");
Self(Box::new(kind))
}
}
};
}
impl From<ElicitErrorKind> for ElicitError {
#[track_caller]
fn from(kind: ElicitErrorKind) -> Self {
tracing::error!(error_kind = %kind, "Error created");
Self(Box::new(kind))
}
}
error_from!(rmcp::ErrorData);
error_from!(rmcp::service::ServiceError);
error_from!(serde_json::Error);
impl From<crate::verification::types::ValidationError> for ElicitError {
#[track_caller]
fn from(err: crate::verification::types::ValidationError) -> Self {
let kind = ElicitErrorKind::Validation(err.to_string());
tracing::error!(error_kind = %kind, "Error from ValidationError");
Self(Box::new(kind))
}
}
impl From<rmcp::service::ElicitationError> for ElicitError {
#[track_caller]
fn from(err: rmcp::service::ElicitationError) -> Self {
use rmcp::service::ElicitationError as EE;
let kind = match err {
EE::Service(se) => ElicitErrorKind::Service(ServiceError::from(se)),
EE::UserDeclined | EE::UserCancelled => ElicitErrorKind::InvalidFormat {
expected: "user response".to_string(),
received: "cancelled".to_string(),
},
EE::ParseError { error, .. } => ElicitErrorKind::Json(JsonError::from(error)),
EE::CapabilityNotSupported => ElicitErrorKind::InvalidFormat {
expected: "elicitation capability".to_string(),
received: "unsupported".to_string(),
},
EE::NoContent => ElicitErrorKind::InvalidFormat {
expected: "elicitation response".to_string(),
received: "no content".to_string(),
},
};
tracing::error!(error_kind = %kind, "Error from ElicitationError");
Self(Box::new(kind))
}
}
pub type ElicitResult<T> = Result<T, ElicitError>;