1use crate::{
2 client::{TencentCloudAsync, TencentCloudBlocking},
3 core::{Endpoint, TencentCloudResult},
4 services::{Filter, Tag},
5};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::borrow::Cow;
9use std::collections::HashMap;
10
11pub type TagRef<'a> = Tag<'a>;
13
14#[derive(Debug, Deserialize)]
15pub struct DescribeVpcsResponse {
16 #[serde(rename = "Response")]
17 pub response: DescribeVpcsResult,
18}
19
20#[derive(Debug, Deserialize)]
21pub struct DescribeVpcsResult {
22 #[serde(rename = "TotalCount")]
23 pub total_count: Option<u64>,
24 #[serde(rename = "VpcSet")]
25 #[serde(default)]
26 pub vpc_set: Vec<VpcSummary>,
27 #[serde(rename = "RequestId")]
28 pub request_id: String,
29}
30
31#[derive(Debug, Deserialize)]
32pub struct VpcSummary {
33 #[serde(rename = "VpcId")]
34 pub vpc_id: Option<String>,
35 #[serde(rename = "VpcName")]
36 pub vpc_name: Option<String>,
37 #[serde(rename = "CidrBlock")]
38 pub cidr_block: Option<String>,
39 #[serde(rename = "IsDefault")]
40 pub is_default: Option<bool>,
41 #[serde(rename = "EnableMulticast")]
42 pub enable_multicast: Option<bool>,
43 #[serde(rename = "TagSet")]
44 pub tag_set: Option<Vec<ResourceTag>>,
45 #[serde(rename = "CreatedTime")]
46 pub created_time: Option<String>,
47 #[serde(rename = "VpcIdString")]
48 pub vpc_id_string: Option<String>,
49 #[serde(default)]
50 pub ipv6_cidr_block: Option<String>,
51 #[serde(flatten, default)]
52 pub extra: HashMap<String, Value>,
53}
54
55#[derive(Debug, Deserialize)]
56pub struct ResourceTag {
57 #[serde(rename = "Key")]
58 pub key: Option<String>,
59 #[serde(rename = "Value")]
60 pub value: Option<String>,
61}
62
63#[derive(Serialize)]
64#[serde(rename_all = "PascalCase")]
65struct DescribeVpcsPayload<'a> {
66 #[serde(skip_serializing_if = "Option::is_none")]
67 vpc_ids: Option<&'a [&'a str]>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 filters: Option<&'a [Filter<'a>]>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 limit: Option<u32>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 offset: Option<u32>,
74}
75
76pub struct DescribeVpcs<'a> {
78 pub region: Option<&'a str>,
79 pub filters: Option<Vec<Filter<'a>>>,
80 pub vpc_ids: Option<Vec<&'a str>>,
81 pub limit: Option<u32>,
82 pub offset: Option<u32>,
83}
84
85impl<'a> Default for DescribeVpcs<'a> {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91impl<'a> DescribeVpcs<'a> {
92 pub fn new() -> Self {
94 Self {
95 region: None,
96 filters: None,
97 vpc_ids: None,
98 limit: None,
99 offset: None,
100 }
101 }
102
103 pub fn with_region(mut self, region: &'a str) -> Self {
104 self.region = Some(region);
105 self
106 }
107
108 pub fn push_filter(mut self, filter: Filter<'a>) -> Self {
109 self.filters.get_or_insert_with(Vec::new).push(filter);
110 self
111 }
112
113 pub fn push_vpc_id(mut self, vpc_id: &'a str) -> Self {
114 self.vpc_ids.get_or_insert_with(Vec::new).push(vpc_id);
115 self
116 }
117
118 pub fn with_limit(mut self, limit: u32) -> Self {
119 self.limit = Some(limit);
120 self
121 }
122
123 pub fn with_offset(mut self, offset: u32) -> Self {
124 self.offset = Some(offset);
125 self
126 }
127}
128
129impl<'a> Endpoint for DescribeVpcs<'a> {
130 type Output = DescribeVpcsResponse;
131
132 fn service(&self) -> Cow<'static, str> {
133 Cow::Borrowed("vpc")
134 }
135
136 fn action(&self) -> Cow<'static, str> {
137 Cow::Borrowed("DescribeVpcs")
138 }
139
140 fn version(&self) -> Cow<'static, str> {
141 Cow::Borrowed("2017-03-12")
142 }
143
144 fn region(&self) -> Option<Cow<'_, str>> {
145 self.region.map(Cow::Borrowed)
146 }
147
148 fn payload(&self) -> Value {
149 let payload = DescribeVpcsPayload {
150 vpc_ids: self.vpc_ids.as_deref(),
151 filters: self.filters.as_deref(),
152 limit: self.limit,
153 offset: self.offset,
154 };
155 serde_json::to_value(payload).expect("serialize DescribeVpcs payload")
156 }
157}
158
159#[derive(Debug, Deserialize)]
160pub struct CreateVpcResponse {
161 #[serde(rename = "Response")]
162 pub response: CreateVpcResult,
163}
164
165#[derive(Debug, Deserialize)]
166pub struct CreateVpcResult {
167 #[serde(rename = "Vpc")]
168 pub vpc: Option<VpcSummary>,
169 #[serde(rename = "RequestId")]
170 pub request_id: String,
171}
172
173#[derive(Serialize)]
174#[serde(rename_all = "PascalCase")]
175struct CreateVpcPayload<'a> {
176 vpc_name: &'a str,
177 cidr_block: &'a str,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 enable_multicast: Option<bool>,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 dns_servers: Option<&'a [&'a str]>,
182 #[serde(skip_serializing_if = "Option::is_none")]
183 domain_name: Option<&'a str>,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 tags: Option<&'a [Tag<'a>]>,
186}
187
188pub struct CreateVpc<'a> {
190 pub region: Option<&'a str>,
191 pub vpc_name: &'a str,
192 pub cidr_block: &'a str,
193 pub enable_multicast: Option<bool>,
194 pub dns_servers: Option<Vec<&'a str>>,
195 pub domain_name: Option<&'a str>,
196 pub tags: Option<Vec<Tag<'a>>>,
197}
198
199impl<'a> CreateVpc<'a> {
200 pub fn new(vpc_name: &'a str, cidr_block: &'a str) -> Self {
201 Self {
202 region: None,
203 vpc_name,
204 cidr_block,
205 enable_multicast: None,
206 dns_servers: None,
207 domain_name: None,
208 tags: None,
209 }
210 }
211
212 pub fn with_region(mut self, region: &'a str) -> Self {
213 self.region = Some(region);
214 self
215 }
216
217 pub fn enable_multicast(mut self, enable: bool) -> Self {
218 self.enable_multicast = Some(enable);
219 self
220 }
221
222 pub fn with_dns_servers(mut self, servers: Vec<&'a str>) -> Self {
223 self.dns_servers = Some(servers);
224 self
225 }
226
227 pub fn with_domain_name(mut self, domain: &'a str) -> Self {
228 self.domain_name = Some(domain);
229 self
230 }
231
232 pub fn push_tag(mut self, tag: Tag<'a>) -> Self {
233 self.tags.get_or_insert_with(Vec::new).push(tag);
234 self
235 }
236}
237
238impl<'a> Endpoint for CreateVpc<'a> {
239 type Output = CreateVpcResponse;
240
241 fn service(&self) -> Cow<'static, str> {
242 Cow::Borrowed("vpc")
243 }
244
245 fn action(&self) -> Cow<'static, str> {
246 Cow::Borrowed("CreateVpc")
247 }
248
249 fn version(&self) -> Cow<'static, str> {
250 Cow::Borrowed("2017-03-12")
251 }
252
253 fn region(&self) -> Option<Cow<'_, str>> {
254 self.region.map(Cow::Borrowed)
255 }
256
257 fn payload(&self) -> Value {
258 let payload = CreateVpcPayload {
259 vpc_name: self.vpc_name,
260 cidr_block: self.cidr_block,
261 enable_multicast: self.enable_multicast,
262 dns_servers: self.dns_servers.as_deref(),
263 domain_name: self.domain_name,
264 tags: self.tags.as_deref(),
265 };
266 serde_json::to_value(payload).expect("serialize CreateVpc payload")
267 }
268}
269
270#[derive(Debug, Deserialize)]
271pub struct CreateSubnetResponse {
272 #[serde(rename = "Response")]
273 pub response: CreateSubnetResult,
274}
275
276#[derive(Debug, Deserialize)]
277pub struct CreateSubnetResult {
278 #[serde(rename = "Subnet")]
279 pub subnet: Option<SubnetSummary>,
280 #[serde(rename = "RequestId")]
281 pub request_id: String,
282}
283
284#[derive(Debug, Deserialize)]
285pub struct SubnetSummary {
286 #[serde(rename = "SubnetId")]
287 pub subnet_id: Option<String>,
288 #[serde(rename = "SubnetName")]
289 pub subnet_name: Option<String>,
290 #[serde(rename = "CidrBlock")]
291 pub cidr_block: Option<String>,
292 #[serde(rename = "Zone")]
293 pub zone: Option<String>,
294 #[serde(rename = "IsDefault")]
295 pub is_default: Option<bool>,
296 #[serde(flatten)]
297 pub extra: HashMap<String, Value>,
298}
299
300#[derive(Serialize)]
301#[serde(rename_all = "PascalCase")]
302struct CreateSubnetPayload<'a> {
303 vpc_id: &'a str,
304 subnet_name: &'a str,
305 cidr_block: &'a str,
306 zone: &'a str,
307 #[serde(skip_serializing_if = "Option::is_none")]
308 is_default: Option<bool>,
309 #[serde(skip_serializing_if = "Option::is_none")]
310 tags: Option<&'a [Tag<'a>]>,
311}
312
313pub struct CreateSubnet<'a> {
315 pub region: Option<&'a str>,
316 pub vpc_id: &'a str,
317 pub subnet_name: &'a str,
318 pub cidr_block: &'a str,
319 pub zone: &'a str,
320 pub is_default: Option<bool>,
321 pub tags: Option<Vec<Tag<'a>>>,
322}
323
324impl<'a> CreateSubnet<'a> {
325 pub fn new(vpc_id: &'a str, subnet_name: &'a str, cidr_block: &'a str, zone: &'a str) -> Self {
326 Self {
327 region: None,
328 vpc_id,
329 subnet_name,
330 cidr_block,
331 zone,
332 is_default: None,
333 tags: None,
334 }
335 }
336
337 pub fn with_region(mut self, region: &'a str) -> Self {
338 self.region = Some(region);
339 self
340 }
341
342 pub fn mark_default(mut self, value: bool) -> Self {
343 self.is_default = Some(value);
344 self
345 }
346
347 pub fn push_tag(mut self, tag: Tag<'a>) -> Self {
348 self.tags.get_or_insert_with(Vec::new).push(tag);
349 self
350 }
351}
352
353impl<'a> Endpoint for CreateSubnet<'a> {
354 type Output = CreateSubnetResponse;
355
356 fn service(&self) -> Cow<'static, str> {
357 Cow::Borrowed("vpc")
358 }
359
360 fn action(&self) -> Cow<'static, str> {
361 Cow::Borrowed("CreateSubnet")
362 }
363
364 fn version(&self) -> Cow<'static, str> {
365 Cow::Borrowed("2017-03-12")
366 }
367
368 fn region(&self) -> Option<Cow<'_, str>> {
369 self.region.map(Cow::Borrowed)
370 }
371
372 fn payload(&self) -> Value {
373 let payload = CreateSubnetPayload {
374 vpc_id: self.vpc_id,
375 subnet_name: self.subnet_name,
376 cidr_block: self.cidr_block,
377 zone: self.zone,
378 is_default: self.is_default,
379 tags: self.tags.as_deref(),
380 };
381 serde_json::to_value(payload).expect("serialize CreateSubnet payload")
382 }
383}
384
385#[derive(Debug, Deserialize)]
386pub struct DescribeSubnetsResponse {
387 #[serde(rename = "Response")]
388 pub response: DescribeSubnetsResult,
389}
390
391#[derive(Debug, Deserialize)]
392pub struct DescribeSubnetsResult {
393 #[serde(rename = "TotalCount")]
394 pub total_count: Option<u64>,
395 #[serde(rename = "SubnetSet")]
396 #[serde(default)]
397 pub subnet_set: Vec<SubnetSummary>,
398 #[serde(rename = "RequestId")]
399 pub request_id: String,
400}
401
402#[derive(Serialize)]
403#[serde(rename_all = "PascalCase")]
404struct DescribeSubnetsPayload<'a> {
405 #[serde(skip_serializing_if = "Option::is_none")]
406 filters: Option<&'a [Filter<'a>]>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 subnet_ids: Option<&'a [&'a str]>,
409 #[serde(skip_serializing_if = "Option::is_none")]
410 vpc_id: Option<&'a str>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 limit: Option<u32>,
413 #[serde(skip_serializing_if = "Option::is_none")]
414 offset: Option<u32>,
415}
416
417pub struct DescribeSubnets<'a> {
419 pub region: Option<&'a str>,
420 pub filters: Option<Vec<Filter<'a>>>,
421 pub subnet_ids: Option<Vec<&'a str>>,
422 pub vpc_id: Option<&'a str>,
423 pub limit: Option<u32>,
424 pub offset: Option<u32>,
425}
426
427impl<'a> Default for DescribeSubnets<'a> {
428 fn default() -> Self {
429 Self::new()
430 }
431}
432
433impl<'a> DescribeSubnets<'a> {
434 pub fn new() -> Self {
435 Self {
436 region: None,
437 filters: None,
438 subnet_ids: None,
439 vpc_id: None,
440 limit: None,
441 offset: None,
442 }
443 }
444
445 pub fn with_region(mut self, region: &'a str) -> Self {
446 self.region = Some(region);
447 self
448 }
449
450 pub fn push_filter(mut self, filter: Filter<'a>) -> Self {
451 self.filters.get_or_insert_with(Vec::new).push(filter);
452 self
453 }
454
455 pub fn push_subnet_id(mut self, subnet_id: &'a str) -> Self {
456 self.subnet_ids.get_or_insert_with(Vec::new).push(subnet_id);
457 self
458 }
459
460 pub fn with_vpc_id(mut self, vpc_id: &'a str) -> Self {
461 self.vpc_id = Some(vpc_id);
462 self
463 }
464
465 pub fn with_limit(mut self, limit: u32) -> Self {
466 self.limit = Some(limit);
467 self
468 }
469
470 pub fn with_offset(mut self, offset: u32) -> Self {
471 self.offset = Some(offset);
472 self
473 }
474}
475
476impl<'a> Endpoint for DescribeSubnets<'a> {
477 type Output = DescribeSubnetsResponse;
478
479 fn service(&self) -> Cow<'static, str> {
480 Cow::Borrowed("vpc")
481 }
482
483 fn action(&self) -> Cow<'static, str> {
484 Cow::Borrowed("DescribeSubnets")
485 }
486
487 fn version(&self) -> Cow<'static, str> {
488 Cow::Borrowed("2017-03-12")
489 }
490
491 fn region(&self) -> Option<Cow<'_, str>> {
492 self.region.map(Cow::Borrowed)
493 }
494
495 fn payload(&self) -> Value {
496 let payload = DescribeSubnetsPayload {
497 filters: self.filters.as_deref(),
498 subnet_ids: self.subnet_ids.as_deref(),
499 vpc_id: self.vpc_id,
500 limit: self.limit,
501 offset: self.offset,
502 };
503 serde_json::to_value(payload).expect("serialize DescribeSubnets payload")
504 }
505}
506
507pub async fn describe_vpcs_async(
509 client: &TencentCloudAsync,
510 request: &DescribeVpcs<'_>,
511) -> TencentCloudResult<DescribeVpcsResponse> {
512 client.request(request).await
513}
514
515pub fn describe_vpcs_blocking(
517 client: &TencentCloudBlocking,
518 request: &DescribeVpcs<'_>,
519) -> TencentCloudResult<DescribeVpcsResponse> {
520 client.request(request)
521}
522
523pub async fn create_vpc_async(
525 client: &TencentCloudAsync,
526 request: &CreateVpc<'_>,
527) -> TencentCloudResult<CreateVpcResponse> {
528 client.request(request).await
529}
530
531pub fn create_vpc_blocking(
533 client: &TencentCloudBlocking,
534 request: &CreateVpc<'_>,
535) -> TencentCloudResult<CreateVpcResponse> {
536 client.request(request)
537}
538
539pub async fn create_subnet_async(
541 client: &TencentCloudAsync,
542 request: &CreateSubnet<'_>,
543) -> TencentCloudResult<CreateSubnetResponse> {
544 client.request(request).await
545}
546
547pub fn create_subnet_blocking(
549 client: &TencentCloudBlocking,
550 request: &CreateSubnet<'_>,
551) -> TencentCloudResult<CreateSubnetResponse> {
552 client.request(request)
553}
554
555pub async fn describe_subnets_async(
557 client: &TencentCloudAsync,
558 request: &DescribeSubnets<'_>,
559) -> TencentCloudResult<DescribeSubnetsResponse> {
560 client.request(request).await
561}
562
563pub fn describe_subnets_blocking(
565 client: &TencentCloudBlocking,
566 request: &DescribeSubnets<'_>,
567) -> TencentCloudResult<DescribeSubnetsResponse> {
568 client.request(request)
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::services::Filter;
575
576 #[test]
577 fn describe_vpcs_payload_contains_ids_and_filters() {
578 let filters = vec![Filter::new("vpc-name", ["prod"])];
579 let request = DescribeVpcs {
580 region: Some("ap-shanghai"),
581 filters: Some(filters.clone()),
582 vpc_ids: Some(vec!["vpc-123", "vpc-456"]),
583 limit: Some(50),
584 offset: Some(10),
585 };
586
587 let payload = request.payload();
588 assert_eq!(payload["VpcIds"], serde_json::json!(["vpc-123", "vpc-456"]));
589 assert_eq!(payload["Filters"][0]["Name"], serde_json::json!("vpc-name"));
590 assert_eq!(payload["Filters"][0]["Values"], serde_json::json!(["prod"]));
591 assert_eq!(payload["Limit"], serde_json::json!(50));
592 assert_eq!(payload["Offset"], serde_json::json!(10));
593 }
594
595 #[test]
596 fn create_vpc_payload_includes_options() {
597 let request = CreateVpc {
598 region: Some("ap-guangzhou"),
599 vpc_name: "demo",
600 cidr_block: "10.0.0.0/16",
601 enable_multicast: Some(true),
602 dns_servers: Some(vec!["1.1.1.1", "8.8.8.8"]),
603 domain_name: Some("local"),
604 tags: Some(vec![Tag::new("env", "test")]),
605 };
606
607 let payload = request.payload();
608 assert_eq!(payload["VpcName"], serde_json::json!("demo"));
609 assert_eq!(payload["CidrBlock"], serde_json::json!("10.0.0.0/16"));
610 assert_eq!(payload["EnableMulticast"], serde_json::json!(true));
611 assert_eq!(
612 payload["DnsServers"],
613 serde_json::json!(["1.1.1.1", "8.8.8.8"])
614 );
615 assert_eq!(payload["DomainName"], serde_json::json!("local"));
616 assert_eq!(
617 payload["Tags"],
618 serde_json::json!([{"Key": "env", "Value": "test"}])
619 );
620 }
621
622 #[test]
623 fn create_subnet_payload_includes_tags() {
624 let request = CreateSubnet {
625 region: Some("ap-beijing"),
626 vpc_id: "vpc-123",
627 subnet_name: "subnet-1",
628 cidr_block: "10.0.1.0/24",
629 zone: "ap-beijing-1",
630 is_default: Some(false),
631 tags: Some(vec![Tag::new("team", "core")]),
632 };
633
634 let payload = request.payload();
635 assert_eq!(payload["VpcId"], serde_json::json!("vpc-123"));
636 assert_eq!(payload["SubnetName"], serde_json::json!("subnet-1"));
637 assert_eq!(payload["CidrBlock"], serde_json::json!("10.0.1.0/24"));
638 assert_eq!(payload["Zone"], serde_json::json!("ap-beijing-1"));
639 assert_eq!(payload["IsDefault"], serde_json::json!(false));
640 assert_eq!(
641 payload["Tags"],
642 serde_json::json!([{"Key": "team", "Value": "core"}])
643 );
644 }
645
646 #[test]
647 fn describe_vpcs_builder_accumulates_filters() {
648 let request = DescribeVpcs::new()
649 .with_region("ap-beijing")
650 .push_filter(Filter::new("vpc-name", ["prod"]))
651 .push_vpc_id("vpc-123")
652 .with_limit(30);
653
654 assert_eq!(request.region, Some("ap-beijing"));
655 assert_eq!(request.limit, Some(30));
656 let filters = request.filters.as_ref().expect("filters set");
657 assert_eq!(filters[0].name, "vpc-name");
658 assert_eq!(filters[0].values[0], "prod");
659 assert_eq!(request.vpc_ids.as_ref().unwrap()[0], "vpc-123");
660 }
661
662 #[test]
663 fn deserialize_create_vpc_response() {
664 let payload = r#"{
665 "Response": {
666 "Vpc": { "VpcId": "vpc-abc" },
667 "RequestId": "req-123"
668 }
669 }"#;
670 let parsed: CreateVpcResponse = serde_json::from_str(payload).unwrap();
671 assert_eq!(parsed.response.request_id, "req-123");
672 assert_eq!(
673 parsed.response.vpc.unwrap().vpc_id.as_deref(),
674 Some("vpc-abc")
675 );
676 }
677
678 #[test]
679 fn deserialize_create_subnet_response() {
680 let payload = r#"{
681 "Response": {
682 "Subnet": { "SubnetId": "subnet-xyz" },
683 "RequestId": "req-456"
684 }
685 }"#;
686 let parsed: CreateSubnetResponse = serde_json::from_str(payload).unwrap();
687 assert_eq!(parsed.response.request_id, "req-456");
688 assert_eq!(
689 parsed.response.subnet.unwrap().subnet_id.as_deref(),
690 Some("subnet-xyz")
691 );
692 }
693
694 #[test]
695 fn describe_subnets_payload_contains_vpc_id() {
696 let request = DescribeSubnets {
697 region: Some("ap-hongkong"),
698 filters: None,
699 subnet_ids: Some(vec!["subnet-aaa"]),
700 vpc_id: Some("vpc-zzz"),
701 limit: None,
702 offset: None,
703 };
704
705 let payload = request.payload();
706 assert_eq!(payload["SubnetIds"], serde_json::json!(["subnet-aaa"]));
707 assert_eq!(payload["VpcId"], serde_json::json!("vpc-zzz"));
708 }
709
710 #[test]
711 fn describe_subnets_builder_eases_filters() {
712 let request = DescribeSubnets::new()
713 .with_region("ap-hongkong")
714 .push_filter(Filter::new("subnet-name", ["blue"]))
715 .push_subnet_id("subnet-aaa")
716 .with_vpc_id("vpc-zzz");
717
718 assert_eq!(request.region, Some("ap-hongkong"));
719 let filters = request.filters.as_ref().expect("filters set");
720 assert_eq!(filters[0].name, "subnet-name");
721 assert_eq!(request.subnet_ids.as_ref().unwrap()[0], "subnet-aaa");
722 assert_eq!(request.vpc_id, Some("vpc-zzz"));
723 }
724}