use crate::error::{
DnsError, DrandError, GovernanceError, IdentityError, NamesError, NetworkClientError,
PublishError, RegistrationError, ResolutionError, StorageError, VdfError,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ApiError {
#[serde(rename = "type")]
pub error_type: String,
pub title: String,
pub status: u16,
pub detail: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instance: Option<String>,
pub code: String,
pub retryable: bool,
#[serde(skip_serializing_if = "serde_json::Value::is_null")]
pub details: serde_json::Value,
pub request_id: String,
}
impl ApiError {
pub fn http_status(&self) -> u16 {
self.status
}
}
fn current_request_id() -> String {
crate::request_id::current().to_string()
}
impl From<ResolutionError> for ApiError {
fn from(e: ResolutionError) -> Self {
let (status, title): (u16, &'static str) = match &e {
ResolutionError::Offline => (503, "Node Offline"),
ResolutionError::NotFound { .. } => (404, "Name Not Found"),
ResolutionError::VdfVerificationFailed { .. } => {
(422, "Cryptographic Verification Failed")
}
ResolutionError::Expired { .. } => (410, "Registration Expired"),
ResolutionError::Timeout { .. } => (504, "Resolution Timeout"),
ResolutionError::Internal { .. } => (500, "Internal Resolution Error"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: e.details(),
request_id: current_request_id(),
}
}
}
impl From<PublishError> for ApiError {
fn from(e: PublishError) -> Self {
let (status, title): (u16, &'static str) = match &e {
PublishError::Offline => (503, "Node Offline"),
PublishError::InvalidProof(_) => (400, "Invalid VDF Proof"),
PublishError::AlreadyOwned { .. } => (409, "Name Already Owned"),
PublishError::AllFailed { .. } => (503, "Publish Failed"),
PublishError::Rejected(_) => (422, "Publish Rejected"),
PublishError::Internal { .. } => (500, "Internal Publish Error"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: e.details(),
request_id: current_request_id(),
}
}
}
impl From<RegistrationError> for ApiError {
fn from(e: RegistrationError) -> Self {
let (status, title): (u16, &'static str) = match &e {
RegistrationError::InvalidName { .. } => (400, "Invalid Name"),
RegistrationError::VdfFailed(_) => (500, "VDF Computation Failed"),
RegistrationError::CommitmentMismatch => (422, "Commitment Mismatch"),
RegistrationError::AlreadyOwned { .. } => (409, "Name Already Owned"),
RegistrationError::AlreadyInProgress { .. } => (409, "Registration In Progress"),
RegistrationError::NetworkRejected { .. } => (422, "Registration Rejected"),
RegistrationError::Internal { .. } => (500, "Internal Registration Error"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: e.details(),
request_id: current_request_id(),
}
}
}
impl From<GovernanceError> for ApiError {
fn from(e: GovernanceError) -> Self {
let (status, title): (u16, &'static str) = match &e {
GovernanceError::MissingRootKey => (500, "Configuration Error"),
GovernanceError::StaleProposal
| GovernanceError::TimelockNotExpired
| GovernanceError::NotPendingOrVetoed => (409, "Conflict"),
GovernanceError::InsufficientSignatures => (401, "Unauthorized"),
GovernanceError::GovernanceDisabled => (403, "Forbidden"),
GovernanceError::KeyLengthMismatch | GovernanceError::InvalidPremiumNameLength | GovernanceError::InvalidInfrastructureName => {
(400, "Bad Request")
}
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}
impl From<NetworkClientError> for ApiError {
fn from(e: NetworkClientError) -> Self {
let (status, title): (u16, &'static str) = match &e {
NetworkClientError::Timeout | NetworkClientError::StreamDropped => {
(504, "Gateway Timeout")
}
NetworkClientError::Offline | NetworkClientError::RoutingTableEmpty => {
(503, "Service Unavailable")
}
NetworkClientError::ChannelClosed
| NetworkClientError::StoreError(_)
| NetworkClientError::Other(_) => (500, "Internal Server Error"),
NetworkClientError::UnsupportedProtocol => (501, "Not Implemented"),
NetworkClientError::GossipSubError(_) => (502, "Bad Gateway"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}
impl From<StorageError> for ApiError {
fn from(e: StorageError) -> Self {
let (status, title): (u16, &'static str) = match &e {
StorageError::DatabaseLocked => (423, "Locked"),
StorageError::Corruption(_) => (500, "Storage Corruption"),
StorageError::OperationFailed(_) => (500, "Storage Operation Failed"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}
impl From<VdfError> for ApiError {
fn from(e: VdfError) -> Self {
let (status, title): (u16, &'static str) = match &e {
VdfError::LockFileError(_) | VdfError::LockAcquireError(_) => {
(503, "Service Unavailable")
}
VdfError::DiscriminantError | VdfError::ProofGenerationError => {
(500, "VDF Computation Error")
}
VdfError::UnsupportedPlatform => (501, "Not Implemented"),
VdfError::InvalidProof => (400, "Bad Request"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}
impl From<DrandError> for ApiError {
fn from(e: DrandError) -> Self {
let (status, title): (u16, &'static str) = match &e {
DrandError::AllEndpointsFailed | DrandError::Network(_) | DrandError::Reqwest(_) => {
(502, "Bad Gateway")
}
DrandError::HttpError(s) => (*s, "Upstream Error"),
DrandError::NoCachedKyn => (404, "Not Found"),
DrandError::Serde(_) | DrandError::Storage(_) => (500, "Internal Server Error"),
DrandError::InvalidSignature => (422, "Cryptographic Verification Failed"),
DrandError::StaleKyn { .. } => (400, "Stale Network Kyn"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}
impl From<DnsError> for ApiError {
fn from(e: DnsError) -> Self {
let (status, title): (u16, &'static str) = match &e {
DnsError::NestedTooDeeply
| DnsError::ParseError(_)
| DnsError::TooManyRecords
| DnsError::InvalidLabelLength(_)
| DnsError::InvalidLabelCharacters(_)
| DnsError::InvalidCnameConfiguration(_)
| DnsError::TxtRecordTooLong(_)
| DnsError::InvalidCnameTarget(_)
| DnsError::InvalidPeerId(_)
| DnsError::InvalidKid(_)
| DnsError::InvalidIpfsCid(_) => (400, "Bad Request"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}
impl From<IdentityError> for ApiError {
fn from(e: IdentityError) -> Self {
let (status, title): (u16, &'static str) = match &e {
IdentityError::Io(_) | IdentityError::CorruptedIdentityFile(_) => {
(500, "Internal Server Error")
}
IdentityError::IdentityNotFound(_) => (404, "Not Found"),
IdentityError::InvalidSeedPhrase(_) => (400, "Bad Request"),
IdentityError::DecryptionFailed(_) => (401, "Unauthorized"),
};
ApiError {
error_type: e.error_type_uri(),
title: title.to_string(),
status,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}
impl From<NamesError> for ApiError {
fn from(e: NamesError) -> Self {
ApiError {
error_type: e.error_type_uri(),
title: "Invalid Name".to_string(),
status: 400,
detail: e.user_message(),
instance: None,
code: e.code().to_string(),
retryable: e.is_retryable(),
details: serde_json::Value::Null,
request_id: current_request_id(),
}
}
}