use thiserror::Error;
#[derive(Error, Debug)]
pub enum CoreError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Validation error: {0}")]
Validation(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Auth error: {0}")]
Auth(String),
#[error("Conflict: {0}")]
Conflict(String),
#[error("Rate limited: {0}")]
RateLimit(String),
#[error("Parse error: {0}")]
Parse(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("API error: {0}")]
Api(#[from] openapi_contract::ApiError),
#[error("Embedding cap reached: {used}/{cap}")]
EmbedCapReached { cap: u32, used: u32 },
}
pub type Result<T, E = CoreError> = std::result::Result<T, E>;
impl CoreError {
pub fn internal(msg: impl std::fmt::Display) -> Self {
Self::Internal(msg.to_string())
}
}
pub trait InternalResultExt<T> {
fn internal(self) -> Result<T>;
}
impl<T, E: std::fmt::Display> InternalResultExt<T> for std::result::Result<T, E> {
fn internal(self) -> Result<T> {
self.map_err(|e| CoreError::Internal(e.to_string()))
}
}
#[must_use]
pub fn error_chain_text(e: &(dyn std::error::Error + 'static)) -> String {
let mut message = e.to_string();
let mut source = e.source();
while let Some(cause) = source {
let cause_text = cause.to_string();
if !message.contains(&cause_text) {
message.push_str(": ");
message.push_str(&cause_text);
}
source = cause.source();
}
message
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Error, Debug)]
#[error("error sending request for url (https://x.example/)")]
struct Outer {
#[source]
cause: Middle,
}
#[derive(Error, Debug)]
#[error("client error (Connect)")]
struct Middle {
#[source]
cause: std::io::Error,
}
#[test]
fn error_chain_text_appends_hidden_sources() {
let outer = Outer {
cause: Middle {
cause: std::io::Error::other("dns error: failed to lookup address"),
},
};
let text = error_chain_text(&outer);
assert_eq!(
text,
"error sending request for url (https://x.example/): client error (Connect): \
dns error: failed to lookup address"
);
}
#[test]
fn error_chain_text_skips_causes_already_embedded() {
let wrapped = CoreError::Io(std::io::Error::other("disk full"));
assert_eq!(error_chain_text(&wrapped), "IO error: disk full");
}
}