use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use crate::release_access::ReleaseNotCovered;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum ErrorType {
ApiError,
LicenseInvalid,
LicenseExpired,
StorageError,
ConfigurationError,
OperationNotSupported,
LicenseDeviceMismatch,
DeviceIdentityUnavailable,
ActivationRequestEnded,
StoreClosed,
NoEligibleLicense,
ReleaseNotCovered,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApiError {
pub(crate) status_code: u16,
pub(crate) message: String,
pub(crate) title: String,
pub(crate) detail: String,
pub(crate) retry_after: Option<Duration>,
}
impl ApiError {
pub fn unreachable(message: impl Into<String>) -> Self {
Self::new(0, message)
}
pub fn new(status_code: u16, message: impl Into<String>) -> Self {
Self { status_code, message: message.into(), title: String::new(), detail: String::new(), retry_after: None }
}
pub fn status_code(&self) -> u16 {
self.status_code
}
pub fn message(&self) -> &str {
&self.message
}
pub fn title(&self) -> &str {
&self.title
}
pub fn detail(&self) -> &str {
&self.detail
}
pub fn retry_after(&self) -> Option<Duration> {
self.retry_after
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for ApiError {}
#[derive(Debug, Clone)]
pub struct StorageError {
message: String,
source: Option<Arc<std::io::Error>>,
}
impl StorageError {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into(), source: None }
}
pub fn io(message: impl Into<String>, source: std::io::Error) -> Self {
Self { message: message.into(), source: Some(Arc::new(source)) }
}
pub fn message(&self) -> &str {
&self.message
}
pub fn io_error(&self) -> Option<&std::io::Error> {
self.source.as_deref()
}
}
impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for StorageError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_deref().map(|error| error as &(dyn std::error::Error + 'static))
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("{0}")]
Configuration(String),
#[error(transparent)]
Api(#[from] ApiError),
#[error("{0}")]
LicenseInvalid(String),
#[error("{0}")]
DeviceMismatch(String),
#[error("{0}")]
ReleaseNotCovered(Box<ReleaseNotCovered>),
#[error("{0}")]
LicenseExpired(String),
#[error("{0}")]
StoreClosed(String),
#[error("{0}")]
ActivationRequestEnded(String),
#[error("Could not identify this device (platform: {platform}): {reason}")]
InsufficientDeviceIdentity { platform: String, reason: String },
#[error("Duplicate fingerprint parameter name: {0}")]
DuplicateFingerprintParameter(String),
#[error(transparent)]
Storage(#[from] StorageError),
#[error("{0}")]
OperationNotSupported(String),
#[error("{0}")]
NoEligibleLicense(String),
}
impl Error {
pub(crate) fn insufficient_identity(platform: impl Into<String>) -> Self {
Self::InsufficientDeviceIdentity {
platform: platform.into(),
reason: "no identity parameter could be read".into(),
}
}
pub fn error_type(&self) -> ErrorType {
match self {
Self::Configuration(_) | Self::DuplicateFingerprintParameter(_) => ErrorType::ConfigurationError,
Self::Api(_) => ErrorType::ApiError,
Self::LicenseInvalid(_) => ErrorType::LicenseInvalid,
Self::DeviceMismatch(_) => ErrorType::LicenseDeviceMismatch,
Self::ReleaseNotCovered(_) => ErrorType::ReleaseNotCovered,
Self::LicenseExpired(_) => ErrorType::LicenseExpired,
Self::StoreClosed(_) => ErrorType::StoreClosed,
Self::ActivationRequestEnded(_) => ErrorType::ActivationRequestEnded,
Self::InsufficientDeviceIdentity { .. } => ErrorType::DeviceIdentityUnavailable,
Self::Storage(_) => ErrorType::StorageError,
Self::OperationNotSupported(_) => ErrorType::OperationNotSupported,
Self::NoEligibleLicense(_) => ErrorType::NoEligibleLicense,
}
}
pub fn is_license_invalid(&self) -> bool {
matches!(self, Self::LicenseInvalid(_) | Self::DeviceMismatch(_) | Self::ReleaseNotCovered(_))
}
pub fn is_license_rejection(&self) -> bool {
self.is_license_invalid() || matches!(self, Self::LicenseExpired(_))
}
pub fn as_release_not_covered(&self) -> Option<&ReleaseNotCovered> {
match self {
Self::ReleaseNotCovered(refusal) => Some(refusal),
_ => None,
}
}
pub fn as_api_error(&self) -> Option<&ApiError> {
match self {
Self::Api(error) => Some(error),
_ => None,
}
}
}