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