Skip to main content

platform/realm/
validation.rs

1//! Realm 验证逻辑
2//!
3//! 包含 Realm 相关的业务规则验证和检查
4
5use super::model::{Realm, RealmStatus};
6
7/// Structured realm validation error.
8///
9/// `RealmUnavailable` maps to HTTP 403: the realm exists but is not usable.
10/// `StoreError` maps to HTTP 500: the database query itself failed.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum RealmValidationError {
13    /// Realm not found, disabled, expired, or in a non-Active status.
14    RealmUnavailable { realm_id: u32, reason: String },
15    /// Database error during realm lookup.
16    StoreError { realm_id: u32, message: String },
17}
18
19impl std::fmt::Display for RealmValidationError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            Self::RealmUnavailable { realm_id, reason } => {
23                write!(f, "Realm {realm_id} unavailable: {reason}")
24            }
25            Self::StoreError { realm_id, message } => {
26                write!(f, "Database error for realm {realm_id}: {message}")
27            }
28        }
29    }
30}
31
32impl std::error::Error for RealmValidationError {}
33
34/// Realm 验证相关实现
35impl Realm {
36    /// 验证 Realm 是否可用(存在、未过期、状态正常)
37    ///
38    /// 返回 Ok(Realm) 表示 Realm 可用
39    /// 返回 Err(RealmValidationError) 表示 Realm 不可用或数据库错误
40    pub async fn validate_realm(realm_id: u32) -> Result<Realm, RealmValidationError> {
41        let realm = Self::get(realm_id)
42            .await
43            .map_err(|e| RealmValidationError::StoreError {
44                realm_id,
45                message: format!("Failed to query realm: {e}"),
46            })?;
47
48        let Some(realm) = realm else {
49            return Err(RealmValidationError::RealmUnavailable {
50                realm_id,
51                reason: format!("Realm {realm_id} not found"),
52            });
53        };
54
55        if realm.is_expired() {
56            return Err(RealmValidationError::RealmUnavailable {
57                realm_id,
58                reason: format!("Realm {realm_id} has expired"),
59            });
60        }
61
62        if realm.status != RealmStatus::Active {
63            return Err(RealmValidationError::RealmUnavailable {
64                realm_id,
65                reason: format!(
66                    "Realm {} is not in Active status (current: {})",
67                    realm_id, realm.status
68                ),
69            });
70        }
71
72        if !realm.enabled {
73            return Err(RealmValidationError::RealmUnavailable {
74                realm_id,
75                reason: format!("Realm {realm_id} is disabled"),
76            });
77        }
78
79        Ok(realm)
80    }
81
82    /// Map a [RealmValidationError] to the appropriate HTTP status code
83    /// and a human-readable error string (for backward-compatible callers).
84    ///
85    /// - `RealmUnavailable` → (403, reason)
86    /// - `StoreError` → (500, message)
87    pub fn map_validation_error(err: RealmValidationError) -> (u32, String) {
88        match &err {
89            RealmValidationError::RealmUnavailable { reason, .. } => (403, reason.clone()),
90            RealmValidationError::StoreError { message, .. } => (500, message.clone()),
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use chrono::Utc;
99
100    #[test]
101    fn test_expiration_check() {
102        let realm = Realm {
103            expires_at: Some(Utc::now().timestamp() as u64 - 3600),
104            ..Default::default()
105        };
106        assert!(realm.is_expired());
107
108        let realm2 = Realm::default();
109        assert!(!realm2.is_expired());
110    }
111
112    #[test]
113    fn test_is_active() {
114        let realm = Realm {
115            expires_at: Some(Utc::now().timestamp() as u64 + 3600),
116            ..Default::default()
117        };
118        assert!(realm.is_active());
119
120        let realm2 = Realm {
121            expires_at: Some(Utc::now().timestamp() as u64 - 3600),
122            ..Default::default()
123        };
124        assert!(!realm2.is_active());
125
126        let realm3 = Realm {
127            status: RealmStatus::Suspended,
128            ..Default::default()
129        };
130        assert!(!realm3.is_active());
131    }
132
133    #[test]
134    fn test_validation_error_display() {
135        let err = RealmValidationError::RealmUnavailable {
136            realm_id: 1,
137            reason: "test reason".to_string(),
138        };
139        assert_eq!(err.to_string(), "Realm 1 unavailable: test reason");
140        assert_eq!(
141            Realm::map_validation_error(err),
142            (403, "test reason".to_string())
143        );
144
145        let err2 = RealmValidationError::StoreError {
146            realm_id: 2,
147            message: "timeout".to_string(),
148        };
149        assert_eq!(err2.to_string(), "Database error for realm 2: timeout");
150        assert_eq!(
151            Realm::map_validation_error(err2),
152            (500, "timeout".to_string())
153        );
154    }
155}