use std::sync::OnceLock;
use crate::capability::{Capability, CapabilitySet};
use crate::error::{Error, Result};
use crate::tier::Tier;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct LicenseInfo {
pub tier: Tier,
pub expires_at: Option<String>,
pub capabilities: CapabilitySet,
pub output_is_marked: bool,
}
static GLOBAL_TIER: OnceLock<Tier> = OnceLock::new();
pub fn set_license_key(key: &str) -> Result<()> {
let tier = parse_key_to_tier(key)?;
match GLOBAL_TIER.set(tier) {
Ok(()) => Ok(()),
Err(_existing) => {
let existing = *GLOBAL_TIER.get().expect("initialised");
if existing == tier {
Ok(())
} else {
Err(Error::InvalidLicense {
reason: format!(
"license already set to {existing:?}; restart the process to switch to {tier:?}",
),
})
}
}
}
}
pub fn license_info() -> LicenseInfo {
let tier = effective_tier();
LicenseInfo {
tier,
expires_at: None,
capabilities: tier.capabilities(),
output_is_marked: tier.is_marked(),
}
}
pub(crate) fn effective_tier() -> Tier {
if let Some(&t) = GLOBAL_TIER.get() {
return t;
}
if let Ok(key) = std::env::var("PDFLUENT_LICENSE_KEY") {
if let Ok(t) = parse_key_to_tier(&key) {
return t;
}
}
Tier::Trial
}
fn parse_key_to_tier(key: &str) -> Result<Tier> {
let trimmed = key.trim();
let lowered = trimmed.to_ascii_lowercase();
let after_prefix = lowered
.strip_prefix("tier:")
.ok_or_else(|| Error::InvalidLicense {
reason: format!("expected `tier:<name>` format, got {trimmed:?}"),
})?;
match after_prefix.trim() {
"trial" => Ok(Tier::Trial),
"developer" => Ok(Tier::Developer),
"team" => Ok(Tier::Team),
"business" => Ok(Tier::Business),
"enterprise" => Ok(Tier::Enterprise),
other => Err(Error::InvalidLicense {
reason: format!(
"unknown tier {other:?}; expected trial/developer/team/business/enterprise"
),
}),
}
}
pub(crate) fn require_capability_with_override(
cap: Capability,
override_key: Option<&str>,
) -> Result<()> {
let tier = match override_key {
Some(key) => parse_key_to_tier(key)?,
None => effective_tier(),
};
if tier.capabilities().contains(cap) {
return Ok(());
}
let required = [
Tier::Developer,
Tier::Team,
Tier::Business,
Tier::Enterprise,
]
.iter()
.copied()
.find(|t| t.capabilities().contains(cap))
.unwrap_or(Tier::Enterprise);
Err(Error::FeatureNotInTier {
capability: cap,
current_tier: tier,
required_tier: required,
})
}
pub(crate) fn require_capability(cap: Capability) -> Result<()> {
require_capability_with_override(cap, None)
}