use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct TaskRequest {
#[serde(default = "Uuid::new_v4")]
pub id: Uuid,
pub skill: String,
pub input: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub caller_agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
#[serde(default = "Utc::now")]
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct TaskResponse {
pub id: Uuid,
pub status: TaskStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub merkle_hash: Option<String>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
Pending,
Running,
Completed,
Failed,
Cancelled,
}
impl TaskRequest {
pub fn new(skill: impl Into<String>, input: serde_json::Value) -> Self {
Self {
id: Uuid::new_v4(),
skill: skill.into(),
input,
caller_agent: None,
nonce: None,
timestamp: Utc::now(),
}
}
pub fn with_caller(mut self, agent: impl Into<String>) -> Self {
self.caller_agent = Some(agent.into());
self
}
pub fn with_nonce(mut self, nonce: impl Into<String>) -> Self {
self.nonce = Some(nonce.into());
self
}
}
impl TaskResponse {
pub fn pending(id: Uuid) -> Self {
Self {
id,
status: TaskStatus::Pending,
result: None,
error: None,
merkle_hash: None,
timestamp: Utc::now(),
}
}
pub fn completed(id: Uuid, result: serde_json::Value, merkle_hash: impl Into<String>) -> Self {
Self {
id,
status: TaskStatus::Completed,
result: Some(result),
error: None,
merkle_hash: Some(merkle_hash.into()),
timestamp: Utc::now(),
}
}
pub fn failed(id: Uuid, error: impl Into<String>) -> Self {
Self {
id,
status: TaskStatus::Failed,
result: None,
error: Some(error.into()),
merkle_hash: None,
timestamp: Utc::now(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_task_request_new() {
let req = TaskRequest::new("verify", serde_json::json!({"claim": "test"}));
assert_eq!(req.skill, "verify");
assert!(req.caller_agent.is_none());
}
#[test]
fn test_task_request_builder() {
let req = TaskRequest::new("verify", serde_json::json!({}))
.with_caller("other-agent")
.with_nonce("abc123");
assert_eq!(req.caller_agent, Some("other-agent".to_string()));
assert_eq!(req.nonce, Some("abc123".to_string()));
}
#[test]
fn test_task_response_completed() {
let id = Uuid::new_v4();
let resp = TaskResponse::completed(id, serde_json::json!({"verified": true}), "hash123");
assert_eq!(resp.id, id);
assert_eq!(resp.status, TaskStatus::Completed);
assert!(resp.result.is_some());
assert_eq!(resp.merkle_hash, Some("hash123".to_string()));
}
#[test]
fn test_task_response_failed() {
let id = Uuid::new_v4();
let resp = TaskResponse::failed(id, "Verification failed");
assert_eq!(resp.status, TaskStatus::Failed);
assert_eq!(resp.error, Some("Verification failed".to_string()));
}
#[test]
fn test_serialization() {
let req = TaskRequest::new("hash", serde_json::json!({"text": "hello"}));
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("hash"));
let resp = TaskResponse::pending(req.id);
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("pending"));
}
}