use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
Working,
#[serde(rename = "input_required")]
InputRequired,
Completed,
Failed,
Cancelled,
}
impl TaskStatus {
pub fn is_terminal(&self) -> bool {
matches!(
self,
TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Cancelled
)
}
pub fn is_active(&self) -> bool {
!self.is_terminal()
}
pub fn can_transition_to(&self, _next: &TaskStatus) -> bool {
match self {
TaskStatus::Working => true, TaskStatus::InputRequired => true, TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Cancelled => false, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Task {
#[serde(rename = "taskId")]
pub task_id: String,
pub status: TaskStatus,
#[serde(rename = "statusMessage", skip_serializing_if = "Option::is_none")]
pub status_message: Option<String>,
#[serde(rename = "createdAt")]
pub created_at: String,
#[serde(rename = "lastUpdatedAt")]
pub last_updated_at: String,
pub ttl: Option<u64>,
#[serde(rename = "pollInterval", skip_serializing_if = "Option::is_none")]
pub poll_interval: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TaskMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RelatedTaskMetadata {
#[serde(rename = "taskId")]
pub task_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateTaskResult {
pub task: Task,
#[serde(skip_serializing_if = "Option::is_none")]
pub _meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskRequest {
#[serde(rename = "taskId")]
pub task_id: String,
}
pub type GetTaskResult = Task;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskPayloadRequest {
#[serde(rename = "taskId")]
pub task_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskPayloadResult {
#[serde(flatten)]
pub result: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub _meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ListTasksRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListTasksResult {
pub tasks: Vec<Task>,
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub _meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelTaskRequest {
#[serde(rename = "taskId")]
pub task_id: String,
}
pub type CancelTaskResult = Task;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskStatusNotification {
#[serde(rename = "taskId")]
pub task_id: String,
pub status: TaskStatus,
#[serde(rename = "statusMessage", skip_serializing_if = "Option::is_none")]
pub status_message: Option<String>,
#[serde(rename = "createdAt")]
pub created_at: String,
pub ttl: Option<u64>,
#[serde(rename = "pollInterval", skip_serializing_if = "Option::is_none")]
pub poll_interval: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub _meta: Option<HashMap<String, serde_json::Value>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_task_status_terminal() {
assert!(!TaskStatus::Working.is_terminal());
assert!(!TaskStatus::InputRequired.is_terminal());
assert!(TaskStatus::Completed.is_terminal());
assert!(TaskStatus::Failed.is_terminal());
assert!(TaskStatus::Cancelled.is_terminal());
}
#[test]
fn test_task_status_active() {
assert!(TaskStatus::Working.is_active());
assert!(TaskStatus::InputRequired.is_active());
assert!(!TaskStatus::Completed.is_active());
assert!(!TaskStatus::Failed.is_active());
assert!(!TaskStatus::Cancelled.is_active());
}
#[test]
fn test_task_status_transitions() {
assert!(TaskStatus::Working.can_transition_to(&TaskStatus::InputRequired));
assert!(TaskStatus::Working.can_transition_to(&TaskStatus::Completed));
assert!(TaskStatus::Working.can_transition_to(&TaskStatus::Failed));
assert!(TaskStatus::Working.can_transition_to(&TaskStatus::Cancelled));
assert!(TaskStatus::InputRequired.can_transition_to(&TaskStatus::Working));
assert!(TaskStatus::InputRequired.can_transition_to(&TaskStatus::Completed));
assert!(!TaskStatus::Completed.can_transition_to(&TaskStatus::Working));
assert!(!TaskStatus::Failed.can_transition_to(&TaskStatus::Working));
assert!(!TaskStatus::Cancelled.can_transition_to(&TaskStatus::Working));
}
#[test]
fn test_task_status_serialization() {
assert_eq!(
serde_json::to_string(&TaskStatus::Working).unwrap(),
"\"working\""
);
assert_eq!(
serde_json::to_string(&TaskStatus::InputRequired).unwrap(),
"\"input_required\""
);
assert_eq!(
serde_json::to_string(&TaskStatus::Completed).unwrap(),
"\"completed\""
);
assert_eq!(
serde_json::to_string(&TaskStatus::Failed).unwrap(),
"\"failed\""
);
assert_eq!(
serde_json::to_string(&TaskStatus::Cancelled).unwrap(),
"\"cancelled\""
);
}
#[test]
fn test_task_serialization() {
let task = Task {
task_id: "task-123".to_string(),
status: TaskStatus::Working,
status_message: Some("Processing...".to_string()),
created_at: "2025-11-25T10:30:00Z".to_string(),
last_updated_at: "2025-11-25T10:30:00Z".to_string(),
ttl: Some(60000),
poll_interval: Some(5000),
};
let json = serde_json::to_string(&task).unwrap();
assert!(json.contains("\"taskId\":\"task-123\""));
assert!(json.contains("\"status\":\"working\""));
assert!(json.contains("\"statusMessage\":\"Processing...\""));
assert!(json.contains("\"createdAt\":\"2025-11-25T10:30:00Z\""));
assert!(json.contains("\"lastUpdatedAt\":\"2025-11-25T10:30:00Z\""));
assert!(json.contains("\"ttl\":60000"));
assert!(json.contains("\"pollInterval\":5000"));
let deserialized: Task = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.task_id, "task-123");
assert_eq!(deserialized.status, TaskStatus::Working);
}
#[test]
fn test_task_metadata_serialization() {
let metadata = TaskMetadata { ttl: Some(300000) };
let json = serde_json::to_string(&metadata).unwrap();
assert!(json.contains("\"ttl\":300000"));
let deserialized: TaskMetadata = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.ttl, Some(300000));
let metadata = TaskMetadata { ttl: None };
let json = serde_json::to_string(&metadata).unwrap();
assert_eq!(json, "{}"); }
#[test]
fn test_related_task_metadata() {
let metadata = RelatedTaskMetadata {
task_id: "task-abc".to_string(),
};
let json = serde_json::to_string(&metadata).unwrap();
assert!(json.contains("\"taskId\":\"task-abc\""));
let deserialized: RelatedTaskMetadata = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.task_id, "task-abc");
}
#[test]
fn test_create_task_result() {
let result = CreateTaskResult {
task: Task {
task_id: "task-123".to_string(),
status: TaskStatus::Working,
status_message: None,
created_at: "2025-11-25T10:30:00Z".to_string(),
last_updated_at: "2025-11-25T10:30:00Z".to_string(),
ttl: Some(60000),
poll_interval: Some(5000),
},
_meta: None,
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("\"task\""));
assert!(json.contains("\"taskId\":\"task-123\""));
}
#[test]
fn test_get_task_request() {
let request = GetTaskRequest {
task_id: "task-456".to_string(),
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"taskId\":\"task-456\""));
let deserialized: GetTaskRequest = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.task_id, "task-456");
}
#[test]
fn test_list_tasks_result() {
let result = ListTasksResult {
tasks: vec![
Task {
task_id: "task-1".to_string(),
status: TaskStatus::Working,
status_message: None,
created_at: "2025-11-25T10:30:00Z".to_string(),
last_updated_at: "2025-11-25T10:30:00Z".to_string(),
ttl: Some(60000),
poll_interval: None,
},
Task {
task_id: "task-2".to_string(),
status: TaskStatus::Completed,
status_message: Some("Done".to_string()),
created_at: "2025-11-25T09:00:00Z".to_string(),
last_updated_at: "2025-11-25T09:30:00Z".to_string(),
ttl: Some(30000),
poll_interval: None,
},
],
next_cursor: Some("next-page".to_string()),
_meta: None,
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("\"tasks\""));
assert!(json.contains("\"task-1\""));
assert!(json.contains("\"task-2\""));
assert!(json.contains("\"nextCursor\":\"next-page\""));
}
#[test]
fn test_cancel_task_request() {
let request = CancelTaskRequest {
task_id: "task-789".to_string(),
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"taskId\":\"task-789\""));
}
#[test]
fn test_task_status_notification() {
let notification = TaskStatusNotification {
task_id: "task-999".to_string(),
status: TaskStatus::Completed,
status_message: Some("Task finished successfully".to_string()),
created_at: "2025-11-25T10:30:00Z".to_string(),
ttl: Some(60000),
poll_interval: None,
_meta: None,
};
let json = serde_json::to_string(¬ification).unwrap();
assert!(json.contains("\"taskId\":\"task-999\""));
assert!(json.contains("\"status\":\"completed\""));
assert!(json.contains("\"statusMessage\":\"Task finished successfully\""));
}
}