Skip to main content

vex_domain/
lib.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
6pub struct ProjectId(pub String);
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct WorkstreamId(pub String);
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct AgentId(pub String);
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct ShellId(pub String);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum AgentType {
20    Claude,
21    Codex,
22}
23
24impl fmt::Display for AgentType {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            AgentType::Claude => write!(f, "Claude"),
28            AgentType::Codex => write!(f, "Codex"),
29        }
30    }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34pub enum AgentState {
35    Idle,
36    Running,
37    NeedsIntervention,
38}
39
40impl fmt::Display for ProjectId {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(f, "{}", self.0)
43    }
44}
45
46impl fmt::Display for WorkstreamId {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "{}", self.0)
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53pub enum WorkstreamState {
54    Active,
55    Paused,
56    Completed,
57    Archived,
58}
59
60/// Validates a slug (used for project names and workstream names).
61/// Must be non-empty, start with alphanumeric, and contain only alphanumeric + hyphens.
62pub fn validate_slug(name: &str) -> bool {
63    !name.is_empty()
64        && name.chars().next().unwrap().is_ascii_alphanumeric()
65        && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
66}
67
68impl fmt::Display for WorkstreamState {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            WorkstreamState::Active => write!(f, "active"),
72            WorkstreamState::Paused => write!(f, "paused"),
73            WorkstreamState::Completed => write!(f, "completed"),
74            WorkstreamState::Archived => write!(f, "archived"),
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn newtype_construction() {
85        let pid = ProjectId("my-project".into());
86        assert_eq!(pid.0, "my-project");
87
88        let wid = WorkstreamId("ws-1".into());
89        assert_eq!(wid.0, "ws-1");
90
91        let aid = AgentId("agent-0".into());
92        assert_eq!(aid.0, "agent-0");
93
94        let sid = ShellId("shell-0".into());
95        assert_eq!(sid.0, "shell-0");
96    }
97
98    #[test]
99    fn agent_type_display() {
100        assert_eq!(AgentType::Claude.to_string(), "Claude");
101        assert_eq!(AgentType::Codex.to_string(), "Codex");
102    }
103
104    #[test]
105    fn newtypes_are_eq_and_hash() {
106        use std::collections::HashSet;
107        let mut set = HashSet::new();
108        set.insert(ProjectId("a".into()));
109        set.insert(ProjectId("b".into()));
110        set.insert(ProjectId("a".into()));
111        assert_eq!(set.len(), 2);
112    }
113}