Skip to content

Commit

Permalink
Add i18n function.
Browse files Browse the repository at this point in the history
  • Loading branch information
gudaoxuri committed Jul 19, 2022
1 parent 4122541 commit 6cbcdae
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 24 deletions.
1 change: 1 addition & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
type-complexity-threshold = 512
2 changes: 1 addition & 1 deletion examples/multi-apps/doc/src/api/doc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ impl DocApi {
/// Add
#[oai(path = "/", method = "post")]
async fn add(&self, add_req: Json<DocAddReq>, cxt: TardisContextExtractor) -> TardisApiResult<i32> {
let mut funs = TardisFuns::inst_with_db_conn("doc".to_string());
let mut funs = TardisFuns::inst_with_db_conn("doc".to_string(), None);
funs.begin().await?;
let result = DocServ::add_doc(&add_req.0, &funs, &cxt.0).await?;
funs.commit().await?;
Expand Down
2 changes: 1 addition & 1 deletion examples/multi-apps/tag/src/api/tag_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ impl TagApi {
/// Add
#[oai(path = "/", method = "post")]
async fn add(&self, add_req: Json<TagAddReq>, cxt: TardisContextExtractor) -> TardisApiResult<TagResp> {
let mut funs = TardisFuns::inst_with_db_conn("tag".to_string());
let mut funs = TardisFuns::inst_with_db_conn("tag".to_string(), None);
funs.begin().await?;
let result = TagServ::add_doc(&add_req.0, &funs, &cxt.0).await?;
funs.commit().await?;
Expand Down
12 changes: 10 additions & 2 deletions src/basic/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ pub struct AppConfig {
///
/// 一个应用可以有多个实例,每个实例都有自己的标识,默认使用nanoid.
pub inst: String,
/// Application default language / 应用默认语言
/// https://www.andiamo.co.uk/resources/iso-language-codes/
pub default_lang: Option<String>,
}

impl Default for AppConfig {
Expand All @@ -111,6 +114,7 @@ impl Default for AppConfig {
url: "".to_string(),
email: "".to_string(),
inst: format!("inst_{}", TardisFuns::field.nanoid()),
default_lang: None,
}
}
}
Expand Down Expand Up @@ -730,7 +734,7 @@ impl TardisConfig {
);
debug!("=====[Tardis.Config] Content=====\n{:#?}\n=====", framework_config);

if framework_config.adv.salt.is_empty() {
let config = if framework_config.adv.salt.is_empty() {
Ok(TardisConfig {
cs: workspace_config,
fw: framework_config,
Expand Down Expand Up @@ -766,7 +770,11 @@ impl TardisConfig {
fw: framework_config,
})
}
}
};

TardisError::init_locale(path)?;

config
}
}

Expand Down
120 changes: 108 additions & 12 deletions src/basic/error.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
use std::collections::HashMap;
use std::convert::Infallible;
use std::error::Error;
use std::fs::File;
use std::io::{prelude::*, BufReader};
use std::num::ParseIntError;
use std::path::Path;
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use std::sync::Mutex;

use derive_more::Display;
use log::info;
use regex::Regex;

use crate::basic::field::GENERAL_SPLIT;
use crate::TardisResult;

pub static ERROR_DEFAULT_CODE: &str = "-1";

lazy_static! {
static ref LOCALE_CONFIG: Mutex<HashMap<String, HashMap<String, (String, Option<Regex>)>>> = Mutex::new(HashMap::new());
}

/// Tardis unified error wrapper / Tardis统一错误封装
#[derive(Display, Debug)]
pub enum TardisError {
Expand Down Expand Up @@ -40,13 +52,81 @@ pub enum TardisError {
}

impl TardisError {
pub fn init_locale(path: &Path) -> TardisResult<()> {
let path = path.join("locale");
info!("[Tardis.Error] Initializing, base path:{:?}", path);

let mut conf = LOCALE_CONFIG.lock().map_err(|e| TardisError::InternalError(format!("{:?}", e)))?;

let paths = path.read_dir().map_err(|e| TardisError::BadRequest(format!("[Tardis.Error] path {:#?} dir error:{:#?}", path, e)))?;
for entry in paths {
let entry = entry?;
let name = entry.file_name();
let lang = name
.to_str()
.ok_or_else(|| TardisError::BadRequest(format!("[Tardis.Error] file name error {:#?}", entry)))?
// Ignore module name, just take language flag
.split('.')
.next()
.unwrap_or("")
.to_lowercase();
if lang.is_empty() {
continue;
}
let reader = BufReader::new(File::open(entry.path())?);
for line in reader.lines() {
let line = line?;
if line.trim().is_empty() {
continue;
}
let mut items = line.split('\t');
let code = items.next().ok_or_else(|| TardisError::BadRequest(format!("[Tardis.Error] code not exist in {}", line)))?.trim();
let message = items.next().ok_or_else(|| TardisError::BadRequest(format!("[Tardis.Error] message not exist in {}", line)))?.trim();
let regex = if let Some(regex) = items.next() {
Some(Regex::new(regex.trim()).map_err(|_| TardisError::BadRequest(format!("[Tardis.Error] regex illegal in {}", line)))?)
} else {
None
};
conf.entry(lang.to_string()).or_insert_with(HashMap::new).insert(code.to_string(), (message.to_string(), regex));
}
}
Ok(())
}

pub fn get_localized_message(code: &str, default_message: &str, lang: &str) -> TardisResult<String> {
let lang = lang.to_lowercase();
let conf = LOCALE_CONFIG.lock().map_err(|e| TardisError::Conflict(format!("[Tardis.Error] locale config lock error: {:?}", e)))?;
if let Some(conf) = conf.get(&lang) {
if let Some((message, regex)) = conf.get(code) {
let mut localized_message = message.clone();
if let Some(regex) = regex {
if let Some(cap) = regex.captures(default_message) {
for (idx, cap) in cap.iter().enumerate() {
if let Some(cap) = cap {
localized_message = localized_message.replace(&format!("{{{}}}", idx), cap.as_str());
}
}
return Ok(localized_message);
} else {
// Regex not match, fallback to default message
return Ok(default_message.to_string());
}
}
// No regex, return default message
return Ok(message.to_string());
}
}
// No locale config, return default message
Ok(default_message.to_string())
}

pub fn form(msg: &str) -> TardisError {
let (code, message) = Self::to_tuple(msg.to_string());
TardisError::Custom(code, message)
}

fn to_tuple(msg: String) -> (String, String) {
let split_idx = msg.find(GENERAL_SPLIT).expect("Illegal error description format");
let split_idx = msg.find(GENERAL_SPLIT).expect("[Tardis.Error] illegal error description format");
let code = &msg[..split_idx];
let message = &msg[split_idx + 2..];
(code.to_string(), message.to_string())
Expand Down Expand Up @@ -74,58 +154,74 @@ impl TardisError {

pub fn code(&self) -> String {
let text = self.to_string();
let split_idx = text.find(GENERAL_SPLIT).expect("Illegal error description format");
let split_idx = text.find(GENERAL_SPLIT).expect("[Tardis.Error] illegal code description format");
let code = &text[..split_idx];
code.to_string()
}

pub fn message(&self) -> String {
let text = self.to_string();
let split_idx = text.find(GENERAL_SPLIT).expect("Illegal error description format");
let split_idx = text.find(GENERAL_SPLIT).expect("[Tardis.Error] illegal message description format");
let message = &text[split_idx + 2..];
message.to_string()
}
}

pub struct TardisErrorWithExt {
pub ext: String,
/// https://www.andiamo.co.uk/resources/iso-language-codes/
pub lang: Option<String>,
}

impl TardisErrorWithExt {
fn error(&self, code: &str, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
let code = format!("{}-{}-{}-{}", code, self.ext, obj_name, obj_opt);
let msg = self.localized_message(&code, msg);
TardisError::Custom(code, msg)
}

pub fn localized_message(&self, code: &str, msg: &str) -> String {
if let Some(lang) = &self.lang {
TardisError::get_localized_message(code, msg, lang).unwrap_or_else(|_| msg.to_string())
} else {
msg.to_string()
}
}

pub fn internal_error(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("500-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("500", obj_name, obj_opt, msg)
}

pub fn not_implemented(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("501-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("501", obj_name, obj_opt, msg)
}

pub fn io_error(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("503-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("503", obj_name, obj_opt, msg)
}

pub fn bad_request(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("400-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("400", obj_name, obj_opt, msg)
}

pub fn unauthorized(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("401-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("401", obj_name, obj_opt, msg)
}

pub fn not_found(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("404-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("404", obj_name, obj_opt, msg)
}

pub fn format_error(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("406-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("406", obj_name, obj_opt, msg)
}

pub fn timeout(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("408-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("408", obj_name, obj_opt, msg)
}

pub fn conflict(&self, obj_name: &str, obj_opt: &str, msg: &str) -> TardisError {
TardisError::Custom(format!("409-{}-{}-{}", self.ext, obj_name, obj_opt), msg.to_string())
self.error("409", obj_name, obj_opt, msg)
}
}

Expand Down
22 changes: 14 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,13 +454,13 @@ impl TardisFuns {
TardisResult::Ok(())
}

pub fn inst<'a>(code: String) -> TardisFunsInst<'a> {
TardisFunsInst::new(code)
pub fn inst<'a>(code: String, lang: Option<String>) -> TardisFunsInst<'a> {
TardisFunsInst::new(code, lang)
}

#[cfg(feature = "reldb")]
pub fn inst_with_db_conn<'a>(code: String) -> TardisFunsInst<'a> {
TardisFunsInst::new_with_db_conn(code)
pub fn inst_with_db_conn<'a>(code: String, lang: Option<String>) -> TardisFunsInst<'a> {
TardisFunsInst::new_with_db_conn(code, lang)
}

/// Get the custom configuration object / 获取自定义配置对象
Expand Down Expand Up @@ -964,10 +964,13 @@ pub struct TardisFunsInst<'a> {
}

impl<'a> TardisFunsInst<'a> {
pub(crate) fn new(code: String) -> Self {
pub(crate) fn new(code: String, lang: Option<String>) -> Self {
Self {
module_code: code.to_lowercase(),
err: TardisErrorWithExt { ext: code.to_lowercase() },
err: TardisErrorWithExt {
ext: code.to_lowercase(),
lang: if lang.is_some() { lang } else { TardisFuns::fw_config().app.default_lang.clone() },
},
#[cfg(feature = "reldb")]
db: None,
#[cfg(not(feature = "reldb"))]
Expand All @@ -976,11 +979,14 @@ impl<'a> TardisFunsInst<'a> {
}

#[cfg(feature = "reldb")]
pub(crate) fn new_with_db_conn(code: String) -> Self {
pub(crate) fn new_with_db_conn(code: String, lang: Option<String>) -> Self {
let reldb = TardisFuns::reldb_by_module_or_default(&code);
Self {
module_code: code.to_lowercase(),
err: TardisErrorWithExt { ext: code.to_lowercase() },
err: TardisErrorWithExt {
ext: code.to_lowercase(),
lang: if lang.is_some() { lang } else { TardisFuns::fw_config().app.default_lang.clone() },
},
db: Some(reldb.conn()),
}
}
Expand Down
2 changes: 2 additions & 0 deletions tests/config/locale/en
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
404 Not found resource
500 Server error
2 changes: 2 additions & 0 deletions tests/config/locale/zh-cn
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
404 找不到资源
500 服务内部错误
1 change: 1 addition & 0 deletions tests/config/locale/zh-cn.m1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
404-m1-res-add 在{2}中找不到[{1}]资源 Not found (\w+) resource in (\w+)
41 changes: 41 additions & 0 deletions tests/test_basic_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use tardis::basic::error::TardisError;
use tardis::basic::result::TardisResult;
use tardis::TardisFuns;

#[tokio::test]
async fn test_basic_error() -> TardisResult<()> {
TardisFuns::init("tests/config").await?;

assert_eq!(TardisError::get_localized_message("404", "", "zh-cn")?, "找不到资源");
assert_eq!(TardisError::get_localized_message("404", "", "en")?, "Not found resource");
assert_eq!(TardisError::get_localized_message("400xx", "default message", "en")?, "default message");
// Un-matched
assert_eq!(
TardisError::get_localized_message("404-m1-res-add", "Not found ## resource in m1", "zh-cn")?,
"Not found ## resource in m1"
);
assert_eq!(
TardisError::get_localized_message("404-m1-res-add", "Not found RES1 resource in m1", "en")?,
"Not found RES1 resource in m1"
);
assert_eq!(
TardisError::get_localized_message("404-m1-res-add", "Not found RES1 resource in m1", "zh-cn")?,
"在m1中找不到[RES1]资源"
);

let inst = TardisFuns::inst("m1".to_string(), Some("zh-CN".to_string()));

assert_eq!(inst.err().not_found("res", "add", "Not found RES1 resource in m1").message(), "在m1中找不到[RES1]资源");

assert_eq!(
inst.err().not_found("xx", "add", "Not found RES1 resource in m1").message(),
"Not found RES1 resource in m1"
);

assert_eq!(
inst.err().not_found("xx", "add", &inst.err().localized_message("404-m1-res-add", "Not found RES1 resource in m1")).message(),
"在m1中找不到[RES1]资源"
);

Ok(())
}

0 comments on commit 6cbcdae

Please sign in to comment.