// Centralized feature → tier mapping for license entitlements. // // This module is the single source of truth for which features are gated by which tier. // To change what is gated where, modify FEATURE_TIERS only — never sprinkle edition checks // throughout the codebase. /// Editions, ordered from least to most privileged. pub const EDITION_FREE: &str = "free"; pub const EDITION_BASE: &str = "base"; pub const EDITION_PREMIUM: &str = "premium"; /// Maps feature name → list of editions allowed to use it. /// A feature absent from this list is denied for all editions. const FEATURE_TIERS: &[(&str, &[&str])] = &[ ("auto-update", &[EDITION_BASE, EDITION_PREMIUM]), ("web-sync", &[EDITION_PREMIUM]), ("cloud-backup", &[EDITION_PREMIUM]), ("advanced-reports", &[EDITION_PREMIUM]), ]; /// Pure check: does `edition` grant access to `feature`? pub fn is_feature_allowed(feature: &str, edition: &str) -> bool { FEATURE_TIERS .iter() .find(|(name, _)| *name == feature) .map(|(_, tiers)| tiers.contains(&edition)) .unwrap_or(false) } #[tauri::command] pub fn check_entitlement(app: tauri::AppHandle, feature: String) -> Result { let edition = crate::commands::license_commands::current_edition(&app); Ok(is_feature_allowed(&feature, &edition)) } #[cfg(test)] mod tests { use super::*; #[test] fn free_blocks_auto_update() { assert!(!is_feature_allowed("auto-update", EDITION_FREE)); } #[test] fn base_unlocks_auto_update() { assert!(is_feature_allowed("auto-update", EDITION_BASE)); } #[test] fn premium_unlocks_everything() { assert!(is_feature_allowed("auto-update", EDITION_PREMIUM)); assert!(is_feature_allowed("web-sync", EDITION_PREMIUM)); assert!(is_feature_allowed("cloud-backup", EDITION_PREMIUM)); } #[test] fn base_does_not_unlock_premium_features() { assert!(!is_feature_allowed("web-sync", EDITION_BASE)); assert!(!is_feature_allowed("cloud-backup", EDITION_BASE)); } #[test] fn unknown_feature_denied() { assert!(!is_feature_allowed("nonexistent", EDITION_PREMIUM)); } }