tencent_sdk/services/ssl/
upload.rs

1use crate::{
2    client::{TencentCloudAsync, TencentCloudBlocking},
3    core::{Endpoint, TencentCloudResult},
4};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::borrow::Cow;
8
9#[derive(Debug, Deserialize)]
10pub struct UploadCertificateResponse {
11    #[serde(rename = "Response")]
12    pub response: UploadCertificateResult,
13}
14
15#[derive(Debug, Deserialize)]
16pub struct UploadCertificateResult {
17    #[serde(rename = "CertificateId")]
18    pub certificate_id: Option<String>,
19    #[serde(rename = "RepeatCertId")]
20    pub repeat_cert_id: Option<String>,
21    #[serde(rename = "RequestId")]
22    pub request_id: String,
23}
24
25#[derive(Serialize, Clone, Debug, PartialEq)]
26#[serde(untagged)]
27pub enum CertificateType<'a> {
28    #[serde(rename = "CA")]
29    Ca,
30    #[serde(rename = "SVR")]
31    Svr,
32    Custom(&'a str),
33}
34
35impl<'a> From<&'a str> for CertificateType<'a> {
36    fn from(s: &'a str) -> Self {
37        match s.to_uppercase().as_str() {
38            "CA" => CertificateType::Ca,
39            "SVR" => CertificateType::Svr,
40            _ => CertificateType::Custom(s),
41        }
42    }
43}
44
45impl<'a> CertificateType<'a> {
46    pub fn as_str(&self) -> &str {
47        match self {
48            CertificateType::Ca => "CA",
49            CertificateType::Svr => "SVR",
50            CertificateType::Custom(value) => value,
51        }
52    }
53}
54
55#[derive(Serialize, Clone, Debug, PartialEq)]
56#[serde(untagged)]
57pub enum CertificateUse<'a> {
58    #[serde(rename = "CLB")]
59    Clb,
60    #[serde(rename = "CDN")]
61    Cdn,
62    #[serde(rename = "WAF")]
63    Waf,
64    #[serde(rename = "LIVE")]
65    Live,
66    #[serde(rename = "DDOS")]
67    Ddos,
68    Custom(&'a str),
69}
70
71impl<'a> From<&'a str> for CertificateUse<'a> {
72    fn from(s: &'a str) -> Self {
73        match s.to_uppercase().as_str() {
74            "CLB" => CertificateUse::Clb,
75            "CDN" => CertificateUse::Cdn,
76            "WAF" => CertificateUse::Waf,
77            "LIVE" => CertificateUse::Live,
78            "DDOS" => CertificateUse::Ddos,
79            _ => CertificateUse::Custom(s),
80        }
81    }
82}
83
84impl<'a> CertificateUse<'a> {
85    pub fn as_str(&self) -> &str {
86        match self {
87            CertificateUse::Clb => "CLB",
88            CertificateUse::Cdn => "CDN",
89            CertificateUse::Waf => "WAF",
90            CertificateUse::Live => "LIVE",
91            CertificateUse::Ddos => "DDOS",
92            CertificateUse::Custom(value) => value,
93        }
94    }
95}
96
97#[derive(Debug, Serialize)]
98pub struct Tag<'a> {
99    #[serde(rename = "TagKey")]
100    pub key: &'a str,
101    #[serde(rename = "TagValue")]
102    pub value: &'a str,
103}
104
105/// Request payload for `UploadCertificate`.
106pub struct UploadCertificate<'a> {
107    pub region: Option<&'a str>,
108    pub certificate_public_key: &'a str,
109    pub certificate_private_key: Option<&'a str>,
110    pub certificate_type: Option<CertificateType<'a>>,
111    pub alias: Option<&'a str>,
112    pub project_id: Option<u64>,
113    pub certificate_use: Option<CertificateUse<'a>>,
114    pub tags: Option<Vec<Tag<'a>>>,
115    pub repeatable: Option<bool>,
116    pub key_password: Option<&'a str>,
117}
118
119impl<'a> UploadCertificate<'a> {
120    /// Create a new upload certificate request with the required public key.
121    pub fn new(certificate_public_key: &'a str) -> Self {
122        Self {
123            region: None,
124            certificate_public_key,
125            certificate_private_key: None,
126            certificate_type: None,
127            alias: None,
128            project_id: None,
129            certificate_use: None,
130            tags: None,
131            repeatable: None,
132            key_password: None,
133        }
134    }
135
136    /// Set the region (not required according to documentation, but kept for consistency).
137    pub fn with_region(mut self, region: &'a str) -> Self {
138        self.region = Some(region);
139        self
140    }
141
142    /// Set the private key (required for SVR certificates).
143    pub fn with_private_key(mut self, private_key: &'a str) -> Self {
144        self.certificate_private_key = Some(private_key);
145        self
146    }
147
148    /// Set the certificate type (CA or SVR).
149    pub fn with_certificate_type(mut self, cert_type: &'a str) -> Self {
150        self.certificate_type = Some(cert_type.into());
151        self
152    }
153
154    /// Set an alias/name for the certificate.
155    pub fn with_alias(mut self, alias: &'a str) -> Self {
156        self.alias = Some(alias);
157        self
158    }
159
160    /// Set the project ID.
161    pub fn with_project_id(mut self, project_id: u64) -> Self {
162        self.project_id = Some(project_id);
163        self
164    }
165
166    /// Set the certificate use/source (CLB, CDN, WAF, LIVE, DDOS).
167    pub fn with_certificate_use(mut self, cert_use: &'a str) -> Self {
168        self.certificate_use = Some(cert_use.into());
169        self
170    }
171
172    /// Add a tag to the certificate.
173    pub fn with_tag(mut self, key: &'a str, value: &'a str) -> Self {
174        let tag = Tag { key, value };
175        self.tags.get_or_insert_with(Vec::new).push(tag);
176        self
177    }
178
179    /// Set whether duplicate certificates are allowed.
180    pub fn with_repeatable(mut self, repeatable: bool) -> Self {
181        self.repeatable = Some(repeatable);
182        self
183    }
184
185    /// Set the private key password.
186    pub fn with_key_password(mut self, password: &'a str) -> Self {
187        self.key_password = Some(password);
188        self
189    }
190}
191
192impl<'a> Endpoint for UploadCertificate<'a> {
193    type Output = UploadCertificateResponse;
194
195    fn service(&self) -> Cow<'static, str> {
196        Cow::Borrowed("ssl")
197    }
198
199    fn action(&self) -> Cow<'static, str> {
200        Cow::Borrowed("UploadCertificate")
201    }
202
203    fn version(&self) -> Cow<'static, str> {
204        Cow::Borrowed("2019-12-05")
205    }
206
207    fn region(&self) -> Option<Cow<'_, str>> {
208        self.region.map(Cow::Borrowed)
209    }
210
211    fn payload(&self) -> Value {
212        let mut payload = serde_json::json!({
213            "CertificatePublicKey": self.certificate_public_key,
214        });
215
216        if let Some(private_key) = self.certificate_private_key {
217            payload["CertificatePrivateKey"] = serde_json::json!(private_key);
218        }
219        if let Some(cert_type) = &self.certificate_type {
220            payload["CertificateType"] = serde_json::json!(cert_type.as_str());
221        }
222        if let Some(alias) = self.alias {
223            payload["Alias"] = serde_json::json!(alias);
224        }
225        if let Some(project_id) = self.project_id {
226            payload["ProjectId"] = serde_json::json!(project_id);
227        }
228        if let Some(cert_use) = &self.certificate_use {
229            payload["CertificateUse"] = serde_json::json!(cert_use.as_str());
230        }
231        if let Some(tags) = &self.tags {
232            payload["Tags"] = serde_json::json!(tags);
233        }
234        if let Some(repeatable) = self.repeatable {
235            payload["Repeatable"] = serde_json::json!(repeatable);
236        }
237        if let Some(key_password) = self.key_password {
238            payload["KeyPassword"] = serde_json::json!(key_password);
239        }
240
241        payload
242    }
243}
244
245/// Upload a certificate with the async client.
246pub async fn upload_certificate_async(
247    client: &TencentCloudAsync,
248    request: &UploadCertificate<'_>,
249) -> TencentCloudResult<UploadCertificateResponse> {
250    client.request(request).await
251}
252
253/// Upload a certificate with the blocking client.
254pub fn upload_certificate_blocking(
255    client: &TencentCloudBlocking,
256    request: &UploadCertificate<'_>,
257) -> TencentCloudResult<UploadCertificateResponse> {
258    client.request(request)
259}
260
261#[cfg(test)]
262mod tests {
263
264    use super::*;
265    use serde_json::json;
266
267    #[test]
268    fn upload_certificate_payload_basic() {
269        let public_key = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----";
270        let private_key = "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----";
271
272        let request = UploadCertificate::new(public_key)
273            .with_private_key(private_key)
274            .with_certificate_type("SVR")
275            .with_alias("my-website-cert")
276            .with_project_id(123456)
277            .with_certificate_use("CLB")
278            .with_repeatable(false);
279
280        let payload = request.payload();
281
282        assert!(payload["CertificatePublicKey"]
283            .as_str()
284            .expect("certificate public key should be string")
285            .contains("BEGIN CERTIFICATE"));
286        assert!(payload["CertificatePrivateKey"]
287            .as_str()
288            .expect("certificate private key should be string")
289            .contains("BEGIN RSA PRIVATE KEY"));
290        assert_eq!(payload["CertificateType"], json!("SVR"));
291        assert_eq!(payload["Alias"], json!("my-website-cert"));
292        assert_eq!(payload["ProjectId"], json!(123456));
293        assert_eq!(payload["CertificateUse"], json!("CLB"));
294        assert_eq!(payload["Repeatable"], json!(false));
295    }
296
297    #[test]
298    fn upload_certificate_payload_ca() {
299        let public_key = "-----BEGIN CERTIFICATE-----\nCA_CERT\n-----END CERTIFICATE-----";
300
301        let request = UploadCertificate::new(public_key)
302            .with_certificate_type("CA")
303            .with_alias("root-ca")
304            .with_repeatable(true);
305
306        let payload = request.payload();
307
308        assert!(payload["CertificatePublicKey"]
309            .as_str()
310            .expect("certificate public key should be string")
311            .contains("CA_CERT"));
312        assert_eq!(payload["CertificateType"], json!("CA"));
313        assert_eq!(payload["Alias"], json!("root-ca"));
314        assert_eq!(payload["Repeatable"], json!(true));
315        // CA certificates should not include a private key
316        assert!(payload.get("CertificatePrivateKey").is_none());
317    }
318
319    #[test]
320    fn upload_certificate_with_tags() {
321        let public_key = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----";
322
323        let request = UploadCertificate::new(public_key)
324            .with_tag("environment", "production")
325            .with_tag("application", "web-server")
326            .with_tag("owner", "team-a");
327
328        let payload = request.payload();
329
330        let tags = payload["Tags"].as_array().expect("Tags should be an array");
331        assert_eq!(tags.len(), 3);
332
333        // First tag
334        assert_eq!(tags[0]["TagKey"], json!("environment"));
335        assert_eq!(tags[0]["TagValue"], json!("production"));
336
337        // Second tag
338        assert_eq!(tags[1]["TagKey"], json!("application"));
339        assert_eq!(tags[1]["TagValue"], json!("web-server"));
340    }
341
342    #[test]
343    fn deserialize_upload_certificate_response() {
344        let payload = r#"{
345            "Response": {
346                "CertificateId": "cert-abc123xyz",
347                "RepeatCertId": "cert-duplicate456",
348                "RequestId": "req-789def"
349            }
350        }"#;
351
352        let parsed: UploadCertificateResponse =
353            serde_json::from_str(payload).expect("deserialize UploadCertificateResponse");
354
355        assert_eq!(
356            parsed.response.certificate_id.as_deref(),
357            Some("cert-abc123xyz")
358        );
359        assert_eq!(
360            parsed.response.repeat_cert_id.as_deref(),
361            Some("cert-duplicate456")
362        );
363        assert_eq!(parsed.response.request_id, "req-789def");
364    }
365
366    #[test]
367    fn deserialize_upload_certificate_response_without_duplicate() {
368        let payload = r#"{
369            "Response": {
370                "CertificateId": "cert-new-unique",
371                "RequestId": "req-123abc"
372            }
373        }"#;
374
375        let parsed: UploadCertificateResponse =
376            serde_json::from_str(payload).expect("deserialize UploadCertificateResponse");
377
378        assert_eq!(
379            parsed.response.certificate_id.as_deref(),
380            Some("cert-new-unique")
381        );
382        assert!(parsed.response.repeat_cert_id.is_none());
383        assert_eq!(parsed.response.request_id, "req-123abc");
384    }
385
386    #[test]
387    fn certificate_type_enum_conversion() {
388        let ca: CertificateType = "CA".into();
389        let svr: CertificateType = "SVR".into();
390        let custom: CertificateType = "CUSTOM_TYPE".into();
391
392        assert!(matches!(ca, CertificateType::Ca));
393        assert!(matches!(svr, CertificateType::Svr));
394        assert!(matches!(custom, CertificateType::Custom("CUSTOM_TYPE")));
395    }
396
397    #[test]
398    fn certificate_use_enum_conversion() {
399        let uses = ["CLB", "CDN", "WAF", "LIVE", "DDOS", "CUSTOM"];
400
401        for use_str in uses {
402            let cert_use: CertificateUse = use_str.into();
403
404            match use_str {
405                "CLB" => assert!(matches!(cert_use, CertificateUse::Clb)),
406                "CDN" => assert!(matches!(cert_use, CertificateUse::Cdn)),
407                "WAF" => assert!(matches!(cert_use, CertificateUse::Waf)),
408                "LIVE" => assert!(matches!(cert_use, CertificateUse::Live)),
409                "DDOS" => assert!(matches!(cert_use, CertificateUse::Ddos)),
410                _ => assert!(matches!(cert_use, CertificateUse::Custom("CUSTOM"))),
411            }
412        }
413    }
414
415    #[test]
416    fn certificate_enums_serialize_as_bare_strings() {
417        let custom_type: CertificateType = "any-type".into();
418        let custom_use: CertificateUse = "custom-use".into();
419
420        let payload = serde_json::json!({
421            "CertificateType": custom_type,
422            "CertificateUse": custom_use
423        });
424
425        assert_eq!(payload["CertificateType"], json!("any-type"));
426        assert_eq!(payload["CertificateUse"], json!("custom-use"));
427    }
428}