use std::fmt;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("GraphQL error in {query_name}: {messages}")]
Graphql {
query_name: String,
status: reqwest::StatusCode,
messages: String,
errors: Vec<GraphqlErrorEntry>,
},
#[error("rate limited on {query_name}: {messages}")]
RateLimited {
query_name: String,
retry_after: Option<std::time::Duration>,
limit: Option<u64>,
remaining: Option<u64>,
messages: String,
},
#[error("unexpected HTTP {status} in {query_name}: {body}")]
UnexpectedStatus {
query_name: String,
status: reqwest::StatusCode,
body: String,
},
#[error("failed to execute {query_name} after {attempts} attempts: {source}")]
Connection {
query_name: String,
attempts: u32,
#[source]
source: reqwest::Error,
},
#[error("missing field '{field}' in {query_name} response")]
MissingField { query_name: String, field: String },
#[error("failed to decode {query_name} response: {source}")]
Decode {
query_name: String,
#[source]
source: serde_json::Error,
},
#[error("currency underflow: {0} - {1} would be negative")]
CurrencyUnderflow(u64, u64),
#[error("invalid currency format: {0}")]
InvalidCurrency(String),
}
pub mod codes {
pub const BLOCK_RANGE_ERROR: &str = "BLOCK_RANGE_ERROR";
pub const ACTION_STATE_NOT_FOUND: &str = "ACTION_STATE_NOT_FOUND";
pub const ACTION_STATE_OUT_OF_RANGE: &str = "ACTION_STATE_OUT_OF_RANGE";
pub const RATE_LIMITED: &str = "RATE_LIMITED";
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct GraphqlErrorEntry {
pub message: String,
#[serde(skip)]
pub code: Option<String>,
#[serde(default)]
pub path: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub locations: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub extensions: Option<serde_json::Value>,
}
impl GraphqlErrorEntry {
pub(crate) fn with_lifted_code(mut self) -> Self {
self.code = self
.extensions
.as_ref()
.and_then(|e| e.get("code"))
.and_then(|c| c.as_str())
.map(str::to_string);
self
}
}
impl fmt::Display for GraphqlErrorEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl Error {
pub fn graphql_codes(&self) -> Vec<&str> {
match self {
Error::Graphql { errors, .. } => {
errors.iter().filter_map(|e| e.code.as_deref()).collect()
}
_ => Vec::new(),
}
}
pub fn has_graphql_code(&self, code: &str) -> bool {
self.graphql_codes().contains(&code)
}
}
pub type Result<T> = std::result::Result<T, Error>;