use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MessageKey {
ConnectionFailed,
ConnectionTimeout,
QueryError,
NotFound,
ConstraintViolation,
PoolExhausted,
PoolTimeout,
TxNotStarted,
TxCommitFailed,
TxRollbackFailed,
CacheMiss,
CacheWriteFailed,
SqlInjectionDetected,
MissingParameter,
TypeMismatch,
Custom,
}
impl MessageKey {
pub fn default_msg(self) -> &'static str {
match self {
MessageKey::ConnectionFailed => "连接失败",
MessageKey::ConnectionTimeout => "连接超时",
MessageKey::QueryError => "查询错误",
MessageKey::NotFound => "未找到",
MessageKey::ConstraintViolation => "约束违反",
MessageKey::PoolExhausted => "连接池耗尽",
MessageKey::PoolTimeout => "连接池超时",
MessageKey::TxNotStarted => "事务未启动",
MessageKey::TxCommitFailed => "事务提交失败",
MessageKey::TxRollbackFailed => "事务回滚失败",
MessageKey::CacheMiss => "缓存未命中",
MessageKey::CacheWriteFailed => "缓存写入失败",
MessageKey::SqlInjectionDetected => "检测到 SQL 注入",
MessageKey::MissingParameter => "参数绑定缺失",
MessageKey::TypeMismatch => "类型转换失败",
MessageKey::Custom => "",
}
}
}
pub type MessageCatalog = HashMap<MessageKey, String>;
static CATALOG: OnceLock<RwLock<MessageCatalog>> = OnceLock::new();
fn catalog() -> &'static RwLock<MessageCatalog> {
CATALOG.get_or_init(|| RwLock::new(MessageCatalog::new()))
}
pub fn set_catalog(new_catalog: MessageCatalog) {
let mut guard = catalog()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*guard = new_catalog;
}
pub fn register(key: MessageKey, msg: impl Into<String>) {
let mut guard = catalog()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard.insert(key, msg.into());
}
pub fn clear() {
let mut guard = catalog()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard.clear();
}
pub fn translate(key: MessageKey, args: &[&str]) -> String {
let catalog = catalog().read().map(|g| g.clone()).unwrap_or_default();
if let Some(template) = catalog.get(&key) {
format_args(template, args)
} else {
key.default_msg().to_string()
}
}
fn format_args(template: &str, args: &[&str]) -> String {
let mut result = String::with_capacity(template.len());
let mut chars = template.chars().peekable();
while let Some(c) = chars.next() {
if c == '{' {
let mut idx_str = String::new();
while let Some(&next) = chars.peek() {
if next == '}' {
chars.next();
break;
}
idx_str.push(next);
chars.next();
}
if let Ok(idx) = idx_str.parse::<usize>() {
if let Some(arg) = args.get(idx) {
result.push_str(arg);
} else {
result.push('{');
result.push_str(&idx_str);
result.push('}');
}
} else {
result.push('{');
result.push_str(&idx_str);
result.push('}');
}
} else {
result.push(c);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_message() {
assert_eq!(MessageKey::ConnectionFailed.default_msg(), "连接失败");
assert_eq!(MessageKey::QueryError.default_msg(), "查询错误");
}
#[test]
fn test_translate_default() {
clear();
let msg = translate(MessageKey::ConnectionFailed, &[]);
assert_eq!(msg, "连接失败");
}
#[test]
fn test_translate_with_catalog() {
clear();
let mut catalog = MessageCatalog::new();
catalog.insert(
MessageKey::ConnectionFailed,
"Connection failed: {0}".to_string(),
);
set_catalog(catalog);
let msg = translate(MessageKey::ConnectionFailed, &["timeout"]);
assert_eq!(msg, "Connection failed: timeout");
clear();
}
#[test]
fn test_register_single() {
clear();
register(MessageKey::NotFound, "Not found");
let msg = translate(MessageKey::NotFound, &[]);
assert_eq!(msg, "Not found");
clear();
}
#[test]
fn test_format_args_out_of_bounds() {
let result = format_args("Hello {0} {1}", &["world"]);
assert_eq!(result, "Hello world {1}");
}
#[test]
fn test_format_args_no_placeholders() {
let result = format_args("Hello world", &[]);
assert_eq!(result, "Hello world");
}
#[test]
fn test_format_args_invalid_index() {
let result = format_args("Hello {abc}", &[]);
assert_eq!(result, "Hello {abc}");
}
#[test]
fn test_clear() {
register(MessageKey::QueryError, "Query error");
assert_eq!(translate(MessageKey::QueryError, &[]), "Query error");
clear();
assert_eq!(translate(MessageKey::QueryError, &[]), "查询错误");
}
}