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#[derive(Debug, Serialize)]
12pub struct Query<'q> {
13 pub q: &'q str,
18 pub from: &'q str,
22 pub to: &'q str,
26}
27
28impl<'q> Query<'q> {
29 #[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#[derive(Debug, Deserialize)]
38#[serde(rename = "niutrans")] pub struct User {
40 pub key: String,
42 #[serde(default = "default_qps")]
44 pub qps: u8,
45 #[serde(default = "default_limit")]
47 pub limit: Limit,
49 #[serde(default)]
51 pub dict: String,
52 #[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#[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#[derive(Debug, Deserialize)]
96#[serde(untagged)]
97pub enum Response<'r> {
98 Ok {
99 from: &'r str,
100 to: &'r str,
101 #[serde(borrow)]
103 tgt_text: Cow<'r, str>,
104 },
106 Err {
107 from: &'r str,
108 to: &'r str,
109 apikey: &'r str,
113 #[serde(flatten)]
114 error: Error,
115 },
116}
117
118impl<'r> Response<'r> {
119 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 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 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#[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 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}