datadog_api_client/datadogV2/api/
api_test_optimization.rs

1// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
2// This product includes software developed at Datadog (https://www.datadoghq.com/).
3// Copyright 2019-Present Datadog, Inc.
4use crate::datadog;
5use async_stream::try_stream;
6use flate2::{
7    write::{GzEncoder, ZlibEncoder},
8    Compression,
9};
10use futures_core::stream::Stream;
11use log::warn;
12use reqwest::header::{HeaderMap, HeaderValue};
13use serde::{Deserialize, Serialize};
14use std::io::Write;
15
16/// SearchFlakyTestsOptionalParams is a struct for passing parameters to the method [`TestOptimizationAPI::search_flaky_tests`]
17#[non_exhaustive]
18#[derive(Clone, Default, Debug)]
19pub struct SearchFlakyTestsOptionalParams {
20    pub body: Option<crate::datadogV2::model::FlakyTestsSearchRequest>,
21}
22
23impl SearchFlakyTestsOptionalParams {
24    pub fn body(mut self, value: crate::datadogV2::model::FlakyTestsSearchRequest) -> Self {
25        self.body = Some(value);
26        self
27    }
28}
29
30/// SearchFlakyTestsError is a struct for typed errors of method [`TestOptimizationAPI::search_flaky_tests`]
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(untagged)]
33pub enum SearchFlakyTestsError {
34    APIErrorResponse(crate::datadogV2::model::APIErrorResponse),
35    UnknownValue(serde_json::Value),
36}
37
38/// Search and manage flaky tests through Test Optimization. See the [Test Optimization page](<https://docs.datadoghq.com/tests/>) for more information.
39#[derive(Debug, Clone)]
40pub struct TestOptimizationAPI {
41    config: datadog::Configuration,
42    client: reqwest_middleware::ClientWithMiddleware,
43}
44
45impl Default for TestOptimizationAPI {
46    fn default() -> Self {
47        Self::with_config(datadog::Configuration::default())
48    }
49}
50
51impl TestOptimizationAPI {
52    pub fn new() -> Self {
53        Self::default()
54    }
55    pub fn with_config(config: datadog::Configuration) -> Self {
56        let mut reqwest_client_builder = reqwest::Client::builder();
57
58        if let Some(proxy_url) = &config.proxy_url {
59            let proxy = reqwest::Proxy::all(proxy_url).expect("Failed to parse proxy URL");
60            reqwest_client_builder = reqwest_client_builder.proxy(proxy);
61        }
62
63        let mut middleware_client_builder =
64            reqwest_middleware::ClientBuilder::new(reqwest_client_builder.build().unwrap());
65
66        if config.enable_retry {
67            struct RetryableStatus;
68            impl reqwest_retry::RetryableStrategy for RetryableStatus {
69                fn handle(
70                    &self,
71                    res: &Result<reqwest::Response, reqwest_middleware::Error>,
72                ) -> Option<reqwest_retry::Retryable> {
73                    match res {
74                        Ok(success) => reqwest_retry::default_on_request_success(success),
75                        Err(_) => None,
76                    }
77                }
78            }
79            let backoff_policy = reqwest_retry::policies::ExponentialBackoff::builder()
80                .build_with_max_retries(config.max_retries);
81
82            let retry_middleware =
83                reqwest_retry::RetryTransientMiddleware::new_with_policy_and_strategy(
84                    backoff_policy,
85                    RetryableStatus,
86                );
87
88            middleware_client_builder = middleware_client_builder.with(retry_middleware);
89        }
90
91        let client = middleware_client_builder.build();
92
93        Self { config, client }
94    }
95
96    pub fn with_client_and_config(
97        config: datadog::Configuration,
98        client: reqwest_middleware::ClientWithMiddleware,
99    ) -> Self {
100        Self { config, client }
101    }
102
103    /// List endpoint returning flaky tests from Flaky Test Management. Results are paginated.
104    pub async fn search_flaky_tests(
105        &self,
106        params: SearchFlakyTestsOptionalParams,
107    ) -> Result<
108        crate::datadogV2::model::FlakyTestsSearchResponse,
109        datadog::Error<SearchFlakyTestsError>,
110    > {
111        match self.search_flaky_tests_with_http_info(params).await {
112            Ok(response_content) => {
113                if let Some(e) = response_content.entity {
114                    Ok(e)
115                } else {
116                    Err(datadog::Error::Serde(serde::de::Error::custom(
117                        "response content was None",
118                    )))
119                }
120            }
121            Err(err) => Err(err),
122        }
123    }
124
125    pub fn search_flaky_tests_with_pagination(
126        &self,
127        mut params: SearchFlakyTestsOptionalParams,
128    ) -> impl Stream<
129        Item = Result<crate::datadogV2::model::FlakyTest, datadog::Error<SearchFlakyTestsError>>,
130    > + '_ {
131        try_stream! {
132            let mut page_size: i64 = 10;
133            if params.body.is_none() {
134                params.body = Some(crate::datadogV2::model::FlakyTestsSearchRequest::new());
135            }
136            if params.body.as_ref().unwrap().data.is_none() {
137                params.body.as_mut().unwrap().data = Some(crate::datadogV2::model::FlakyTestsSearchRequestData::new());
138            }
139            if params.body.as_ref().unwrap().data.as_ref().unwrap().attributes.is_none() {
140                params.body.as_mut().unwrap().data.as_mut().unwrap().attributes = Some(crate::datadogV2::model::FlakyTestsSearchRequestAttributes::new());
141            }
142            if params.body.as_ref().unwrap().data.as_ref().unwrap().attributes.as_ref().unwrap().page.is_none() {
143                params.body.as_mut().unwrap().data.as_mut().unwrap().attributes.as_mut().unwrap().page = Some(crate::datadogV2::model::FlakyTestsSearchPageOptions::new());
144            }
145            if params.body.as_ref().unwrap().data.as_ref().unwrap().attributes.as_ref().unwrap().page.as_ref().unwrap().limit.is_none() {
146                params.body.as_mut().unwrap().data.as_mut().unwrap().attributes.as_mut().unwrap().page.as_mut().unwrap().limit = Some(page_size);
147            } else {
148                page_size = params.body.as_ref().unwrap().data.as_ref().unwrap().attributes.as_ref().unwrap().page.as_ref().unwrap().limit.unwrap().clone();
149            }
150            loop {
151                let resp = self.search_flaky_tests(params.clone()).await?;
152                let Some(data) = resp.data else { break };
153
154                let r = data;
155                let count = r.len();
156                for team in r {
157                    yield team;
158                }
159
160                if count < page_size as usize {
161                    break;
162                }
163                let Some(meta) = resp.meta else { break };
164                let Some(pagination) = meta.pagination else { break };
165                let Some(next_page) = pagination.next_page.unwrap() else { break };
166
167                params.body.as_mut().unwrap().data.as_mut().unwrap().attributes.as_mut().unwrap().page.as_mut().unwrap().cursor = Some(next_page);
168            }
169        }
170    }
171
172    /// List endpoint returning flaky tests from Flaky Test Management. Results are paginated.
173    pub async fn search_flaky_tests_with_http_info(
174        &self,
175        params: SearchFlakyTestsOptionalParams,
176    ) -> Result<
177        datadog::ResponseContent<crate::datadogV2::model::FlakyTestsSearchResponse>,
178        datadog::Error<SearchFlakyTestsError>,
179    > {
180        let local_configuration = &self.config;
181        let operation_id = "v2.search_flaky_tests";
182        if local_configuration.is_unstable_operation_enabled(operation_id) {
183            warn!("Using unstable operation {operation_id}");
184        } else {
185            let local_error = datadog::UnstableOperationDisabledError {
186                msg: "Operation 'v2.search_flaky_tests' is not enabled".to_string(),
187            };
188            return Err(datadog::Error::UnstableOperationDisabledError(local_error));
189        }
190
191        // unbox and build optional parameters
192        let body = params.body;
193
194        let local_client = &self.client;
195
196        let local_uri_str = format!(
197            "{}/api/v2/test/flaky-test-management/tests",
198            local_configuration.get_operation_host(operation_id)
199        );
200        let mut local_req_builder =
201            local_client.request(reqwest::Method::POST, local_uri_str.as_str());
202
203        // build headers
204        let mut headers = HeaderMap::new();
205        headers.insert("Content-Type", HeaderValue::from_static("application/json"));
206        headers.insert("Accept", HeaderValue::from_static("application/json"));
207
208        // build user agent
209        match HeaderValue::from_str(local_configuration.user_agent.as_str()) {
210            Ok(user_agent) => headers.insert(reqwest::header::USER_AGENT, user_agent),
211            Err(e) => {
212                log::warn!("Failed to parse user agent header: {e}, falling back to default");
213                headers.insert(
214                    reqwest::header::USER_AGENT,
215                    HeaderValue::from_static(datadog::DEFAULT_USER_AGENT.as_str()),
216                )
217            }
218        };
219
220        // build auth
221        if let Some(local_key) = local_configuration.auth_keys.get("apiKeyAuth") {
222            headers.insert(
223                "DD-API-KEY",
224                HeaderValue::from_str(local_key.key.as_str())
225                    .expect("failed to parse DD-API-KEY header"),
226            );
227        };
228        if let Some(local_key) = local_configuration.auth_keys.get("appKeyAuth") {
229            headers.insert(
230                "DD-APPLICATION-KEY",
231                HeaderValue::from_str(local_key.key.as_str())
232                    .expect("failed to parse DD-APPLICATION-KEY header"),
233            );
234        };
235
236        // build body parameters
237        let output = Vec::new();
238        let mut ser = serde_json::Serializer::with_formatter(output, datadog::DDFormatter);
239        if body.serialize(&mut ser).is_ok() {
240            if let Some(content_encoding) = headers.get("Content-Encoding") {
241                match content_encoding.to_str().unwrap_or_default() {
242                    "gzip" => {
243                        let mut enc = GzEncoder::new(Vec::new(), Compression::default());
244                        let _ = enc.write_all(ser.into_inner().as_slice());
245                        match enc.finish() {
246                            Ok(buf) => {
247                                local_req_builder = local_req_builder.body(buf);
248                            }
249                            Err(e) => return Err(datadog::Error::Io(e)),
250                        }
251                    }
252                    "deflate" => {
253                        let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
254                        let _ = enc.write_all(ser.into_inner().as_slice());
255                        match enc.finish() {
256                            Ok(buf) => {
257                                local_req_builder = local_req_builder.body(buf);
258                            }
259                            Err(e) => return Err(datadog::Error::Io(e)),
260                        }
261                    }
262                    "zstd1" => {
263                        let mut enc = zstd::stream::Encoder::new(Vec::new(), 0).unwrap();
264                        let _ = enc.write_all(ser.into_inner().as_slice());
265                        match enc.finish() {
266                            Ok(buf) => {
267                                local_req_builder = local_req_builder.body(buf);
268                            }
269                            Err(e) => return Err(datadog::Error::Io(e)),
270                        }
271                    }
272                    _ => {
273                        local_req_builder = local_req_builder.body(ser.into_inner());
274                    }
275                }
276            } else {
277                local_req_builder = local_req_builder.body(ser.into_inner());
278            }
279        }
280
281        local_req_builder = local_req_builder.headers(headers);
282        let local_req = local_req_builder.build()?;
283        log::debug!("request content: {:?}", local_req.body());
284        let local_resp = local_client.execute(local_req).await?;
285
286        let local_status = local_resp.status();
287        let local_content = local_resp.text().await?;
288        log::debug!("response content: {}", local_content);
289
290        if !local_status.is_client_error() && !local_status.is_server_error() {
291            match serde_json::from_str::<crate::datadogV2::model::FlakyTestsSearchResponse>(
292                &local_content,
293            ) {
294                Ok(e) => {
295                    return Ok(datadog::ResponseContent {
296                        status: local_status,
297                        content: local_content,
298                        entity: Some(e),
299                    })
300                }
301                Err(e) => return Err(datadog::Error::Serde(e)),
302            };
303        } else {
304            let local_entity: Option<SearchFlakyTestsError> =
305                serde_json::from_str(&local_content).ok();
306            let local_error = datadog::ResponseContent {
307                status: local_status,
308                content: local_content,
309                entity: local_entity,
310            };
311            Err(datadog::Error::ResponseError(local_error))
312        }
313    }
314}