const KEY_PREFIX: &str = "sirr_lic_";
const KEY_HEX_LEN: usize = 40;
#[derive(Debug, Clone, PartialEq)]
pub enum LicenseTier {
Solo,
Team,
Business,
Enterprise,
}
impl LicenseTier {
pub fn max_orgs(&self) -> Option<usize> {
match self {
Self::Solo | Self::Team => Some(1),
_ => None,
}
}
pub fn max_principals_per_org(&self) -> Option<usize> {
match self {
Self::Solo => Some(1),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LicenseStatus {
Free,
Licensed(LicenseTier),
Invalid(String),
}
pub fn check_format(key: &str) -> LicenseStatus {
if !key.starts_with(KEY_PREFIX) {
return LicenseStatus::Invalid(format!("license key must start with '{KEY_PREFIX}'"));
}
let hex_part = &key[KEY_PREFIX.len()..];
if hex_part.len() != KEY_HEX_LEN {
return LicenseStatus::Invalid(format!(
"license key hex part must be {KEY_HEX_LEN} characters, got {}",
hex_part.len()
));
}
if !hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
return LicenseStatus::Invalid("license key contains non-hex characters".into());
}
LicenseStatus::Licensed(LicenseTier::Business)
}
pub fn effective_status(license_key: Option<&str>) -> LicenseStatus {
match license_key {
None | Some("") => LicenseStatus::Free,
Some(key) => check_format(key),
}
}
pub fn effective_tier(status: &LicenseStatus) -> &LicenseTier {
static SOLO: LicenseTier = LicenseTier::Solo;
match status {
LicenseStatus::Free => &SOLO,
LicenseStatus::Licensed(tier) => tier,
LicenseStatus::Invalid(_) => &SOLO,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_key() {
let key = format!("{}{}", KEY_PREFIX, "a".repeat(KEY_HEX_LEN));
assert_eq!(
check_format(&key),
LicenseStatus::Licensed(LicenseTier::Business)
);
}
#[test]
fn wrong_prefix() {
assert!(matches!(
check_format("wrong_prefix_aabbcc"),
LicenseStatus::Invalid(_)
));
}
#[test]
fn short_hex() {
let key = format!("{}{}", KEY_PREFIX, "abc");
assert!(matches!(check_format(&key), LicenseStatus::Invalid(_)));
}
#[test]
fn none_is_free() {
assert_eq!(effective_status(None), LicenseStatus::Free);
}
#[test]
fn tier_limits() {
assert_eq!(LicenseTier::Solo.max_orgs(), Some(1));
assert_eq!(LicenseTier::Solo.max_principals_per_org(), Some(1));
assert_eq!(LicenseTier::Team.max_orgs(), Some(1));
assert_eq!(LicenseTier::Team.max_principals_per_org(), None);
assert_eq!(LicenseTier::Business.max_orgs(), None);
assert_eq!(LicenseTier::Business.max_principals_per_org(), None);
assert_eq!(LicenseTier::Enterprise.max_orgs(), None);
assert_eq!(LicenseTier::Enterprise.max_principals_per_org(), None);
}
#[test]
fn effective_tier_free_is_solo() {
let tier = effective_tier(&LicenseStatus::Free);
assert_eq!(tier, &LicenseTier::Solo);
}
}