Skip to main content

graph_flow/
task.rs

1//! Task definition and execution control.
2//!
3//! This module contains the core [`Task`] trait and related types for defining
4//! workflow steps and controlling execution flow.
5//!
6//! # Examples
7//!
8//! ## Basic Task Implementation
9//!
10//! ```rust
11//! use graph_flow::{Task, TaskResult, NextAction, Context};
12//! use async_trait::async_trait;
13//!
14//! struct HelloTask;
15//!
16//! #[async_trait]
17//! impl Task for HelloTask {
18//!     fn id(&self) -> &str {
19//!         "hello_task"
20//!     }
21//!
22//!     async fn run(&self, context: Context) -> graph_flow::Result<TaskResult> {
23//!         let name: String = context.get("name").await.unwrap_or("World".to_string());
24//!         let greeting = format!("Hello, {}!", name);
25//!         
26//!         // Store result for next task
27//!         context.set("greeting", greeting.clone()).await;
28//!         
29//!         Ok(TaskResult::new(Some(greeting), NextAction::Continue))
30//!     }
31//! }
32//! ```
33//!
34//! ## Task with Different Control Flow
35//!
36//! ```rust
37//! # use graph_flow::{Task, TaskResult, NextAction, Context};
38//! # use async_trait::async_trait;
39//! struct ConditionalTask;
40//!
41//! #[async_trait]
42//! impl Task for ConditionalTask {
43//!     fn id(&self) -> &str {
44//!         "conditional_task"
45//!     }
46//!
47//!     async fn run(&self, context: Context) -> graph_flow::Result<TaskResult> {
48//!         let user_input: Option<String> = context.get("user_input").await;
49//!         
50//!         match user_input {
51//!             Some(input) if !input.is_empty() => {
52//!                 // Process input and continue automatically
53//!                 context.set("processed", input.to_uppercase()).await;
54//!                 Ok(TaskResult::new(
55//!                     Some("Input processed".to_string()),
56//!                     NextAction::ContinueAndExecute
57//!                 ))
58//!             }
59//!             _ => {
60//!                 // Wait for user input
61//!                 Ok(TaskResult::new(
62//!                     Some("Please provide input".to_string()),
63//!                     NextAction::WaitForInput
64//!                 ))
65//!             }
66//!         }
67//!     }
68//! }
69//! ```
70
71use async_trait::async_trait;
72use serde::{Deserialize, Serialize};
73
74use crate::{context::Context, error::Result};
75
76/// Result of a task execution.
77///
78/// Contains the response to send to the user and the next action to take.
79/// The `task_id` field is automatically set by the graph execution engine.
80///
81/// # Examples
82///
83/// ```rust
84/// use graph_flow::{TaskResult, NextAction};
85///
86/// // Basic task result
87/// let result = TaskResult::new(
88///     Some("Task completed successfully".to_string()),
89///     NextAction::Continue
90/// );
91///
92/// // Task result with status message
93/// let result = TaskResult::new_with_status(
94///     Some("Data validated".to_string()),
95///     NextAction::Continue,
96///     Some("All validation checks passed".to_string())
97/// );
98///
99/// // Convenience methods
100/// let result = TaskResult::move_to_next();        // Continue to next task
101/// let result = TaskResult::move_to_next_direct(); // Continue and execute immediately
102/// ```
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct TaskResult {
105    /// Response to send to the user
106    pub response: Option<String>,
107    /// Next action to take
108    pub next_action: NextAction,
109    /// ID of the task that generated this result
110    pub task_id: String,
111    /// Optional status message that describes the current state of the task
112    pub status_message: Option<String>,
113}
114
115impl TaskResult {
116    /// Create a new TaskResult with the given response and next action.
117    ///
118    /// The task_id will be set automatically by the graph execution engine.
119    ///
120    /// # Examples
121    ///
122    /// ```rust
123    /// use graph_flow::{TaskResult, NextAction};
124    ///
125    /// let result = TaskResult::new(
126    ///     Some("Hello, World!".to_string()),
127    ///     NextAction::Continue
128    /// );
129    /// ```
130    pub fn new(response: Option<String>, next_action: NextAction) -> Self {
131        Self {
132            response,
133            next_action,
134            task_id: String::new(),
135            status_message: None,
136        }
137    }
138
139    /// Create a new TaskResult with response, next action, and status message.
140    ///
141    /// The status message is used to describe the current state of the task.
142    /// It's only persisted in the context but not returned to the user.
143    /// Specifically aimed at debugging and logging.
144    ///
145    /// # Examples
146    ///
147    /// ```rust
148    /// use graph_flow::{TaskResult, NextAction};
149    ///
150    /// let result = TaskResult::new_with_status(
151    ///     Some("Data processed".to_string()),
152    ///     NextAction::Continue,
153    ///     Some("Processing completed with 95% confidence".to_string())
154    /// );
155    /// ```
156    pub fn new_with_status(
157        response: Option<String>,
158        next_action: NextAction,
159        status_message: Option<String>,
160    ) -> Self {
161        Self {
162            response,
163            next_action,
164            task_id: String::new(),
165            status_message,
166        }
167    }
168
169    /// Create a TaskResult that moves to the next task (step-by-step execution).
170    ///
171    /// This is a convenience method equivalent to:
172    /// ```rust
173    /// # use graph_flow::{TaskResult, NextAction};
174    /// TaskResult::new(None, NextAction::Continue);
175    /// ```
176    pub fn move_to_next() -> Self {
177        Self {
178            response: None,
179            next_action: NextAction::Continue,
180            task_id: String::new(),
181            status_message: None,
182        }
183    }
184
185    /// Create a TaskResult that moves to the next task and executes it immediately.
186    ///
187    /// This is a convenience method equivalent to:
188    /// ```rust
189    /// # use graph_flow::{TaskResult, NextAction};
190    /// TaskResult::new(None, NextAction::ContinueAndExecute);
191    /// ```
192    pub fn move_to_next_direct() -> Self {
193        Self {
194            response: None,
195            next_action: NextAction::ContinueAndExecute,
196            task_id: String::new(),
197            status_message: None,
198        }
199    }
200}
201
202/// Defines what should happen after a task completes.
203///
204/// This enum controls the flow of execution in your workflow graph.
205/// Different variants provide different execution behaviors.
206///
207/// # Examples
208///
209/// ```rust
210/// use graph_flow::{NextAction, TaskResult};
211///
212/// // Step-by-step execution (pause after this task)
213/// let result = TaskResult::new(
214///     Some("Step 1 complete".to_string()),
215///     NextAction::Continue
216/// );
217///
218/// // Continuous execution (run next task immediately)
219/// let result = TaskResult::new(
220///     Some("Processing...".to_string()),
221///     NextAction::ContinueAndExecute
222/// );
223///
224/// // Wait for user input
225/// let result = TaskResult::new(
226///     Some("Please provide more information".to_string()),
227///     NextAction::WaitForInput
228/// );
229///
230/// // Jump to specific task
231/// let result = TaskResult::new(
232///     Some("Redirecting to error handler".to_string()),
233///     NextAction::GoTo("error_handler".to_string())
234/// );
235///
236/// // End the workflow
237/// let result = TaskResult::new(
238///     Some("Workflow completed!".to_string()),
239///     NextAction::End
240/// );
241/// ```
242#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
243pub enum NextAction {
244    /// Continue to the next task in the default path (step-by-step execution).
245    ///
246    /// The workflow will pause after this task and wait for the next
247    /// execution call. This gives you control over when the next task runs.
248    ///
249    /// Best for: Interactive applications, web services, debugging
250    Continue,
251
252    /// Continue to the next task and execute it immediately (continuous execution).
253    ///
254    /// The workflow will automatically proceed to the next task without
255    /// pausing. This creates a recursive execution until the workflow
256    /// reaches `End`, `WaitForInput`, or an error.
257    ///
258    /// Best for: Batch processing, automated workflows
259    ContinueAndExecute,
260
261    /// Go to a specific task by ID.
262    ///
263    /// Jump directly to the specified task, skipping the normal edge-based
264    /// flow. Useful for error handling, loops, or dynamic routing.
265    ///
266    /// # Examples
267    ///
268    /// ```rust
269    /// # use graph_flow::{NextAction, TaskResult};
270    /// // Jump to error handler
271    /// let result = TaskResult::new(
272    ///     Some("Error detected, routing to handler".to_string()),
273    ///     NextAction::GoTo("error_handler".to_string())
274    /// );
275    ///
276    /// // Create a retry loop
277    /// let result = TaskResult::new(
278    ///     Some("Retrying...".to_string()),
279    ///     NextAction::GoTo("validation_task".to_string())
280    /// );
281    /// ```
282    GoTo(String),
283
284    /// Go back to the previous task.
285    ///
286    /// Note: This was never implemented - it behaves like `WaitForInput`
287    /// (stays at the current task). It will be removed in a future release.
288    #[deprecated(
289        since = "0.5.2",
290        note = "GoBack is not implemented (it behaves like WaitForInput) and will be removed; \
291                use NextAction::GoTo(task_id) for explicit navigation"
292    )]
293    GoBack,
294
295    /// End the graph execution.
296    ///
297    /// Terminates the workflow completely. No further tasks will be executed.
298    End,
299
300    /// Wait for user input before continuing.
301    ///
302    /// Pauses the workflow and waits for external input. The workflow
303    /// will stay at the current task until new data is provided and
304    /// execution is resumed.
305    ///
306    /// Best for: Human-in-the-loop workflows, interactive applications
307    WaitForInput,
308}
309
310/// Core trait that all tasks must implement.
311///
312/// Tasks are the building blocks of your workflow. Each task represents
313/// a unit of work that can access shared context and control the flow
314/// of execution.
315///
316/// # Examples
317///
318/// ## Basic Task
319///
320/// ```rust
321/// use graph_flow::{Task, TaskResult, NextAction, Context};
322/// use async_trait::async_trait;
323///
324/// struct GreetingTask;
325///
326/// #[async_trait]
327/// impl Task for GreetingTask {
328///     fn id(&self) -> &str {
329///         "greeting"
330///     }
331///
332///     async fn run(&self, context: Context) -> graph_flow::Result<TaskResult> {
333///         let name: String = context.get("name").await.unwrap_or("World".to_string());
334///         let greeting = format!("Hello, {}!", name);
335///         
336///         Ok(TaskResult::new(Some(greeting), NextAction::Continue))
337///     }
338/// }
339/// ```
340///
341/// ## Task with Default ID
342///
343/// ```rust
344/// # use graph_flow::{Task, TaskResult, NextAction, Context};
345/// # use async_trait::async_trait;
346/// struct DefaultIdTask;
347///
348/// #[async_trait]
349/// impl Task for DefaultIdTask {
350///     // id() is automatically implemented using the type name
351///     
352///     async fn run(&self, context: Context) -> graph_flow::Result<TaskResult> {
353///         Ok(TaskResult::new(None, NextAction::End))
354///     }
355/// }
356/// ```
357///
358/// ## Complex Task with Error Handling
359///
360/// ```rust
361/// # use graph_flow::{Task, TaskResult, NextAction, Context, GraphError};
362/// # use async_trait::async_trait;
363/// struct ValidationTask {
364///     max_retries: usize,
365/// }
366///
367/// #[async_trait]
368/// impl Task for ValidationTask {
369///     fn id(&self) -> &str {
370///         "validator"
371///     }
372///
373///     async fn run(&self, context: Context) -> graph_flow::Result<TaskResult> {
374///         let data: Option<String> = context.get("data").await;
375///         let retry_count: usize = context.get("retry_count").await.unwrap_or(0);
376///         
377///         match data {
378///             Some(data) if self.validate(&data) => {
379///                 context.set("retry_count", 0).await; // Reset counter
380///                 Ok(TaskResult::new(
381///                     Some("Validation passed".to_string()),
382///                     NextAction::Continue
383///                 ))
384///             }
385///             Some(_) if retry_count < self.max_retries => {
386///                 context.set("retry_count", retry_count + 1).await;
387///                 Ok(TaskResult::new(
388///                     Some("Validation failed, retrying...".to_string()),
389///                     NextAction::GoTo("data_input".to_string())
390///                 ))
391///             }
392///             _ => {
393///                 Err(GraphError::TaskExecutionFailed(
394///                     "Validation failed after max retries".to_string()
395///                 ))
396///             }
397///         }
398///     }
399/// }
400///
401/// impl ValidationTask {
402///     fn validate(&self, data: &str) -> bool {
403///         !data.is_empty() && data.len() > 5
404///     }
405/// }
406/// ```
407#[async_trait]
408pub trait Task: Send + Sync {
409    /// Unique identifier for this task.
410    ///
411    /// By default, this returns the type name of the implementing struct.
412    /// Override this method if you need a custom identifier.
413    ///
414    /// # Examples
415    ///
416    /// ```rust
417    /// # use graph_flow::Task;
418    /// # use async_trait::async_trait;
419    /// # use graph_flow::{TaskResult, NextAction, Context};
420    /// // Using default implementation (type name)
421    /// struct MyTask;
422    ///
423    /// #[async_trait]
424    /// impl Task for MyTask {
425    ///     // id() will return "my_module::MyTask"
426    ///     async fn run(&self, _context: Context) -> graph_flow::Result<TaskResult> {
427    ///         Ok(TaskResult::new(None, NextAction::End))
428    ///     }
429    /// }
430    ///
431    /// // Using custom ID
432    /// struct CustomTask;
433    ///
434    /// #[async_trait]
435    /// impl Task for CustomTask {
436    ///     fn id(&self) -> &str {
437    ///         "custom_task_id"
438    ///     }
439    ///
440    ///     async fn run(&self, _context: Context) -> graph_flow::Result<TaskResult> {
441    ///         Ok(TaskResult::new(None, NextAction::End))
442    ///     }
443    /// }
444    /// ```
445    fn id(&self) -> &str {
446        std::any::type_name::<Self>()
447    }
448
449    /// Execute the task with the given context.
450    ///
451    /// This is where you implement your task's logic. You have access to
452    /// the shared context for reading input data and storing results.
453    ///
454    /// # Parameters
455    ///
456    /// * `context` - Shared context containing workflow state and data
457    ///
458    /// # Returns
459    ///
460    /// Returns a `Result<TaskResult>` where:
461    /// - `Ok(TaskResult)` indicates successful execution
462    /// - `Err(GraphError)` indicates an error that should stop the workflow
463    ///
464    /// # Examples
465    ///
466    /// ```rust
467    /// # use graph_flow::{Task, TaskResult, NextAction, Context};
468    /// # use async_trait::async_trait;
469    /// struct DataProcessor;
470    ///
471    /// #[async_trait]
472    /// impl Task for DataProcessor {
473    ///     fn id(&self) -> &str {
474    ///         "data_processor"
475    ///     }
476    ///
477    ///     async fn run(&self, context: Context) -> graph_flow::Result<TaskResult> {
478    ///         // Read input from context
479    ///         let input: String = context.get("raw_data").await
480    ///             .unwrap_or_default();
481    ///         
482    ///         // Process the data
483    ///         let processed = self.process_data(&input).await?;
484    ///         
485    ///         // Store result for next task
486    ///         context.set("processed_data", processed.clone()).await;
487    ///         
488    ///         // Return result with next action
489    ///         Ok(TaskResult::new(
490    ///             Some(format!("Processed {} bytes", processed.len())),
491    ///             NextAction::Continue
492    ///         ))
493    ///     }
494    /// }
495    ///
496    /// impl DataProcessor {
497    ///     async fn process_data(&self, input: &str) -> graph_flow::Result<String> {
498    ///         // Your processing logic here
499    ///         Ok(input.to_uppercase())
500    ///     }
501    /// }
502    /// ```
503    async fn run(&self, context: Context) -> Result<TaskResult>;
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509    use async_trait::async_trait;
510
511    struct TestTaskWithDefaultId;
512
513    #[async_trait]
514    impl Task for TestTaskWithDefaultId {
515        async fn run(&self, _context: Context) -> Result<TaskResult> {
516            Ok(TaskResult::new(None, NextAction::End))
517        }
518    }
519
520    struct TestTaskWithCustomId;
521
522    #[async_trait]
523    impl Task for TestTaskWithCustomId {
524        fn id(&self) -> &str {
525            "custom_task_id"
526        }
527
528        async fn run(&self, _context: Context) -> Result<TaskResult> {
529            Ok(TaskResult::new(None, NextAction::End))
530        }
531    }
532
533    #[test]
534    fn test_default_id_implementation() {
535        let task = TestTaskWithDefaultId;
536        assert_eq!(task.id(), "graph_flow::task::tests::TestTaskWithDefaultId");
537    }
538
539    #[test]
540    fn test_custom_id_override() {
541        let task = TestTaskWithCustomId;
542        assert_eq!(task.id(), "custom_task_id");
543    }
544}