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)]
struct AmbiguousCodeText(Option<CodeText>);
impl fmt::Display for AmbiguousCodeText {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(code) => write!(formatter, " ({code})"),
None => 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,
AmbiguousCodeText(code.map(|code| CodeText { code, name: *name }))
)]
AmbiguousMutation {
operation: &'static str,
code: Option<i32>,
name: Option<&'static str>,
},
#[error("{task} background task terminated unexpectedly")]
BackgroundTaskFailed {
task: &'static str,
},
}