rusty_beads/types/
issue_type.rs

1//! Issue type definitions.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6
7/// The category of work an issue represents.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
9#[serde(rename_all = "snake_case")]
10pub enum IssueType {
11    /// Defect or malfunction.
12    Bug,
13    /// New capability or enhancement.
14    Feature,
15    /// General work item (default).
16    #[default]
17    Task,
18    /// Large initiative with multiple steps.
19    Epic,
20    /// Routine maintenance.
21    Chore,
22    /// Ephemeral communication.
23    Message,
24    /// Template for issue hierarchies.
25    Molecule,
26    /// Async coordination mechanism.
27    Gate,
28    /// Agent identity bead.
29    Agent,
30    /// Agent role definition.
31    Role,
32    /// Rig identity (multi-repo workspace).
33    Rig,
34    /// Cross-project tracking.
35    Convoy,
36    /// Operational state change record.
37    Event,
38    /// Merge queue entry.
39    MergeRequest,
40    /// Exclusive access slot.
41    Slot,
42}
43
44impl IssueType {
45    /// All valid issue types.
46    pub fn all() -> &'static [IssueType] {
47        &[
48            IssueType::Bug,
49            IssueType::Feature,
50            IssueType::Task,
51            IssueType::Epic,
52            IssueType::Chore,
53            IssueType::Message,
54            IssueType::Molecule,
55            IssueType::Gate,
56            IssueType::Agent,
57            IssueType::Role,
58            IssueType::Rig,
59            IssueType::Convoy,
60            IssueType::Event,
61            IssueType::MergeRequest,
62            IssueType::Slot,
63        ]
64    }
65
66    /// Returns the string representation for database storage.
67    pub fn as_str(&self) -> &'static str {
68        match self {
69            IssueType::Bug => "bug",
70            IssueType::Feature => "feature",
71            IssueType::Task => "task",
72            IssueType::Epic => "epic",
73            IssueType::Chore => "chore",
74            IssueType::Message => "message",
75            IssueType::Molecule => "molecule",
76            IssueType::Gate => "gate",
77            IssueType::Agent => "agent",
78            IssueType::Role => "role",
79            IssueType::Rig => "rig",
80            IssueType::Convoy => "convoy",
81            IssueType::Event => "event",
82            IssueType::MergeRequest => "merge_request",
83            IssueType::Slot => "slot",
84        }
85    }
86
87    /// Returns true if this type represents a container for other issues.
88    pub fn is_container(&self) -> bool {
89        matches!(self, IssueType::Epic | IssueType::Molecule | IssueType::Convoy)
90    }
91
92    /// Returns true if this type represents agent-related work.
93    pub fn is_agent_type(&self) -> bool {
94        matches!(self, IssueType::Agent | IssueType::Role | IssueType::Rig)
95    }
96}
97
98impl fmt::Display for IssueType {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(f, "{}", self.as_str())
101    }
102}
103
104impl FromStr for IssueType {
105    type Err = String;
106
107    fn from_str(s: &str) -> Result<Self, Self::Err> {
108        match s.to_lowercase().as_str() {
109            "bug" => Ok(IssueType::Bug),
110            "feature" => Ok(IssueType::Feature),
111            "task" => Ok(IssueType::Task),
112            "epic" => Ok(IssueType::Epic),
113            "chore" => Ok(IssueType::Chore),
114            "message" => Ok(IssueType::Message),
115            "molecule" => Ok(IssueType::Molecule),
116            "gate" => Ok(IssueType::Gate),
117            "agent" => Ok(IssueType::Agent),
118            "role" => Ok(IssueType::Role),
119            "rig" => Ok(IssueType::Rig),
120            "convoy" => Ok(IssueType::Convoy),
121            "event" => Ok(IssueType::Event),
122            "merge_request" | "merge-request" | "mergerequest" => Ok(IssueType::MergeRequest),
123            "slot" => Ok(IssueType::Slot),
124            _ => Err(format!("unknown issue type: {}", s)),
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_issue_type_roundtrip() {
135        for issue_type in IssueType::all() {
136            let s = issue_type.as_str();
137            let parsed: IssueType = s.parse().unwrap();
138            assert_eq!(*issue_type, parsed);
139        }
140    }
141}