fleetforge_common/
licensing.rs

1use std::fmt;
2
3use anyhow::{anyhow, Result};
4use once_cell::sync::OnceCell;
5
6const LICENSE_ENV: &str = "FLEETFORGE_LICENSE_TIER";
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub enum LicenseTier {
10    Oss = 0,
11    Pro = 1,
12    Enterprise = 2,
13}
14
15impl LicenseTier {
16    fn from_str(value: &str) -> Self {
17        match value.trim().to_ascii_lowercase().as_str() {
18            "pro" => LicenseTier::Pro,
19            "enterprise" | "ee" => LicenseTier::Enterprise,
20            _ => LicenseTier::Oss,
21        }
22    }
23
24    pub fn as_str(self) -> &'static str {
25        match self {
26            LicenseTier::Oss => "oss",
27            LicenseTier::Pro => "pro",
28            LicenseTier::Enterprise => "enterprise",
29        }
30    }
31}
32
33static LICENSE_CACHE: OnceCell<LicenseTier> = OnceCell::new();
34
35pub fn current_license_tier() -> LicenseTier {
36    *LICENSE_CACHE.get_or_init(|| {
37        std::env::var(LICENSE_ENV)
38            .map(|value| LicenseTier::from_str(&value))
39            .unwrap_or(LicenseTier::Oss)
40    })
41}
42
43#[derive(Debug, Clone, Copy)]
44pub enum LicensedFeature {
45    RegulatedPolicies,
46    LongRetention,
47    ExternalKms,
48    ScittTransparency,
49}
50
51impl LicensedFeature {
52    fn name(self) -> &'static str {
53        match self {
54            LicensedFeature::RegulatedPolicies => "regulated policy packs",
55            LicensedFeature::LongRetention => "extended retention",
56            LicensedFeature::ExternalKms => "external KMS/HSM signing",
57            LicensedFeature::ScittTransparency => "SCITT transparency publishing",
58        }
59    }
60
61    fn required_tier(self) -> LicenseTier {
62        match self {
63            LicensedFeature::RegulatedPolicies => LicenseTier::Pro,
64            LicensedFeature::LongRetention => LicenseTier::Pro,
65            LicensedFeature::ExternalKms => LicenseTier::Enterprise,
66            LicensedFeature::ScittTransparency => LicenseTier::Enterprise,
67        }
68    }
69}
70
71impl fmt::Display for LicensedFeature {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "{}", self.name())
74    }
75}
76
77pub fn feature_allowed(feature: LicensedFeature) -> bool {
78    current_license_tier() >= feature.required_tier()
79}
80
81pub fn ensure_feature_allowed(feature: LicensedFeature) -> Result<()> {
82    if feature_allowed(feature) {
83        Ok(())
84    } else {
85        Err(anyhow!(
86            "{} requires a {} license (current tier: {}). Set {}=pro or enterprise to unlock.",
87            feature,
88            feature.required_tier().as_str(),
89            current_license_tier().as_str(),
90            LICENSE_ENV,
91        ))
92    }
93}