rust_license_key/
models.rs

1//! Data models for license representation and constraints.
2//!
3//! This module defines the core data structures used to represent licenses,
4//! their constraints, and validation results. All structures are designed
5//! to be serializable, versioned, and extensible.
6
7use chrono::{DateTime, Utc};
8use semver::Version;
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11
12// =============================================================================
13// License Format Version
14// =============================================================================
15
16/// Current version of the license format.
17///
18/// This version number is embedded in every license and used to ensure
19/// compatibility between license files and library versions.
20/// Increment this when making breaking changes to the license format.
21pub const LICENSE_FORMAT_VERSION: u32 = 1;
22
23/// Minimum supported license format version.
24///
25/// Licenses with versions below this will be rejected during parsing.
26pub const MIN_SUPPORTED_LICENSE_VERSION: u32 = 1;
27
28/// Maximum supported license format version.
29///
30/// Licenses with versions above this will be rejected during parsing.
31pub const MAX_SUPPORTED_LICENSE_VERSION: u32 = 1;
32
33// =============================================================================
34// License Payload
35// =============================================================================
36
37/// The core license payload containing all license information.
38///
39/// This structure holds all the data that defines a license, including
40/// identification, temporal constraints, and feature restrictions.
41/// It is serialized to JSON and then signed by the publisher.
42///
43/// # Security Note
44///
45/// The payload itself is not encrypted, only signed. Anyone with access
46/// to the license file can read its contents. Do not store secrets in
47/// the license payload.
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
49pub struct LicensePayload {
50    /// Version of the license format for forward compatibility.
51    /// This allows the library to reject incompatible future formats.
52    #[serde(rename = "v")]
53    pub format_version: u32,
54
55    /// Unique identifier for this specific license.
56    /// Should be a UUID or similar unique identifier.
57    #[serde(rename = "id")]
58    pub license_id: String,
59
60    /// Identifier for the customer or organization this license is issued to.
61    #[serde(rename = "customer")]
62    pub customer_id: String,
63
64    /// Human-readable name of the customer or organization.
65    #[serde(rename = "customer_name", skip_serializing_if = "Option::is_none")]
66    pub customer_name: Option<String>,
67
68    /// Timestamp when this license was issued.
69    #[serde(rename = "issued_at")]
70    pub issued_at: DateTime<Utc>,
71
72    /// All constraints and restrictions applied to this license.
73    #[serde(rename = "constraints")]
74    pub constraints: LicenseConstraints,
75
76    /// Optional additional metadata as key-value pairs.
77    /// Useful for application-specific data that doesn't fit standard fields.
78    #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")]
79    pub metadata: Option<HashMap<String, serde_json::Value>>,
80}
81
82impl LicensePayload {
83    /// Returns the license ID as a string slice.
84    pub fn id(&self) -> &str {
85        &self.license_id
86    }
87
88    /// Returns the customer ID as a string slice.
89    pub fn customer(&self) -> &str {
90        &self.customer_id
91    }
92
93    /// Checks if the license format version is supported by this library.
94    pub fn is_version_supported(&self) -> bool {
95        self.format_version >= MIN_SUPPORTED_LICENSE_VERSION
96            && self.format_version <= MAX_SUPPORTED_LICENSE_VERSION
97    }
98
99    // =========================================================================
100    // Custom Key/Value Getters
101    // =========================================================================
102
103    /// Gets a custom value from the license metadata by key.
104    ///
105    /// Returns `None` if the key doesn't exist or if no metadata is present.
106    ///
107    /// # Example
108    ///
109    /// ```
110    /// use rust_license_key::prelude::*;
111    ///
112    /// // After validating a license:
113    /// // if let Some(value) = payload.get_value("max_users") {
114    /// //     println!("Max users: {}", value);
115    /// // }
116    /// ```
117    pub fn get_value(&self, key: &str) -> Option<&serde_json::Value> {
118        self.metadata.as_ref().and_then(|m| m.get(key))
119    }
120
121    /// Gets a custom value from the license metadata, or returns a default value.
122    ///
123    /// # Example
124    ///
125    /// ```
126    /// use rust_license_key::prelude::*;
127    /// use serde_json::json;
128    ///
129    /// // After validating a license:
130    /// // let max_users = payload.get_value_or("max_users", &json!(10));
131    /// ```
132    pub fn get_value_or<'a>(
133        &'a self,
134        key: &str,
135        default: &'a serde_json::Value,
136    ) -> &'a serde_json::Value {
137        self.get_value(key).unwrap_or(default)
138    }
139
140    /// Gets a string value from the license metadata.
141    ///
142    /// Returns `None` if the key doesn't exist or the value is not a string.
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use rust_license_key::prelude::*;
148    ///
149    /// // After validating a license:
150    /// // if let Some(tier) = payload.get_string("tier") {
151    /// //     println!("License tier: {}", tier);
152    /// // }
153    /// ```
154    pub fn get_string(&self, key: &str) -> Option<&str> {
155        self.get_value(key).and_then(|v| v.as_str())
156    }
157
158    /// Gets a string value from the license metadata, or returns a default.
159    ///
160    /// # Example
161    ///
162    /// ```
163    /// use rust_license_key::prelude::*;
164    ///
165    /// // After validating a license:
166    /// // let tier = payload.get_string_or("tier", "basic");
167    /// ```
168    pub fn get_string_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
169        self.get_string(key).unwrap_or(default)
170    }
171
172    /// Gets an i64 value from the license metadata.
173    ///
174    /// Returns `None` if the key doesn't exist or the value is not a number.
175    ///
176    /// # Example
177    ///
178    /// ```
179    /// use rust_license_key::prelude::*;
180    ///
181    /// // After validating a license:
182    /// // if let Some(max_users) = payload.get_i64("max_users") {
183    /// //     println!("Max users: {}", max_users);
184    /// // }
185    /// ```
186    pub fn get_i64(&self, key: &str) -> Option<i64> {
187        self.get_value(key).and_then(|v| v.as_i64())
188    }
189
190    /// Gets an i64 value from the license metadata, or returns a default.
191    ///
192    /// # Example
193    ///
194    /// ```
195    /// use rust_license_key::prelude::*;
196    ///
197    /// // After validating a license:
198    /// // let max_users = payload.get_i64_or("max_users", 10);
199    /// ```
200    pub fn get_i64_or(&self, key: &str, default: i64) -> i64 {
201        self.get_i64(key).unwrap_or(default)
202    }
203
204    /// Gets a u64 value from the license metadata.
205    ///
206    /// Returns `None` if the key doesn't exist or the value is not a positive number.
207    pub fn get_u64(&self, key: &str) -> Option<u64> {
208        self.get_value(key).and_then(|v| v.as_u64())
209    }
210
211    /// Gets a u64 value from the license metadata, or returns a default.
212    pub fn get_u64_or(&self, key: &str, default: u64) -> u64 {
213        self.get_u64(key).unwrap_or(default)
214    }
215
216    /// Gets an f64 value from the license metadata.
217    ///
218    /// Returns `None` if the key doesn't exist or the value is not a number.
219    pub fn get_f64(&self, key: &str) -> Option<f64> {
220        self.get_value(key).and_then(|v| v.as_f64())
221    }
222
223    /// Gets an f64 value from the license metadata, or returns a default.
224    pub fn get_f64_or(&self, key: &str, default: f64) -> f64 {
225        self.get_f64(key).unwrap_or(default)
226    }
227
228    /// Gets a boolean value from the license metadata.
229    ///
230    /// Returns `None` if the key doesn't exist or the value is not a boolean.
231    pub fn get_bool(&self, key: &str) -> Option<bool> {
232        self.get_value(key).and_then(|v| v.as_bool())
233    }
234
235    /// Gets a boolean value from the license metadata, or returns a default.
236    pub fn get_bool_or(&self, key: &str, default: bool) -> bool {
237        self.get_bool(key).unwrap_or(default)
238    }
239
240    /// Gets an array value from the license metadata.
241    ///
242    /// Returns `None` if the key doesn't exist or the value is not an array.
243    ///
244    /// # Example
245    ///
246    /// ```
247    /// use rust_license_key::prelude::*;
248    ///
249    /// // After validating a license:
250    /// // if let Some(modules) = payload.get_array("allowed_modules") {
251    /// //     for module in modules {
252    /// //         println!("Module: {}", module);
253    /// //     }
254    /// // }
255    /// ```
256    pub fn get_array(&self, key: &str) -> Option<&Vec<serde_json::Value>> {
257        self.get_value(key).and_then(|v| v.as_array())
258    }
259
260    /// Gets a string array from the license metadata.
261    ///
262    /// Returns `None` if the key doesn't exist or the value is not an array.
263    /// Non-string elements in the array are filtered out.
264    ///
265    /// # Example
266    ///
267    /// ```
268    /// use rust_license_key::prelude::*;
269    ///
270    /// // After validating a license:
271    /// // if let Some(modules) = payload.get_string_array("allowed_modules") {
272    /// //     for module in modules {
273    /// //         println!("Module: {}", module);
274    /// //     }
275    /// // }
276    /// ```
277    pub fn get_string_array(&self, key: &str) -> Option<Vec<&str>> {
278        self.get_array(key)
279            .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
280    }
281
282    /// Gets an object value from the license metadata.
283    ///
284    /// Returns `None` if the key doesn't exist or the value is not an object.
285    pub fn get_object(&self, key: &str) -> Option<&serde_json::Map<String, serde_json::Value>> {
286        self.get_value(key).and_then(|v| v.as_object())
287    }
288
289    /// Checks if a key exists in the license metadata.
290    ///
291    /// # Example
292    ///
293    /// ```
294    /// use rust_license_key::prelude::*;
295    ///
296    /// // After validating a license:
297    /// // if payload.has_key("enterprise_features") {
298    /// //     // Enable enterprise features
299    /// // }
300    /// ```
301    pub fn has_key(&self, key: &str) -> bool {
302        self.metadata
303            .as_ref()
304            .map(|m| m.contains_key(key))
305            .unwrap_or(false)
306    }
307
308    /// Returns all metadata keys.
309    ///
310    /// Returns an empty iterator if no metadata is present.
311    pub fn keys(&self) -> impl Iterator<Item = &String> {
312        self.metadata.iter().flat_map(|m| m.keys())
313    }
314}
315
316// =============================================================================
317// License Constraints
318// =============================================================================
319
320/// All constraints and restrictions that can be applied to a license.
321///
322/// Each field is optional, allowing flexible license configurations.
323/// An absent constraint means "no restriction" for that aspect.
324#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
325pub struct LicenseConstraints {
326    /// Date and time when this license expires.
327    /// If `None`, the license never expires.
328    #[serde(rename = "expires_at", skip_serializing_if = "Option::is_none")]
329    pub expiration_date: Option<DateTime<Utc>>,
330
331    /// Date and time when this license becomes valid.
332    /// If `None`, the license is immediately valid upon issuance.
333    #[serde(rename = "valid_from", skip_serializing_if = "Option::is_none")]
334    pub valid_from: Option<DateTime<Utc>>,
335
336    /// Set of features or plugins that are explicitly allowed.
337    /// If `None`, no feature restrictions apply (all features allowed).
338    /// If `Some(empty_set)`, no features are allowed.
339    #[serde(rename = "allowed_features", skip_serializing_if = "Option::is_none")]
340    pub allowed_features: Option<HashSet<String>>,
341
342    /// Set of features or plugins that are explicitly denied.
343    /// Takes precedence over `allowed_features` if both are specified.
344    #[serde(rename = "denied_features", skip_serializing_if = "Option::is_none")]
345    pub denied_features: Option<HashSet<String>>,
346
347    /// Maximum number of concurrent connections or seats allowed.
348    /// If `None`, no connection limit applies.
349    #[serde(rename = "max_connections", skip_serializing_if = "Option::is_none")]
350    pub max_connections: Option<u32>,
351
352    /// Set of hostnames where this license can be used.
353    /// If `None`, no hostname restrictions apply.
354    #[serde(rename = "allowed_hostnames", skip_serializing_if = "Option::is_none")]
355    pub allowed_hostnames: Option<HashSet<String>>,
356
357    /// Set of machine identifiers where this license can be used.
358    /// Machine identifiers can be hardware IDs, container IDs, etc.
359    /// If `None`, no machine restrictions apply.
360    #[serde(
361        rename = "allowed_machine_ids",
362        skip_serializing_if = "Option::is_none"
363    )]
364    pub allowed_machine_ids: Option<HashSet<String>>,
365
366    /// Minimum software version required to use this license.
367    /// If `None`, no minimum version requirement.
368    #[serde(rename = "min_version", skip_serializing_if = "Option::is_none")]
369    pub minimum_software_version: Option<Version>,
370
371    /// Maximum software version allowed to use this license.
372    /// Useful for deprecating old licenses with newer software versions.
373    /// If `None`, no maximum version requirement.
374    #[serde(rename = "max_version", skip_serializing_if = "Option::is_none")]
375    pub maximum_software_version: Option<Version>,
376
377    /// Custom key-value constraints for application-specific validation.
378    /// The application is responsible for interpreting these constraints.
379    #[serde(rename = "custom", skip_serializing_if = "Option::is_none")]
380    pub custom_constraints: Option<HashMap<String, serde_json::Value>>,
381}
382
383impl LicenseConstraints {
384    /// Creates a new empty constraints object with no restrictions.
385    pub fn new() -> Self {
386        Self::default()
387    }
388
389    /// Checks if a feature is allowed by this license.
390    ///
391    /// # Logic
392    ///
393    /// 1. If the feature is in `denied_features`, it is not allowed.
394    /// 2. If `allowed_features` is `None`, the feature is allowed.
395    /// 3. If `allowed_features` is `Some`, the feature must be in the set.
396    pub fn is_feature_allowed(&self, feature: &str) -> bool {
397        // Check denied list first (takes precedence)
398        if let Some(ref denied) = self.denied_features {
399            if denied.contains(feature) {
400                return false;
401            }
402        }
403
404        // Check allowed list
405        match &self.allowed_features {
406            None => true, // No restrictions
407            Some(allowed) => allowed.contains(feature),
408        }
409    }
410
411    /// Checks if a hostname is allowed by this license.
412    ///
413    /// Returns `true` if no hostname restrictions exist or if the hostname
414    /// is in the allowed set.
415    pub fn is_hostname_allowed(&self, hostname: &str) -> bool {
416        match &self.allowed_hostnames {
417            None => true,
418            Some(allowed) => allowed.contains(hostname),
419        }
420    }
421
422    /// Checks if a machine identifier is allowed by this license.
423    ///
424    /// Returns `true` if no machine ID restrictions exist or if the machine ID
425    /// is in the allowed set.
426    pub fn is_machine_id_allowed(&self, machine_id: &str) -> bool {
427        match &self.allowed_machine_ids {
428            None => true,
429            Some(allowed) => allowed.contains(machine_id),
430        }
431    }
432
433    /// Checks if a software version is compatible with this license.
434    ///
435    /// Returns `Ok(())` if the version is within the allowed range,
436    /// or `Err` with a description of why it's incompatible.
437    pub fn check_version_compatibility(&self, version: &Version) -> Result<(), String> {
438        if let Some(ref min_version) = self.minimum_software_version {
439            if version < min_version {
440                return Err(format!(
441                    "version {} is below minimum required version {}",
442                    version, min_version
443                ));
444            }
445        }
446
447        if let Some(ref max_version) = self.maximum_software_version {
448            if version > max_version {
449                return Err(format!(
450                    "version {} exceeds maximum allowed version {}",
451                    version, max_version
452                ));
453            }
454        }
455
456        Ok(())
457    }
458}
459
460// =============================================================================
461// Signed License Container
462// =============================================================================
463
464/// A complete signed license ready for distribution.
465///
466/// This structure contains the license payload along with its cryptographic
467/// signature. It is what gets serialized and distributed to customers.
468///
469/// # Format
470///
471/// When serialized for distribution, this becomes a JSON object with:
472/// - `payload`: The base64-encoded JSON payload
473/// - `signature`: The base64-encoded Ed25519 signature
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct SignedLicense {
476    /// The license payload, base64-encoded JSON.
477    #[serde(rename = "payload")]
478    pub encoded_payload: String,
479
480    /// The Ed25519 signature of the payload, base64-encoded.
481    #[serde(rename = "signature")]
482    pub encoded_signature: String,
483}
484
485impl SignedLicense {
486    /// Creates a new signed license from encoded components.
487    ///
488    /// # Arguments
489    ///
490    /// * `encoded_payload` - Base64-encoded JSON payload
491    /// * `encoded_signature` - Base64-encoded signature bytes
492    pub fn new(encoded_payload: String, encoded_signature: String) -> Self {
493        Self {
494            encoded_payload,
495            encoded_signature,
496        }
497    }
498
499    /// Serializes this signed license to a JSON string for distribution.
500    ///
501    /// This is the format that should be saved to a file or transmitted
502    /// to the customer.
503    pub fn to_json(&self) -> Result<String, serde_json::Error> {
504        serde_json::to_string_pretty(self)
505    }
506
507    /// Deserializes a signed license from a JSON string.
508    ///
509    /// This is how license files are loaded for validation.
510    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
511        serde_json::from_str(json)
512    }
513}
514
515// =============================================================================
516// Validation Context
517// =============================================================================
518
519/// Context information provided during license validation.
520///
521/// This structure contains all the runtime information needed to validate
522/// a license against the current execution environment.
523#[derive(Debug, Clone, Default)]
524pub struct ValidationContext {
525    /// The current date and time to check against temporal constraints.
526    /// If `None`, uses the system's current time.
527    pub current_time: Option<DateTime<Utc>>,
528
529    /// The current hostname to check against hostname constraints.
530    pub current_hostname: Option<String>,
531
532    /// The current machine identifier to check against machine ID constraints.
533    pub current_machine_id: Option<String>,
534
535    /// The current software version to check against version constraints.
536    pub current_software_version: Option<Version>,
537
538    /// The current number of connections to check against connection limits.
539    pub current_connection_count: Option<u32>,
540
541    /// Features being requested for this validation.
542    /// Each feature will be checked against the license constraints.
543    pub requested_features: Vec<String>,
544
545    /// Custom values to check against custom constraints.
546    pub custom_values: HashMap<String, serde_json::Value>,
547}
548
549impl ValidationContext {
550    /// Creates a new empty validation context.
551    pub fn new() -> Self {
552        Self::default()
553    }
554
555    /// Sets the current time for validation.
556    pub fn with_time(mut self, time: DateTime<Utc>) -> Self {
557        self.current_time = Some(time);
558        self
559    }
560
561    /// Sets the current hostname for validation.
562    pub fn with_hostname(mut self, hostname: impl Into<String>) -> Self {
563        self.current_hostname = Some(hostname.into());
564        self
565    }
566
567    /// Sets the current machine identifier for validation.
568    pub fn with_machine_id(mut self, machine_id: impl Into<String>) -> Self {
569        self.current_machine_id = Some(machine_id.into());
570        self
571    }
572
573    /// Sets the current software version for validation.
574    pub fn with_software_version(mut self, version: Version) -> Self {
575        self.current_software_version = Some(version);
576        self
577    }
578
579    /// Sets the current connection count for validation.
580    pub fn with_connection_count(mut self, count: u32) -> Self {
581        self.current_connection_count = Some(count);
582        self
583    }
584
585    /// Adds a requested feature to check against the license.
586    pub fn with_feature(mut self, feature: impl Into<String>) -> Self {
587        self.requested_features.push(feature.into());
588        self
589    }
590
591    /// Adds multiple requested features to check against the license.
592    pub fn with_features(mut self, features: impl IntoIterator<Item = impl Into<String>>) -> Self {
593        self.requested_features
594            .extend(features.into_iter().map(Into::into));
595        self
596    }
597
598    /// Adds a custom value for constraint checking.
599    pub fn with_custom_value(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
600        self.custom_values.insert(key.into(), value);
601        self
602    }
603}
604
605// =============================================================================
606// Validation Result
607// =============================================================================
608
609/// The result of validating a license.
610///
611/// This structure provides comprehensive information about the validation
612/// outcome, including whether it succeeded and detailed status information.
613#[derive(Debug, Clone)]
614pub struct ValidationResult {
615    /// Whether the license is valid.
616    pub is_valid: bool,
617
618    /// The validated license payload (only present if signature was valid).
619    pub payload: Option<LicensePayload>,
620
621    /// List of validation failures encountered.
622    /// Empty if validation succeeded.
623    pub failures: Vec<crate::error::ValidationFailure>,
624
625    /// Time remaining until the license expires.
626    /// `None` if the license never expires or is already expired.
627    pub time_remaining: Option<chrono::Duration>,
628
629    /// List of features that are allowed by this license.
630    /// Only populated if validation succeeded.
631    pub allowed_features: Option<HashSet<String>>,
632
633    /// List of features that are denied by this license.
634    /// Only populated if validation succeeded.
635    pub denied_features: Option<HashSet<String>>,
636}
637
638impl ValidationResult {
639    /// Creates a successful validation result.
640    pub fn success(payload: LicensePayload) -> Self {
641        let time_remaining = payload
642            .constraints
643            .expiration_date
644            .map(|exp| exp.signed_duration_since(Utc::now()));
645
646        let allowed_features = payload.constraints.allowed_features.clone();
647        let denied_features = payload.constraints.denied_features.clone();
648
649        Self {
650            is_valid: true,
651            payload: Some(payload),
652            failures: Vec::new(),
653            time_remaining,
654            allowed_features,
655            denied_features,
656        }
657    }
658
659    /// Creates a failed validation result with the given failures.
660    pub fn failure(failures: Vec<crate::error::ValidationFailure>) -> Self {
661        Self {
662            is_valid: false,
663            payload: None,
664            failures,
665            time_remaining: None,
666            allowed_features: None,
667            denied_features: None,
668        }
669    }
670
671    /// Adds a failure to the result and marks it as invalid.
672    pub fn add_failure(&mut self, failure: crate::error::ValidationFailure) {
673        self.is_valid = false;
674        self.failures.push(failure);
675    }
676
677    /// Returns true if the license is valid and not expired.
678    pub fn is_active(&self) -> bool {
679        self.is_valid
680            && self
681                .time_remaining
682                .map(|d| d.num_seconds() > 0)
683                .unwrap_or(true)
684    }
685
686    /// Returns the number of days remaining until expiration.
687    /// Returns `None` if the license never expires.
688    pub fn days_remaining(&self) -> Option<i64> {
689        self.time_remaining.map(|d| d.num_days())
690    }
691
692    /// Checks if a specific feature is allowed by the validated license.
693    pub fn is_feature_allowed(&self, feature: &str) -> bool {
694        if !self.is_valid {
695            return false;
696        }
697
698        // Check denied list first
699        if let Some(ref denied) = self.denied_features {
700            if denied.contains(feature) {
701                return false;
702            }
703        }
704
705        // Check allowed list
706        match &self.allowed_features {
707            None => true,
708            Some(allowed) => allowed.contains(feature),
709        }
710    }
711
712    // =========================================================================
713    // Custom Key/Value Getters (delegates to payload)
714    // =========================================================================
715
716    /// Gets a custom value from the license metadata by key.
717    ///
718    /// Returns `None` if validation failed, the key doesn't exist,
719    /// or if no metadata is present.
720    ///
721    /// # Example
722    ///
723    /// ```
724    /// use rust_license_key::prelude::*;
725    ///
726    /// // After validating:
727    /// // if let Some(value) = result.get_value("max_users") {
728    /// //     println!("Max users: {}", value);
729    /// // }
730    /// ```
731    pub fn get_value(&self, key: &str) -> Option<&serde_json::Value> {
732        self.payload.as_ref().and_then(|p| p.get_value(key))
733    }
734
735    /// Gets a custom value from the license metadata, or returns a default value.
736    pub fn get_value_or<'a>(
737        &'a self,
738        key: &str,
739        default: &'a serde_json::Value,
740    ) -> &'a serde_json::Value {
741        self.get_value(key).unwrap_or(default)
742    }
743
744    /// Gets a string value from the license metadata.
745    ///
746    /// Returns `None` if validation failed, the key doesn't exist,
747    /// or the value is not a string.
748    pub fn get_string(&self, key: &str) -> Option<&str> {
749        self.payload.as_ref().and_then(|p| p.get_string(key))
750    }
751
752    /// Gets a string value from the license metadata, or returns a default.
753    pub fn get_string_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
754        self.get_string(key).unwrap_or(default)
755    }
756
757    /// Gets an i64 value from the license metadata.
758    ///
759    /// Returns `None` if validation failed, the key doesn't exist,
760    /// or the value is not a number.
761    pub fn get_i64(&self, key: &str) -> Option<i64> {
762        self.payload.as_ref().and_then(|p| p.get_i64(key))
763    }
764
765    /// Gets an i64 value from the license metadata, or returns a default.
766    pub fn get_i64_or(&self, key: &str, default: i64) -> i64 {
767        self.get_i64(key).unwrap_or(default)
768    }
769
770    /// Gets a u64 value from the license metadata.
771    ///
772    /// Returns `None` if validation failed, the key doesn't exist,
773    /// or the value is not a positive number.
774    pub fn get_u64(&self, key: &str) -> Option<u64> {
775        self.payload.as_ref().and_then(|p| p.get_u64(key))
776    }
777
778    /// Gets a u64 value from the license metadata, or returns a default.
779    pub fn get_u64_or(&self, key: &str, default: u64) -> u64 {
780        self.get_u64(key).unwrap_or(default)
781    }
782
783    /// Gets an f64 value from the license metadata.
784    ///
785    /// Returns `None` if validation failed, the key doesn't exist,
786    /// or the value is not a number.
787    pub fn get_f64(&self, key: &str) -> Option<f64> {
788        self.payload.as_ref().and_then(|p| p.get_f64(key))
789    }
790
791    /// Gets an f64 value from the license metadata, or returns a default.
792    pub fn get_f64_or(&self, key: &str, default: f64) -> f64 {
793        self.get_f64(key).unwrap_or(default)
794    }
795
796    /// Gets a boolean value from the license metadata.
797    ///
798    /// Returns `None` if validation failed, the key doesn't exist,
799    /// or the value is not a boolean.
800    pub fn get_bool(&self, key: &str) -> Option<bool> {
801        self.payload.as_ref().and_then(|p| p.get_bool(key))
802    }
803
804    /// Gets a boolean value from the license metadata, or returns a default.
805    pub fn get_bool_or(&self, key: &str, default: bool) -> bool {
806        self.get_bool(key).unwrap_or(default)
807    }
808
809    /// Gets an array value from the license metadata.
810    ///
811    /// Returns `None` if validation failed, the key doesn't exist,
812    /// or the value is not an array.
813    pub fn get_array(&self, key: &str) -> Option<&Vec<serde_json::Value>> {
814        self.payload.as_ref().and_then(|p| p.get_array(key))
815    }
816
817    /// Gets a string array from the license metadata.
818    ///
819    /// Returns `None` if validation failed, the key doesn't exist,
820    /// or the value is not an array.
821    pub fn get_string_array(&self, key: &str) -> Option<Vec<&str>> {
822        self.payload.as_ref().and_then(|p| p.get_string_array(key))
823    }
824
825    /// Gets an object value from the license metadata.
826    ///
827    /// Returns `None` if validation failed, the key doesn't exist,
828    /// or the value is not an object.
829    pub fn get_object(&self, key: &str) -> Option<&serde_json::Map<String, serde_json::Value>> {
830        self.payload.as_ref().and_then(|p| p.get_object(key))
831    }
832
833    /// Checks if a key exists in the license metadata.
834    ///
835    /// Returns `false` if validation failed or no metadata is present.
836    pub fn has_key(&self, key: &str) -> bool {
837        self.payload
838            .as_ref()
839            .map(|p| p.has_key(key))
840            .unwrap_or(false)
841    }
842}
843
844#[cfg(test)]
845mod tests {
846    use super::*;
847
848    #[test]
849    fn test_license_constraints_feature_allowed() {
850        let mut constraints = LicenseConstraints::new();
851
852        // No restrictions - all features allowed
853        assert!(constraints.is_feature_allowed("any_feature"));
854
855        // With allowed list
856        constraints.allowed_features = Some(HashSet::from([
857            "feature_a".to_string(),
858            "feature_b".to_string(),
859        ]));
860        assert!(constraints.is_feature_allowed("feature_a"));
861        assert!(!constraints.is_feature_allowed("feature_c"));
862
863        // Denied list takes precedence
864        constraints.denied_features = Some(HashSet::from(["feature_a".to_string()]));
865        assert!(!constraints.is_feature_allowed("feature_a"));
866        assert!(constraints.is_feature_allowed("feature_b"));
867    }
868
869    #[test]
870    fn test_license_constraints_version_compatibility() {
871        let mut constraints = LicenseConstraints::new();
872        constraints.minimum_software_version = Some(Version::new(1, 0, 0));
873        constraints.maximum_software_version = Some(Version::new(2, 0, 0));
874
875        assert!(constraints
876            .check_version_compatibility(&Version::new(1, 5, 0))
877            .is_ok());
878        assert!(constraints
879            .check_version_compatibility(&Version::new(0, 9, 0))
880            .is_err());
881        assert!(constraints
882            .check_version_compatibility(&Version::new(2, 1, 0))
883            .is_err());
884    }
885
886    #[test]
887    fn test_validation_context_builder() {
888        let context = ValidationContext::new()
889            .with_hostname("server.example.com")
890            .with_software_version(Version::new(1, 2, 3))
891            .with_feature("premium")
892            .with_features(vec!["analytics", "reports"]);
893
894        assert_eq!(
895            context.current_hostname.as_deref(),
896            Some("server.example.com")
897        );
898        assert_eq!(
899            context.current_software_version,
900            Some(Version::new(1, 2, 3))
901        );
902        assert_eq!(context.requested_features.len(), 3);
903    }
904
905    #[test]
906    fn test_license_payload_version_check() {
907        let payload = LicensePayload {
908            format_version: LICENSE_FORMAT_VERSION,
909            license_id: "test".to_string(),
910            customer_id: "customer".to_string(),
911            customer_name: None,
912            issued_at: Utc::now(),
913            constraints: LicenseConstraints::new(),
914            metadata: None,
915        };
916
917        assert!(payload.is_version_supported());
918    }
919
920    #[test]
921    fn test_signed_license_json_roundtrip() {
922        let license = SignedLicense::new(
923            "encoded_payload".to_string(),
924            "encoded_signature".to_string(),
925        );
926
927        let json = license.to_json().unwrap();
928        let parsed = SignedLicense::from_json(&json).unwrap();
929
930        assert_eq!(license.encoded_payload, parsed.encoded_payload);
931        assert_eq!(license.encoded_signature, parsed.encoded_signature);
932    }
933
934    #[test]
935    fn test_license_payload_get_value() {
936        let mut metadata = HashMap::new();
937        metadata.insert("tier".to_string(), serde_json::json!("enterprise"));
938        metadata.insert("max_users".to_string(), serde_json::json!(100));
939        metadata.insert("is_beta".to_string(), serde_json::json!(true));
940        metadata.insert(
941            "modules".to_string(),
942            serde_json::json!(["core", "analytics"]),
943        );
944
945        let payload = LicensePayload {
946            format_version: LICENSE_FORMAT_VERSION,
947            license_id: "test".to_string(),
948            customer_id: "customer".to_string(),
949            customer_name: None,
950            issued_at: Utc::now(),
951            constraints: LicenseConstraints::new(),
952            metadata: Some(metadata),
953        };
954
955        // Test get_value
956        assert!(payload.get_value("tier").is_some());
957        assert!(payload.get_value("nonexistent").is_none());
958
959        // Test get_string
960        assert_eq!(payload.get_string("tier"), Some("enterprise"));
961        assert_eq!(payload.get_string("max_users"), None); // Not a string
962        assert_eq!(payload.get_string_or("tier", "basic"), "enterprise");
963        assert_eq!(payload.get_string_or("nonexistent", "default"), "default");
964
965        // Test get_i64
966        assert_eq!(payload.get_i64("max_users"), Some(100));
967        assert_eq!(payload.get_i64("tier"), None); // Not a number
968        assert_eq!(payload.get_i64_or("max_users", 50), 100);
969        assert_eq!(payload.get_i64_or("nonexistent", 50), 50);
970
971        // Test get_bool
972        assert_eq!(payload.get_bool("is_beta"), Some(true));
973        assert_eq!(payload.get_bool("tier"), None); // Not a bool
974        assert_eq!(payload.get_bool_or("is_beta", false), true);
975        assert_eq!(payload.get_bool_or("nonexistent", false), false);
976
977        // Test get_array
978        assert!(payload.get_array("modules").is_some());
979        assert_eq!(payload.get_array("modules").unwrap().len(), 2);
980
981        // Test get_string_array
982        let modules = payload.get_string_array("modules").unwrap();
983        assert_eq!(modules, vec!["core", "analytics"]);
984
985        // Test has_key
986        assert!(payload.has_key("tier"));
987        assert!(!payload.has_key("nonexistent"));
988
989        // Test keys
990        let keys: Vec<_> = payload.keys().collect();
991        assert_eq!(keys.len(), 4);
992    }
993
994    #[test]
995    fn test_license_payload_get_value_no_metadata() {
996        let payload = LicensePayload {
997            format_version: LICENSE_FORMAT_VERSION,
998            license_id: "test".to_string(),
999            customer_id: "customer".to_string(),
1000            customer_name: None,
1001            issued_at: Utc::now(),
1002            constraints: LicenseConstraints::new(),
1003            metadata: None,
1004        };
1005
1006        assert!(payload.get_value("any").is_none());
1007        assert_eq!(payload.get_string_or("any", "default"), "default");
1008        assert_eq!(payload.get_i64_or("any", 42), 42);
1009        assert!(!payload.has_key("any"));
1010        assert_eq!(payload.keys().count(), 0);
1011    }
1012
1013    #[test]
1014    fn test_validation_result_get_value() {
1015        let mut metadata = HashMap::new();
1016        metadata.insert("tier".to_string(), serde_json::json!("premium"));
1017        metadata.insert("limit".to_string(), serde_json::json!(500));
1018
1019        let payload = LicensePayload {
1020            format_version: LICENSE_FORMAT_VERSION,
1021            license_id: "test".to_string(),
1022            customer_id: "customer".to_string(),
1023            customer_name: None,
1024            issued_at: Utc::now(),
1025            constraints: LicenseConstraints::new(),
1026            metadata: Some(metadata),
1027        };
1028
1029        let result = ValidationResult::success(payload);
1030
1031        // Test getters on ValidationResult
1032        assert_eq!(result.get_string("tier"), Some("premium"));
1033        assert_eq!(result.get_string_or("tier", "basic"), "premium");
1034        assert_eq!(result.get_i64("limit"), Some(500));
1035        assert_eq!(result.get_i64_or("limit", 100), 500);
1036        assert!(result.has_key("tier"));
1037        assert!(!result.has_key("nonexistent"));
1038    }
1039
1040    #[test]
1041    fn test_validation_result_get_value_failure() {
1042        let result = ValidationResult::failure(vec![]);
1043
1044        // All getters should return None/default for failed validation
1045        assert!(result.get_value("any").is_none());
1046        assert_eq!(result.get_string_or("any", "default"), "default");
1047        assert_eq!(result.get_i64_or("any", 42), 42);
1048        assert!(!result.has_key("any"));
1049    }
1050}