translation_api_cn/tencent/
response.rs

1use serde::Deserialize;
2use std::borrow::Cow;
3
4#[derive(Debug, Deserialize)]
5pub struct Response<'r> {
6    #[serde(borrow)]
7    #[serde(rename = "Response")]
8    pub res: ResponseInner<'r>,
9}
10
11impl<'r> Response<'r> {
12    /// 提取翻译内容。
13    pub fn dst(&self) -> Result<impl Iterator<Item = &str>, ResponseError> {
14        match &self.res {
15            ResponseInner::Ok { res, .. } => Ok(res.iter().map(|s| s.as_ref())),
16            ResponseInner::Err { error, .. } => Err(error.clone()),
17        }
18    }
19
20    /// 提取翻译内容。
21    pub fn dst_owned(self) -> Result<Vec<String>, ResponseError> {
22        match self.res {
23            ResponseInner::Ok { res, .. } => Ok(res.into_iter().map(|x| x.into()).collect()),
24            ResponseInner::Err { error, .. } => Err(error),
25        }
26    }
27
28    /// 翻译内容是否为 `str` 类型。无翻译内容或出错时,返回 `None`。
29    pub fn is_borrowed(&self) -> Option<bool> {
30        match &self.res {
31            ResponseInner::Ok { res, .. } if !res.is_empty() => {
32                Some(matches!(res[0], Cow::Borrowed(_)))
33            }
34            _ => None,
35        }
36    }
37}
38
39/// 响应的信息。要么返回翻译结果,要么返回错误信息。
40#[derive(Debug, Deserialize)]
41#[serde(untagged)]
42pub enum ResponseInner<'r> {
43    Ok {
44        #[serde(rename = "RequestId")]
45        id:   &'r str,
46        #[serde(rename = "Source")]
47        from: &'r str,
48        #[serde(rename = "Target")]
49        to:   &'r str,
50        #[serde(borrow)]
51        #[serde(rename = "TargetTextList")]
52        res:  Vec<Cow<'r, str>>,
53    },
54    Err {
55        #[serde(rename = "RequestId")]
56        id:    &'r str,
57        #[serde(rename = "Error")]
58        error: ResponseError,
59    },
60}
61
62/// 错误处理 / 错误码
63///
64/// see:
65/// - https://cloud.tencent.com/document/product/551/30637
66/// - https://cloud.tencent.com/api/error-center?group=PLATFORM&page=1
67/// - https://cloud.tencent.com/document/product/551/40566
68#[derive(Debug, Clone, Deserialize)]
69pub struct ResponseError {
70    #[serde(rename = "Code")]
71    pub code: String,
72    #[serde(rename = "Message")]
73    pub msg:  String,
74}
75
76impl std::error::Error for ResponseError {}
77impl std::fmt::Display for ResponseError {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        write!(f,
80               "错误码:`{}`\n错误信息:`{}`\n错误含义:{}\n以上内容由腾讯云 API 返回",
81               self.code,
82               self.msg,
83               self.solution())
84    }
85}
86
87impl ResponseError {
88    /// 参考:[错误码列表](https://cloud.tencent.com/document/product/551/30637)
89    pub fn solution(&self) -> &str {
90        match self.code.as_bytes() {
91            b"ActionOffline" => "接口已下线。",
92            b"AuthFailure.InvalidAuthorization" => "请求头部的 Authorization 不符合腾讯云标准。",
93            b"AuthFailure.InvalidSecretId" => "密钥非法(不是云 API 密钥类型)。",
94            b"AuthFailure.MFAFailure" => {
95                "[MFA](https://cloud.tencent.com/document/product/378/12036) 错误。"
96            }
97            b"AuthFailure.SecretIdNotFound" => {
98                "密钥不存在。请在控制台检查密钥是否已被删除或者禁用,如状态正常,\
99                 请检查密钥是否填写正确,注意前后不得有空格。"
100            }
101            b"AuthFailure.SignatureExpire" => {
102                "签名过期。Timestamp \
103                 和服务器时间相差不得超过五分钟,请检查本地时间是否和标准时间同步。"
104            }
105            b"AuthFailure.SignatureFailure" => {
106                "签名错误。签名计算错误,请对照调用方式中的签名方法文档检查签名计算过程。"
107            }
108            b"AuthFailure.TokenFailure" => "token 错误。",
109            b"AuthFailure.UnauthorizedOperation" => "请求未授权。请参考",
110            b"DryRunOperation" => "DryRun 操作,代表请求将会是成功的,只是多传了 DryRun 参数。",
111            b"FailedOperation" => "操作失败。",
112            b"InternalError" => "内部错误。",
113            b"InvalidAction" => "接口不存在。",
114            b"InvalidParameter" => "参数错误(包括参数格式、类型等错误)。",
115            b"InvalidParameterValue" => "参数取值错误。",
116            b"InvalidRequest" => "请求 body 的 multipart 格式错误。",
117            b"IpInBlacklist" => "IP地址在黑名单中。",
118            b"IpNotInWhitelist" => "IP地址不在白名单中。",
119            b"LimitExceeded" => "超过配额限制。",
120            b"MissingParameter" => "缺少参数。",
121            b"NoSuchProduct" => "产品不存在",
122            b"NoSuchVersion" => "接口版本不存在。",
123            b"RequestLimitExceeded" => "请求的次数超过了频率限制。",
124            b"RequestLimitExceeded.GlobalRegionUinLimitExceeded" => "主账号超过频率限制。",
125            b"RequestLimitExceeded.IPLimitExceeded" => "IP限频。",
126            b"RequestLimitExceeded.UinLimitExceeded" => "主账号限频。",
127            b"RequestSizeLimitExceeded" => "请求包超过限制大小。",
128            b"ResourceInUse" => "资源被占用。",
129            b"ResourceInsufficient" => "资源不足。",
130            b"ResourceNotFound" => "资源不存在。",
131            b"ResourceUnavailable" => "资源不可用。",
132            b"ResponseSizeLimitExceeded" => "返回包超过限制大小。",
133            b"ServiceUnavailable" => "当前服务暂时不可用。",
134            b"UnauthorizedOperation" => "未授权操作。",
135            b"UnknownParameter" => "未知参数错误,用户多传未定义的参数会导致错误。",
136            b"UnsupportedOperation" => "操作不支持。",
137            b"UnsupportedProtocol" => "http(s) 请求协议错误,只支持 GET 和 POST 请求。",
138            b"UnsupportedRegion" => "接口不支持所传地域。",
139            b"FailedOperation.NoFreeAmount" => {
140                "本月免费额度已用完,如需继续使用您可以在机器翻译控制台升级为付费使用。"
141            }
142            b"FailedOperation.ServiceIsolate" => "账号因为欠费停止服务,请在腾讯云账户充值。",
143            b"FailedOperation.UserNotRegistered" => {
144                "服务未开通,请在腾讯云官网机器翻译控制台开通服务。"
145            }
146            b"InternalError.BackendTimeout" => "后台服务超时,请稍后重试。",
147            b"InternalError.ErrorUnknown" => "未知错误。",
148            b"InternalError.RequestFailed" => "请求失败。",
149            b"InvalidParameter.DuplicatedSessionIdAndSeq" => "重复的SessionUuid和Seq组合。",
150            b"InvalidParameter.MissingParameter" => "参数错误。",
151            b"InvalidParameter.SeqIntervalTooLarge" => "Seq之间的间隙请不要大于2000。",
152            b"LimitExceeded.LimitedAccessFrequency" => "超出请求频率。",
153            b"UnauthorizedOperation.ActionNotFound" => "请填写正确的Action字段名称。",
154            b"UnsupportedOperation.AudioDurationExceed" => {
155                "音频分片长度超过限制,请保证分片长度小于8s。"
156            }
157            b"UnsupportedOperation.TextTooLong" => {
158                "单次请求text超过长度限制,请保证单次请求长度低于2000。"
159            }
160            b"UnsupportedOperation.UnSupportedTargetLanguage" => {
161                "不支持的目标语言,请参照语言列表。"
162            }
163            b"UnsupportedOperation.UnsupportedLanguage" => "不支持的语言,请参照语言列表。",
164            b"UnsupportedOperation.UnsupportedSourceLanguage" => "不支持的源语言,请参照语言列表。",
165            _ => "未知错误。",
166        }
167    }
168}
169
170#[test]
171fn response_test() -> Result<(), Box<dyn std::error::Error>> {
172    let success = r#"{"Response":{"RequestId":"7895050c-b0bd-45f2-ba88-c95c509020f2","Source":"en","Target":"zh","TargetTextList":["嗨","那里"]}}"#;
173    let res: Response = serde_json::from_str(success)?;
174    assert_eq!(format!("{res:?}"),
175               "Response { res: Ok { id: \"7895050c-b0bd-45f2-ba88-c95c509020f2\", from: \"en\", \
176                to: \"zh\", res: [\"嗨\", \"那里\"] } }");
177    assert!(res.dst().is_ok());
178    assert_eq!(&res.dst()?.collect::<Vec<_>>(), &["嗨", "那里"]);
179
180    let error = r#"{"Response":{"Error":{"Code":"AuthFailure.SignatureFailure","Message":"The provided credentials could not be validated. Please check your signature is correct."},"RequestId":"47546ee3-767c-4671-8f90-2c02c7484a42"}}"#;
181    let res: Response = serde_json::from_str(error)?;
182    #[rustfmt::skip]
183    assert_eq!(
184               format!("{res:?}"),
185               "Response { res: Err { id: \"47546ee3-767c-4671-8f90-2c02c7484a42\", \
186				error: ResponseError { code: \"AuthFailure.SignatureFailure\", \
187				msg: \"The provided credentials could not be validated. \
188				Please check your signature is correct.\" } } }"
189    );
190    assert!(res.dst().is_err());
191    // dbg!(res.dst()?); // this error leads to panic
192
193    #[rustfmt::skip]
194    let error = "{\"Response\":{\"Error\":{\"Code\":\"AuthFailure.SecretIdNotFound\",\"Message\":\
195                 \"The SecretId is not found, please ensure that your SecretId is \
196                 correct.\"},\"RequestId\":\"c3d29f67-6e56-48b9-b583-2cfcde32cad1\"}}";
197    let res: Response = serde_json::from_str(error)?;
198    #[rustfmt::skip]
199    assert_eq!(
200               format!("{res:?}"),
201               "Response { res: Err { id: \"c3d29f67-6e56-48b9-b583-2cfcde32cad1\", \
202				error: ResponseError { code: \"AuthFailure.SecretIdNotFound\", \
203				msg: \"The SecretId is not found, please ensure that your SecretId is correct.\" } } }"
204    );
205    assert!(res.dst().is_err());
206    // dbg!(res.dst()?); // this error leads to panic
207
208    Ok(())
209}