Skip to content

Commit 94cdfdd

Browse files
feat(cloud): add cost-report API support (Beta) (#479)
Add support for the new Redis Cloud cost-report API endpoints: - POST /cost-report - Generate cost reports in FOCUS format - GET /cost-report/{costReportId} - Download generated reports Library changes (redis-cloud): - Add cost_report module with CostReportHandler - Add CostReportCreateRequest with builder pattern - Add CostReportFormat, SubscriptionType, Tag types - Add get_bytes() method to CloudClient for binary downloads CLI changes (redisctl): - Add 'cloud cost-report generate' command with filters: - Date range (start-date, end-date) - Format (csv, json) - Subscription IDs, database IDs - Subscription type (pro, essentials) - Regions and tags - Add 'cloud cost-report download' command - Add CLI help tests The cost report API is currently in Beta.
1 parent 6f79b0e commit 94cdfdd

File tree

8 files changed

+866
-0
lines changed

8 files changed

+866
-0
lines changed

crates/redis-cloud/src/client.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,50 @@ impl CloudClient {
271271
self.get(path).await
272272
}
273273

274+
/// Execute GET request returning raw bytes
275+
///
276+
/// Useful for downloading binary content like cost reports or other files.
277+
#[instrument(skip(self), fields(method = "GET"))]
278+
pub async fn get_bytes(&self, path: &str) -> Result<Vec<u8>> {
279+
let url = self.normalize_url(path);
280+
debug!("GET {} (bytes)", url);
281+
282+
let response = self
283+
.client
284+
.get(&url)
285+
.header("x-api-key", &self.api_key)
286+
.header("x-api-secret-key", &self.api_secret)
287+
.send()
288+
.await?;
289+
290+
trace!("Response status: {}", response.status());
291+
let status = response.status();
292+
293+
if status.is_success() {
294+
response
295+
.bytes()
296+
.await
297+
.map(|b| b.to_vec())
298+
.map_err(|e| RestError::ConnectionError(format!("Failed to read response: {}", e)))
299+
} else {
300+
let text = response.text().await.unwrap_or_default();
301+
302+
match status.as_u16() {
303+
400 => Err(RestError::BadRequest { message: text }),
304+
401 => Err(RestError::AuthenticationFailed { message: text }),
305+
403 => Err(RestError::Forbidden { message: text }),
306+
404 => Err(RestError::NotFound { message: text }),
307+
412 => Err(RestError::PreconditionFailed),
308+
500 => Err(RestError::InternalServerError { message: text }),
309+
503 => Err(RestError::ServiceUnavailable { message: text }),
310+
_ => Err(RestError::ApiError {
311+
code: status.as_u16(),
312+
message: text,
313+
}),
314+
}
315+
}
316+
}
317+
274318
/// Execute raw POST request with JSON body
275319
#[instrument(skip(self, body), fields(method = "POST"))]
276320
pub async fn post_raw(&self, path: &str, body: serde_json::Value) -> Result<serde_json::Value> {

0 commit comments

Comments
 (0)