volcengine_rust_sdk/volcengine/request/request.rs
1/*
2 * @Author: Jerry.Yang
3 * @Date: 2024-10-17 16:35:22
4 * @LastEditors: Jerry.Yang
5 * @LastEditTime: 2025-02-05 11:36:18
6 * @Description: request
7 */
8use crate::volcengine::client::client_info;
9use crate::volcengine::client::config;
10use crate::volcengine::error::error;
11use crate::volcengine::request::handles;
12use crate::volcengine::request::operation;
13use crate::volcengine::request::request;
14use crate::volcengine::request::send;
15use crate::volcengine::request::send::SendRequest;
16use std::collections::HashMap;
17use std::future::Future;
18
19/// Trait representing an API request.
20///
21/// This trait is designed for types that represent API requests. It provides methods to
22/// convert the request into a `HashMap` and a byte array, making it easier to prepare
23/// the request for sending over HTTP. Any type implementing this trait must also implement
24/// the `Send` trait, ensuring that the request can be safely transferred between threads.
25pub trait ApiRequest: Send {
26 /// Converts the request into a `HashMap<String, String>`.
27 ///
28 /// This method takes the request and transforms it into a key-value pair representation
29 /// where both keys and values are of type `String`. This is typically useful for
30 /// serializing request data or for cases where the request needs to be converted
31 /// to a more human-readable format (like for logging or debugging).
32 ///
33 /// # Returns
34 /// - `HashMap<String, String>`: A `HashMap` where the keys are strings and the values are strings,
35 /// representing the parameters or fields of the request.
36 ///
37 /// # Example
38 /// ```rust
39 /// let request = MyRequest { ... };
40 /// let hashmap = request.to_hashmap();
41 /// ```
42 fn to_hashmap(&self) -> HashMap<String, String>;
43
44 /// Converts the request into a byte array suitable for sending in an HTTP request.
45 ///
46 /// This method converts the request data into a `Vec<u8>`, which represents the request body
47 /// as a sequence of bytes. This is particularly useful when the request needs to be sent
48 /// over the network, for example, in the body of an HTTP POST or PUT request.
49 ///
50 /// # Returns
51 /// - `Vec<u8>`: A `Vec<u8>` containing the byte representation of the request body, which can
52 /// be sent as the body of an HTTP request.
53 ///
54 /// # Example
55 /// ```rust
56 /// let request = MyRequest { ... };
57 /// let byte_body = request.to_body();
58 /// ```
59 fn to_body(&self) -> Vec<u8>;
60}
61
62/// Trait for handling requests to the Volcengine API.
63///
64/// This trait defines the core methods required for interacting with the Volcengine API. It includes
65/// a method to format requests into a `HashMap` and another to send the API request and handle the response.
66/// Implementing this trait allows types to convert requests into the appropriate format and handle communication
67/// with the Volcengine service.
68pub trait RequestVolcengine {
69 /// Formats the provided request into a `HashMap<String, String>`.
70 ///
71 /// This method takes a serializable request object and converts it into a `HashMap` where each field
72 /// in the request is represented as a key-value pair. It filters out any `null` or empty values to ensure
73 /// only valid fields are included in the resulting map. This can be useful for serializing data that
74 /// will be sent to the Volcengine API, making it easier to construct HTTP request bodies or query parameters.
75 ///
76 /// # Parameters
77 /// - `request`: A reference to a serializable object (must implement `serde::Serialize`).
78 ///
79 /// # Returns
80 /// - `HashMap<String, String>`: A `HashMap` where the keys are field names and the values are their corresponding
81 /// values from the request. Null or empty values are excluded.
82 ///
83 /// # Example
84 /// ```rust
85 /// let request = MyRequest { ... };
86 /// let formatted_map = request.format_request_to_hashmap(&request);
87 /// ```
88 fn format_request_to_hashmap<T: serde::Serialize>(request: &T) -> HashMap<String, String>;
89
90 /// Sends an API request to the Volcengine API and returns the response.
91 ///
92 /// This method is responsible for sending the request to the Volcengine API and returning the response,
93 /// or an error if something goes wrong during the request. The request must implement the `ApiRequest` trait,
94 /// which ensures that the necessary data can be serialized and transmitted to the API.
95 ///
96 /// # Parameters
97 /// - `request`: An instance that implements the `ApiRequest` trait. This represents the request data to be sent.
98 ///
99 /// # Returns
100 /// - `Result<reqwest::Response, error::Error>`: A `Result` containing either the `reqwest::Response` if the request
101 /// was successful, or an `error::Error` if something went wrong during the request.
102 ///
103 /// # Example
104 /// ```rust
105 /// let request = MyApiRequest { ... };
106 /// let response = my_request_handler.send(request).await;
107 /// ```
108 fn send<T: request::ApiRequest>(
109 &self,
110 request: T,
111 ) -> impl Future<Output = Result<reqwest::Response, error::Error>>;
112}
113
114/// Represents a request to the Volcengine API.
115///
116/// This struct holds all the necessary components for making a request to the Volcengine API.
117/// It contains the configuration, client information, operation details, and handles to manage
118/// the request operations. The struct is designed to encapsulate the various elements required
119/// for interacting with the Volcengine service, ensuring that all necessary data is included
120/// when making the API request.
121#[derive(Debug, Clone)]
122#[allow(dead_code)] // Allows unused code, useful during development or testing.
123pub struct Request {
124 /// Configuration settings for the request.
125 ///
126 /// This field contains the configuration settings necessary to make the request. It might
127 /// include things like authentication details, API keys, endpoint URLs, or other settings
128 /// related to the request environment.
129 pub config: config::Config,
130
131 /// Information about the client making the request.
132 ///
133 /// This field contains details about the client or the system that is making the request.
134 /// This could include information such as the client ID, version, operating system, and
135 /// any other metadata relevant to identifying the client.
136 pub client_info: client_info::ClientInfo,
137
138 /// Handles for managing request operations.
139 ///
140 /// This field provides handles to manage the lifecycle and execution of the request.
141 /// These could include handles for making retries, timeouts, managing API rate limits,
142 /// or other request-related operations.
143 pub handles: handles::Handles,
144
145 /// Operation details for the request.
146 ///
147 /// This field holds the specific operation that the request will perform. It might include
148 /// data like the action or API method being called, parameters for that action, or other
149 /// relevant details for the request's operation.
150 pub operation: operation::Operation,
151}
152
153/// Implementation of the `Request` struct, providing methods to construct and manage a request.
154///
155/// This block of code defines methods associated with the `Request` struct, such as the `builder` method.
156/// The `Request` struct represents an API request to the Volcengine API and is composed of various components
157/// including the configuration, client information, handles, and operation. The methods in this `impl` block
158/// allow for the creation and management of these components, making it easier to construct and send requests.
159///
160/// The builder pattern is used here, allowing you to step-by-step configure the components of the `Request`
161/// before it is finalized and sent. This is especially useful for building requests with varying components.
162///
163/// # Example Usage
164/// ```rust
165/// let request = Request::builder()
166/// .config(config)
167/// .client_info(client_info)
168/// .handles(handles)
169/// .operation(operation)
170/// .build();
171/// ```
172impl Request {
173 /// Constructs a new `RequestBuilder` instance for building a `Request`.
174 ///
175 /// This method provides a fluent interface for constructing a `Request` step by step. It allows
176 /// for setting the configuration, client information, handles, and operation before finalizing
177 /// and creating the actual `Request` object. This is useful when you need to build a request
178 /// with optional components that may vary between requests.
179 ///
180 /// # Returns
181 /// Returns a `RequestBuilder` that enables the construction of a `Request` object. The builder
182 /// can be used to set each of the fields in the `Request` struct, allowing for customization
183 /// of the request before it is finalized.
184 ///
185 /// # Example
186 /// ```
187 /// let request = Request::builder()
188 /// .config(config)
189 /// .client_info(client_info)
190 /// .handles(handles)
191 /// .operation(operation)
192 /// .build();
193 /// ```
194 pub fn builder() -> RequestBuilder {
195 RequestBuilder {
196 config: None,
197 client_info: None,
198 handles: None,
199 operation: None,
200 }
201 }
202}
203
204/// Implementation of the `RequestVolcengine` trait for the `Request` struct.
205///
206/// This block provides methods for formatting the request into a `HashMap` and sending an API request.
207/// The methods implement the functionality needed to interact with the Volcengine API. The `Request` struct
208/// is adapted to handle serialization and request sending according to the Volcengine API's requirements.
209///
210/// The methods in this implementation allow for easy conversion of the request data to a format expected
211/// by the Volcengine API and also provide a way to send the request over HTTP using the `reqwest` library.
212///
213/// # Example Usage
214/// ```rust
215/// let request = Request::builder()
216/// .config(config)
217/// .client_info(client_info)
218/// .handles(handles)
219/// .operation(operation)
220/// .build();
221///
222/// let result = request.send(api_request);
223/// ```
224impl RequestVolcengine for Request {
225 /// Formats the request into a `HashMap<String, String>`.
226 ///
227 /// # Parameters
228 /// - `request`: A reference to a serializable object.
229 ///
230 /// # Returns
231 /// Returns a `HashMap<String, String>` representation of the request, filtering out null or empty values.
232 fn format_request_to_hashmap<T: serde::Serialize>(request: &T) -> HashMap<String, String> {
233 // Serialize the request into a JSON value.
234 let value = serde_json::to_value(request).unwrap();
235 // Convert the JSON value into a map of key-value pairs.
236 let map = value.as_object().unwrap();
237
238 // Convert each entry from `Value` to `String`, filtering out empty or null values.
239 let mut request_hashmap: HashMap<String, String> = HashMap::new();
240 for (k, v) in map.iter() {
241 if v.is_null() || v == "" || v == "null" {
242 // Skip null, empty, or "null" values.
243 continue;
244 }
245
246 if v.is_array() {
247 // If the value is an array, convert each element to `key.index=value`.
248 if let Some(arr) = v.as_array() {
249 if !arr.is_empty() {
250 // Map each array element to "key.index=value" format
251 for (i, x) in arr.iter().enumerate() {
252 // judge x object
253 if x.is_object() {
254 if let Some(mapx) = x.as_object() {
255 for (mapx_key, mapx_value) in mapx.iter() {
256 // judge mapx_value
257 if mapx_value.is_null()
258 || mapx_value == ""
259 || mapx_value == "null"
260 {
261 // Skip null, empty, or "null" values.
262 continue;
263 }
264
265 // judge mapx_value is_array
266 if mapx_value.is_array() {
267 if let Some(mapx_value_arr) = mapx_value.as_array() {
268 if !mapx_value_arr.is_empty() {
269 // for mapx_value_arr
270 for (
271 mapx_value_arr_key,
272 mapx_value_arr_value,
273 ) in mapx_value_arr.iter().enumerate()
274 {
275 // judge mapx_value
276 if mapx_value_arr_value.is_null()
277 || mapx_value_arr_value == ""
278 || mapx_value_arr_value == "null"
279 {
280 // Skip null, empty, or "null" values.
281 continue;
282 }
283 // insert request_hashmap
284 request_hashmap.insert(
285 format!(
286 "{}.{}.{}.{}",
287 k.clone(),
288 i + 1,
289 mapx_key,
290 mapx_value_arr_key + 1
291 ),
292 mapx_value_arr_value
293 .to_string()
294 .replace("\"", ""),
295 );
296 }
297 }
298 }
299 continue;
300 }
301
302 // insert request_hashmap
303 request_hashmap.insert(
304 format!("{}.{}.{}", k.clone(), i + 1, mapx_key),
305 mapx_value.to_string().replace("\"", ""),
306 );
307 }
308 }
309 continue;
310 }
311
312 if !x.is_null() {
313 request_hashmap.insert(
314 format!("{}.{}", k, i + 1),
315 x.to_string().replace("\"", ""),
316 );
317 }
318 }
319 }
320 }
321 } else {
322 // judge k
323 // if k == PolicyDocument
324 if k == "PolicyDocument" {
325 // policy_document
326 let mut policy_document = v.to_string().replace("\\", "");
327 policy_document = policy_document[1..policy_document.len() - 1].to_string();
328 request_hashmap.insert(k.clone(), policy_document);
329 continue;
330 }
331
332 // judge k
333 // if k == NewPolicyDocument
334 if k == "NewPolicyDocument" {
335 // policy_document
336 let mut policy_document = v.to_string().replace("\\", "");
337 policy_document = policy_document[1..policy_document.len() - 1].to_string();
338 request_hashmap.insert(k.clone(), policy_document);
339 continue;
340 }
341
342 // Otherwise, directly convert the value to a string and remove quotes
343 request_hashmap.insert(k.clone(), v.to_string().replace("\"", ""));
344 }
345 }
346
347 request_hashmap
348 }
349
350 /// Sends the request to the Volcengine API.
351 ///
352 /// # Parameters
353 /// - `request`: An instance implementing the `ApiRequest` trait.
354 ///
355 /// # Returns
356 /// Returns a `Result` containing the `reqwest::Response` or an error.
357 ///
358 /// This function sends the request and waits for the API response asynchronously.
359 async fn send<T: request::ApiRequest>(
360 &self,
361 request: T,
362 ) -> Result<reqwest::Response, error::Error> {
363 send::Send::set_request(self).send(&request).await
364 }
365}
366
367/// Builder for constructing a `Request` instance.
368///
369/// The `RequestBuilder` struct provides a flexible way to create a `Request` object by gradually setting its
370/// fields using the builder pattern. Each field of the `Request` is optional and can be set individually.
371/// Once all desired fields are set, the `build` method can be called to finalize the construction of the `Request`
372/// object. This builder pattern ensures that the `Request` is properly configured before being used.
373///
374/// # Example Usage
375/// ```rust
376/// let request = RequestBuilder::new()
377/// .config(config)
378/// .client_info(client_info)
379/// .handles(handles)
380/// .operation(operation)
381/// .build();
382/// ```
383///
384/// # Fields
385/// - `config`: An optional `Config` struct that contains the configuration for the request.
386/// - `client_info`: An optional `ClientInfo` struct that contains information about the client making the request.
387/// - `handles`: An optional `Handles` struct that provides handles for managing request operations.
388/// - `operation`: An optional `Operation` struct that holds details about the operation to be performed in the request.
389pub struct RequestBuilder {
390 pub config: Option<config::Config>, // Optional configuration for the request.
391 pub client_info: Option<client_info::ClientInfo>, // Optional client information.
392 pub handles: Option<handles::Handles>, // Optional handles for request operations.
393 pub operation: Option<operation::Operation>, // Optional operation details.
394}
395
396/// Implementation of methods for constructing and finalizing a `Request` object.
397///
398/// The `RequestBuilder` struct provides methods to set each optional field of the `Request` object. It allows for controlled
399/// construction of a `Request` instance by providing a set of methods to set specific properties (such as configuration,
400/// client info, handles, and operation details). Once all fields are set, the `build()` method finalizes the creation of
401/// the `Request`. This ensures that an incomplete or invalid `Request` cannot be constructed, adhering to the builder pattern.
402///
403/// The builder pattern provides a more readable and maintainable way to construct complex objects. This also helps in
404/// avoiding issues related to having multiple constructors with different signatures.
405impl RequestBuilder {
406 /// Sets the configuration for the request builder.
407 ///
408 /// # Parameters
409 /// - `config`: A reference to the `Config` instance.
410 ///
411 /// # Returns
412 /// Returns the updated `RequestBuilder` with the provided configuration set.
413 pub fn with_config(mut self, config: &config::Config) -> Self {
414 self.config = Some(config.clone());
415 self
416 }
417
418 /// Sets the client information for the request builder.
419 ///
420 /// # Parameters
421 /// - `client_info`: A reference to the `ClientInfo` instance.
422 ///
423 /// # Returns
424 /// Returns the updated `RequestBuilder` with the provided client information set.
425 pub fn with_client_info(mut self, client_info: &client_info::ClientInfo) -> Self {
426 self.client_info = Some(client_info.clone());
427 self
428 }
429
430 /// Sets the handles for the request builder.
431 ///
432 /// # Parameters
433 /// - `handles`: A reference to the `Handles` instance.
434 ///
435 /// # Returns
436 /// Returns the updated `RequestBuilder` with the provided handles set.
437 pub fn with_handles(mut self, handles: &handles::Handles) -> Self {
438 self.handles = Some(handles.clone());
439 self
440 }
441
442 /// Sets the operation details for the request builder.
443 ///
444 /// # Parameters
445 /// - `operation`: A reference to the `Operation` instance.
446 ///
447 /// # Returns
448 /// Returns the updated `RequestBuilder` with the provided operation details set.
449 pub fn with_operation(mut self, operation: &operation::Operation) -> Self {
450 self.operation = Some(operation.clone());
451 self
452 }
453
454 /// Builds the final `Request` object.
455 ///
456 /// This method checks that all required fields are set. If any of the required fields are missing,
457 /// it returns an error. If all fields are provided, it constructs and returns a `Request` object.
458 ///
459 /// # Returns
460 /// Returns a `Result` containing the constructed `Request` or an error if any required field is missing.
461 pub fn build(self) -> Result<Request, error::Error> {
462 // Check if config is provided.
463 if self.config.is_none() {
464 return Err(error::Error::ErrUtilRequestBuildRequestNo(
465 "config".to_string(),
466 ));
467 }
468
469 // Check if client_info is provided.
470 if self.client_info.is_none() {
471 return Err(error::Error::ErrUtilRequestBuildRequestNo(
472 "client_info".to_string(),
473 ));
474 }
475
476 // Check if handles are provided.
477 if self.handles.is_none() {
478 return Err(error::Error::ErrUtilRequestBuildRequestNo(
479 "handles".to_string(),
480 ));
481 }
482
483 // Check if operation is provided.
484 if self.operation.is_none() {
485 return Err(error::Error::ErrUtilRequestBuildRequestNo(
486 "operation".to_string(),
487 ));
488 }
489
490 // Return the constructed Request.
491 Ok(Request {
492 config: self.config.unwrap(),
493 client_info: self.client_info.unwrap(),
494 handles: self.handles.unwrap(),
495 operation: self.operation.unwrap(),
496 })
497 }
498}