tencent_sdk/types/
cvm.rs

1use crate::{
2    Error,
3    client::endpoint::Endpoint,
4    types::{Filter, ImageId, InstanceId, Region, RequestId, SecurityGroupId, SubnetId, VpcId},
5};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10#[derive(Debug, Deserialize)]
11pub struct DescribeInstancesResponse {
12    #[serde(rename = "Response")]
13    pub response: DescribeInstancesResult,
14}
15
16#[derive(Debug, Deserialize)]
17pub struct DescribeInstancesResult {
18    #[serde(rename = "TotalCount")]
19    pub total_count: Option<u64>,
20    #[serde(rename = "InstanceSet")]
21    #[serde(default)]
22    pub instance_set: Vec<InstanceSummary>,
23    #[serde(rename = "RequestId")]
24    pub request_id: RequestId,
25}
26
27#[derive(Debug, Deserialize)]
28pub struct InstanceSummary {
29    #[serde(rename = "InstanceId")]
30    pub instance_id: Option<InstanceId>,
31    #[serde(rename = "InstanceName")]
32    pub instance_name: Option<String>,
33    #[serde(rename = "InstanceState")]
34    pub instance_state: Option<String>,
35    #[serde(rename = "InstanceType")]
36    pub instance_type: Option<String>,
37    #[serde(rename = "Cpu")]
38    pub cpu: Option<u64>,
39    #[serde(rename = "Memory")]
40    pub memory: Option<u64>,
41    #[serde(rename = "PrivateIpAddresses")]
42    pub private_ip_addresses: Option<Vec<String>>,
43    #[serde(rename = "PublicIpAddresses")]
44    pub public_ip_addresses: Option<Vec<String>>,
45    #[serde(default)]
46    pub placement: Option<InstancePlacement>,
47    #[serde(default)]
48    pub system_disk: Option<DiskSummary>,
49    #[serde(default)]
50    pub data_disks: Option<Vec<DiskSummary>>,
51    #[serde(flatten, default)]
52    pub extra: HashMap<String, Value>,
53}
54
55#[derive(Debug, Deserialize)]
56pub struct InstancePlacement {
57    #[serde(rename = "Zone")]
58    pub zone: Option<String>,
59    #[serde(rename = "ProjectId")]
60    pub project_id: Option<i64>,
61    #[serde(flatten, default)]
62    pub extra: HashMap<String, Value>,
63}
64
65#[derive(Debug, Deserialize)]
66pub struct DiskSummary {
67    #[serde(rename = "DiskType")]
68    pub disk_type: Option<String>,
69    #[serde(rename = "DiskSize")]
70    pub disk_size: Option<u64>,
71    #[serde(flatten, default)]
72    pub extra: HashMap<String, Value>,
73}
74
75#[derive(Serialize)]
76#[serde(rename_all = "PascalCase")]
77struct DescribeInstancesPayload<'a> {
78    #[serde(skip_serializing_if = "Option::is_none")]
79    filters: Option<&'a [Filter]>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    limit: Option<u32>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    offset: Option<u32>,
84}
85
86pub struct DescribeInstancesRequest {
87    region: Option<Region>,
88    filters: Vec<Filter>,
89    limit: Option<u32>,
90    offset: Option<u32>,
91}
92
93impl Default for DescribeInstancesRequest {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl DescribeInstancesRequest {
100    pub fn new() -> Self {
101        Self {
102            region: None,
103            filters: Vec::new(),
104            limit: None,
105            offset: None,
106        }
107    }
108
109    pub fn region(mut self, region: impl Into<Region>) -> Self {
110        self.region = Some(region.into());
111        self
112    }
113
114    pub fn push_filter(mut self, filter: Filter) -> Self {
115        self.filters.push(filter);
116        self
117    }
118
119    pub fn limit(mut self, limit: u32) -> Self {
120        self.limit = Some(limit);
121        self
122    }
123
124    pub fn offset(mut self, offset: u32) -> Self {
125        self.offset = Some(offset);
126        self
127    }
128}
129
130impl Endpoint for DescribeInstancesRequest {
131    type Output = DescribeInstancesResponse;
132
133    fn service(&self) -> &'static str {
134        "cvm"
135    }
136
137    fn action(&self) -> &'static str {
138        "DescribeInstances"
139    }
140
141    fn version(&self) -> &'static str {
142        "2017-03-12"
143    }
144
145    fn region(&self) -> Option<&Region> {
146        self.region.as_ref()
147    }
148
149    fn payload(&self) -> Result<Option<Value>, Error> {
150        let filters = (!self.filters.is_empty()).then_some(self.filters.as_slice());
151        let payload = DescribeInstancesPayload {
152            filters,
153            limit: self.limit,
154            offset: self.offset,
155        };
156        let value = serde_json::to_value(payload).map_err(|source| {
157            Error::invalid_request_with_source(
158                "failed to serialize DescribeInstances request payload",
159                Box::new(source),
160            )
161        })?;
162        Ok(Some(value))
163    }
164}
165
166#[derive(Debug, Deserialize)]
167pub struct GenericActionResponse {
168    #[serde(rename = "Response")]
169    pub response: GenericActionResult,
170}
171
172#[derive(Debug, Deserialize)]
173pub struct GenericActionResult {
174    #[serde(rename = "RequestId")]
175    pub request_id: RequestId,
176}
177
178pub struct ResetInstancesPasswordRequest {
179    region: Region,
180    instance_ids: Vec<InstanceId>,
181    password: String,
182    username: Option<String>,
183    force_stop: Option<bool>,
184}
185
186impl ResetInstancesPasswordRequest {
187    pub fn new(
188        region: impl Into<Region>,
189        instance_ids: impl IntoIterator<Item = impl Into<InstanceId>>,
190        password: impl Into<String>,
191    ) -> Self {
192        Self {
193            region: region.into(),
194            instance_ids: instance_ids.into_iter().map(Into::into).collect(),
195            password: password.into(),
196            username: None,
197            force_stop: None,
198        }
199    }
200
201    pub fn username(mut self, username: impl Into<String>) -> Self {
202        self.username = Some(username.into());
203        self
204    }
205
206    pub fn force_stop(mut self, enabled: bool) -> Self {
207        self.force_stop = Some(enabled);
208        self
209    }
210}
211
212#[derive(Serialize)]
213#[serde(rename_all = "PascalCase")]
214struct ResetInstancesPasswordPayload<'a> {
215    instance_ids: &'a [InstanceId],
216    password: &'a str,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    user_name: Option<&'a str>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    force_stop: Option<bool>,
221}
222
223impl Endpoint for ResetInstancesPasswordRequest {
224    type Output = GenericActionResponse;
225
226    fn service(&self) -> &'static str {
227        "cvm"
228    }
229
230    fn action(&self) -> &'static str {
231        "ResetInstancesPassword"
232    }
233
234    fn version(&self) -> &'static str {
235        "2017-03-12"
236    }
237
238    fn region(&self) -> Option<&Region> {
239        Some(&self.region)
240    }
241
242    fn payload(&self) -> Result<Option<Value>, Error> {
243        let payload = ResetInstancesPasswordPayload {
244            instance_ids: &self.instance_ids,
245            password: &self.password,
246            user_name: self.username.as_deref(),
247            force_stop: self.force_stop,
248        };
249        let value = serde_json::to_value(payload).map_err(|source| {
250            Error::invalid_request_with_source(
251                "failed to serialize ResetInstancesPassword request payload",
252                Box::new(source),
253            )
254        })?;
255        Ok(Some(value))
256    }
257}
258
259#[derive(Debug, Deserialize)]
260pub struct DescribeInstanceVncUrlResponse {
261    #[serde(rename = "Response")]
262    pub response: DescribeInstanceVncUrlResult,
263}
264
265#[derive(Debug, Deserialize)]
266pub struct DescribeInstanceVncUrlResult {
267    #[serde(rename = "InstanceVncUrl")]
268    pub instance_vnc_url: Option<String>,
269    #[serde(rename = "RequestId")]
270    pub request_id: RequestId,
271}
272
273pub struct DescribeInstanceVncUrlRequest {
274    region: Region,
275    instance_id: InstanceId,
276}
277
278impl DescribeInstanceVncUrlRequest {
279    pub fn new(region: impl Into<Region>, instance_id: impl Into<InstanceId>) -> Self {
280        Self {
281            region: region.into(),
282            instance_id: instance_id.into(),
283        }
284    }
285}
286
287#[derive(Serialize)]
288#[serde(rename_all = "PascalCase")]
289struct DescribeInstanceVncUrlPayload<'a> {
290    instance_id: &'a InstanceId,
291}
292
293impl Endpoint for DescribeInstanceVncUrlRequest {
294    type Output = DescribeInstanceVncUrlResponse;
295
296    fn service(&self) -> &'static str {
297        "cvm"
298    }
299
300    fn action(&self) -> &'static str {
301        "DescribeInstanceVncUrl"
302    }
303
304    fn version(&self) -> &'static str {
305        "2017-03-12"
306    }
307
308    fn region(&self) -> Option<&Region> {
309        Some(&self.region)
310    }
311
312    fn payload(&self) -> Result<Option<Value>, Error> {
313        let payload = DescribeInstanceVncUrlPayload {
314            instance_id: &self.instance_id,
315        };
316        let value = serde_json::to_value(payload).map_err(|source| {
317            Error::invalid_request_with_source(
318                "failed to serialize DescribeInstanceVncUrl request payload",
319                Box::new(source),
320            )
321        })?;
322        Ok(Some(value))
323    }
324}
325
326#[derive(Debug, Deserialize)]
327pub struct RunInstancesResponse {
328    #[serde(rename = "Response")]
329    pub response: RunInstancesResult,
330}
331
332#[derive(Debug, Deserialize)]
333pub struct RunInstancesResult {
334    #[serde(rename = "InstanceIdSet")]
335    pub instance_id_set: Option<Vec<InstanceId>>,
336    #[serde(rename = "RequestId")]
337    pub request_id: RequestId,
338}
339
340#[derive(Serialize)]
341#[serde(rename_all = "PascalCase")]
342struct RunInstancesPayload<'a> {
343    image_id: &'a ImageId,
344    instance_type: &'a str,
345    #[serde(skip_serializing_if = "Option::is_none")]
346    instance_name: Option<&'a str>,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    instance_count: Option<u32>,
349    #[serde(skip_serializing_if = "Option::is_none")]
350    client_token: Option<&'a str>,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    subnet_id: Option<&'a SubnetId>,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    vpc_id: Option<&'a VpcId>,
355    #[serde(skip_serializing_if = "Option::is_none")]
356    security_group_ids: Option<&'a [SecurityGroupId]>,
357}
358
359pub struct RunInstancesRequest {
360    region: Region,
361    image_id: ImageId,
362    instance_type: String,
363    instance_name: Option<String>,
364    instance_count: Option<u32>,
365    client_token: Option<String>,
366    subnet_id: Option<SubnetId>,
367    vpc_id: Option<VpcId>,
368    security_group_ids: Vec<SecurityGroupId>,
369}
370
371impl RunInstancesRequest {
372    pub fn new(
373        region: impl Into<Region>,
374        image_id: impl Into<ImageId>,
375        instance_type: impl Into<String>,
376    ) -> Self {
377        Self {
378            region: region.into(),
379            image_id: image_id.into(),
380            instance_type: instance_type.into(),
381            instance_name: None,
382            instance_count: None,
383            client_token: None,
384            subnet_id: None,
385            vpc_id: None,
386            security_group_ids: Vec::new(),
387        }
388    }
389
390    pub fn instance_name(mut self, name: impl Into<String>) -> Self {
391        self.instance_name = Some(name.into());
392        self
393    }
394
395    pub fn instance_count(mut self, count: u32) -> Self {
396        self.instance_count = Some(count);
397        self
398    }
399
400    pub fn client_token(mut self, token: impl Into<String>) -> Self {
401        self.client_token = Some(token.into());
402        self
403    }
404
405    pub fn subnet_id(mut self, subnet_id: impl Into<SubnetId>) -> Self {
406        self.subnet_id = Some(subnet_id.into());
407        self
408    }
409
410    pub fn vpc_id(mut self, vpc_id: impl Into<VpcId>) -> Self {
411        self.vpc_id = Some(vpc_id.into());
412        self
413    }
414
415    pub fn security_group_ids<I, S>(mut self, groups: I) -> Self
416    where
417        I: IntoIterator<Item = S>,
418        S: Into<SecurityGroupId>,
419    {
420        self.security_group_ids = groups.into_iter().map(Into::into).collect();
421        self
422    }
423}
424
425impl Endpoint for RunInstancesRequest {
426    type Output = RunInstancesResponse;
427
428    fn service(&self) -> &'static str {
429        "cvm"
430    }
431
432    fn action(&self) -> &'static str {
433        "RunInstances"
434    }
435
436    fn version(&self) -> &'static str {
437        "2017-03-12"
438    }
439
440    fn region(&self) -> Option<&Region> {
441        Some(&self.region)
442    }
443
444    fn payload(&self) -> Result<Option<Value>, Error> {
445        let security_group_ids =
446            (!self.security_group_ids.is_empty()).then_some(self.security_group_ids.as_slice());
447
448        let payload = RunInstancesPayload {
449            image_id: &self.image_id,
450            instance_type: &self.instance_type,
451            instance_name: self.instance_name.as_deref(),
452            instance_count: self.instance_count,
453            client_token: self.client_token.as_deref(),
454            subnet_id: self.subnet_id.as_ref(),
455            vpc_id: self.vpc_id.as_ref(),
456            security_group_ids,
457        };
458
459        let value = serde_json::to_value(payload).map_err(|source| {
460            Error::invalid_request_with_source(
461                "failed to serialize RunInstances request payload",
462                Box::new(source),
463            )
464        })?;
465        Ok(Some(value))
466    }
467}
468
469pub struct StartInstancesRequest {
470    region: Region,
471    instance_ids: Vec<InstanceId>,
472}
473
474impl StartInstancesRequest {
475    pub fn new(
476        region: impl Into<Region>,
477        instance_ids: impl IntoIterator<Item = impl Into<InstanceId>>,
478    ) -> Self {
479        Self {
480            region: region.into(),
481            instance_ids: instance_ids.into_iter().map(Into::into).collect(),
482        }
483    }
484}
485
486#[derive(Serialize)]
487#[serde(rename_all = "PascalCase")]
488struct InstanceIdsPayload<'a> {
489    instance_ids: &'a [InstanceId],
490}
491
492impl Endpoint for StartInstancesRequest {
493    type Output = GenericActionResponse;
494
495    fn service(&self) -> &'static str {
496        "cvm"
497    }
498
499    fn action(&self) -> &'static str {
500        "StartInstances"
501    }
502
503    fn version(&self) -> &'static str {
504        "2017-03-12"
505    }
506
507    fn region(&self) -> Option<&Region> {
508        Some(&self.region)
509    }
510
511    fn payload(&self) -> Result<Option<Value>, Error> {
512        let payload = InstanceIdsPayload {
513            instance_ids: &self.instance_ids,
514        };
515        let value = serde_json::to_value(payload).map_err(|source| {
516            Error::invalid_request_with_source(
517                "failed to serialize StartInstances request payload",
518                Box::new(source),
519            )
520        })?;
521        Ok(Some(value))
522    }
523}
524
525pub struct RebootInstancesRequest {
526    region: Region,
527    instance_ids: Vec<InstanceId>,
528    force_reboot: Option<bool>,
529}
530
531impl RebootInstancesRequest {
532    pub fn new(
533        region: impl Into<Region>,
534        instance_ids: impl IntoIterator<Item = impl Into<InstanceId>>,
535    ) -> Self {
536        Self {
537            region: region.into(),
538            instance_ids: instance_ids.into_iter().map(Into::into).collect(),
539            force_reboot: None,
540        }
541    }
542
543    pub fn force_reboot(mut self, enabled: bool) -> Self {
544        self.force_reboot = Some(enabled);
545        self
546    }
547}
548
549#[derive(Serialize)]
550#[serde(rename_all = "PascalCase")]
551struct RebootInstancesPayload<'a> {
552    instance_ids: &'a [InstanceId],
553    #[serde(skip_serializing_if = "Option::is_none")]
554    force_reboot: Option<bool>,
555}
556
557impl Endpoint for RebootInstancesRequest {
558    type Output = GenericActionResponse;
559
560    fn service(&self) -> &'static str {
561        "cvm"
562    }
563
564    fn action(&self) -> &'static str {
565        "RebootInstances"
566    }
567
568    fn version(&self) -> &'static str {
569        "2017-03-12"
570    }
571
572    fn region(&self) -> Option<&Region> {
573        Some(&self.region)
574    }
575
576    fn payload(&self) -> Result<Option<Value>, Error> {
577        let payload = RebootInstancesPayload {
578            instance_ids: &self.instance_ids,
579            force_reboot: self.force_reboot,
580        };
581        let value = serde_json::to_value(payload).map_err(|source| {
582            Error::invalid_request_with_source(
583                "failed to serialize RebootInstances request payload",
584                Box::new(source),
585            )
586        })?;
587        Ok(Some(value))
588    }
589}
590
591pub struct StopInstancesRequest {
592    region: Region,
593    instance_ids: Vec<InstanceId>,
594    stop_type: Option<String>,
595}
596
597impl StopInstancesRequest {
598    pub fn new(
599        region: impl Into<Region>,
600        instance_ids: impl IntoIterator<Item = impl Into<InstanceId>>,
601    ) -> Self {
602        Self {
603            region: region.into(),
604            instance_ids: instance_ids.into_iter().map(Into::into).collect(),
605            stop_type: None,
606        }
607    }
608
609    pub fn stop_type(mut self, stop_type: impl Into<String>) -> Self {
610        self.stop_type = Some(stop_type.into());
611        self
612    }
613}
614
615#[derive(Serialize)]
616#[serde(rename_all = "PascalCase")]
617struct StopInstancesPayload<'a> {
618    instance_ids: &'a [InstanceId],
619    #[serde(skip_serializing_if = "Option::is_none")]
620    stop_type: Option<&'a str>,
621}
622
623impl Endpoint for StopInstancesRequest {
624    type Output = GenericActionResponse;
625
626    fn service(&self) -> &'static str {
627        "cvm"
628    }
629
630    fn action(&self) -> &'static str {
631        "StopInstances"
632    }
633
634    fn version(&self) -> &'static str {
635        "2017-03-12"
636    }
637
638    fn region(&self) -> Option<&Region> {
639        Some(&self.region)
640    }
641
642    fn payload(&self) -> Result<Option<Value>, Error> {
643        let payload = StopInstancesPayload {
644            instance_ids: &self.instance_ids,
645            stop_type: self.stop_type.as_deref(),
646        };
647        let value = serde_json::to_value(payload).map_err(|source| {
648            Error::invalid_request_with_source(
649                "failed to serialize StopInstances request payload",
650                Box::new(source),
651            )
652        })?;
653        Ok(Some(value))
654    }
655}
656
657pub struct ModifyInstancesProjectRequest {
658    region: Region,
659    instance_ids: Vec<InstanceId>,
660    project_id: u64,
661}
662
663impl ModifyInstancesProjectRequest {
664    pub fn new(
665        region: impl Into<Region>,
666        instance_ids: impl IntoIterator<Item = impl Into<InstanceId>>,
667        project_id: u64,
668    ) -> Self {
669        Self {
670            region: region.into(),
671            instance_ids: instance_ids.into_iter().map(Into::into).collect(),
672            project_id,
673        }
674    }
675}
676
677#[derive(Serialize)]
678#[serde(rename_all = "PascalCase")]
679struct ModifyInstancesProjectPayload<'a> {
680    instance_ids: &'a [InstanceId],
681    project_id: u64,
682}
683
684impl Endpoint for ModifyInstancesProjectRequest {
685    type Output = GenericActionResponse;
686
687    fn service(&self) -> &'static str {
688        "cvm"
689    }
690
691    fn action(&self) -> &'static str {
692        "ModifyInstancesProject"
693    }
694
695    fn version(&self) -> &'static str {
696        "2017-03-12"
697    }
698
699    fn region(&self) -> Option<&Region> {
700        Some(&self.region)
701    }
702
703    fn payload(&self) -> Result<Option<Value>, Error> {
704        let payload = ModifyInstancesProjectPayload {
705            instance_ids: &self.instance_ids,
706            project_id: self.project_id,
707        };
708        let value = serde_json::to_value(payload).map_err(|source| {
709            Error::invalid_request_with_source(
710                "failed to serialize ModifyInstancesProject request payload",
711                Box::new(source),
712            )
713        })?;
714        Ok(Some(value))
715    }
716}
717
718pub struct TerminateInstancesRequest {
719    region: Region,
720    instance_ids: Vec<InstanceId>,
721}
722
723impl TerminateInstancesRequest {
724    pub fn new(
725        region: impl Into<Region>,
726        instance_ids: impl IntoIterator<Item = impl Into<InstanceId>>,
727    ) -> Self {
728        Self {
729            region: region.into(),
730            instance_ids: instance_ids.into_iter().map(Into::into).collect(),
731        }
732    }
733}
734
735impl Endpoint for TerminateInstancesRequest {
736    type Output = GenericActionResponse;
737
738    fn service(&self) -> &'static str {
739        "cvm"
740    }
741
742    fn action(&self) -> &'static str {
743        "TerminateInstances"
744    }
745
746    fn version(&self) -> &'static str {
747        "2017-03-12"
748    }
749
750    fn region(&self) -> Option<&Region> {
751        Some(&self.region)
752    }
753
754    fn payload(&self) -> Result<Option<Value>, Error> {
755        let payload = InstanceIdsPayload {
756            instance_ids: &self.instance_ids,
757        };
758        let value = serde_json::to_value(payload).map_err(|source| {
759            Error::invalid_request_with_source(
760                "failed to serialize TerminateInstances request payload",
761                Box::new(source),
762            )
763        })?;
764        Ok(Some(value))
765    }
766}
767
768#[derive(Debug, Deserialize)]
769pub struct DescribeImagesResponse {
770    #[serde(rename = "Response")]
771    pub response: DescribeImagesResult,
772}
773
774#[derive(Debug, Deserialize)]
775pub struct DescribeImagesResult {
776    #[serde(rename = "TotalCount")]
777    pub total_count: Option<u64>,
778    #[serde(rename = "ImageSet")]
779    pub image_set: Vec<ImageSummary>,
780    #[serde(rename = "RequestId")]
781    pub request_id: RequestId,
782}
783
784#[derive(Debug, Deserialize)]
785pub struct ImageSummary {
786    #[serde(rename = "ImageId")]
787    pub image_id: Option<ImageId>,
788    #[serde(rename = "ImageName")]
789    pub image_name: Option<String>,
790    #[serde(rename = "ImageType")]
791    pub image_type: Option<String>,
792    #[serde(rename = "CreatedTime")]
793    pub created_time: Option<String>,
794    #[serde(flatten, default)]
795    pub extra: HashMap<String, Value>,
796}
797
798#[derive(Serialize)]
799#[serde(rename_all = "PascalCase")]
800struct DescribeImagesPayload<'a> {
801    #[serde(skip_serializing_if = "Option::is_none")]
802    image_ids: Option<&'a [ImageId]>,
803    #[serde(skip_serializing_if = "Option::is_none")]
804    filters: Option<&'a [Filter]>,
805    #[serde(skip_serializing_if = "Option::is_none")]
806    limit: Option<u32>,
807    #[serde(skip_serializing_if = "Option::is_none")]
808    offset: Option<u32>,
809}
810
811pub struct DescribeImagesRequest {
812    region: Option<Region>,
813    image_ids: Vec<ImageId>,
814    filters: Vec<Filter>,
815    limit: Option<u32>,
816    offset: Option<u32>,
817}
818
819impl Default for DescribeImagesRequest {
820    fn default() -> Self {
821        Self::new()
822    }
823}
824
825impl DescribeImagesRequest {
826    pub fn new() -> Self {
827        Self {
828            region: None,
829            image_ids: Vec::new(),
830            filters: Vec::new(),
831            limit: None,
832            offset: None,
833        }
834    }
835
836    pub fn region(mut self, region: impl Into<Region>) -> Self {
837        self.region = Some(region.into());
838        self
839    }
840
841    pub fn push_image_id(mut self, id: impl Into<ImageId>) -> Self {
842        self.image_ids.push(id.into());
843        self
844    }
845
846    pub fn push_filter(mut self, filter: Filter) -> Self {
847        self.filters.push(filter);
848        self
849    }
850
851    pub fn limit(mut self, limit: u32) -> Self {
852        self.limit = Some(limit);
853        self
854    }
855
856    pub fn offset(mut self, offset: u32) -> Self {
857        self.offset = Some(offset);
858        self
859    }
860}
861
862impl Endpoint for DescribeImagesRequest {
863    type Output = DescribeImagesResponse;
864
865    fn service(&self) -> &'static str {
866        "cvm"
867    }
868
869    fn action(&self) -> &'static str {
870        "DescribeImages"
871    }
872
873    fn version(&self) -> &'static str {
874        "2017-03-12"
875    }
876
877    fn region(&self) -> Option<&Region> {
878        self.region.as_ref()
879    }
880
881    fn payload(&self) -> Result<Option<Value>, Error> {
882        let image_ids = (!self.image_ids.is_empty()).then_some(self.image_ids.as_slice());
883        let filters = (!self.filters.is_empty()).then_some(self.filters.as_slice());
884        let payload = DescribeImagesPayload {
885            image_ids,
886            filters,
887            limit: self.limit,
888            offset: self.offset,
889        };
890
891        let value = serde_json::to_value(payload).map_err(|source| {
892            Error::invalid_request_with_source(
893                "failed to serialize DescribeImages request payload",
894                Box::new(source),
895            )
896        })?;
897        Ok(Some(value))
898    }
899}
900
901#[cfg(test)]
902mod tests {
903    use super::*;
904    use serde_json::{Value, json};
905
906    #[test]
907    fn describe_instances_payload_supports_filters() {
908        let request = DescribeInstancesRequest::new()
909            .region("ap-shanghai")
910            .limit(20)
911            .offset(0)
912            .push_filter(Filter::new("instance-id", ["ins-123"]))
913            .push_filter(Filter::new("zone", ["ap-shanghai-1"]));
914
915        let payload = request.payload().unwrap().unwrap();
916        assert_eq!(payload["Filters"][0]["Name"], json!("instance-id"));
917        assert_eq!(payload["Filters"][1]["Values"], json!(["ap-shanghai-1"]));
918        assert_eq!(payload["Limit"], json!(20));
919        assert_eq!(payload["Offset"], json!(0));
920    }
921
922    #[test]
923    fn deserialize_generic_action_response() {
924        let payload = r#"{
925            "Response": {
926                "RequestId": "req-abc"
927            }
928        }"#;
929        let parsed: GenericActionResponse = serde_json::from_str(payload).unwrap();
930        assert_eq!(parsed.response.request_id.as_str(), "req-abc");
931    }
932
933    #[test]
934    fn deserialize_vnc_url_response() {
935        let payload = r#"{
936            "Response": {
937                "InstanceVncUrl": "https://example.com",
938                "RequestId": "req-xyz"
939            }
940        }"#;
941        let parsed: DescribeInstanceVncUrlResponse = serde_json::from_str(payload).unwrap();
942        assert_eq!(
943            parsed.response.instance_vnc_url.as_deref(),
944            Some("https://example.com")
945        );
946    }
947
948    #[test]
949    fn describe_instances_builder_accumulates_filters() {
950        let request = DescribeInstancesRequest::new()
951            .region("ap-guangzhou")
952            .push_filter(Filter::new("zone", ["ap-guangzhou-1"]))
953            .limit(10)
954            .offset(5);
955
956        assert_eq!(
957            request.region.as_ref().map(Region::as_str),
958            Some("ap-guangzhou")
959        );
960        assert_eq!(request.filters.len(), 1);
961        assert_eq!(request.filters[0].name, "zone");
962        assert_eq!(request.filters[0].values[0], "ap-guangzhou-1");
963        assert_eq!(request.limit, Some(10));
964        assert_eq!(request.offset, Some(5));
965    }
966
967    #[test]
968    fn run_instances_payload_includes_optional_fields() {
969        let request = RunInstancesRequest::new("ap-beijing", "img-123", "S4.SMALL1")
970            .instance_name("demo")
971            .instance_count(2)
972            .client_token("token")
973            .subnet_id("subnet-123")
974            .security_group_ids(["sg-1", "sg-2"]);
975
976        let payload = request.payload().unwrap().unwrap();
977        assert_eq!(payload["ImageId"], json!("img-123"));
978        assert_eq!(payload["InstanceType"], json!("S4.SMALL1"));
979        assert_eq!(payload["InstanceName"], json!("demo"));
980        assert_eq!(payload["InstanceCount"], json!(2));
981        assert_eq!(payload["ClientToken"], json!("token"));
982        assert_eq!(payload["SubnetId"], json!("subnet-123"));
983        assert_eq!(payload["SecurityGroupIds"], json!(["sg-1", "sg-2"]));
984    }
985
986    #[test]
987    fn describe_images_payload_supports_filters() {
988        let request = DescribeImagesRequest::new()
989            .region("ap-beijing")
990            .push_image_id("img-123")
991            .push_filter(Filter::new("image-type", ["PUBLIC_IMAGE"]))
992            .limit(10);
993
994        let payload = request.payload().unwrap().unwrap();
995        assert_eq!(payload["ImageIds"], json!(["img-123"]));
996        assert_eq!(
997            payload["Filters"],
998            json!([{ "Name": "image-type", "Values": ["PUBLIC_IMAGE"] }])
999        );
1000        assert_eq!(payload["Limit"], json!(10));
1001    }
1002
1003    #[test]
1004    fn deserialize_run_instances_response() {
1005        let payload = r#"{
1006            "Response": {
1007                "InstanceIdSet": ["ins-1", "ins-2"],
1008                "RequestId": "req-789"
1009            }
1010        }"#;
1011        let parsed: RunInstancesResponse = serde_json::from_str(payload).unwrap();
1012        assert_eq!(
1013            parsed.response.instance_id_set.clone().unwrap(),
1014            vec![InstanceId::from("ins-1"), InstanceId::from("ins-2")]
1015        );
1016    }
1017
1018    #[test]
1019    fn deserialize_describe_images_response() {
1020        let payload = r#"{
1021            "Response": {
1022                "TotalCount": 1,
1023                "ImageSet": [{
1024                    "ImageId": "img-1",
1025                    "ImageName": "test"
1026                }],
1027                "RequestId": "req-111"
1028            }
1029        }"#;
1030        let parsed: DescribeImagesResponse = serde_json::from_str(payload).unwrap();
1031        assert_eq!(parsed.response.total_count, Some(1));
1032        assert_eq!(
1033            parsed.response.image_set[0]
1034                .image_id
1035                .as_ref()
1036                .map(ImageId::as_str),
1037            Some("img-1")
1038        );
1039    }
1040
1041    #[test]
1042    fn deserialize_instance_summary_preserves_unknown_fields() {
1043        let payload = r#"{
1044            "Response": {
1045                "TotalCount": 1,
1046                "InstanceSet": [{
1047                    "InstanceId": "ins-1",
1048                    "UnknownField": "extra"
1049                }],
1050                "RequestId": "req-xyz"
1051            }
1052        }"#;
1053
1054        let parsed: DescribeInstancesResponse = serde_json::from_str(payload).unwrap();
1055        assert_eq!(
1056            parsed.response.instance_set[0]
1057                .instance_id
1058                .as_ref()
1059                .map(InstanceId::as_str),
1060            Some("ins-1")
1061        );
1062        assert_eq!(
1063            parsed.response.instance_set[0]
1064                .extra
1065                .get("UnknownField")
1066                .unwrap(),
1067            &Value::String("extra".to_string())
1068        );
1069    }
1070}