use serde::{Deserialize, Serialize};
use crate::auth::MeResponse;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Tier {
Free,
Plus,
}
impl Tier {
pub fn from_str_loose(s: &str) -> Self {
match s.to_lowercase().as_str() {
"plus" => Tier::Plus,
_ => Tier::Free,
}
}
}
impl std::fmt::Display for Tier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Tier::Free => write!(f, "free"),
Tier::Plus => write!(f, "plus"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BillingState {
pub tier: Tier,
pub workspace_limit: u32,
pub storage_limit_bytes: u64,
pub published_site_limit: u32,
}
const FREE_WORKSPACE_LIMIT: u32 = 1;
const FREE_STORAGE_LIMIT_BYTES: u64 = 200 * 1024 * 1024; const FREE_PUBLISHED_SITE_LIMIT: u32 = 1;
const PLUS_WORKSPACE_LIMIT: u32 = 10;
const PLUS_STORAGE_LIMIT_BYTES: u64 = 2 * 1024 * 1024 * 1024; const PLUS_PUBLISHED_SITE_LIMIT: u32 = 1;
impl BillingState {
pub fn free() -> Self {
Self {
tier: Tier::Free,
workspace_limit: FREE_WORKSPACE_LIMIT,
storage_limit_bytes: FREE_STORAGE_LIMIT_BYTES,
published_site_limit: FREE_PUBLISHED_SITE_LIMIT,
}
}
pub fn plus() -> Self {
Self {
tier: Tier::Plus,
workspace_limit: PLUS_WORKSPACE_LIMIT,
storage_limit_bytes: PLUS_STORAGE_LIMIT_BYTES,
published_site_limit: PLUS_PUBLISHED_SITE_LIMIT,
}
}
pub fn from_me_response(me: &MeResponse) -> Self {
let tier = Tier::from_str_loose(&me.tier);
Self {
tier,
workspace_limit: me.workspace_limit,
storage_limit_bytes: me.attachment_limit_bytes,
published_site_limit: me.published_site_limit,
}
}
pub fn can_sync(&self) -> bool {
self.tier == Tier::Plus
}
pub fn can_live_share(&self) -> bool {
self.tier == Tier::Plus
}
pub fn can_collaborate(&self) -> bool {
self.can_live_share()
}
pub fn can_publish(&self) -> bool {
self.tier == Tier::Plus
}
pub fn max_workspaces(&self) -> u32 {
self.workspace_limit
}
pub fn max_published_sites(&self) -> u32 {
self.published_site_limit
}
pub fn max_storage_bytes(&self) -> u64 {
self.storage_limit_bytes
}
pub fn is_plus(&self) -> bool {
self.tier == Tier::Plus
}
}
impl Default for BillingState {
fn default() -> Self {
Self::free()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_free_tier_defaults() {
let state = BillingState::free();
assert_eq!(state.tier, Tier::Free);
assert!(!state.can_sync());
assert!(!state.can_live_share());
assert!(!state.can_collaborate());
assert!(!state.can_publish());
assert_eq!(state.max_workspaces(), 1);
assert_eq!(state.max_published_sites(), 1);
assert_eq!(state.max_storage_bytes(), 200 * 1024 * 1024);
}
#[test]
fn test_plus_tier_defaults() {
let state = BillingState::plus();
assert_eq!(state.tier, Tier::Plus);
assert!(state.can_sync());
assert!(state.can_live_share());
assert!(state.can_collaborate());
assert!(state.can_publish());
assert_eq!(state.max_workspaces(), 10);
assert_eq!(state.max_published_sites(), 1);
assert_eq!(state.max_storage_bytes(), 2 * 1024 * 1024 * 1024);
}
#[test]
fn test_from_me_response() {
let me = MeResponse {
user: crate::auth::User {
id: "uid".to_string(),
email: "u@e.com".to_string(),
},
workspaces: vec![],
devices: vec![],
workspace_limit: 10,
tier: "plus".to_string(),
published_site_limit: 1,
attachment_limit_bytes: 2_147_483_648,
};
let state = BillingState::from_me_response(&me);
assert_eq!(state.tier, Tier::Plus);
assert_eq!(state.max_workspaces(), 10);
assert!(state.can_sync());
}
#[test]
fn test_tier_from_str_loose() {
assert_eq!(Tier::from_str_loose("plus"), Tier::Plus);
assert_eq!(Tier::from_str_loose("Plus"), Tier::Plus);
assert_eq!(Tier::from_str_loose("PLUS"), Tier::Plus);
assert_eq!(Tier::from_str_loose("free"), Tier::Free);
assert_eq!(Tier::from_str_loose("unknown"), Tier::Free);
}
#[test]
fn test_tier_display() {
assert_eq!(Tier::Free.to_string(), "free");
assert_eq!(Tier::Plus.to_string(), "plus");
}
#[test]
fn test_default_is_free() {
let state = BillingState::default();
assert_eq!(state.tier, Tier::Free);
}
#[test]
fn test_free_tier_me_response() {
let me = MeResponse {
user: crate::auth::User {
id: "uid".to_string(),
email: "u@e.com".to_string(),
},
workspaces: vec![],
devices: vec![],
workspace_limit: 1,
tier: "free".to_string(),
published_site_limit: 1,
attachment_limit_bytes: 200 * 1024 * 1024,
};
let state = BillingState::from_me_response(&me);
assert_eq!(state.tier, Tier::Free);
assert!(!state.can_sync());
assert_eq!(state.max_workspaces(), 1);
}
}