use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SddPhase {
Spec,
Design,
Task,
Coding,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SddStatus {
Pending,
Running,
AwaitingHitl,
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PhaseEventKind {
PhaseStarted,
PhaseCompleted,
PhaseFailed,
AwaitingReview,
ReviewSubmitted,
Log,
ArtifactGenerated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReviewDecision {
Confirm,
Modify,
Supplement,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactKind {
Spec,
Design,
Tasks,
Source,
Preview,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeviceType {
Desktop,
Tablet,
Mobile,
}
impl DeviceType {
pub fn viewport(&self) -> (u32, u32) {
match self {
Self::Desktop => (1280, 800),
Self::Tablet => (768, 1024),
Self::Mobile => (375, 667),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhaseEvent {
pub trace_id: String,
pub feature: String,
pub phase: SddPhase,
pub kind: PhaseEventKind,
pub timestamp: DateTime<Utc>,
pub metadata: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SddSession {
pub session_id: String,
#[serde(skip_serializing)]
pub trace_id: String,
pub feature_name: String,
pub current_phase: SddPhase,
pub status: SddStatus,
pub started_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEvent {
pub trace_id: String,
pub level: String,
pub message: String,
pub timestamp: DateTime<Utc>,
pub fields: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusChangedEvent {
pub session_id: String,
pub old_status: SddStatus,
pub new_status: SddStatus,
pub timestamp: DateTime<Utc>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdd_phase_serde() {
let json = serde_json::to_string(&SddPhase::Spec).unwrap();
assert_eq!(json, "\"spec\"");
let phase: SddPhase = serde_json::from_str("\"design\"").unwrap();
assert_eq!(phase, SddPhase::Design);
}
#[test]
fn test_sdd_status_serde() {
let json = serde_json::to_string(&SddStatus::AwaitingHitl).unwrap();
assert_eq!(json, "\"awaiting_hitl\"");
let status: SddStatus = serde_json::from_str("\"running\"").unwrap();
assert_eq!(status, SddStatus::Running);
}
#[test]
fn test_review_decision_serde() {
let json = serde_json::to_string(&ReviewDecision::Confirm).unwrap();
assert_eq!(json, "\"confirm\"");
let decision: ReviewDecision = serde_json::from_str("\"modify\"").unwrap();
assert_eq!(decision, ReviewDecision::Modify);
}
#[test]
fn test_device_type_viewport() {
assert_eq!(DeviceType::Desktop.viewport(), (1280, 800));
assert_eq!(DeviceType::Tablet.viewport(), (768, 1024));
assert_eq!(DeviceType::Mobile.viewport(), (375, 667));
}
#[test]
fn test_phase_event_serde() {
let event = PhaseEvent {
trace_id: "trace-1".to_string(),
feature: "crm".to_string(),
phase: SddPhase::Spec,
kind: PhaseEventKind::PhaseStarted,
timestamp: Utc::now(),
metadata: serde_json::json!({"key": "value"}),
};
let json = serde_json::to_string(&event).unwrap();
let parsed: PhaseEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.trace_id, "trace-1");
assert_eq!(parsed.feature, "crm");
assert_eq!(parsed.phase, SddPhase::Spec);
}
#[test]
fn test_sdd_session_skip_serializing() {
let session = SddSession {
session_id: "s-1".to_string(),
trace_id: "t-1".to_string(),
feature_name: "test".to_string(),
current_phase: SddPhase::Design,
status: SddStatus::Running,
started_at: Utc::now(),
updated_at: Utc::now(),
};
let json = serde_json::to_string(&session).unwrap();
assert!(!json.contains("trace_id"));
assert!(json.contains("session_id"));
}
#[test]
fn test_log_event_serde() {
let event = LogEvent {
trace_id: "t-1".to_string(),
level: "info".to_string(),
message: "phase started".to_string(),
timestamp: Utc::now(),
fields: serde_json::json!({}),
};
let json = serde_json::to_string(&event).unwrap();
let parsed: LogEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.level, "info");
}
#[test]
fn test_status_changed_event_serde() {
let event = StatusChangedEvent {
session_id: "s-1".to_string(),
old_status: SddStatus::Running,
new_status: SddStatus::AwaitingHitl,
timestamp: Utc::now(),
};
let json = serde_json::to_string(&event).unwrap();
let parsed: StatusChangedEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.old_status, SddStatus::Running);
assert_eq!(parsed.new_status, SddStatus::AwaitingHitl);
}
#[test]
fn test_artifact_kind_serde() {
let json = serde_json::to_string(&ArtifactKind::Spec).unwrap();
assert_eq!(json, "\"spec\"");
let kind: ArtifactKind = serde_json::from_str("\"source\"").unwrap();
assert_eq!(kind, ArtifactKind::Source);
}
}