use std::fmt;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Provider(#[from] ProviderError),
#[error(transparent)]
Tool(#[from] ToolError),
#[error(transparent)]
Service(#[from] ServiceError),
#[error(transparent)]
Schema(#[from] SchemaError),
#[error("invalid configuration: {0}")]
Config(String),
#[error("not found: {0}")]
NotFound(String),
#[error("already exists: {0}")]
AlreadyExists(String),
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
#[error("{0}")]
Other(String),
}
impl Error {
pub fn config(msg: impl Into<String>) -> Self {
Self::Config(msg.into())
}
pub fn not_found(msg: impl Into<String>) -> Self {
Self::NotFound(msg.into())
}
pub fn already_exists(msg: impl Into<String>) -> Self {
Self::AlreadyExists(msg.into())
}
pub fn invalid_input(msg: impl Into<String>) -> Self {
Self::InvalidInput(msg.into())
}
pub fn other(msg: impl Into<String>) -> Self {
Self::Other(msg.into())
}
}
#[derive(Debug, thiserror::Error)]
pub enum ProviderError {
#[error("provider transport error: {0}")]
Transport(String),
#[error("provider returned status {status}: {body}")]
Http {
status: u16,
body: String,
},
#[error("could not decode provider response: {0}")]
Decode(String),
#[error("provider authentication error: {0}")]
Auth(String),
#[error("provider rate limit: {0}")]
RateLimit(String),
#[error("provider streaming error: {0}")]
Stream(String),
#[error("unsupported provider feature: {0}")]
Unsupported(&'static str),
}
#[derive(Debug, thiserror::Error)]
pub enum ToolError {
#[error("tool {tool} got invalid args: {message}")]
InvalidArgs {
tool: String,
message: String,
},
#[error("tool {tool} execution failed: {message}")]
Execution {
tool: String,
message: String,
},
#[error("tool {tool} aborted invocation: {message}")]
Aborted {
tool: String,
message: String,
},
#[error("unknown tool: {0}")]
Unknown(String),
}
#[derive(Debug, thiserror::Error)]
pub enum ServiceError {
#[error("session not found: {0}")]
SessionNotFound(String),
#[error("artifact not found: {0}")]
ArtifactNotFound(String),
#[error("stale session: {0}")]
StaleSession(String),
#[error("storage backend error: {0}")]
Backend(String),
}
#[derive(Debug, thiserror::Error)]
pub enum SchemaError {
#[error("schema could not be sanitized: {0}")]
Sanitize(String),
#[error("invalid schema: {0}")]
Invalid(String),
}
pub trait Context<T> {
fn context<C: fmt::Display>(self, ctx: C) -> Result<T>;
}
impl<T, E: fmt::Display> Context<T> for std::result::Result<T, E> {
fn context<C: fmt::Display>(self, ctx: C) -> Result<T> {
self.map_err(|e| Error::Other(format!("{ctx}: {e}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_error_variants_render() {
let e: Error = ProviderError::Http {
status: 500,
body: "boom".into(),
}
.into();
assert!(e.to_string().contains("500"));
assert!(e.to_string().contains("boom"));
}
#[test]
fn context_wraps_string_errors() {
let r: std::result::Result<(), String> = Err("inner".to_string());
let e = r.context("outer").unwrap_err();
assert_eq!(e.to_string(), "outer: inner");
}
#[test]
fn constructors_compose() {
let e = Error::not_found("session sess-1");
assert!(matches!(e, Error::NotFound(_)));
assert!(e.to_string().contains("sess-1"));
}
}