1use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::fmt;
10use std::str::FromStr;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub enum ArtifactType {
32 GoalTitle,
34 PlanDocument,
36 DraftPackage,
38 ReviewVerdict,
40 AuditEntry,
42 ConstitutionReport,
44 AgentMessage,
46 FileArtifact,
48 TestResult,
50 Custom(String),
52}
53
54impl fmt::Display for ArtifactType {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::GoalTitle => write!(f, "GoalTitle"),
58 Self::PlanDocument => write!(f, "PlanDocument"),
59 Self::DraftPackage => write!(f, "DraftPackage"),
60 Self::ReviewVerdict => write!(f, "ReviewVerdict"),
61 Self::AuditEntry => write!(f, "AuditEntry"),
62 Self::ConstitutionReport => write!(f, "ConstitutionReport"),
63 Self::AgentMessage => write!(f, "AgentMessage"),
64 Self::FileArtifact => write!(f, "FileArtifact"),
65 Self::TestResult => write!(f, "TestResult"),
66 Self::Custom(s) => write!(f, "{}", s),
67 }
68 }
69}
70
71impl FromStr for ArtifactType {
72 type Err = std::convert::Infallible;
73
74 fn from_str(s: &str) -> Result<Self, Self::Err> {
75 Ok(match s {
76 "GoalTitle" => Self::GoalTitle,
77 "PlanDocument" => Self::PlanDocument,
78 "DraftPackage" => Self::DraftPackage,
79 "ReviewVerdict" => Self::ReviewVerdict,
80 "AuditEntry" => Self::AuditEntry,
81 "ConstitutionReport" => Self::ConstitutionReport,
82 "AgentMessage" => Self::AgentMessage,
83 "FileArtifact" => Self::FileArtifact,
84 "TestResult" => Self::TestResult,
85 other => Self::Custom(other.to_string()),
86 })
87 }
88}
89
90impl Serialize for ArtifactType {
92 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
93 serializer.serialize_str(&self.to_string())
94 }
95}
96
97impl<'de> Deserialize<'de> for ArtifactType {
99 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
100 let s = String::deserialize(deserializer)?;
101 Ok(s.parse().unwrap()) }
103}
104
105impl ArtifactType {
106 pub fn is_custom(&self) -> bool {
108 matches!(self, Self::Custom(_))
109 }
110
111 pub fn built_ins() -> Vec<ArtifactType> {
113 vec![
114 ArtifactType::GoalTitle,
115 ArtifactType::PlanDocument,
116 ArtifactType::DraftPackage,
117 ArtifactType::ReviewVerdict,
118 ArtifactType::AuditEntry,
119 ArtifactType::ConstitutionReport,
120 ArtifactType::AgentMessage,
121 ArtifactType::FileArtifact,
122 ArtifactType::TestResult,
123 ]
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn display_builtin_types() {
133 assert_eq!(ArtifactType::PlanDocument.to_string(), "PlanDocument");
134 assert_eq!(ArtifactType::DraftPackage.to_string(), "DraftPackage");
135 assert_eq!(ArtifactType::ReviewVerdict.to_string(), "ReviewVerdict");
136 assert_eq!(ArtifactType::GoalTitle.to_string(), "GoalTitle");
137 assert_eq!(ArtifactType::AuditEntry.to_string(), "AuditEntry");
138 assert_eq!(
139 ArtifactType::ConstitutionReport.to_string(),
140 "ConstitutionReport"
141 );
142 assert_eq!(ArtifactType::AgentMessage.to_string(), "AgentMessage");
143 assert_eq!(ArtifactType::FileArtifact.to_string(), "FileArtifact");
144 assert_eq!(ArtifactType::TestResult.to_string(), "TestResult");
145 }
146
147 #[test]
148 fn display_custom_type() {
149 assert_eq!(
150 ArtifactType::Custom("x-my-thing".to_string()).to_string(),
151 "x-my-thing"
152 );
153 }
154
155 #[test]
156 fn from_str_builtin() {
157 assert_eq!(
158 "PlanDocument".parse::<ArtifactType>().unwrap(),
159 ArtifactType::PlanDocument
160 );
161 assert_eq!(
162 "DraftPackage".parse::<ArtifactType>().unwrap(),
163 ArtifactType::DraftPackage
164 );
165 assert_eq!(
166 "TestResult".parse::<ArtifactType>().unwrap(),
167 ArtifactType::TestResult
168 );
169 }
170
171 #[test]
172 fn from_str_custom_falls_back() {
173 let t: ArtifactType = "x-custom-thing".parse().unwrap();
174 assert_eq!(t, ArtifactType::Custom("x-custom-thing".to_string()));
175 assert!(t.is_custom());
176 }
177
178 #[test]
179 fn is_custom_builtin_false() {
180 assert!(!ArtifactType::PlanDocument.is_custom());
181 assert!(!ArtifactType::DraftPackage.is_custom());
182 }
183
184 #[test]
185 fn serde_roundtrip_builtin() {
186 let t = ArtifactType::ReviewVerdict;
187 let json = serde_json::to_string(&t).unwrap();
188 assert_eq!(json, "\"ReviewVerdict\"");
189 let back: ArtifactType = serde_json::from_str(&json).unwrap();
190 assert_eq!(t, back);
191 }
192
193 #[test]
194 fn serde_roundtrip_custom() {
195 let t = ArtifactType::Custom("x-special".to_string());
196 let json = serde_json::to_string(&t).unwrap();
197 assert_eq!(json, "\"x-special\"");
198 let back: ArtifactType = serde_json::from_str(&json).unwrap();
199 assert_eq!(t, back);
200 }
201
202 #[test]
203 fn serde_in_vec() {
204 let types = vec![
205 ArtifactType::PlanDocument,
206 ArtifactType::DraftPackage,
207 ArtifactType::Custom("x-custom".to_string()),
208 ];
209 let json = serde_json::to_string(&types).unwrap();
210 assert_eq!(json, r#"["PlanDocument","DraftPackage","x-custom"]"#);
211 let back: Vec<ArtifactType> = serde_json::from_str(&json).unwrap();
212 assert_eq!(types, back);
213 }
214
215 #[test]
216 fn built_ins_count() {
217 assert_eq!(ArtifactType::built_ins().len(), 9);
218 }
219
220 #[test]
221 fn built_ins_none_are_custom() {
222 for t in ArtifactType::built_ins() {
223 assert!(!t.is_custom(), "{} should not be custom", t);
224 }
225 }
226}