use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::types::{Artifact, Message};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TaskState {
#[serde(rename = "TASK_STATE_UNSPECIFIED")]
Unspecified,
#[serde(rename = "TASK_STATE_SUBMITTED")]
Submitted,
#[serde(rename = "TASK_STATE_WORKING")]
Working,
#[serde(rename = "TASK_STATE_COMPLETED")]
Completed,
#[serde(rename = "TASK_STATE_FAILED")]
Failed,
#[serde(rename = "TASK_STATE_CANCELED")]
Canceled,
#[serde(rename = "TASK_STATE_REJECTED")]
Rejected,
#[serde(rename = "TASK_STATE_INPUT_REQUIRED")]
InputRequired,
#[serde(rename = "TASK_STATE_AUTH_REQUIRED")]
AuthRequired,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TaskStatus {
pub state: TaskState,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Task {
pub id: String,
#[serde(rename = "contextId", skip_serializing_if = "Option::is_none")]
pub context_id: Option<String>,
pub status: TaskStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub artifacts: Option<Vec<Artifact>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub history: Option<Vec<Message>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[cfg(test)]
mod tests {
use super::*;
fn submitted_task(id: &str) -> Task {
Task {
id: id.to_string(),
context_id: None,
status: TaskStatus {
state: TaskState::Submitted,
message: None,
timestamp: None,
},
artifacts: None,
history: None,
metadata: None,
}
}
#[test]
fn task_state_serializes_to_uppercase_snake() {
assert_eq!(
serde_json::to_string(&TaskState::Submitted).unwrap(),
r#""TASK_STATE_SUBMITTED""#
);
assert_eq!(
serde_json::to_string(&TaskState::Completed).unwrap(),
r#""TASK_STATE_COMPLETED""#
);
assert_eq!(
serde_json::to_string(&TaskState::Failed).unwrap(),
r#""TASK_STATE_FAILED""#
);
}
#[test]
fn all_task_states_roundtrip() {
let states = [
TaskState::Unspecified,
TaskState::Submitted,
TaskState::Working,
TaskState::Completed,
TaskState::Failed,
TaskState::Canceled,
TaskState::Rejected,
TaskState::InputRequired,
TaskState::AuthRequired,
];
for state in states {
let json = serde_json::to_string(&state).unwrap();
let back: TaskState = serde_json::from_str(&json).unwrap();
assert_eq!(back, state);
}
}
#[test]
fn task_status_roundtrip() {
let status = TaskStatus {
state: TaskState::Working,
message: None,
timestamp: Some("2024-01-01T00:00:00Z".to_string()),
};
let json = serde_json::to_string(&status).unwrap();
let back: TaskStatus = serde_json::from_str(&json).unwrap();
assert_eq!(back, status);
}
#[test]
fn task_status_omits_optional_fields_when_none() {
let status = TaskStatus {
state: TaskState::Submitted,
message: None,
timestamp: None,
};
let json = serde_json::to_string(&status).unwrap();
assert!(!json.contains("message"));
assert!(!json.contains("timestamp"));
}
#[test]
fn task_roundtrip_minimal() {
let task = submitted_task("task-001");
let json = serde_json::to_string(&task).unwrap();
let back: Task = serde_json::from_str(&json).unwrap();
assert_eq!(back, task);
}
#[test]
fn task_optional_fields_omitted_when_none() {
let task = submitted_task("t1");
let json = serde_json::to_string(&task).unwrap();
assert!(!json.contains("contextId"));
assert!(!json.contains("artifacts"));
assert!(!json.contains("history"));
assert!(!json.contains("metadata"));
}
#[test]
fn task_json_uses_camel_case_context_id() {
let mut task = submitted_task("t2");
task.context_id = Some("ctx-1".to_string());
let json = serde_json::to_string(&task).unwrap();
assert!(json.contains("contextId"));
assert!(!json.contains("context_id"));
}
#[test]
fn task_with_history_roundtrip() {
use crate::types::Message;
let mut task = submitted_task("t3");
task.history = Some(vec![Message::user_text("hello")]);
let json = serde_json::to_string(&task).unwrap();
let back: Task = serde_json::from_str(&json).unwrap();
assert_eq!(back.history.as_ref().unwrap().len(), 1);
}
}