1use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, PartialEq, thiserror::Error)]
13#[error("invalid {type_name} value: {value:?}")]
14pub struct ParseEnumError {
15 pub type_name: &'static str,
16 pub value: String,
17}
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[serde(untagged)]
26pub enum FieldValue {
27 Scalar(String),
28 List(Vec<String>),
29 Block(String),
30}
31
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
38pub struct Span {
39 pub start_line: usize,
40 pub end_line: usize,
41}
42
43impl Span {
44 #[must_use]
45 pub fn new(start_line: usize, end_line: usize) -> Self {
46 Self {
47 start_line,
48 end_line,
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case")]
59pub enum NodeType {
60 Facts,
61 Rules,
62 Workflow,
63 Entity,
64 Decision,
65 Exception,
66 Example,
67 Glossary,
68 AntiPattern,
69 Orchestration,
70 Custom(String),
71}
72
73impl fmt::Display for NodeType {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::Facts => write!(f, "facts"),
77 Self::Rules => write!(f, "rules"),
78 Self::Workflow => write!(f, "workflow"),
79 Self::Entity => write!(f, "entity"),
80 Self::Decision => write!(f, "decision"),
81 Self::Exception => write!(f, "exception"),
82 Self::Example => write!(f, "example"),
83 Self::Glossary => write!(f, "glossary"),
84 Self::AntiPattern => write!(f, "anti_pattern"),
85 Self::Orchestration => write!(f, "orchestration"),
86 Self::Custom(s) => write!(f, "{s}"),
87 }
88 }
89}
90
91impl FromStr for NodeType {
92 type Err = std::convert::Infallible;
93
94 fn from_str(s: &str) -> Result<Self, Self::Err> {
95 Ok(match s {
96 "facts" => Self::Facts,
97 "rules" => Self::Rules,
98 "workflow" => Self::Workflow,
99 "entity" => Self::Entity,
100 "decision" => Self::Decision,
101 "exception" => Self::Exception,
102 "example" => Self::Example,
103 "glossary" => Self::Glossary,
104 "anti_pattern" => Self::AntiPattern,
105 "orchestration" => Self::Orchestration,
106 other => Self::Custom(other.to_owned()),
107 })
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116#[serde(rename_all = "snake_case")]
117pub enum Priority {
118 Critical,
119 High,
120 Normal,
121 Low,
122}
123
124impl fmt::Display for Priority {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match self {
127 Self::Critical => write!(f, "critical"),
128 Self::High => write!(f, "high"),
129 Self::Normal => write!(f, "normal"),
130 Self::Low => write!(f, "low"),
131 }
132 }
133}
134
135impl FromStr for Priority {
136 type Err = ParseEnumError;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 match s {
140 "critical" => Ok(Self::Critical),
141 "high" => Ok(Self::High),
142 "normal" => Ok(Self::Normal),
143 "low" => Ok(Self::Low),
144 _ => Err(ParseEnumError {
145 type_name: "Priority",
146 value: s.to_owned(),
147 }),
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157#[serde(rename_all = "snake_case")]
158pub enum Stability {
159 High,
160 Medium,
161 Low,
162 Volatile,
163}
164
165impl fmt::Display for Stability {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 match self {
168 Self::High => write!(f, "high"),
169 Self::Medium => write!(f, "medium"),
170 Self::Low => write!(f, "low"),
171 Self::Volatile => write!(f, "volatile"),
172 }
173 }
174}
175
176impl FromStr for Stability {
177 type Err = ParseEnumError;
178
179 fn from_str(s: &str) -> Result<Self, Self::Err> {
180 match s {
181 "high" => Ok(Self::High),
182 "medium" => Ok(Self::Medium),
183 "low" => Ok(Self::Low),
184 "volatile" => Ok(Self::Volatile),
185 _ => Err(ParseEnumError {
186 type_name: "Stability",
187 value: s.to_owned(),
188 }),
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
198#[serde(rename_all = "snake_case")]
199pub enum Confidence {
200 High,
201 Medium,
202 Low,
203 Inferred,
204 Tentative,
205}
206
207impl fmt::Display for Confidence {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 match self {
210 Self::High => write!(f, "high"),
211 Self::Medium => write!(f, "medium"),
212 Self::Low => write!(f, "low"),
213 Self::Inferred => write!(f, "inferred"),
214 Self::Tentative => write!(f, "tentative"),
215 }
216 }
217}
218
219impl FromStr for Confidence {
220 type Err = ParseEnumError;
221
222 fn from_str(s: &str) -> Result<Self, Self::Err> {
223 match s {
224 "high" => Ok(Self::High),
225 "medium" => Ok(Self::Medium),
226 "low" => Ok(Self::Low),
227 "inferred" => Ok(Self::Inferred),
228 "tentative" => Ok(Self::Tentative),
229 _ => Err(ParseEnumError {
230 type_name: "Confidence",
231 value: s.to_owned(),
232 }),
233 }
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242#[serde(rename_all = "snake_case")]
243pub enum NodeStatus {
244 Active,
245 Draft,
246 Deprecated,
247 Superseded,
248}
249
250impl fmt::Display for NodeStatus {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 match self {
253 Self::Active => write!(f, "active"),
254 Self::Draft => write!(f, "draft"),
255 Self::Deprecated => write!(f, "deprecated"),
256 Self::Superseded => write!(f, "superseded"),
257 }
258 }
259}
260
261impl FromStr for NodeStatus {
262 type Err = ParseEnumError;
263
264 fn from_str(s: &str) -> Result<Self, Self::Err> {
265 match s {
266 "active" => Ok(Self::Active),
267 "draft" => Ok(Self::Draft),
268 "deprecated" => Ok(Self::Deprecated),
269 "superseded" => Ok(Self::Superseded),
270 _ => Err(ParseEnumError {
271 type_name: "NodeStatus",
272 value: s.to_owned(),
273 }),
274 }
275 }
276}
277
278#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn test_node_type_from_str_known_returns_variant() {
288 assert_eq!("facts".parse::<NodeType>().unwrap(), NodeType::Facts);
289 assert_eq!("rules".parse::<NodeType>().unwrap(), NodeType::Rules);
290 assert_eq!("workflow".parse::<NodeType>().unwrap(), NodeType::Workflow);
291 assert_eq!("entity".parse::<NodeType>().unwrap(), NodeType::Entity);
292 assert_eq!("decision".parse::<NodeType>().unwrap(), NodeType::Decision);
293 assert_eq!(
294 "exception".parse::<NodeType>().unwrap(),
295 NodeType::Exception
296 );
297 assert_eq!("example".parse::<NodeType>().unwrap(), NodeType::Example);
298 assert_eq!("glossary".parse::<NodeType>().unwrap(), NodeType::Glossary);
299 assert_eq!(
300 "anti_pattern".parse::<NodeType>().unwrap(),
301 NodeType::AntiPattern
302 );
303 assert_eq!(
304 "orchestration".parse::<NodeType>().unwrap(),
305 NodeType::Orchestration
306 );
307 }
308
309 #[test]
310 fn test_node_type_from_str_unknown_returns_custom() {
311 assert_eq!(
312 "unknown_custom".parse::<NodeType>().unwrap(),
313 NodeType::Custom("unknown_custom".to_owned())
314 );
315 }
316
317 #[test]
318 fn test_node_type_display_roundtrip() {
319 let types = [
320 NodeType::Facts,
321 NodeType::Rules,
322 NodeType::Workflow,
323 NodeType::Entity,
324 NodeType::Decision,
325 NodeType::Exception,
326 NodeType::Example,
327 NodeType::Glossary,
328 NodeType::AntiPattern,
329 NodeType::Orchestration,
330 NodeType::Custom("my_type".to_owned()),
331 ];
332 for t in &types {
333 let s = t.to_string();
334 let parsed: NodeType = s.parse().unwrap();
335 assert_eq!(&parsed, t);
336 }
337 }
338
339 #[test]
340 fn test_priority_from_str_valid_returns_ok() {
341 assert_eq!("critical".parse::<Priority>().unwrap(), Priority::Critical);
342 assert_eq!("high".parse::<Priority>().unwrap(), Priority::High);
343 assert_eq!("normal".parse::<Priority>().unwrap(), Priority::Normal);
344 assert_eq!("low".parse::<Priority>().unwrap(), Priority::Low);
345 }
346
347 #[test]
348 fn test_priority_from_str_invalid_returns_error() {
349 let err = "invalid".parse::<Priority>().unwrap_err();
350 assert_eq!(err.type_name, "Priority");
351 assert_eq!(err.value, "invalid");
352 }
353
354 #[test]
355 fn test_priority_display_roundtrip() {
356 for p in [
357 Priority::Critical,
358 Priority::High,
359 Priority::Normal,
360 Priority::Low,
361 ] {
362 let s = p.to_string();
363 assert_eq!(s.parse::<Priority>().unwrap(), p);
364 }
365 }
366
367 #[test]
368 fn test_stability_from_str_valid_returns_ok() {
369 assert_eq!("high".parse::<Stability>().unwrap(), Stability::High);
370 assert_eq!("medium".parse::<Stability>().unwrap(), Stability::Medium);
371 assert_eq!("low".parse::<Stability>().unwrap(), Stability::Low);
372 assert_eq!(
373 "volatile".parse::<Stability>().unwrap(),
374 Stability::Volatile
375 );
376 }
377
378 #[test]
379 fn test_stability_from_str_invalid_returns_error() {
380 let err = "wrong".parse::<Stability>().unwrap_err();
381 assert_eq!(err.type_name, "Stability");
382 }
383
384 #[test]
385 fn test_stability_display_roundtrip() {
386 for s in [
387 Stability::High,
388 Stability::Medium,
389 Stability::Low,
390 Stability::Volatile,
391 ] {
392 let text = s.to_string();
393 assert_eq!(text.parse::<Stability>().unwrap(), s);
394 }
395 }
396
397 #[test]
398 fn test_confidence_from_str_valid_returns_ok() {
399 assert_eq!("high".parse::<Confidence>().unwrap(), Confidence::High);
400 assert_eq!("medium".parse::<Confidence>().unwrap(), Confidence::Medium);
401 assert_eq!("low".parse::<Confidence>().unwrap(), Confidence::Low);
402 assert_eq!(
403 "inferred".parse::<Confidence>().unwrap(),
404 Confidence::Inferred
405 );
406 assert_eq!(
407 "tentative".parse::<Confidence>().unwrap(),
408 Confidence::Tentative
409 );
410 }
411
412 #[test]
413 fn test_confidence_from_str_invalid_returns_error() {
414 let err = "maybe".parse::<Confidence>().unwrap_err();
415 assert_eq!(err.type_name, "Confidence");
416 }
417
418 #[test]
419 fn test_confidence_display_roundtrip() {
420 for c in [
421 Confidence::High,
422 Confidence::Medium,
423 Confidence::Low,
424 Confidence::Inferred,
425 Confidence::Tentative,
426 ] {
427 let text = c.to_string();
428 assert_eq!(text.parse::<Confidence>().unwrap(), c);
429 }
430 }
431
432 #[test]
433 fn test_node_status_from_str_valid_returns_ok() {
434 assert_eq!("active".parse::<NodeStatus>().unwrap(), NodeStatus::Active);
435 assert_eq!("draft".parse::<NodeStatus>().unwrap(), NodeStatus::Draft);
436 assert_eq!(
437 "deprecated".parse::<NodeStatus>().unwrap(),
438 NodeStatus::Deprecated
439 );
440 assert_eq!(
441 "superseded".parse::<NodeStatus>().unwrap(),
442 NodeStatus::Superseded
443 );
444 }
445
446 #[test]
447 fn test_node_status_from_str_invalid_returns_error() {
448 let err = "archived".parse::<NodeStatus>().unwrap_err();
449 assert_eq!(err.type_name, "NodeStatus");
450 }
451
452 #[test]
453 fn test_node_status_display_roundtrip() {
454 for ns in [
455 NodeStatus::Active,
456 NodeStatus::Draft,
457 NodeStatus::Deprecated,
458 NodeStatus::Superseded,
459 ] {
460 let text = ns.to_string();
461 assert_eq!(text.parse::<NodeStatus>().unwrap(), ns);
462 }
463 }
464
465 #[test]
466 fn test_field_value_scalar_debug() {
467 let v = FieldValue::Scalar("hello".to_owned());
468 assert!(format!("{v:?}").contains("Scalar"));
469 }
470
471 #[test]
472 fn test_field_value_list_clone() {
473 let v = FieldValue::List(vec!["a".to_owned(), "b".to_owned()]);
474 assert_eq!(v.clone(), v);
475 }
476
477 #[test]
478 fn test_field_value_serde_roundtrip_scalar() {
479 let v = FieldValue::Scalar("test".to_owned());
480 let json = serde_json::to_string(&v).unwrap();
481 let back: FieldValue = serde_json::from_str(&json).unwrap();
482 assert_eq!(v, back);
483 }
484
485 #[test]
486 fn test_field_value_serde_roundtrip_list() {
487 let v = FieldValue::List(vec!["a".to_owned(), "b".to_owned()]);
488 let json = serde_json::to_string(&v).unwrap();
489 let back: FieldValue = serde_json::from_str(&json).unwrap();
490 assert_eq!(v, back);
491 }
492
493 #[test]
494 fn test_span_new() {
495 let s = Span::new(1, 10);
496 assert_eq!(s.start_line, 1);
497 assert_eq!(s.end_line, 10);
498 }
499}