use bytes::Bytes;
#[derive(Debug)]
pub enum LLError {
Transport(Box<dyn std::error::Error + Send + Sync>),
NotSupported,
ResourceExhausted,
Protocol {
code: u32,
detail: Bytes,
},
}
impl std::fmt::Display for LLError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LLError::Transport(e) => write!(f, "transport error: {}", e),
LLError::NotSupported => write!(f, "operation not supported"),
LLError::ResourceExhausted => write!(f, "resource exhausted"),
LLError::Protocol { code, detail } => {
if detail.is_empty() {
write!(f, "protocol error: code {}", code)
} else {
match std::str::from_utf8(detail) {
Ok(s) => write!(f, "protocol error: code {} - {}", code, s),
Err(_) => write!(f, "protocol error: code {} - {:?}", code, detail),
}
}
}
}
}
}
impl std::error::Error for LLError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
LLError::Transport(e) => Some(e.as_ref()),
_ => None,
}
}
}
impl From<std::io::Error> for LLError {
fn from(e: std::io::Error) -> Self {
LLError::Transport(Box::new(e))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as StdError;
#[test]
fn error_display_works() {
let e = LLError::NotSupported;
assert_eq!(format!("{}", e), "operation not supported");
let e = LLError::Protocol {
code: 42,
detail: Bytes::from_static(b"something went wrong"),
};
assert!(format!("{}", e).contains("42"));
assert!(format!("{}", e).contains("something went wrong"));
}
#[test]
fn resource_exhausted_display() {
let e = LLError::ResourceExhausted;
assert_eq!(format!("{}", e), "resource exhausted");
}
#[test]
fn protocol_empty_detail_display() {
let e = LLError::Protocol {
code: 100,
detail: Bytes::new(),
};
assert_eq!(format!("{}", e), "protocol error: code 100");
}
#[test]
fn protocol_non_utf8_detail_display() {
let e = LLError::Protocol {
code: 200,
detail: Bytes::from_static(&[0xFF, 0xFE, 0x00]),
};
let display = format!("{}", e);
assert!(display.contains("200"));
assert!(display.contains("protocol error"));
}
#[test]
fn transport_error_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let e = LLError::Transport(Box::new(io_err));
let display = format!("{}", e);
assert!(display.contains("transport error"));
assert!(display.contains("file not found"));
}
#[test]
fn transport_error_source() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let e = LLError::Transport(Box::new(io_err));
assert!(StdError::source(&e).is_some());
}
#[test]
fn non_transport_error_source_is_none() {
let e = LLError::NotSupported;
assert!(StdError::source(&e).is_none());
let e = LLError::ResourceExhausted;
assert!(StdError::source(&e).is_none());
let e = LLError::Protocol {
code: 1,
detail: Bytes::new(),
};
assert!(StdError::source(&e).is_none());
}
#[test]
fn io_error_converts() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let ll_err: LLError = io_err.into();
assert!(matches!(ll_err, LLError::Transport(_)));
}
}