use crate::identity::{AgentId, UserId};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TaskId([u8; 32]);
impl TaskId {
#[must_use]
pub fn new(title: &str, creator: &AgentId, timestamp: u64) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(title.as_bytes());
hasher.update(creator.as_bytes());
hasher.update(×tamp.to_le_bytes());
let hash = hasher.finalize();
Self(*hash.as_bytes())
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn from_string(s: &str) -> Result<Self, String> {
if s.len() != 64 {
return Err(format!(
"Invalid TaskId length: expected 64 hex chars, got {}",
s.len()
));
}
let bytes = hex::decode(s).map_err(|e| format!("Invalid hex encoding: {}", e))?;
if bytes.len() != 32 {
return Err(format!(
"Invalid TaskId bytes: expected 32 bytes, got {}",
bytes.len()
));
}
let mut array = [0u8; 32];
array.copy_from_slice(&bytes);
Ok(Self(array))
}
}
impl fmt::Display for TaskId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TaskMetadata {
pub title: String,
pub description: String,
pub priority: u8,
pub created_by: AgentId,
pub owner: Option<UserId>,
pub created_at: u64,
pub tags: Vec<String>,
}
impl TaskMetadata {
#[must_use]
pub fn new(
title: impl Into<String>,
description: impl Into<String>,
priority: u8,
created_by: AgentId,
created_at: u64,
) -> Self {
Self {
title: title.into(),
description: description.into(),
priority,
created_by,
owner: None,
created_at,
tags: Vec::new(),
}
}
#[must_use]
pub fn with_default_priority(
title: impl Into<String>,
description: impl Into<String>,
created_by: AgentId,
created_at: u64,
) -> Self {
Self::new(title, description, 128, created_by, created_at)
}
#[must_use]
pub fn with_owner(mut self, user_id: UserId) -> Self {
self.owner = Some(user_id);
self
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
pub fn with_tags<I, S>(mut self, tags: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.tags.extend(tags.into_iter().map(|t| t.into()));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
fn agent(n: u8) -> AgentId {
AgentId([n; 32])
}
#[test]
fn test_task_id_deterministic() {
let agent = agent(1);
let title = "Test task";
let timestamp = 1000;
let id1 = TaskId::new(title, &agent, timestamp);
let id2 = TaskId::new(title, &agent, timestamp);
assert_eq!(id1, id2, "Same inputs should produce same TaskId");
}
#[test]
fn test_task_id_different_titles() {
let agent = agent(1);
let timestamp = 1000;
let id1 = TaskId::new("Title A", &agent, timestamp);
let id2 = TaskId::new("Title B", &agent, timestamp);
assert_ne!(id1, id2, "Different titles should produce different IDs");
}
#[test]
fn test_task_id_different_creators() {
let agent1 = agent(1);
let agent2 = agent(2);
let title = "Same title";
let timestamp = 1000;
let id1 = TaskId::new(title, &agent1, timestamp);
let id2 = TaskId::new(title, &agent2, timestamp);
assert_ne!(id1, id2, "Different creators should produce different IDs");
}
#[test]
fn test_task_id_different_timestamps() {
let agent = agent(1);
let title = "Same title";
let id1 = TaskId::new(title, &agent, 1000);
let id2 = TaskId::new(title, &agent, 2000);
assert_ne!(
id1, id2,
"Different timestamps should produce different IDs"
);
}
#[test]
fn test_task_id_as_bytes() {
let agent = agent(1);
let id = TaskId::new("Test", &agent, 1000);
let bytes = id.as_bytes();
assert_eq!(bytes.len(), 32, "TaskId should be 32 bytes");
}
#[test]
fn test_task_id_from_bytes() {
let original_bytes = [42u8; 32];
let id = TaskId::from_bytes(original_bytes);
assert_eq!(id.as_bytes(), &original_bytes);
}
#[test]
fn test_task_id_display() {
let agent = agent(1);
let id = TaskId::new("Test", &agent, 1000);
let display = format!("{}", id);
assert_eq!(display.len(), 64);
assert!(display.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_task_id_serialization() {
let agent = agent(1);
let id = TaskId::new("Test", &agent, 1000);
let serialized = bincode::serialize(&id).ok().unwrap();
let deserialized: TaskId = bincode::deserialize(&serialized).ok().unwrap();
assert_eq!(id, deserialized);
}
#[test]
fn test_task_metadata_new() {
let agent = agent(1);
let metadata = TaskMetadata::new("Title", "Description", 200, agent, 1234567890);
assert_eq!(metadata.title, "Title");
assert_eq!(metadata.description, "Description");
assert_eq!(metadata.priority, 200);
assert_eq!(metadata.created_by, agent);
assert_eq!(metadata.created_at, 1234567890);
assert!(metadata.tags.is_empty());
}
#[test]
fn test_task_metadata_default_priority() {
let agent = agent(1);
let metadata =
TaskMetadata::with_default_priority("Title", "Description", agent, 1234567890);
assert_eq!(metadata.priority, 128);
}
#[test]
fn test_task_metadata_with_tag() {
let agent = agent(1);
let metadata = TaskMetadata::new("Title", "Desc", 128, agent, 1000).with_tag("backend");
assert_eq!(metadata.tags.len(), 1);
assert_eq!(metadata.tags[0], "backend");
}
#[test]
fn test_task_metadata_with_tags() {
let agent = agent(1);
let metadata = TaskMetadata::new("Title", "Desc", 128, agent, 1000)
.with_tags(vec!["backend", "api", "feature"]);
assert_eq!(metadata.tags.len(), 3);
assert_eq!(metadata.tags[0], "backend");
assert_eq!(metadata.tags[1], "api");
assert_eq!(metadata.tags[2], "feature");
}
#[test]
fn test_task_metadata_chaining() {
let agent = agent(1);
let metadata = TaskMetadata::new("Title", "Desc", 128, agent, 1000)
.with_tag("backend")
.with_tag("urgent")
.with_tags(vec!["api", "feature"]);
assert_eq!(metadata.tags.len(), 4);
assert_eq!(metadata.tags, vec!["backend", "urgent", "api", "feature"]);
}
#[test]
fn test_task_metadata_serialization() {
let agent = agent(1);
let metadata = TaskMetadata::new("Title", "Description", 200, agent, 1234567890)
.with_tags(vec!["tag1", "tag2"]);
let serialized = bincode::serialize(&metadata).ok().unwrap();
let deserialized: TaskMetadata = bincode::deserialize(&serialized).ok().unwrap();
assert_eq!(metadata, deserialized);
}
#[test]
fn test_task_metadata_equality() {
let agent = agent(1);
let meta1 = TaskMetadata::new("Title", "Desc", 128, agent, 1000).with_tag("test");
let meta2 = TaskMetadata::new("Title", "Desc", 128, agent, 1000).with_tag("test");
assert_eq!(meta1, meta2);
}
#[test]
fn test_task_metadata_inequality_different_title() {
let agent = agent(1);
let meta1 = TaskMetadata::new("Title A", "Desc", 128, agent, 1000);
let meta2 = TaskMetadata::new("Title B", "Desc", 128, agent, 1000);
assert_ne!(meta1, meta2);
}
#[test]
fn test_task_metadata_inequality_different_tags() {
let agent = agent(1);
let meta1 = TaskMetadata::new("Title", "Desc", 128, agent, 1000).with_tag("tag1");
let meta2 = TaskMetadata::new("Title", "Desc", 128, agent, 1000).with_tag("tag2");
assert_ne!(meta1, meta2);
}
}