use crate::Limit;
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
pub const URL: &str = "https://api.niutrans.com/NiuTransServer/translation";
#[derive(Debug, Serialize)]
pub struct Query<'q> {
pub q: &'q str,
pub from: &'q str,
pub to: &'q str,
}
impl<'q> Query<'q> {
#[rustfmt::skip]
pub fn new(q: &'q str, from: &'q str, to: &'q str) -> Self { Self { q, from, to } }
pub fn form(&self, user: &'q User) -> Form { Form::new(user, self) }
}
#[derive(Debug, Deserialize)]
#[serde(rename = "niutrans")] pub struct User {
pub key: String,
#[serde(default = "default_qps")]
pub qps: u8,
#[serde(default = "default_limit")]
pub limit: Limit,
#[serde(default)]
pub dict: String,
#[serde(default)]
pub memory: String,
}
fn default_qps() -> u8 { 50 }
fn default_limit() -> Limit { Limit::Char(5000) }
impl Default for User {
fn default() -> Self {
Self { key: String::new(),
qps: default_qps(),
limit: default_limit(),
dict: String::new(),
memory: String::new(), }
}
}
#[derive(Debug, Serialize)]
pub struct Form<'f> {
pub src_text: &'f str,
pub from: &'f str,
pub to: &'f str,
pub apikey: &'f str,
#[serde(rename = "dictNo")]
pub dict: &'f str,
#[serde(rename = "memoryNo")]
pub memory: &'f str,
}
impl<'f> Form<'f> {
pub fn new(user: &'f User, query: &'f Query) -> Self {
Self { src_text: query.q,
from: query.from,
to: query.to,
apikey: &user.key,
dict: &user.dict,
memory: &user.dict, }
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum Response<'r> {
Ok {
from: &'r str,
to: &'r str,
#[serde(borrow)]
tgt_text: Cow<'r, str>,
},
Err {
from: &'r str,
to: &'r str,
apikey: &'r str,
#[serde(flatten)]
error: Error,
},
}
impl<'r> Response<'r> {
pub fn dst(&self) -> Result<impl Iterator<Item = &str>, Error> {
match self {
Response::Ok { tgt_text, .. } => Ok(tgt_text.trim_end().split('\n')),
Response::Err { error, .. } => Err(error.clone()),
}
}
pub fn dst_owned(self) -> Result<Vec<String>, Error> {
match self {
Response::Ok { tgt_text, .. } => {
Ok(tgt_text.trim_end().split('\n').map(|s| s.into()).collect())
}
Response::Err { error, .. } => Err(error),
}
}
pub fn is_borrowed(&self) -> Option<bool> {
match self {
Response::Ok { tgt_text, .. } => Some(matches!(tgt_text, Cow::Borrowed(_))),
_ => None,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Error {
#[serde(rename = "error_code")]
pub code: String,
#[serde(rename = "error_msg")]
pub msg: String,
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,
"错误码:`{}`\n错误信息:`{}`\n错误含义:{}\n以上内容由小牛翻译 API 返回",
self.code,
self.msg,
self.solution())
}
}
impl Error {
pub fn solution(&self) -> &str {
match self.code.as_bytes() {
b"10000" => "输入为空",
b"10001" => "请求频繁,超出QPS限制",
b"10003" => "请求字符串长度超过限制",
b"10005" => "源语编码有问题,非UTF-8",
b"13001" => "字符流量不足或者没有访问权限",
b"13002" => "apikey 参数不可以是空",
b"13003" => "内容过滤异常",
b"13007" => "语言不支持",
b"13008" => "请求处理超时",
b"14001" => "分句异常",
b"14002" => "分词异常",
b"14003" => "后处理异常",
b"14004" => "对齐失败,不能够返回正确的对应关系",
b"000000" => "请求参数有误,请检查参数",
b"000001" => "Content-Type不支持【multipart/form-data】",
_ => "未知错误。",
}
}
}
#[test]
fn response_test() {
let success = "{\"tgt_text\":\"嗨\\n那里\\n\",\"to\":\"zh\",\"from\":\"en\"}";
let res: Response = serde_json::from_str(success).unwrap();
assert_eq!(res.is_borrowed(), Some(false));
let success = "{\"tgt_text\":\"嗨 那里\",\"to\":\"zh\",\"from\":\"en\"}";
let res: Response = serde_json::from_str(success).unwrap();
assert_eq!(res.is_borrowed(), Some(true));
#[rustfmt::skip]
let error = "{\"to\":\"zh\",\"error_code\":\"13001\",\"from\":\"en\",\
\"error_msg\":\"apikey error OR apikey unauthorized OR service package \
running out\",\"src_text\":\"hi\\nthere\",\"apikey\":\"xx\"}";
let res: Response = serde_json::from_str(error).unwrap();
assert!(res.dst().is_err());
}