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}