translation_api_cn/tencent/
mod.rs1use crate::Limit;
2use hmac::{
3 digest::{CtOutput as HmacOutput, InvalidLength},
4 Hmac, Mac,
5};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8use std::collections::HashMap;
9use time::OffsetDateTime;
10
11mod region;
12pub use region::Region;
13
14#[cfg(test)]
16pub mod ser_json;
17
18mod hash;
19pub use hash::*;
20
21mod response;
22pub use response::{Response, ResponseError, ResponseInner};
23
24pub const URL: &str = "https://tmt.tencentcloudapi.com";
25
26pub type HmacSha256 = Hmac<Sha256>;
28pub type Output = HmacOutput<HmacSha256>;
29pub type Result<T> = std::result::Result<T, Error>;
30
31#[derive(thiserror::Error, Debug)]
32pub enum Error {
33 #[error("序列化时出错")]
34 Ser(#[from] serde_json::Error),
35 #[error("计算 HMAC-SHA256 时出错")]
36 Hash(#[from] InvalidLength),
37 #[error("计算 unix timestamp 时出错")]
38 UnixTimeStamp(#[from] time::error::ComponentRange),
39}
40
41#[derive(Debug, Serialize)]
45pub struct Query<'q> {
46 #[serde(rename = "Source")]
50 pub from: &'q str,
51 #[serde(rename = "Target")]
61 pub to: &'q str,
62 #[serde(rename = "ProjectId")]
63 pub projectid: u8,
64 #[serde(rename = "SourceTextList")]
65 pub q: &'q [&'q str],
66}
67
68impl<'q> Query<'q> {
69 #[rustfmt::skip]
70 pub fn new(q: &'q [&'q str], from: &'q str, to: &'q str, projectid: u8) -> Self {
71 Self { q, from, to, projectid }
72 }
73
74 pub fn to_hashed(&self) -> Result<String> { Ok(hash256(&serde_json::to_vec(self)?)) }
75
76 pub fn to_json_string(&self) -> Result<String> {
77 serde_json::to_string(self).map_err(|e| e.into())
78 }
79
80 #[cfg(test)]
86 pub fn to_hashed2(&self) -> Result<String> { Ok(hash256(&ser_json::to_vec(self)?)) }
87
88 #[cfg(test)]
89 pub fn to_json_string2(&self) -> Result<String> {
90 ser_json::to_string(self).map_err(|e| e.into())
91 }
92}
93
94#[derive(Debug, Deserialize)]
97#[serde(rename = "tencent")] pub struct User {
99 pub id: String,
101 pub key: String,
103 #[serde(default)]
105 pub region: Region,
106 #[serde(default = "default_projectid")]
108 pub projectid: u8,
109 #[serde(default = "default_qps")]
111 pub qps: u8,
113 #[serde(default = "default_limit")]
115 pub limit: Limit,
117}
118
119fn default_qps() -> u8 { 5 }
120fn default_limit() -> Limit { Limit::Char(2000) }
121fn default_projectid() -> u8 { 0 }
122
123impl Default for User {
124 fn default() -> Self {
125 Self { id: String::new(),
126 key: String::new(),
127 region: Region::default(),
128 projectid: 0,
129 qps: 5,
130 limit: default_limit(), }
131 }
132}
133
134#[derive(Debug)]
136pub struct Header<'u, 'q> {
137 pub datetime: OffsetDateTime,
138 pub timestamp: String,
139 pub credential_scope: String,
140 pub authorization: String,
141 pub user: &'u User,
142 pub query: &'q Query<'q>,
143}
144
145impl<'u, 'q> Header<'u, 'q> {
146 const ACTION: &'static str = "TextTranslateBatch";
147 const ALGORITHM: &'static str = "TC3-HMAC-SHA256";
148 const CANONICALHEADERS: &'static str =
149 "content-type:application/json\nhost:tmt.tencentcloudapi.com\n";
150 const CANONICALQUERYSTRING: &'static str = "";
153 const CANONICALURI: &'static str = "/";
154 const CONTENTTYPE: &'static str = "application/json";
155 const CREDENTIALSCOPE: &'static str = "tc3_request";
157 const HOST: &'static str = "tmt.tencentcloudapi.com";
158 const HTTPREQUESTMETHOD: &'static str = "POST";
159 const SERVICE: &'static str = "tmt";
160 const SIGNEDHEADERS: &'static str = "content-type;host";
161 const VERSION: &'static str = "2018-03-21";
162
163 #[rustfmt::skip]
164 pub fn new(user: &'u User, query: &'q Query) -> Self {
165 let datetime = OffsetDateTime::now_utc();
166 let timestamp = datetime.unix_timestamp().to_string();
167 Self { datetime, timestamp, credential_scope: String::new(),
168 authorization: String::new(), user, query }
169 }
170
171 pub fn signature(&mut self) -> Result<String> {
172 let canonical_request = format!("{}\n{}\n{}\n{}\n{}\n{}",
173 Self::HTTPREQUESTMETHOD,
174 Self::CANONICALURI,
175 Self::CANONICALQUERYSTRING,
176 Self::CANONICALHEADERS,
177 Self::SIGNEDHEADERS,
178 self.query.to_hashed()?);
179
180 let date = self.datetime.date();
181 self.credential_scope = format!("{}/{}/{}", date, Self::SERVICE, Self::CREDENTIALSCOPE);
182 let stringtosign = format!("{}\n{}\n{}\n{}",
183 Self::ALGORITHM,
184 self.timestamp,
185 self.credential_scope,
186 hash256(canonical_request.as_bytes()));
187 let secret_date =
188 hash_2u8(format!("TC3{}", self.user.key).as_bytes(), format!("{date}").as_bytes())?;
189 let secret_service = hash_hash_u8(secret_date, Self::SERVICE.as_bytes())?;
190 let secret_signing = hash_hash_u8(secret_service, Self::CREDENTIALSCOPE.as_bytes())?;
191 Ok(hmac_sha256_string(hash_hash_u8(secret_signing, stringtosign.as_bytes())?))
192 }
193
194 pub fn authorization(&mut self) -> Result<&str> {
195 let signature = self.signature()?;
196 self.authorization = format!("{} Credential={}/{}, SignedHeaders={}, Signature={}",
197 Self::ALGORITHM,
198 self.user.id,
199 self.credential_scope,
200 Self::SIGNEDHEADERS,
201 signature);
202 Ok(&self.authorization)
203 }
204
205 pub fn header(&self) -> HashMap<&str, &str> {
206 let mut map = HashMap::with_capacity(8);
207 map.insert("authorization", self.authorization.as_str()).unwrap_or_default();
208 map.insert("content-type", Self::CONTENTTYPE).unwrap_or_default();
209 map.insert("host", Self::HOST).unwrap_or_default();
210 map.insert("x-tc-action", Self::ACTION).unwrap_or_default();
211 map.insert("x-tc-version", Self::VERSION).unwrap_or_default();
212 map.insert("x-tc-region", self.user.region.as_str()).unwrap_or_default();
213 map.insert("x-tc-timestamp", &self.timestamp).unwrap_or_default();
214 map
215 }
216}