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}