1use std::collections::HashMap;
34use std::sync::{OnceLock, RwLock};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum MessageKey {
42 ConnectionFailed,
44 ConnectionTimeout,
46 QueryError,
48 NotFound,
50 ConstraintViolation,
52 PoolExhausted,
54 PoolTimeout,
56 TxNotStarted,
58 TxCommitFailed,
60 TxRollbackFailed,
62 CacheMiss,
64 CacheWriteFailed,
66 SqlInjectionDetected,
68 MissingParameter,
70 TypeMismatch,
72 Custom,
74}
75
76impl MessageKey {
77 pub fn default_msg(self) -> &'static str {
79 match self {
80 MessageKey::ConnectionFailed => "连接失败",
81 MessageKey::ConnectionTimeout => "连接超时",
82 MessageKey::QueryError => "查询错误",
83 MessageKey::NotFound => "未找到",
84 MessageKey::ConstraintViolation => "约束违反",
85 MessageKey::PoolExhausted => "连接池耗尽",
86 MessageKey::PoolTimeout => "连接池超时",
87 MessageKey::TxNotStarted => "事务未启动",
88 MessageKey::TxCommitFailed => "事务提交失败",
89 MessageKey::TxRollbackFailed => "事务回滚失败",
90 MessageKey::CacheMiss => "缓存未命中",
91 MessageKey::CacheWriteFailed => "缓存写入失败",
92 MessageKey::SqlInjectionDetected => "检测到 SQL 注入",
93 MessageKey::MissingParameter => "参数绑定缺失",
94 MessageKey::TypeMismatch => "类型转换失败",
95 MessageKey::Custom => "",
96 }
97 }
98}
99
100pub type MessageCatalog = HashMap<MessageKey, String>;
105
106static CATALOG: OnceLock<RwLock<MessageCatalog>> = OnceLock::new();
108
109fn catalog() -> &'static RwLock<MessageCatalog> {
111 CATALOG.get_or_init(|| RwLock::new(MessageCatalog::new()))
112}
113
114pub fn set_catalog(new_catalog: MessageCatalog) {
118 let mut guard = catalog()
119 .write()
120 .unwrap_or_else(|poisoned| poisoned.into_inner());
121 *guard = new_catalog;
122}
123
124pub fn register(key: MessageKey, msg: impl Into<String>) {
128 let mut guard = catalog()
129 .write()
130 .unwrap_or_else(|poisoned| poisoned.into_inner());
131 guard.insert(key, msg.into());
132}
133
134pub fn clear() {
138 let mut guard = catalog()
139 .write()
140 .unwrap_or_else(|poisoned| poisoned.into_inner());
141 guard.clear();
142}
143
144pub fn translate(key: MessageKey, args: &[&str]) -> String {
149 let catalog = catalog().read().map(|g| g.clone()).unwrap_or_default();
150 if let Some(template) = catalog.get(&key) {
151 format_args(template, args)
152 } else {
153 key.default_msg().to_string()
154 }
155}
156
157fn format_args(template: &str, args: &[&str]) -> String {
162 let mut result = String::with_capacity(template.len());
163 let mut chars = template.chars().peekable();
164 while let Some(c) = chars.next() {
165 if c == '{' {
166 let mut idx_str = String::new();
167 while let Some(&next) = chars.peek() {
168 if next == '}' {
169 chars.next();
170 break;
171 }
172 idx_str.push(next);
173 chars.next();
174 }
175 if let Ok(idx) = idx_str.parse::<usize>() {
176 if let Some(arg) = args.get(idx) {
177 result.push_str(arg);
178 } else {
179 result.push('{');
180 result.push_str(&idx_str);
181 result.push('}');
182 }
183 } else {
184 result.push('{');
185 result.push_str(&idx_str);
186 result.push('}');
187 }
188 } else {
189 result.push(c);
190 }
191 }
192 result
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_default_message() {
201 assert_eq!(MessageKey::ConnectionFailed.default_msg(), "连接失败");
202 assert_eq!(MessageKey::QueryError.default_msg(), "查询错误");
203 }
204
205 #[test]
206 fn test_translate_default() {
207 clear();
208 let msg = translate(MessageKey::ConnectionFailed, &[]);
209 assert_eq!(msg, "连接失败");
210 }
211
212 #[test]
213 fn test_translate_with_catalog() {
214 clear();
215 let mut catalog = MessageCatalog::new();
216 catalog.insert(
217 MessageKey::ConnectionFailed,
218 "Connection failed: {0}".to_string(),
219 );
220 set_catalog(catalog);
221 let msg = translate(MessageKey::ConnectionFailed, &["timeout"]);
222 assert_eq!(msg, "Connection failed: timeout");
223 clear();
224 }
225
226 #[test]
227 fn test_register_single() {
228 clear();
229 register(MessageKey::NotFound, "Not found");
230 let msg = translate(MessageKey::NotFound, &[]);
231 assert_eq!(msg, "Not found");
232 clear();
233 }
234
235 #[test]
236 fn test_format_args_out_of_bounds() {
237 let result = format_args("Hello {0} {1}", &["world"]);
238 assert_eq!(result, "Hello world {1}");
239 }
240
241 #[test]
242 fn test_format_args_no_placeholders() {
243 let result = format_args("Hello world", &[]);
244 assert_eq!(result, "Hello world");
245 }
246
247 #[test]
248 fn test_format_args_invalid_index() {
249 let result = format_args("Hello {abc}", &[]);
250 assert_eq!(result, "Hello {abc}");
251 }
252
253 #[test]
254 fn test_clear() {
255 register(MessageKey::QueryError, "Query error");
256 assert_eq!(translate(MessageKey::QueryError, &[]), "Query error");
257 clear();
258 assert_eq!(translate(MessageKey::QueryError, &[]), "查询错误");
259 }
260}