redis_cloud/tasks.rs
1//! Asynchronous task tracking and management
2//!
3//! This module provides functionality for tracking long-running operations in
4//! Redis Cloud. Many API operations are asynchronous and return a task ID that
5//! can be used to monitor progress and completion status.
6//!
7//! # Overview
8//!
9//! Redis Cloud uses tasks for operations that may take time to complete, such as:
10//! - Creating or deleting subscriptions
11//! - Database creation, updates, and deletion
12//! - Backup and restore operations
13//! - Import/export operations
14//! - Network configuration changes
15//!
16//! # Task Lifecycle
17//!
18//! 1. **Initiated**: Task is created and queued
19//! 2. **Processing**: Task is being executed
20//! 3. **Completed**: Task finished successfully
21//! 4. **Failed**: Task encountered an error
22//!
23//! # Key Features
24//!
25//! - **Task Status**: Check current status of any task
26//! - **Progress Tracking**: Monitor completion percentage for long operations
27//! - **Result Retrieval**: Get operation results once completed
28//! - **Error Information**: Access detailed error messages for failed tasks
29//! - **Task History**: Query historical task information
30//!
31//! # Example Usage
32//!
33//! ```no_run
34//! use redis_cloud::{CloudClient, TaskHandler};
35//! use redis_cloud::types::TaskStatus;
36//!
37//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
38//! let client = CloudClient::builder()
39//! .api_key("your-api-key")
40//! .api_secret("your-api-secret")
41//! .build()?;
42//!
43//! let handler = TaskHandler::new(client);
44//!
45//! // Get task status
46//! let task = handler.get_task_by_id("task-123".to_string()).await?;
47//!
48//! // Check if task is complete
49//! if matches!(task.status, Some(TaskStatus::ProcessingCompleted)) {
50//! println!("Task completed successfully");
51//! if let Some(response) = task.response {
52//! println!("Result: {:?}", response);
53//! }
54//! }
55//! # Ok(())
56//! # }
57//! ```
58
59use crate::error::CloudError;
60use crate::{CloudClient, Result};
61
62// Canonical task models live in `crate::types` (#64). Re-exported here so the
63// historical `tasks::TaskStateUpdate` path keeps resolving.
64pub use crate::types::TaskStateUpdate;
65use crate::types::TasksStateUpdate;
66
67// ============================================================================
68// Handler
69// ============================================================================
70
71/// Handler for asynchronous task operations
72///
73/// Tracks and manages long-running operations, providing status updates,
74/// progress monitoring, and result retrieval for asynchronous API calls.
75pub struct TasksHandler {
76 client: CloudClient,
77}
78
79impl TasksHandler {
80 /// Create a new handler
81 #[must_use]
82 pub fn new(client: CloudClient) -> Self {
83 Self { client }
84 }
85
86 /// Get tasks
87 /// Gets a list of all currently running tasks for this account.
88 ///
89 /// The OpenAPI spec defines the response as `TasksStateUpdate { tasks: [...] }`
90 /// (a wrapper object). In practice the API also returns:
91 /// - an empty object `{}` when there are no tasks,
92 /// - and historically a bare JSON array.
93 ///
94 /// All three shapes deserialize cleanly to `Vec<TaskStateUpdate>`. Any other
95 /// shape surfaces as [`CloudError::JsonError`] rather than silently returning
96 /// an empty list, so a future schema change is loud instead of invisible.
97 ///
98 /// GET /tasks
99 pub async fn get_all_tasks(&self) -> Result<Vec<TaskStateUpdate>> {
100 let value: serde_json::Value = self.client.get_raw("/tasks").await?;
101 match value {
102 // Canonical spec shape: {"tasks": [...]}
103 serde_json::Value::Object(ref obj) if obj.contains_key("tasks") => {
104 let wrapped: TasksStateUpdate = serde_json::from_value(value)?;
105 Ok(wrapped.tasks)
106 }
107 // No tasks: API returns `{}` (or null) instead of the wrapper.
108 serde_json::Value::Object(obj) if obj.is_empty() => Ok(Vec::new()),
109 serde_json::Value::Null => Ok(Vec::new()),
110 // Legacy bare-array shape, still tolerated.
111 serde_json::Value::Array(_) => Ok(serde_json::from_value(value)?),
112 other => Err(CloudError::JsonError(format!(
113 "GET /tasks: expected {{\"tasks\": [...]}}, {{}}, null, or a bare array; got {other}"
114 ))),
115 }
116 }
117
118 /// Get tasks (raw JSON)
119 /// Gets a list of all currently running tasks for this account.
120 ///
121 /// GET /tasks
122 pub async fn get_all_tasks_raw(&self) -> Result<serde_json::Value> {
123 self.client.get_raw("/tasks").await
124 }
125
126 /// Get a single task
127 /// Gets details and status of a single task by the Task ID.
128 ///
129 /// GET /tasks/{taskId}
130 ///
131 /// # Example
132 ///
133 /// ```no_run
134 /// use redis_cloud::CloudClient;
135 ///
136 /// # async fn example() -> redis_cloud::Result<()> {
137 /// let client = CloudClient::builder()
138 /// .api_key("your-api-key")
139 /// .api_secret("your-api-secret")
140 /// .build()?;
141 ///
142 /// let task = client.tasks().get_task_by_id("task-id".to_string()).await?;
143 /// println!("Task status: {:?}", task.status);
144 /// # Ok(())
145 /// # }
146 /// ```
147 pub async fn get_task_by_id(&self, task_id: String) -> Result<TaskStateUpdate> {
148 self.client.get(&format!("/tasks/{task_id}")).await
149 }
150
151 // ============================================================================
152 // Simplified aliases
153 // ============================================================================
154
155 /// List tasks (simplified)
156 ///
157 /// Alias for [`get_all_tasks`](Self::get_all_tasks).
158 ///
159 /// # Example
160 ///
161 /// ```no_run
162 /// use redis_cloud::CloudClient;
163 ///
164 /// # async fn example() -> redis_cloud::Result<()> {
165 /// let client = CloudClient::builder()
166 /// .api_key("your-api-key")
167 /// .api_secret("your-api-secret")
168 /// .build()?;
169 ///
170 /// let tasks = client.tasks().list().await?;
171 /// # Ok(())
172 /// # }
173 /// ```
174 pub async fn list(&self) -> Result<Vec<TaskStateUpdate>> {
175 self.get_all_tasks().await
176 }
177
178 /// Get a task by ID (simplified)
179 ///
180 /// Alias for [`get_task_by_id`](Self::get_task_by_id).
181 ///
182 /// # Arguments
183 ///
184 /// * `task_id` - The task ID
185 ///
186 /// # Example
187 ///
188 /// ```no_run
189 /// use redis_cloud::CloudClient;
190 ///
191 /// # async fn example() -> redis_cloud::Result<()> {
192 /// let client = CloudClient::builder()
193 /// .api_key("your-api-key")
194 /// .api_secret("your-api-secret")
195 /// .build()?;
196 ///
197 /// let task = client.tasks().get("task-id".to_string()).await?;
198 /// # Ok(())
199 /// # }
200 /// ```
201 pub async fn get(&self, task_id: String) -> Result<TaskStateUpdate> {
202 self.get_task_by_id(task_id).await
203 }
204}