1use crate::{
2 client::{TencentCloudAsync, TencentCloudBlocking},
3 core::{Endpoint, TencentCloudResult},
4 services::Filter,
5};
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use std::borrow::Cow;
9use std::collections::HashMap;
10
11#[derive(Debug, Deserialize)]
12pub struct DescribeInstancesResponse {
13 #[serde(rename = "Response")]
14 pub response: DescribeInstancesResult,
15}
16
17#[derive(Debug, Deserialize)]
18pub struct DescribeInstancesResult {
19 #[serde(rename = "TotalCount")]
20 pub total_count: Option<u64>,
21 #[serde(rename = "InstanceSet")]
22 #[serde(default)]
23 pub instance_set: Vec<InstanceSummary>,
24 #[serde(rename = "RequestId")]
25 pub request_id: String,
26}
27
28#[derive(Debug, Deserialize)]
29pub struct InstanceSummary {
30 #[serde(rename = "InstanceId")]
31 pub instance_id: Option<String>,
32 #[serde(rename = "InstanceName")]
33 pub instance_name: Option<String>,
34 #[serde(rename = "InstanceState")]
35 pub instance_state: Option<String>,
36 #[serde(rename = "InstanceType")]
37 pub instance_type: Option<String>,
38 #[serde(rename = "Cpu")]
39 pub cpu: Option<u64>,
40 #[serde(rename = "Memory")]
41 pub memory: Option<u64>,
42 #[serde(rename = "PrivateIpAddresses")]
43 pub private_ip_addresses: Option<Vec<String>>,
44 #[serde(rename = "PublicIpAddresses")]
45 pub public_ip_addresses: Option<Vec<String>>,
46 #[serde(default)]
47 pub placement: Option<InstancePlacement>,
48 #[serde(default)]
49 pub system_disk: Option<DiskSummary>,
50 #[serde(default)]
51 pub data_disks: Option<Vec<DiskSummary>>,
52 #[serde(flatten, default)]
53 pub extra: HashMap<String, Value>,
54}
55
56#[derive(Debug, Deserialize)]
57pub struct InstancePlacement {
58 #[serde(rename = "Zone")]
59 pub zone: Option<String>,
60 #[serde(rename = "ProjectId")]
61 pub project_id: Option<i64>,
62 #[serde(flatten, default)]
63 pub extra: HashMap<String, Value>,
64}
65
66#[derive(Debug, Deserialize)]
67pub struct DiskSummary {
68 #[serde(rename = "DiskType")]
69 pub disk_type: Option<String>,
70 #[serde(rename = "DiskSize")]
71 pub disk_size: Option<u64>,
72 #[serde(flatten, default)]
73 pub extra: HashMap<String, Value>,
74}
75
76#[derive(Serialize)]
77#[serde(rename_all = "PascalCase")]
78struct DescribeInstancesPayload<'a> {
79 #[serde(skip_serializing_if = "Option::is_none")]
80 filters: Option<&'a [Filter<'a>]>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 limit: Option<u32>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 offset: Option<u32>,
85}
86
87pub struct DescribeInstances<'a> {
89 pub region: Option<&'a str>,
90 pub filters: Option<Vec<Filter<'a>>>,
91 pub limit: Option<u32>,
92 pub offset: Option<u32>,
93}
94
95impl<'a> Default for DescribeInstances<'a> {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl<'a> DescribeInstances<'a> {
102 pub fn new() -> Self {
104 Self {
105 region: None,
106 filters: None,
107 limit: None,
108 offset: None,
109 }
110 }
111
112 pub fn with_region(mut self, region: &'a str) -> Self {
114 self.region = Some(region);
115 self
116 }
117
118 pub fn push_filter(mut self, filter: Filter<'a>) -> Self {
120 self.filters.get_or_insert_with(Vec::new).push(filter);
121 self
122 }
123
124 pub fn with_limit(mut self, limit: u32) -> Self {
126 self.limit = Some(limit);
127 self
128 }
129
130 pub fn with_offset(mut self, offset: u32) -> Self {
132 self.offset = Some(offset);
133 self
134 }
135}
136
137impl<'a> Endpoint for DescribeInstances<'a> {
138 type Output = DescribeInstancesResponse;
139
140 fn service(&self) -> Cow<'static, str> {
141 Cow::Borrowed("cvm")
142 }
143
144 fn action(&self) -> Cow<'static, str> {
145 Cow::Borrowed("DescribeInstances")
146 }
147
148 fn version(&self) -> Cow<'static, str> {
149 Cow::Borrowed("2017-03-12")
150 }
151
152 fn region(&self) -> Option<Cow<'_, str>> {
153 self.region.map(Cow::Borrowed)
154 }
155
156 fn payload(&self) -> Value {
157 let filters = self.filters.as_deref();
158 serde_json::to_value(DescribeInstancesPayload {
159 filters,
160 limit: self.limit,
161 offset: self.offset,
162 })
163 .expect("serialize DescribeInstances payload")
164 }
165}
166
167#[derive(Debug, Deserialize)]
168pub struct GenericActionResponse {
169 #[serde(rename = "Response")]
170 pub response: GenericActionResult,
171}
172
173#[derive(Debug, Deserialize)]
174pub struct GenericActionResult {
175 #[serde(rename = "RequestId")]
176 pub request_id: String,
177}
178
179pub struct ResetInstancesPassword<'a> {
181 pub region: &'a str,
182 pub instance_ids: &'a [&'a str],
183 pub password: &'a str,
184 pub username: Option<&'a str>,
185 pub force_stop: Option<bool>,
186}
187
188impl<'a> Endpoint for ResetInstancesPassword<'a> {
189 type Output = GenericActionResponse;
190
191 fn service(&self) -> Cow<'static, str> {
192 Cow::Borrowed("cvm")
193 }
194
195 fn action(&self) -> Cow<'static, str> {
196 Cow::Borrowed("ResetInstancesPassword")
197 }
198
199 fn version(&self) -> Cow<'static, str> {
200 Cow::Borrowed("2017-03-12")
201 }
202
203 fn region(&self) -> Option<Cow<'_, str>> {
204 Some(Cow::Borrowed(self.region))
205 }
206
207 fn payload(&self) -> Value {
208 let mut payload = json!({
209 "InstanceIds": self.instance_ids,
210 "Password": self.password
211 });
212
213 if let Some(username) = self.username {
214 payload["UserName"] = json!(username);
215 }
216 if let Some(force_stop) = self.force_stop {
217 payload["ForceStop"] = json!(force_stop);
218 }
219 payload
220 }
221}
222
223#[derive(Debug, Deserialize)]
224pub struct DescribeInstanceVncUrlResponse {
225 #[serde(rename = "Response")]
226 pub response: DescribeInstanceVncUrlResult,
227}
228
229#[derive(Debug, Deserialize)]
230pub struct DescribeInstanceVncUrlResult {
231 #[serde(rename = "InstanceVncUrl")]
232 pub instance_vnc_url: Option<String>,
233 #[serde(rename = "RequestId")]
234 pub request_id: String,
235}
236
237pub struct DescribeInstanceVncUrl<'a> {
239 pub region: &'a str,
240 pub instance_id: &'a str,
241}
242
243impl<'a> Endpoint for DescribeInstanceVncUrl<'a> {
244 type Output = DescribeInstanceVncUrlResponse;
245
246 fn service(&self) -> Cow<'static, str> {
247 Cow::Borrowed("cvm")
248 }
249
250 fn action(&self) -> Cow<'static, str> {
251 Cow::Borrowed("DescribeInstanceVncUrl")
252 }
253
254 fn version(&self) -> Cow<'static, str> {
255 Cow::Borrowed("2017-03-12")
256 }
257
258 fn region(&self) -> Option<Cow<'_, str>> {
259 Some(Cow::Borrowed(self.region))
260 }
261
262 fn payload(&self) -> Value {
263 json!({ "InstanceId": self.instance_id })
264 }
265}
266
267pub struct StartInstances<'a> {
269 pub region: &'a str,
270 pub instance_ids: &'a [&'a str],
271}
272
273impl<'a> Endpoint for StartInstances<'a> {
274 type Output = GenericActionResponse;
275
276 fn service(&self) -> Cow<'static, str> {
277 Cow::Borrowed("cvm")
278 }
279
280 fn action(&self) -> Cow<'static, str> {
281 Cow::Borrowed("StartInstances")
282 }
283
284 fn version(&self) -> Cow<'static, str> {
285 Cow::Borrowed("2017-03-12")
286 }
287
288 fn region(&self) -> Option<Cow<'_, str>> {
289 Some(Cow::Borrowed(self.region))
290 }
291
292 fn payload(&self) -> Value {
293 json!({ "InstanceIds": self.instance_ids })
294 }
295}
296
297pub struct RebootInstances<'a> {
299 pub region: &'a str,
300 pub instance_ids: &'a [&'a str],
301 pub force_reboot: Option<bool>,
302}
303
304impl<'a> Endpoint for RebootInstances<'a> {
305 type Output = GenericActionResponse;
306
307 fn service(&self) -> Cow<'static, str> {
308 Cow::Borrowed("cvm")
309 }
310
311 fn action(&self) -> Cow<'static, str> {
312 Cow::Borrowed("RebootInstances")
313 }
314
315 fn version(&self) -> Cow<'static, str> {
316 Cow::Borrowed("2017-03-12")
317 }
318
319 fn region(&self) -> Option<Cow<'_, str>> {
320 Some(Cow::Borrowed(self.region))
321 }
322
323 fn payload(&self) -> Value {
324 let mut payload = json!({ "InstanceIds": self.instance_ids });
325 if let Some(force_reboot) = self.force_reboot {
326 payload["ForceReboot"] = json!(force_reboot);
327 }
328 payload
329 }
330}
331
332pub struct StopInstances<'a> {
334 pub region: &'a str,
335 pub instance_ids: &'a [&'a str],
336 pub stop_type: Option<&'a str>,
337}
338
339impl<'a> Endpoint for StopInstances<'a> {
340 type Output = GenericActionResponse;
341
342 fn service(&self) -> Cow<'static, str> {
343 Cow::Borrowed("cvm")
344 }
345
346 fn action(&self) -> Cow<'static, str> {
347 Cow::Borrowed("StopInstances")
348 }
349
350 fn version(&self) -> Cow<'static, str> {
351 Cow::Borrowed("2017-03-12")
352 }
353
354 fn region(&self) -> Option<Cow<'_, str>> {
355 Some(Cow::Borrowed(self.region))
356 }
357
358 fn payload(&self) -> Value {
359 let mut payload = json!({ "InstanceIds": self.instance_ids });
360 if let Some(stop_type) = self.stop_type {
361 payload["StopType"] = json!(stop_type);
362 }
363 payload
364 }
365}
366
367pub struct ModifyInstancesProject<'a> {
369 pub region: &'a str,
370 pub instance_ids: &'a [&'a str],
371 pub project_id: u64,
372}
373
374impl<'a> Endpoint for ModifyInstancesProject<'a> {
375 type Output = GenericActionResponse;
376
377 fn service(&self) -> Cow<'static, str> {
378 Cow::Borrowed("cvm")
379 }
380
381 fn action(&self) -> Cow<'static, str> {
382 Cow::Borrowed("ModifyInstancesProject")
383 }
384
385 fn version(&self) -> Cow<'static, str> {
386 Cow::Borrowed("2017-03-12")
387 }
388
389 fn region(&self) -> Option<Cow<'_, str>> {
390 Some(Cow::Borrowed(self.region))
391 }
392
393 fn payload(&self) -> Value {
394 json!({
395 "InstanceIds": self.instance_ids,
396 "ProjectId": self.project_id
397 })
398 }
399}
400
401pub async fn describe_instances_async(
403 client: &TencentCloudAsync,
404 request: &DescribeInstances<'_>,
405) -> TencentCloudResult<DescribeInstancesResponse> {
406 client.request(request).await
407}
408
409pub fn describe_instances_blocking(
411 client: &TencentCloudBlocking,
412 request: &DescribeInstances<'_>,
413) -> TencentCloudResult<DescribeInstancesResponse> {
414 client.request(request)
415}
416
417pub async fn reset_instances_password_async(
419 client: &TencentCloudAsync,
420 request: &ResetInstancesPassword<'_>,
421) -> TencentCloudResult<GenericActionResponse> {
422 client.request(request).await
423}
424
425pub fn reset_instances_password_blocking(
427 client: &TencentCloudBlocking,
428 request: &ResetInstancesPassword<'_>,
429) -> TencentCloudResult<GenericActionResponse> {
430 client.request(request)
431}
432
433pub async fn describe_instance_vnc_url_async(
435 client: &TencentCloudAsync,
436 request: &DescribeInstanceVncUrl<'_>,
437) -> TencentCloudResult<DescribeInstanceVncUrlResponse> {
438 client.request(request).await
439}
440
441pub fn describe_instance_vnc_url_blocking(
443 client: &TencentCloudBlocking,
444 request: &DescribeInstanceVncUrl<'_>,
445) -> TencentCloudResult<DescribeInstanceVncUrlResponse> {
446 client.request(request)
447}
448
449pub async fn start_instances_async(
451 client: &TencentCloudAsync,
452 request: &StartInstances<'_>,
453) -> TencentCloudResult<GenericActionResponse> {
454 client.request(request).await
455}
456
457pub fn start_instances_blocking(
459 client: &TencentCloudBlocking,
460 request: &StartInstances<'_>,
461) -> TencentCloudResult<GenericActionResponse> {
462 client.request(request)
463}
464
465pub async fn reboot_instances_async(
467 client: &TencentCloudAsync,
468 request: &RebootInstances<'_>,
469) -> TencentCloudResult<GenericActionResponse> {
470 client.request(request).await
471}
472
473pub fn reboot_instances_blocking(
475 client: &TencentCloudBlocking,
476 request: &RebootInstances<'_>,
477) -> TencentCloudResult<GenericActionResponse> {
478 client.request(request)
479}
480
481pub async fn stop_instances_async(
483 client: &TencentCloudAsync,
484 request: &StopInstances<'_>,
485) -> TencentCloudResult<GenericActionResponse> {
486 client.request(request).await
487}
488
489pub fn stop_instances_blocking(
491 client: &TencentCloudBlocking,
492 request: &StopInstances<'_>,
493) -> TencentCloudResult<GenericActionResponse> {
494 client.request(request)
495}
496
497pub async fn modify_instances_project_async(
499 client: &TencentCloudAsync,
500 request: &ModifyInstancesProject<'_>,
501) -> TencentCloudResult<GenericActionResponse> {
502 client.request(request).await
503}
504
505pub fn modify_instances_project_blocking(
507 client: &TencentCloudBlocking,
508 request: &ModifyInstancesProject<'_>,
509) -> TencentCloudResult<GenericActionResponse> {
510 client.request(request)
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use crate::services::Filter;
517
518 #[test]
519 fn describe_instances_payload_supports_filters() {
520 let filters = vec![
521 Filter::new("instance-id", ["ins-123"]),
522 Filter::new("zone", ["ap-shanghai-1"]),
523 ];
524 let request = DescribeInstances {
525 region: Some("ap-shanghai"),
526 filters: Some(filters.clone()),
527 limit: Some(20),
528 offset: Some(0),
529 };
530
531 let payload = request.payload();
532 assert_eq!(payload["Filters"][0]["Name"], json!("instance-id"));
533 assert_eq!(payload["Filters"][1]["Values"], json!(["ap-shanghai-1"]));
534 assert_eq!(payload["Limit"], json!(20));
535 assert_eq!(payload["Offset"], json!(0));
536 }
537
538 #[test]
539 fn deserialize_generic_action_response() {
540 let payload = r#"{
541 "Response": {
542 "RequestId": "req-abc"
543 }
544 }"#;
545 let parsed: GenericActionResponse = serde_json::from_str(payload).unwrap();
546 assert_eq!(parsed.response.request_id, "req-abc");
547 }
548
549 #[test]
550 fn deserialize_vnc_url_response() {
551 let payload = r#"{
552 "Response": {
553 "InstanceVncUrl": "https://example.com",
554 "RequestId": "req-xyz"
555 }
556 }"#;
557 let parsed: DescribeInstanceVncUrlResponse = serde_json::from_str(payload).unwrap();
558 assert_eq!(
559 parsed.response.instance_vnc_url.as_deref(),
560 Some("https://example.com")
561 );
562 }
563
564 #[test]
565 fn describe_instances_builder_accumulates_filters() {
566 let request = DescribeInstances::new()
567 .with_region("ap-guangzhou")
568 .push_filter(Filter::new("zone", ["ap-guangzhou-1"]))
569 .with_limit(10)
570 .with_offset(5);
571
572 assert_eq!(request.region, Some("ap-guangzhou"));
573 let filters = request.filters.as_ref().expect("filters set");
574 assert_eq!(filters.len(), 1);
575 assert_eq!(filters[0].name, "zone");
576 assert_eq!(filters[0].values[0], "ap-guangzhou-1");
577 assert_eq!(request.limit, Some(10));
578 assert_eq!(request.offset, Some(5));
579 }
580}