use thiserror::Error;
#[derive(Debug, Error)]
pub enum LicenseError {
#[error("failed to generate key pair: {reason}")]
KeyGenerationFailed {
reason: String,
},
#[error("invalid private key: {reason}")]
InvalidPrivateKey {
reason: String,
},
#[error("invalid public key: {reason}")]
InvalidPublicKey {
reason: String,
},
#[error("invalid signature: the license signature does not match the content")]
InvalidSignature,
#[error("signing failed: {reason}")]
SigningFailed {
reason: String,
},
#[error("base64 encoding failed: {reason}")]
Base64EncodingFailed {
reason: String,
},
#[error("base64 decoding failed: {reason}")]
Base64DecodingFailed {
reason: String,
},
#[error("JSON serialization failed: {reason}")]
JsonSerializationFailed {
reason: String,
},
#[error("JSON deserialization failed: {reason}")]
JsonDeserializationFailed {
reason: String,
},
#[error("invalid license format: {reason}")]
InvalidLicenseFormat {
reason: String,
},
#[error("unsupported license version: found {found}, supported versions are {supported}")]
UnsupportedLicenseVersion {
found: u32,
supported: String,
},
#[error("missing required field: {field_name}")]
MissingRequiredField {
field_name: String,
},
#[error("license expired: expired on {expiration_date}")]
LicenseExpired {
expiration_date: String,
},
#[error("license not yet valid: becomes valid on {valid_from}")]
LicenseNotYetValid {
valid_from: String,
},
#[error("software version {current} is not compatible: {reason}")]
IncompatibleSoftwareVersion {
current: String,
reason: String,
},
#[error("feature not allowed: '{feature}' is not included in this license")]
FeatureNotAllowed {
feature: String,
},
#[error("hostname not allowed: '{hostname}' is not in the allowed list")]
HostnameNotAllowed {
hostname: String,
},
#[error("machine identifier not allowed: '{machine_id}' is not in the allowed list")]
MachineIdNotAllowed {
machine_id: String,
},
#[error("connection limit exceeded: maximum {max_allowed} connections allowed")]
ConnectionLimitExceeded {
max_allowed: u32,
},
#[error("constraint validation failed: {constraint_name} - {reason}")]
ConstraintValidationFailed {
constraint_name: String,
reason: String,
},
#[error("license builder incomplete: {missing_fields}")]
BuilderIncomplete {
missing_fields: String,
},
#[error("invalid builder value for '{field}': {reason}")]
InvalidBuilderValue {
field: String,
reason: String,
},
}
pub type Result<T> = std::result::Result<T, LicenseError>;
#[derive(Debug, Clone)]
pub struct ValidationFailure {
pub failure_type: ValidationFailureType,
pub message: String,
pub context: Option<String>,
}
impl ValidationFailure {
pub fn new(failure_type: ValidationFailureType, message: impl Into<String>) -> Self {
Self {
failure_type,
message: message.into(),
context: None,
}
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationFailureType {
InvalidSignature,
Expired,
NotYetValid,
UnsupportedVersion,
FeatureConstraint,
HostnameConstraint,
MachineIdConstraint,
VersionConstraint,
ConnectionLimit,
MalformedLicense,
CustomConstraint,
}
impl std::fmt::Display for ValidationFailureType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidSignature => write!(f, "invalid_signature"),
Self::Expired => write!(f, "expired"),
Self::NotYetValid => write!(f, "not_yet_valid"),
Self::UnsupportedVersion => write!(f, "unsupported_version"),
Self::FeatureConstraint => write!(f, "feature_constraint"),
Self::HostnameConstraint => write!(f, "hostname_constraint"),
Self::MachineIdConstraint => write!(f, "machine_id_constraint"),
Self::VersionConstraint => write!(f, "version_constraint"),
Self::ConnectionLimit => write!(f, "connection_limit"),
Self::MalformedLicense => write!(f, "malformed_license"),
Self::CustomConstraint => write!(f, "custom_constraint"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display_messages() {
let error = LicenseError::LicenseExpired {
expiration_date: "2024-01-01".to_string(),
};
assert!(error.to_string().contains("2024-01-01"));
let error = LicenseError::FeatureNotAllowed {
feature: "premium".to_string(),
};
assert!(error.to_string().contains("premium"));
}
#[test]
fn test_validation_failure_with_context() {
let failure = ValidationFailure::new(ValidationFailureType::Expired, "License expired")
.with_context("Expired 30 days ago");
assert_eq!(failure.failure_type, ValidationFailureType::Expired);
assert!(failure.context.is_some());
}
}