fleetforge_common/
licensing.rs1use 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}