keygen-rs 0.10.1

Unofficial Rust SDK for integrating with the keygen.sh licensing service.
Documentation
use reqwest::header::InvalidHeaderValue;
use serde_urlencoded::ser::Error as UrlEncodedError;
use thiserror::Error;
use url::ParseError;

use crate::{license::License, license_file::LicenseFileDataset, machine_file::MachineFileDataset};

#[derive(Error, Debug)]
pub enum Error {
    #[error("Unexpected error: {0}")]
    UnexpectedError(String),

    #[error("Invalid URL")]
    InvalidUrl,

    #[error("System clock is out of sync")]
    SystemClockUnsynced,

    #[error("Decryption error: {0}")]
    DecryptionError(String),

    #[error("HTTP client error: {0}")]
    HttpClient(#[from] reqwest::Error),

    #[error("URL parse error: {0}")]
    UrlParse(#[from] ParseError),

    #[error("JSON error: {0}")]
    JsonError(#[from] serde_json::Error),

    #[error("Invalid header value: {0}")]
    InvalidHeader(#[from] InvalidHeaderValue),

    #[error("URL encoding error: {0}")]
    UrlEncode(#[from] UrlEncodedError),

    #[error("Rate limit exceeded")]
    RateLimitExceeded {
        window: String,
        count: u32,
        limit: u32,
        remaining: u32,
        reset: u64,
        retry_after: u64,
    },

    #[error("License key is missing")]
    LicenseKeyMissing,

    #[error("License scheme is missing")]
    LicenseSchemeMissing,

    #[error("License scheme is not supported")]
    LicenseSchemeNotSupported,

    #[error("License is not signed")]
    LicenseNotSigned,

    #[error("License key is not genuine")]
    LicenseKeyNotGenuine,

    #[error("Public key is missing")]
    PublicKeyMissing,

    #[error("Public key is invalid")]
    PublicKeyInvalid,

    #[error("License scheme unsupported")]
    LicenseSchemeUnsupported,

    #[error("{0}")]
    CertificateFileInvalid(String),

    #[error("{0}")]
    CertificateFileNotGenuine(String),

    #[error("{0}")]
    CertificateFileNotSupported(String),

    #[error("Certificate file expired")]
    CertificateFileExpired,

    #[error("License file invalid: {0}")]
    LicenseFileInvalid(String),

    #[error("License file not genuine: {0}")]
    LicenseFileNotGenuine(String),

    #[error("License file not supported: {0}")]
    LicenseFileNotSupported(String),

    #[error("License file not encrypted")]
    LicenseFileNotEncrypted,

    #[error("License file expired")]
    LicenseFileExpired(Box<LicenseFileDataset>),

    #[error("Machine file invalid")]
    MachineFileInvalid(String),

    #[error("Machine file not genuine")]
    MachineFileNotGenuine(String),

    #[error("Machine file not supported")]
    MachineFileNotSupported(String),

    #[error("License file expired")]
    MachineFileExpired(Box<MachineFileDataset>),

    #[error("API error: {detail}")]
    KeygenApiError {
        code: String,
        detail: String,
        body: serde_json::Value,
    },

    #[error("Token not allowed")]
    TokenNotAllowed { code: String, detail: String },

    #[error("Token format invalid")]
    TokenFormatInvalid { code: String, detail: String },

    #[error("Token invalid")]
    TokenInvalid { code: String, detail: String },

    #[error("Token expired")]
    TokenExpired { code: String, detail: String },

    #[error("License suspended")]
    LicenseSuspended { code: String, detail: String },

    #[error("License expired")]
    LicenseExpired { code: String, detail: String },

    #[error("License not allowed")]
    LicenseNotAllowed { code: String, detail: String },

    #[error("License not activated")]
    LicenseNotActivated {
        code: String,
        detail: String,
        license: Box<License>,
    },

    #[error("License key invalid")]
    LicenseKeyInvalid { code: String, detail: String },

    #[error("License token invalid")]
    LicenseTokenInvalid { code: String, detail: String },

    #[error("License has too many machines")]
    LicenseTooManyMachines { code: String, detail: String },

    #[error("License has too many cores")]
    LicenseTooManyCores { code: String, detail: String },

    #[error("License has too many processes")]
    LicenseTooManyProcesses { code: String, detail: String },

    #[error("Machine already activated")]
    MachineAlreadyActivated { code: String, detail: String },

    #[error("Machine limit exceeded")]
    MachineLimitExceeded { code: String, detail: String },

    #[error("Machine no longer exists")]
    MachineNotFound,

    #[error("Process limit exceeded")]
    ProcessLimitExceeded { code: String, detail: String },

    #[error("Process no longer exists")]
    ProcessNotFound,

    #[error("Component conflict")]
    ComponentConflict { code: String, detail: String },

    #[error("Component already activated")]
    ComponentAlreadyActivated { code: String, detail: String },

    #[error("Component is not activated")]
    ComponentNotActivated { code: String, detail: String },

    #[error("Environment error")]
    EnvironmentError { code: String, detail: String },

    #[error("Heartbeat dead")]
    HeartbeatDead { code: String, detail: String },

    #[error("Heartbeat ping failed")]
    HeartbeatPingFailed { code: String, detail: String },

    #[error("Heartbeat is required")]
    HeartbeatRequired { code: String, detail: String },

    #[error("Validation fingerprint scope is missing")]
    ValidationFingerprintMissing { code: String, detail: String },

    #[error("Validation components scope is missing")]
    ValidationComponentsMissing { code: String, detail: String },

    #[error("Validation product scope is missing")]
    ValidationProductMissing { code: String, detail: String },

    #[error("Not found")]
    NotFound { code: String, detail: String },

    #[error("Keygen signature validation failed: {reason}")]
    KeygenSignatureInvalid { reason: String },

    #[error("Keygen signature missing")]
    KeygenSignatureMissing,

    #[error("Configuration missing")]
    MissingConfiguration,
}

pub trait ErrorMeta {
    fn code(&self) -> String;
    fn detail(&self) -> String;
}

impl ErrorMeta for Error {
    fn code(&self) -> String {
        match self {
            Error::KeygenApiError { code, .. }
            | Error::TokenNotAllowed { code, .. }
            | Error::TokenFormatInvalid { code, .. }
            | Error::TokenInvalid { code, .. }
            | Error::TokenExpired { code, .. }
            | Error::LicenseSuspended { code, .. }
            | Error::LicenseExpired { code, .. }
            | Error::LicenseNotAllowed { code, .. }
            | Error::LicenseNotActivated { code, .. }
            | Error::LicenseKeyInvalid { code, .. }
            | Error::LicenseTokenInvalid { code, .. }
            | Error::LicenseTooManyMachines { code, .. }
            | Error::LicenseTooManyCores { code, .. }
            | Error::LicenseTooManyProcesses { code, .. }
            | Error::MachineAlreadyActivated { code, .. }
            | Error::MachineLimitExceeded { code, .. }
            | Error::ProcessLimitExceeded { code, .. }
            | Error::ComponentConflict { code, .. }
            | Error::ComponentAlreadyActivated { code, .. }
            | Error::ComponentNotActivated { code, .. }
            | Error::EnvironmentError { code, .. }
            | Error::HeartbeatDead { code, .. }
            | Error::HeartbeatPingFailed { code, .. }
            | Error::HeartbeatRequired { code, .. }
            | Error::ValidationFingerprintMissing { code, .. }
            | Error::ValidationComponentsMissing { code, .. }
            | Error::ValidationProductMissing { code, .. }
            | Error::NotFound { code, .. } => code.to_string(),
            _ => "ERROR".to_string(),
        }
    }

    fn detail(&self) -> String {
        match self {
            Error::KeygenApiError { detail, .. }
            | Error::TokenNotAllowed { detail, .. }
            | Error::TokenFormatInvalid { detail, .. }
            | Error::TokenInvalid { detail, .. }
            | Error::TokenExpired { detail, .. }
            | Error::LicenseSuspended { detail, .. }
            | Error::LicenseExpired { detail, .. }
            | Error::LicenseNotAllowed { detail, .. }
            | Error::LicenseNotActivated { detail, .. }
            | Error::LicenseKeyInvalid { detail, .. }
            | Error::LicenseTokenInvalid { detail, .. }
            | Error::LicenseTooManyMachines { detail, .. }
            | Error::LicenseTooManyCores { detail, .. }
            | Error::LicenseTooManyProcesses { detail, .. }
            | Error::MachineAlreadyActivated { detail, .. }
            | Error::MachineLimitExceeded { detail, .. }
            | Error::ProcessLimitExceeded { detail, .. }
            | Error::ComponentConflict { detail, .. }
            | Error::ComponentAlreadyActivated { detail, .. }
            | Error::ComponentNotActivated { detail, .. }
            | Error::EnvironmentError { detail, .. }
            | Error::HeartbeatDead { detail, .. }
            | Error::HeartbeatPingFailed { detail, .. }
            | Error::HeartbeatRequired { detail, .. }
            | Error::ValidationFingerprintMissing { detail, .. }
            | Error::ValidationComponentsMissing { detail, .. }
            | Error::ValidationProductMissing { detail, .. }
            | Error::NotFound { detail, .. } => detail.to_string(),
            _ => self.to_string(),
        }
    }
}