translation_api_cn/
baidu.rs

1use crate::Limit;
2use serde::{Deserialize, Serialize};
3use std::borrow::Cow;
4
5pub const URL: &str = "https://fanyi-api.baidu.com/api/trans/vip/translate";
6
7/// 翻译前的必要信息
8#[derive(Debug)]
9pub struct Query<'q> {
10    /// 请求翻译 query,必须为 UTF-8 编码。
11    ///
12    /// TODO: 在传入之前应该把文字控制在 6000 字节以内(汉字约为 2000 个字符),
13    ///       超过 6000 字节要分段请求。
14    pub q:    &'q str,
15    /// 翻译源语言,可设置为 auto
16    ///
17    /// TODO:变成 Option + enum 类型,None 表示 auto
18    pub from: &'q str,
19    /// 翻译目标语言,不可设置为 auto
20    ///
21    /// TODO:和 `from` 共用 enum 类型,但无需是 Option 类型
22    pub to:   &'q str,
23    /// appid+q+salt+密钥的 MD5 值,q 是待查询的原文字符串
24    pub sign: String,
25}
26
27/// 账户信息
28#[derive(Debug, Deserialize)]
29#[serde(rename = "baidu")] // for config or cmd
30pub struct User {
31    /// 用户申请得到的 APP ID
32    pub appid: String,
33    /// 用户申请得到的密钥,这个字段用于生成 MD5 ,不用于直接构造请求内容
34    pub key:   String,
35    /// 随机的字母或数字的字符串
36    #[serde(default = "default_salt")]
37    pub salt:  String,
38    /// TODO: QPS:这涉及并发请求,允许不填,默认为 1。
39    /// 高级版用户可设置为 10。
40    #[serde(default = "default_qps")]
41    pub qps:   u8,
42    /// 每秒并发请求的限制,默认为 Byte(6000)。
43    #[serde(default = "default_limit")]
44    // #[serde(skip_deserializing)]
45    pub limit: Limit,
46}
47
48fn default_qps() -> u8 { 1 }
49fn default_salt() -> String { String::from("0") }
50fn default_limit() -> Limit { Limit::Byte(6000) }
51
52impl Default for User {
53    fn default() -> Self {
54        Self { appid: String::new(),
55               key:   String::new(),
56               salt:  default_salt(),
57               qps:   default_qps(),
58               limit: default_limit(), }
59    }
60}
61
62impl<'q> Query<'q> {
63    /// 实例化
64    pub fn new(q: &'q str, from: &'q str, to: &'q str) -> Self {
65        Self { q, from, to, sign: "".into() }
66    }
67
68    /// 计算 MD5 值,返回以表单方式提交的数据,用于身份验证/登录。
69    /// 当以下内容至少一项发生变动时,必须调用此方法:
70    /// - User: [appid]、[salt]、[key]
71    /// - Query: [q][`Query::q`]
72    ///
73    /// [appid]: `User::appid`
74    /// [salt]: `User::salt`
75    /// [key]: `User::key`
76    pub fn sign<'f>(&'f mut self, user: &'f User) -> Form<'f> {
77        let data = format!("{}{}{}{}", &user.appid, self.q, &user.salt, &user.key);
78        self.sign = format!("{:x}", md5::compute(data));
79        Form::from_user_query(user, self)
80    }
81}
82
83/// 以表单方式提交的数据
84#[derive(Debug, Serialize)]
85pub struct Form<'f> {
86    pub q:     &'f str,
87    pub from:  &'f str,
88    pub to:    &'f str,
89    pub appid: &'f str,
90    pub salt:  &'f str,
91    pub sign:  &'f str,
92}
93
94impl<'f> Form<'f> {
95    pub fn from_user_query(user: &'f User, query: &'f Query) -> Self {
96        Self { q:     query.q,
97               from:  query.from,
98               to:    query.to,
99               appid: &user.appid,
100               salt:  &user.salt,
101               sign:  &query.sign, }
102    }
103}
104
105/// 响应的信息。要么返回翻译结果,要么返回错误信息。
106#[derive(Debug, Deserialize)]
107#[serde(untagged)]
108pub enum Response<'r> {
109    Ok {
110        from: &'r str,
111        to:   &'r str,
112        /// 原文中被 `\n` 分隔的多条翻译文本。
113        #[serde(rename = "trans_result")]
114        #[serde(borrow)]
115        res:  Vec<SrcDst<'r>>,
116    },
117    Err(Error),
118}
119
120impl<'r> Response<'r> {
121    /// 提取翻译内容。无翻译内容时,返回错误。
122    ///
123    /// TODO: [`BaiduError`] 会经过两次内存分配,这种设计的原因是
124    ///       `anyhow` crate 要求错误的类型必须是 `'static`。
125    ///       [`BaiduError`] 一次分配的例子见 `tests/baidu.rs`。
126    pub fn dst(&self) -> Result<impl Iterator<Item = &str>, Error> {
127        match self {
128            Response::Ok { res, .. } => Ok(res.iter().map(|x| x.dst.as_ref())),
129            Response::Err(e) => Err(e.clone()),
130        }
131    }
132
133    /// 提取翻译内容。无翻译内容时,返回错误。
134    pub fn dst_owned(self) -> Result<Vec<String>, Error> {
135        match self {
136            Response::Ok { res, .. } => Ok(res.into_iter().map(|x| x.dst.into()).collect()),
137            Response::Err(e) => Err(e),
138        }
139    }
140
141    /// 翻译内容(即 [`SrcDst`] 的 `dst`字段)是否为 `Cow::Borrowed` 类型。
142    /// 比如英译中时,中文为代码点:
143    /// ```text
144    /// {
145    ///   "from": "en",
146    ///   "to":   "zh",
147    ///   "trans_result":[
148    ///     {"src": "hello", "dst": "\u4f60\u597d"},
149    ///     {"src": "world", "dst": "\u4e16\u754c"}
150    ///   ]
151    /// }
152    /// ```
153    /// 必须使用 `String` 或者 `Cow::Owned` 类型。
154    ///
155    /// 而 dst 为英文时,使用 `&str` 或者 `Cow::Borrowed` 类型可以减少分配。
156    ///
157    /// ## 注意
158    /// 无翻译内容时,返回 `None`。
159    pub fn is_borrowed(&self) -> Option<bool> {
160        match self {
161            Response::Ok { res, .. } if !res.is_empty() => {
162                Some(matches!(res[0].dst, Cow::Borrowed(_)))
163            }
164            _ => None,
165        }
166    }
167}
168
169/// 单条翻译文本
170///
171/// 当包含非 ascii 字符时,为 `Cow::Owned` 类型;
172/// 当全部为 ascii 字符时,为 `Cow::Borrowed` 类型。
173/// 例子见 [`Response::is_borrowed`]。
174///
175/// TODO: `src` 字段暂不考虑序列化,因为这个从原数据 [`Query::q`] 按照 `\n` 字符切分出来即可。
176#[derive(Debug, Deserialize)]
177pub struct SrcDst<'r> {
178    // pub src: Cow<'r, str>,
179    #[serde(borrow)]
180    pub dst: Cow<'r, str>,
181}
182
183/// 错误处理 / 错误码
184#[derive(Debug, Clone, Deserialize)]
185pub struct Error {
186    #[serde(rename = "error_code")]
187    pub code: String,
188    #[serde(rename = "error_msg")]
189    pub msg:  String,
190}
191
192impl std::error::Error for Error {}
193impl std::fmt::Display for Error {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        write!(f,
196               "错误码:`{}`\n错误信息:`{}`\n错误含义:{}\n以上内容由百度翻译 API 返回",
197               self.code,
198               self.msg,
199               self.solution())
200    }
201}
202
203impl Error {
204    /// 参考:[错误码列表](https://fanyi-api.baidu.com/doc/21)
205    pub fn solution(&self) -> &str {
206        match self.code.as_bytes() {
207            b"52000" => "成功。",
208            b"52001" => "请求超时。\n解决方法:请重试。",
209            b"52002" => "系统错误。\n解决方法:请重试。",
210            b"52003" => "未授权用户。\n解决方法:请检查appid是否正确或者服务是否开通。",
211            b"54000" => "必填参数为空。\n解决方法:请检查是否少传参数。",
212            b"54001" => "签名错误。\n解决方法:请检查您的签名生成方法。",
213            b"54003" => {
214                "访问频率受限。\n解决方法:请降低您的调用频率,或进行身份认证后切换为高级版/\
215                 尊享版。"
216            }
217            b"54004" => "账户余额不足。\n解决方法:请前往管理控制台为账户充值。",
218            b"54005" => "长 query 请求频繁。\n解决方法:请降低长 query 的发送频率,3s后再试。",
219            b"58000" => {
220                "客户端 IP 非法。\n解决方法:检查个人资料里填写的 IP \
221                 地址是否正确,可前往开发者信息-基本信息修改。"
222            }
223            b"58001" => "译文语言方向不支持。\n解决方法:检查译文语言是否在语言列表里。",
224            b"58002" => "服务当前已关闭。\n解决方法:请前往管理控制台开启服务。",
225            b"90107" => "认证未通过或未生效。\n解决方法:请前往我的认证查看认证进度。",
226            _ => "未知错误。",
227        }
228    }
229}