translation_api_cn/
niutrans.rs

1use crate::Limit;
2use std::borrow::Cow;
3
4use serde::{Deserialize, Serialize};
5
6pub const URL: &str = "https://api.niutrans.com/NiuTransServer/translation";
7
8/// 翻译前的必要信息
9///
10/// 参考:https://niutrans.com/documents/contents/trans_text
11#[derive(Debug, Serialize)]
12pub struct Query<'q> {
13    /// 请求翻译 query,必须为 UTF-8 编码。
14    ///
15    /// TODO: 在传入之前应该把文字控制在 6000 字节以内(汉字约为 2000 个字符),
16    ///       超过 6000 字节要分段请求。
17    pub q:    &'q str,
18    /// 翻译源语言,不可设置为 auto
19    ///
20    /// TODO:变成 Option + enum 类型,None 表示 auto
21    pub from: &'q str,
22    /// 翻译目标语言,不可设置为 auto
23    ///
24    /// TODO:和 `from` 共用 enum 类型,但无需是 Option 类型
25    pub to:   &'q str,
26}
27
28impl<'q> Query<'q> {
29    /// 实例化
30    #[rustfmt::skip]
31    pub fn new(q: &'q str, from: &'q str, to: &'q str) -> Self { Self { q, from, to } }
32
33    pub fn form(&self, user: &'q User) -> Form { Form::new(user, self) }
34}
35
36/// 账户信息
37#[derive(Debug, Deserialize)]
38#[serde(rename = "niutrans")] // for config or cmd
39pub struct User {
40    /// 用户申请得到的密钥
41    pub key:    String,
42    /// 默认为 50
43    #[serde(default = "default_qps")]
44    pub qps:    u8,
45    /// 每秒并发请求的限制,默认为 Char(5000)。
46    #[serde(default = "default_limit")]
47    // #[serde(skip_deserializing)]
48    pub limit: Limit,
49    /// 术语词典子库 ID
50    #[serde(default)]
51    pub dict:   String,
52    /// 翻译记忆子库 ID
53    #[serde(default)]
54    pub memory: String,
55}
56
57fn default_qps() -> u8 { 50 }
58fn default_limit() -> Limit { Limit::Char(5000) }
59
60impl Default for User {
61    fn default() -> Self {
62        Self { key:    String::new(),
63               qps:    default_qps(),
64               limit:  default_limit(),
65               dict:   String::new(),
66               memory: String::new(), }
67    }
68}
69
70/// 以表单方式提交的数据
71#[derive(Debug, Serialize)]
72pub struct Form<'f> {
73    pub src_text: &'f str,
74    pub from:     &'f str,
75    pub to:       &'f str,
76    pub apikey:   &'f str,
77    #[serde(rename = "dictNo")]
78    pub dict:     &'f str,
79    #[serde(rename = "memoryNo")]
80    pub memory:   &'f str,
81}
82
83impl<'f> Form<'f> {
84    pub fn new(user: &'f User, query: &'f Query) -> Self {
85        Self { src_text: query.q,
86               from:     query.from,
87               to:       query.to,
88               apikey:   &user.key,
89               dict:     &user.dict,
90               memory:   &user.dict, }
91    }
92}
93
94/// 响应的信息。要么返回翻译结果,要么返回错误信息。
95#[derive(Debug, Deserialize)]
96#[serde(untagged)]
97pub enum Response<'r> {
98    Ok {
99        from:     &'r str,
100        to:       &'r str,
101        /// 需要手动进行 `\n` 分隔。(注意:末尾似乎会多出一个 \n)
102        #[serde(borrow)]
103        tgt_text: Cow<'r, str>,
104        // tgt_text: String,
105    },
106    Err {
107        from:   &'r str,
108        to:     &'r str,
109        // /// 需要手动进行 `\n` 分隔。
110        // #[serde(borrow)]
111        // src_text: Cow<'r, str>,
112        apikey: &'r str,
113        #[serde(flatten)]
114        error:  Error,
115    },
116}
117
118impl<'r> Response<'r> {
119    /// 提取翻译内容。
120    pub fn dst(&self) -> Result<impl Iterator<Item = &str>, Error> {
121        match self {
122            Response::Ok { tgt_text, .. } => Ok(tgt_text.trim_end().split('\n')),
123            Response::Err { error, .. } => Err(error.clone()),
124        }
125    }
126
127    /// 提取翻译内容。
128    pub fn dst_owned(self) -> Result<Vec<String>, Error> {
129        match self {
130            Response::Ok { tgt_text, .. } => {
131                Ok(tgt_text.trim_end().split('\n').map(|s| s.into()).collect())
132            }
133            Response::Err { error, .. } => Err(error),
134        }
135    }
136
137    /// 返回的翻译内容是否为 `&str` 类型。
138    /// ## 注意
139    /// 目前发现
140    /// - 有翻译内容时,且含 `\n` 之类的转义符号时,返回 `Some(false)`;
141    /// - 有翻译内容时,且不含转义符号时,返回 `Some(true)`;
142    /// - 无翻译内容时,返回 `None`。
143    pub fn is_borrowed(&self) -> Option<bool> {
144        match self {
145            Response::Ok { tgt_text, .. } => Some(matches!(tgt_text, Cow::Borrowed(_))),
146            _ => None,
147        }
148    }
149}
150
151/// response error
152/// 错误处理 / 错误码
153#[derive(Debug, Clone, Deserialize)]
154pub struct Error {
155    #[serde(rename = "error_code")]
156    pub code: String,
157    #[serde(rename = "error_msg")]
158    pub msg:  String,
159}
160
161impl std::error::Error for Error {}
162impl std::fmt::Display for Error {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        write!(f,
165               "错误码:`{}`\n错误信息:`{}`\n错误含义:{}\n以上内容由小牛翻译 API 返回",
166               self.code,
167               self.msg,
168               self.solution())
169    }
170}
171
172impl Error {
173    /// 参考:[错误码列表](https://niutrans.com/documents/contents/trans_text)
174    pub fn solution(&self) -> &str {
175        match self.code.as_bytes() {
176            b"10000" => "输入为空",
177            b"10001" => "请求频繁,超出QPS限制",
178            b"10003" => "请求字符串长度超过限制",
179            b"10005" => "源语编码有问题,非UTF-8",
180            b"13001" => "字符流量不足或者没有访问权限",
181            b"13002" => "apikey 参数不可以是空",
182            b"13003" => "内容过滤异常",
183            b"13007" => "语言不支持",
184            b"13008" => "请求处理超时",
185            b"14001" => "分句异常",
186            b"14002" => "分词异常",
187            b"14003" => "后处理异常",
188            b"14004" => "对齐失败,不能够返回正确的对应关系",
189            b"000000" => "请求参数有误,请检查参数",
190            b"000001" => "Content-Type不支持【multipart/form-data】",
191            _ => "未知错误。",
192        }
193    }
194}
195
196#[test]
197fn response_test() {
198    let success = "{\"tgt_text\":\"嗨\\n那里\\n\",\"to\":\"zh\",\"from\":\"en\"}";
199    let res: Response = serde_json::from_str(success).unwrap();
200    assert_eq!(res.is_borrowed(), Some(false));
201
202    let success = "{\"tgt_text\":\"嗨 那里\",\"to\":\"zh\",\"from\":\"en\"}";
203    let res: Response = serde_json::from_str(success).unwrap();
204    assert_eq!(res.is_borrowed(), Some(true));
205
206    #[rustfmt::skip]
207    let error = "{\"to\":\"zh\",\"error_code\":\"13001\",\"from\":\"en\",\
208                   \"error_msg\":\"apikey error OR apikey unauthorized OR service package \
209                   running out\",\"src_text\":\"hi\\nthere\",\"apikey\":\"xx\"}";
210    let res: Response = serde_json::from_str(error).unwrap();
211    assert!(res.dst().is_err());
212}