#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
#![deny(unsafe_code)]
pub mod builder;
pub mod crypto;
pub mod error;
pub mod models;
pub mod parser;
pub mod validator;
pub mod prelude {
pub use crate::crypto::{generate_key_pair_base64, KeyPair, PublicKey};
pub use crate::builder::LicenseBuilder;
pub use crate::parser::{parse_license, LicenseParser};
pub use crate::validator::{
is_feature_allowed, is_license_valid, validate_license, LicenseValidator,
};
pub use crate::models::{
LicenseConstraints, LicensePayload, SignedLicense, ValidationContext, ValidationResult,
LICENSE_FORMAT_VERSION,
};
pub use crate::error::{LicenseError, Result, ValidationFailure, ValidationFailureType};
}
pub use builder::LicenseBuilder;
pub use crypto::{generate_key_pair_base64, KeyPair, PublicKey};
pub use error::{LicenseError, Result};
pub use models::{
LicenseConstraints, LicensePayload, SignedLicense, ValidationContext, ValidationResult,
};
pub use parser::{parse_license, LicenseParser};
pub use validator::{is_feature_allowed, is_license_valid, validate_license, LicenseValidator};
#[cfg(test)]
mod integration_tests {
use super::*;
use chrono::Duration;
use semver::Version;
#[test]
fn test_complete_workflow() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let public_key_base64 = key_pair.public_key_base64();
let license_json = LicenseBuilder::new()
.license_id("E2E-TEST-001")
.customer_id("INTEGRATION-TEST")
.customer_name("Integration Test Customer")
.expires_in(Duration::days(365))
.allowed_features(vec!["basic", "premium", "analytics"])
.denied_feature("experimental")
.max_connections(50)
.allowed_hostname("test.example.com")
.minimum_version(Version::new(1, 0, 0))
.maximum_version(Version::new(3, 0, 0))
.metadata("department", serde_json::json!("Engineering"))
.custom_constraint("max_users", serde_json::json!(100))
.build_and_sign_to_json(&key_pair)
.expect("License creation should succeed");
let validator = LicenseValidator::from_public_key_base64(&public_key_base64)
.expect("Validator creation should succeed");
let context = ValidationContext::new()
.with_hostname("test.example.com")
.with_software_version(Version::new(2, 0, 0))
.with_connection_count(25)
.with_feature("premium")
.with_feature("analytics");
let result = validator
.validate_json(&license_json, &context)
.expect("Validation should not error");
assert!(result.is_valid, "License should be valid");
assert!(result.is_active(), "License should be active");
assert!(result.failures.is_empty(), "Should have no failures");
let days_remaining = result.days_remaining().expect("Should have days remaining");
assert!(
days_remaining >= 364,
"Should have approximately 365 days remaining"
);
assert!(result.is_feature_allowed("premium"));
assert!(result.is_feature_allowed("analytics"));
assert!(!result.is_feature_allowed("experimental"));
let payload = result.payload.expect("Should have payload");
assert_eq!(payload.license_id, "E2E-TEST-001");
assert_eq!(payload.customer_id, "INTEGRATION-TEST");
assert_eq!(
payload.customer_name.as_deref(),
Some("Integration Test Customer")
);
let metadata = payload.metadata.expect("Should have metadata");
assert_eq!(metadata["department"], serde_json::json!("Engineering"));
}
#[test]
fn test_tampering_detection() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let license_json = LicenseBuilder::new()
.license_id("TAMPER-TEST")
.customer_id("TAMPER-CUST")
.build_and_sign_to_json(&key_pair)
.expect("License creation should succeed");
let mut signed: SignedLicense =
serde_json::from_str(&license_json).expect("Should parse JSON");
signed.encoded_payload = signed.encoded_payload.replace('A', "B");
let tampered_json = serde_json::to_string(&signed).expect("Should serialize");
let validator = LicenseValidator::new(key_pair.public_key());
let result = validator
.validate_json(&tampered_json, &ValidationContext::new())
.expect("Should return result");
assert!(!result.is_valid, "Tampered license should be invalid");
}
#[test]
fn test_convenience_functions() {
let key_pair = KeyPair::generate().expect("Key generation should succeed");
let public_key_base64 = key_pair.public_key_base64();
let license_json = LicenseBuilder::new()
.license_id("CONVENIENCE-TEST")
.customer_id("CONVENIENCE-CUST")
.allowed_feature("premium")
.build_and_sign_to_json(&key_pair)
.expect("License creation should succeed");
assert!(is_license_valid(&license_json, &public_key_base64));
assert!(is_feature_allowed(
&license_json,
&public_key_base64,
"premium"
));
assert!(!is_feature_allowed(
&license_json,
&public_key_base64,
"enterprise"
));
let payload =
parse_license(&license_json, &public_key_base64).expect("Should parse license");
assert_eq!(payload.license_id, "CONVENIENCE-TEST");
}
}