Skip to main content

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}