use thiserror::Error;
use crate::client::EndpointRef;
use crate::client::SippCancellationReason;
#[cfg(feature = "providers")]
use crate::providers::{ProviderError, ProviderErrorKind};
#[cfg(test)]
#[path = "../tests/client/error_tests.rs"]
mod error_tests;
pub type SippResult<T> = Result<T, SippError>;
#[derive(Debug, Clone, Error)]
#[error("endpoint error ({kind}): {message}")]
pub struct EndpointError {
pub kind: String,
pub status: Option<u16>,
pub code: Option<String>,
pub message: String,
pub retry_after: Option<std::time::Duration>,
pub request_id: Option<String>,
pub raw: Option<Box<serde_json::Value>>,
}
impl EndpointError {
pub fn new(kind: impl Into<String>, message: impl Into<String>) -> Self {
Self {
kind: kind.into(),
status: None,
code: None,
message: message.into(),
retry_after: None,
request_id: None,
raw: None,
}
}
}
#[cfg(feature = "providers")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderEndpointErrorKind {
Authentication,
Authorization,
RateLimited,
QuotaExceeded,
InvalidRequest,
UnsupportedFeature,
ModelNotFound,
Timeout,
Overloaded,
Transport,
Provider,
}
#[cfg(feature = "providers")]
impl ProviderEndpointErrorKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Authentication => "authentication",
Self::Authorization => "authorization",
Self::RateLimited => "rate_limited",
Self::QuotaExceeded => "quota_exceeded",
Self::InvalidRequest => "invalid_request",
Self::UnsupportedFeature => "unsupported_feature",
Self::ModelNotFound => "model_not_found",
Self::Timeout => "timeout",
Self::Overloaded => "overloaded",
Self::Transport => "transport",
Self::Provider => "provider",
}
}
}
#[cfg(feature = "providers")]
#[derive(Debug, Clone, Error)]
#[error("provider error ({} {provider}): {message}", kind.as_str())]
pub struct ProviderEndpointError {
pub kind: ProviderEndpointErrorKind,
pub provider: String,
pub status: Option<u16>,
pub code: Option<String>,
pub message: String,
pub retry_after: Option<std::time::Duration>,
pub request_id: Option<String>,
pub raw: Option<Box<serde_json::Value>>,
}
#[cfg(feature = "providers")]
impl ProviderEndpointError {
pub fn new(
kind: ProviderEndpointErrorKind,
provider: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
kind,
provider: provider.into(),
status: None,
code: None,
message: message.into(),
retry_after: None,
request_id: None,
raw: None,
}
}
pub(crate) fn from_provider_error(error: ProviderError, secrets: &[String]) -> Self {
Self {
kind: match error.kind {
ProviderErrorKind::Authentication => ProviderEndpointErrorKind::Authentication,
ProviderErrorKind::Authorization => ProviderEndpointErrorKind::Authorization,
ProviderErrorKind::RateLimited => ProviderEndpointErrorKind::RateLimited,
ProviderErrorKind::QuotaExceeded => ProviderEndpointErrorKind::QuotaExceeded,
ProviderErrorKind::InvalidRequest => ProviderEndpointErrorKind::InvalidRequest,
ProviderErrorKind::UnsupportedFeature => {
ProviderEndpointErrorKind::UnsupportedFeature
}
ProviderErrorKind::ModelNotFound => ProviderEndpointErrorKind::ModelNotFound,
ProviderErrorKind::Timeout => ProviderEndpointErrorKind::Timeout,
ProviderErrorKind::Overloaded => ProviderEndpointErrorKind::Overloaded,
ProviderErrorKind::Transport => ProviderEndpointErrorKind::Transport,
ProviderErrorKind::Provider => ProviderEndpointErrorKind::Provider,
},
provider: error.provider.as_str().to_string(),
status: error.status,
code: error.code.map(|value| redact_string(value, secrets)),
message: redact_string(error.message, secrets),
retry_after: error.retry_after,
request_id: error.request_id.map(|value| redact_string(value, secrets)),
raw: error
.raw
.map(|value| Box::new(redact_json_value(*value, secrets))),
}
}
}
#[derive(Debug, Error)]
pub enum SippError {
#[error(transparent)]
Local(#[from] crate::error::Error),
#[error(transparent)]
Endpoint(EndpointError),
#[cfg(feature = "providers")]
#[error(transparent)]
Provider(ProviderEndpointError),
#[error("request cancelled ({})", reason.as_str())]
Cancelled {
reason: SippCancellationReason,
},
#[error("internal facade error: {0}")]
Internal(String),
#[error("endpoint not found: {0:?}")]
EndpointNotFound(EndpointRef),
#[error("ambiguous endpoint for {operation}")]
AmbiguousEndpoint {
operation: &'static str,
},
#[error("no supported endpoint for {operation}")]
NoSupportedEndpoint {
operation: &'static str,
},
#[error("unsupported operation {operation} on endpoint {endpoint:?}")]
UnsupportedOperation {
endpoint: EndpointRef,
operation: &'static str,
},
#[error("invalid request: {0}")]
InvalidRequest(String),
}
#[cfg(feature = "providers")]
fn redact_string(mut value: String, secrets: &[String]) -> String {
for secret in secrets {
if secret.is_empty() {
continue;
}
value = value.replace(secret, "[redacted]");
}
value
}
#[cfg(feature = "providers")]
fn redact_json_value(value: serde_json::Value, secrets: &[String]) -> serde_json::Value {
match value {
serde_json::Value::String(value) => {
serde_json::Value::String(redact_string(value, secrets))
}
serde_json::Value::Array(values) => serde_json::Value::Array(
values
.into_iter()
.map(|value| redact_json_value(value, secrets))
.collect(),
),
serde_json::Value::Object(values) => serde_json::Value::Object(
values
.into_iter()
.map(|(key, value)| (key, redact_json_value(value, secrets)))
.collect(),
),
value => value,
}
}