use std::fmt;
use crate::UsageReceipt;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorKind {
InvalidInput,
Unavailable,
Conflict,
Cancelled,
Provider,
Internal,
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
code: &'static str,
message: String,
receipt: Option<Box<UsageReceipt>>,
}
impl Error {
pub(crate) fn new(kind: ErrorKind, code: &'static str, message: impl Into<String>) -> Self {
Self {
kind,
code,
message: message.into(),
receipt: None,
}
}
pub fn invalid(message: impl Into<String>) -> Self {
Self::new(ErrorKind::InvalidInput, "invalid_request", message)
}
pub(crate) fn unavailable(code: &'static str, message: impl Into<String>) -> Self {
Self::new(ErrorKind::Unavailable, code, message)
}
pub(crate) fn conflict(code: &'static str, message: impl Into<String>) -> Self {
Self::new(ErrorKind::Conflict, code, message)
}
pub(crate) fn cancelled() -> Self {
Self::new(
ErrorKind::Cancelled,
"operation_cancelled",
"The operation was stopped by the user.",
)
}
pub(crate) fn provider(code: &'static str, message: impl Into<String>) -> Self {
Self::new(ErrorKind::Provider, code, message)
}
pub(crate) fn internal(code: &'static str, message: impl Into<String>) -> Self {
Self::new(ErrorKind::Internal, code, message)
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn code(&self) -> &'static str {
self.code
}
pub fn message(&self) -> &str {
&self.message
}
pub fn receipt(&self) -> Option<&UsageReceipt> {
self.receipt.as_deref()
}
pub(crate) fn with_receipt(mut self, receipt: UsageReceipt) -> Self {
self.receipt = Some(Box::new(receipt));
self
}
pub fn retryable(&self) -> bool {
matches!(self.kind, ErrorKind::Provider | ErrorKind::Conflict)
|| (self.kind == ErrorKind::Unavailable && self.code != "provider_not_configured")
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retryability_distinguishes_configuration_from_transient_provider_failure() {
assert!(!Error::unavailable("provider_not_configured", "missing key").retryable());
assert!(Error::unavailable("provider_rate_limited", "try later").retryable());
assert!(Error::provider("provider_timeout", "try later").retryable());
assert!(!Error::invalid("bad request").retryable());
}
}