use std::{fmt, time::Duration};
use thiserror::Error;
use crate::RateLimitKind;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
#[error("ProjectX rejected the operation ({})", CodeText { code: *code, name: *name })]
pub struct ProviderError {
pub code: i32,
pub name: Option<&'static str>,
}
#[derive(Clone, Copy, Debug)]
struct CodeText {
code: i32,
name: Option<&'static str>,
}
impl fmt::Display for CodeText {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.name {
Some(name) => write!(formatter, "code: {} {name}", self.code),
None => write!(formatter, "code: {}", self.code),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum AmbiguityOrigin {
Transport,
TransportTimeout,
TransportConnect,
HttpStatus(u16),
Decode,
MissingResult,
ResponseTooLarge,
InconsistentStatus,
RateLimited,
Unclassified,
}
impl fmt::Display for AmbiguityOrigin {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Transport => formatter.write_str("HTTP transport failed after submission"),
Self::TransportTimeout => formatter.write_str("request timed out after submission"),
Self::TransportConnect => formatter.write_str("connection could not be established"),
Self::HttpStatus(status) => write!(formatter, "provider returned HTTP status {status}"),
Self::Decode => formatter.write_str("response could not be decoded"),
Self::MissingResult => {
formatter.write_str("success response omitted a required result field")
}
Self::ResponseTooLarge => {
formatter.write_str("response exceeded the configured size limit")
}
Self::InconsistentStatus => {
formatter.write_str("provider returned inconsistent status fields")
}
Self::RateLimited => formatter.write_str("provider rate limit refused the request"),
Self::Unclassified => formatter.write_str("unclassified transport-level failure"),
}
}
}
#[derive(Clone, Copy, Debug)]
struct AmbiguityText {
code: Option<i32>,
name: Option<&'static str>,
origin: Option<AmbiguityOrigin>,
}
impl fmt::Display for AmbiguityText {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(code) = self.code {
return write!(
formatter,
" ({})",
CodeText {
code,
name: self.name
}
);
}
if let Some(origin) = self.origin {
return write!(formatter, " ({origin})");
}
Ok(())
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("invalid configuration: {0}")]
Configuration(String),
#[error("invalid {kind}: {reason}")]
InvalidIdentifier {
kind: &'static str,
reason: &'static str,
},
#[error("provider rejected the credentials ({})", CodeText { code: *code, name: *name })]
CredentialsRejected {
code: i32,
name: Option<&'static str>,
},
#[error(
"provider rejected session validation ({})",
CodeText { code: *code, name: *name }
)]
SessionValidationRejected {
code: i32,
name: Option<&'static str>,
},
#[error("provider returned inconsistent status (success: {success}, code: {code})")]
InconsistentResponseStatus {
success: bool,
code: i32,
},
#[error("provider returned success without a usable authentication token")]
MissingAuthenticationToken,
#[error("provider returned an invalid authentication token")]
InvalidAuthenticationToken,
#[error("the client is not authenticated")]
NotAuthenticated,
#[error("session-validation outcome is ambiguous; authenticate again before using the client")]
AmbiguousSessionValidation,
#[error(transparent)]
Provider(#[from] ProviderError),
#[error("HTTP transport failed")]
Transport(#[source] reqwest::Error),
#[error("provider returned HTTP status {status}")]
UnexpectedStatus {
status: u16,
},
#[error("provider status endpoint returned an unexpected response")]
UnexpectedStatusResponse,
#[error("local {kind} rate limit is exhausted; retry after {retry_after:?}")]
LocallyRateLimited {
kind: RateLimitKind,
retry_after: Duration,
},
#[error("provider rate limit is exhausted; retry after {retry_after:?}")]
ProviderRateLimited {
kind: RateLimitKind,
retry_after: Duration,
},
#[error("invalid endpoint URL")]
Url(#[source] url::ParseError),
#[error("provider response exceeded the {limit_bytes}-byte limit")]
ResponseTooLarge {
limit_bytes: usize,
},
#[error("provider response was not valid JSON")]
Decode(#[source] serde_json::Error),
#[error("request could not be encoded as JSON")]
Encode(#[source] serde_json::Error),
#[error(
"{} outcome is ambiguous{}; reconcile provider state before retrying",
operation,
AmbiguityText { code: *code, name: *name, origin: *origin }
)]
AmbiguousMutation {
operation: &'static str,
code: Option<i32>,
name: Option<&'static str>,
origin: Option<AmbiguityOrigin>,
},
#[error("{task} background task terminated unexpectedly")]
BackgroundTaskFailed {
task: &'static str,
},
}