rust_license_key/
error.rs

1//! Error types for the rust-license-key library.
2//!
3//! This module defines all error types that can occur during license operations,
4//! including key generation, license creation, parsing, and validation.
5//! All errors are designed to be explicit, informative, and actionable.
6
7use thiserror::Error;
8
9/// The main error type for all rust-license-key operations.
10///
11/// This enum encompasses all possible errors that can occur when working with
12/// licenses, from cryptographic failures to validation issues.
13#[derive(Debug, Error)]
14pub enum LicenseError {
15    // =========================================================================
16    // Cryptographic Errors
17    // =========================================================================
18    /// Failed to generate a cryptographic key pair.
19    #[error("failed to generate key pair: {reason}")]
20    KeyGenerationFailed {
21        /// Detailed reason for the key generation failure.
22        reason: String,
23    },
24
25    /// The provided private key is invalid or malformed.
26    #[error("invalid private key: {reason}")]
27    InvalidPrivateKey {
28        /// Detailed reason why the private key is invalid.
29        reason: String,
30    },
31
32    /// The provided public key is invalid or malformed.
33    #[error("invalid public key: {reason}")]
34    InvalidPublicKey {
35        /// Detailed reason why the public key is invalid.
36        reason: String,
37    },
38
39    /// The cryptographic signature is invalid.
40    #[error("invalid signature: the license signature does not match the content")]
41    InvalidSignature,
42
43    /// Failed to sign the license data.
44    #[error("signing failed: {reason}")]
45    SigningFailed {
46        /// Detailed reason for the signing failure.
47        reason: String,
48    },
49
50    // =========================================================================
51    // Encoding/Decoding Errors
52    // =========================================================================
53    /// Failed to encode data to Base64.
54    #[error("base64 encoding failed: {reason}")]
55    Base64EncodingFailed {
56        /// Detailed reason for the encoding failure.
57        reason: String,
58    },
59
60    /// Failed to decode Base64 data.
61    #[error("base64 decoding failed: {reason}")]
62    Base64DecodingFailed {
63        /// Detailed reason for the decoding failure.
64        reason: String,
65    },
66
67    /// Failed to serialize data to JSON.
68    #[error("JSON serialization failed: {reason}")]
69    JsonSerializationFailed {
70        /// Detailed reason for the serialization failure.
71        reason: String,
72    },
73
74    /// Failed to deserialize JSON data.
75    #[error("JSON deserialization failed: {reason}")]
76    JsonDeserializationFailed {
77        /// Detailed reason for the deserialization failure.
78        reason: String,
79    },
80
81    // =========================================================================
82    // License Format Errors
83    // =========================================================================
84    /// The license format is invalid or corrupted.
85    #[error("invalid license format: {reason}")]
86    InvalidLicenseFormat {
87        /// Detailed reason why the license format is invalid.
88        reason: String,
89    },
90
91    /// The license version is not supported by this library version.
92    #[error("unsupported license version: found {found}, supported versions are {supported}")]
93    UnsupportedLicenseVersion {
94        /// The version found in the license.
95        found: u32,
96        /// Description of supported versions.
97        supported: String,
98    },
99
100    /// A required field is missing from the license.
101    #[error("missing required field: {field_name}")]
102    MissingRequiredField {
103        /// The name of the missing field.
104        field_name: String,
105    },
106
107    // =========================================================================
108    // Validation Errors
109    // =========================================================================
110    /// The license has expired.
111    #[error("license expired: expired on {expiration_date}")]
112    LicenseExpired {
113        /// The date when the license expired.
114        expiration_date: String,
115    },
116
117    /// The license is not yet valid (future start date).
118    #[error("license not yet valid: becomes valid on {valid_from}")]
119    LicenseNotYetValid {
120        /// The date when the license becomes valid.
121        valid_from: String,
122    },
123
124    /// The current software version is not compatible with this license.
125    #[error("software version {current} is not compatible: {reason}")]
126    IncompatibleSoftwareVersion {
127        /// The current software version.
128        current: String,
129        /// Detailed reason for the incompatibility.
130        reason: String,
131    },
132
133    /// The requested feature or plugin is not allowed by this license.
134    #[error("feature not allowed: '{feature}' is not included in this license")]
135    FeatureNotAllowed {
136        /// The name of the disallowed feature.
137        feature: String,
138    },
139
140    /// The current hostname is not allowed by this license.
141    #[error("hostname not allowed: '{hostname}' is not in the allowed list")]
142    HostnameNotAllowed {
143        /// The current hostname that was rejected.
144        hostname: String,
145    },
146
147    /// The current machine identifier is not allowed by this license.
148    #[error("machine identifier not allowed: '{machine_id}' is not in the allowed list")]
149    MachineIdNotAllowed {
150        /// The current machine identifier that was rejected.
151        machine_id: String,
152    },
153
154    /// The maximum number of concurrent connections has been exceeded.
155    #[error("connection limit exceeded: maximum {max_allowed} connections allowed")]
156    ConnectionLimitExceeded {
157        /// The maximum number of connections allowed.
158        max_allowed: u32,
159    },
160
161    /// A custom constraint validation failed.
162    #[error("constraint validation failed: {constraint_name} - {reason}")]
163    ConstraintValidationFailed {
164        /// The name of the constraint that failed.
165        constraint_name: String,
166        /// Detailed reason for the failure.
167        reason: String,
168    },
169
170    // =========================================================================
171    // Builder Errors
172    // =========================================================================
173    /// The license builder is missing required fields.
174    #[error("license builder incomplete: {missing_fields}")]
175    BuilderIncomplete {
176        /// Comma-separated list of missing required fields.
177        missing_fields: String,
178    },
179
180    /// An invalid value was provided to the builder.
181    #[error("invalid builder value for '{field}': {reason}")]
182    InvalidBuilderValue {
183        /// The field with the invalid value.
184        field: String,
185        /// Detailed reason why the value is invalid.
186        reason: String,
187    },
188}
189
190/// A specialized Result type for license operations.
191pub type Result<T> = std::result::Result<T, LicenseError>;
192
193/// Detailed information about a validation failure.
194///
195/// This struct provides comprehensive information about why a license
196/// validation failed, useful for logging, debugging, and user feedback.
197#[derive(Debug, Clone)]
198pub struct ValidationFailure {
199    /// The type of validation that failed.
200    pub failure_type: ValidationFailureType,
201    /// Human-readable message describing the failure.
202    pub message: String,
203    /// Optional additional context about the failure.
204    pub context: Option<String>,
205}
206
207impl ValidationFailure {
208    /// Creates a new validation failure with the given type and message.
209    pub fn new(failure_type: ValidationFailureType, message: impl Into<String>) -> Self {
210        Self {
211            failure_type,
212            message: message.into(),
213            context: None,
214        }
215    }
216
217    /// Adds context information to this validation failure.
218    pub fn with_context(mut self, context: impl Into<String>) -> Self {
219        self.context = Some(context.into());
220        self
221    }
222}
223
224/// Categorizes the type of validation failure.
225///
226/// This enum helps applications handle different failure types appropriately,
227/// for example, showing different messages for expired vs. invalid licenses.
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub enum ValidationFailureType {
230    /// The cryptographic signature is invalid.
231    InvalidSignature,
232    /// The license has expired.
233    Expired,
234    /// The license is not yet valid.
235    NotYetValid,
236    /// The license version is not supported.
237    UnsupportedVersion,
238    /// A feature constraint was not satisfied.
239    FeatureConstraint,
240    /// A hostname constraint was not satisfied.
241    HostnameConstraint,
242    /// A machine identifier constraint was not satisfied.
243    MachineIdConstraint,
244    /// A software version constraint was not satisfied.
245    VersionConstraint,
246    /// A connection limit was exceeded.
247    ConnectionLimit,
248    /// The license format is malformed.
249    MalformedLicense,
250    /// A custom constraint was not satisfied.
251    CustomConstraint,
252}
253
254impl std::fmt::Display for ValidationFailureType {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        match self {
257            Self::InvalidSignature => write!(f, "invalid_signature"),
258            Self::Expired => write!(f, "expired"),
259            Self::NotYetValid => write!(f, "not_yet_valid"),
260            Self::UnsupportedVersion => write!(f, "unsupported_version"),
261            Self::FeatureConstraint => write!(f, "feature_constraint"),
262            Self::HostnameConstraint => write!(f, "hostname_constraint"),
263            Self::MachineIdConstraint => write!(f, "machine_id_constraint"),
264            Self::VersionConstraint => write!(f, "version_constraint"),
265            Self::ConnectionLimit => write!(f, "connection_limit"),
266            Self::MalformedLicense => write!(f, "malformed_license"),
267            Self::CustomConstraint => write!(f, "custom_constraint"),
268        }
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn test_error_display_messages() {
278        let error = LicenseError::LicenseExpired {
279            expiration_date: "2024-01-01".to_string(),
280        };
281        assert!(error.to_string().contains("2024-01-01"));
282
283        let error = LicenseError::FeatureNotAllowed {
284            feature: "premium".to_string(),
285        };
286        assert!(error.to_string().contains("premium"));
287    }
288
289    #[test]
290    fn test_validation_failure_with_context() {
291        let failure = ValidationFailure::new(ValidationFailureType::Expired, "License expired")
292            .with_context("Expired 30 days ago");
293
294        assert_eq!(failure.failure_type, ValidationFailureType::Expired);
295        assert!(failure.context.is_some());
296    }
297}