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 Ticket,
71 Custom(String),
72}
73
74impl fmt::Display for NodeType {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::Facts => write!(f, "facts"),
78 Self::Rules => write!(f, "rules"),
79 Self::Workflow => write!(f, "workflow"),
80 Self::Entity => write!(f, "entity"),
81 Self::Decision => write!(f, "decision"),
82 Self::Exception => write!(f, "exception"),
83 Self::Example => write!(f, "example"),
84 Self::Glossary => write!(f, "glossary"),
85 Self::AntiPattern => write!(f, "anti_pattern"),
86 Self::Orchestration => write!(f, "orchestration"),
87 Self::Ticket => write!(f, "ticket"),
88 Self::Custom(s) => write!(f, "{s}"),
89 }
90 }
91}
92
93impl FromStr for NodeType {
94 type Err = std::convert::Infallible;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 Ok(match s {
98 "facts" => Self::Facts,
99 "rules" => Self::Rules,
100 "workflow" => Self::Workflow,
101 "entity" => Self::Entity,
102 "decision" => Self::Decision,
103 "exception" => Self::Exception,
104 "example" => Self::Example,
105 "glossary" => Self::Glossary,
106 "anti_pattern" => Self::AntiPattern,
107 "orchestration" => Self::Orchestration,
108 "ticket" => Self::Ticket,
109 other => Self::Custom(other.to_owned()),
110 })
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
120#[serde(rename_all = "snake_case")]
121pub enum TicketAction {
122 #[default]
123 Create,
124 Edit,
125 Close,
126 Archive,
127 Split,
128 Link,
129}
130
131impl fmt::Display for TicketAction {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match self {
134 Self::Create => write!(f, "create"),
135 Self::Edit => write!(f, "edit"),
136 Self::Close => write!(f, "close"),
137 Self::Archive => write!(f, "archive"),
138 Self::Split => write!(f, "split"),
139 Self::Link => write!(f, "link"),
140 }
141 }
142}
143
144impl FromStr for TicketAction {
145 type Err = ParseEnumError;
146
147 fn from_str(s: &str) -> Result<Self, Self::Err> {
148 match s {
149 "create" => Ok(Self::Create),
150 "edit" => Ok(Self::Edit),
151 "close" => Ok(Self::Close),
152 "archive" => Ok(Self::Archive),
153 "split" => Ok(Self::Split),
154 "link" => Ok(Self::Link),
155 _ => Err(ParseEnumError {
156 type_name: "TicketAction",
157 value: s.to_owned(),
158 }),
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
169#[serde(rename_all = "snake_case")]
170pub enum SddPhase {
171 #[default]
172 Backlog,
173 Explore,
174 Propose,
175 Spec,
176 Design,
177 Tasks,
178 Apply,
179 Verify,
180 Archive,
181}
182
183impl fmt::Display for SddPhase {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 match self {
186 Self::Backlog => write!(f, "backlog"),
187 Self::Explore => write!(f, "explore"),
188 Self::Propose => write!(f, "propose"),
189 Self::Spec => write!(f, "spec"),
190 Self::Design => write!(f, "design"),
191 Self::Tasks => write!(f, "tasks"),
192 Self::Apply => write!(f, "apply"),
193 Self::Verify => write!(f, "verify"),
194 Self::Archive => write!(f, "archive"),
195 }
196 }
197}
198
199impl FromStr for SddPhase {
200 type Err = ParseEnumError;
201
202 fn from_str(s: &str) -> Result<Self, Self::Err> {
203 match s {
204 "backlog" => Ok(Self::Backlog),
205 "explore" => Ok(Self::Explore),
206 "propose" => Ok(Self::Propose),
207 "spec" => Ok(Self::Spec),
208 "design" => Ok(Self::Design),
209 "tasks" => Ok(Self::Tasks),
210 "apply" => Ok(Self::Apply),
211 "verify" => Ok(Self::Verify),
212 "archive" => Ok(Self::Archive),
213 _ => Err(ParseEnumError {
214 type_name: "SddPhase",
215 value: s.to_owned(),
216 }),
217 }
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
226#[serde(rename_all = "snake_case")]
227pub enum Priority {
228 Critical,
229 High,
230 Normal,
231 Low,
232}
233
234impl fmt::Display for Priority {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 match self {
237 Self::Critical => write!(f, "critical"),
238 Self::High => write!(f, "high"),
239 Self::Normal => write!(f, "normal"),
240 Self::Low => write!(f, "low"),
241 }
242 }
243}
244
245impl FromStr for Priority {
246 type Err = ParseEnumError;
247
248 fn from_str(s: &str) -> Result<Self, Self::Err> {
249 match s {
250 "critical" => Ok(Self::Critical),
251 "high" => Ok(Self::High),
252 "normal" => Ok(Self::Normal),
253 "low" => Ok(Self::Low),
254 _ => Err(ParseEnumError {
255 type_name: "Priority",
256 value: s.to_owned(),
257 }),
258 }
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
267#[serde(rename_all = "snake_case")]
268pub enum Stability {
269 High,
270 Medium,
271 Low,
272 Volatile,
273}
274
275impl fmt::Display for Stability {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 match self {
278 Self::High => write!(f, "high"),
279 Self::Medium => write!(f, "medium"),
280 Self::Low => write!(f, "low"),
281 Self::Volatile => write!(f, "volatile"),
282 }
283 }
284}
285
286impl FromStr for Stability {
287 type Err = ParseEnumError;
288
289 fn from_str(s: &str) -> Result<Self, Self::Err> {
290 match s {
291 "high" => Ok(Self::High),
292 "medium" => Ok(Self::Medium),
293 "low" => Ok(Self::Low),
294 "volatile" => Ok(Self::Volatile),
295 _ => Err(ParseEnumError {
296 type_name: "Stability",
297 value: s.to_owned(),
298 }),
299 }
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case")]
309pub enum Confidence {
310 High,
311 Medium,
312 Low,
313 Inferred,
314 Tentative,
315}
316
317impl fmt::Display for Confidence {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 match self {
320 Self::High => write!(f, "high"),
321 Self::Medium => write!(f, "medium"),
322 Self::Low => write!(f, "low"),
323 Self::Inferred => write!(f, "inferred"),
324 Self::Tentative => write!(f, "tentative"),
325 }
326 }
327}
328
329impl FromStr for Confidence {
330 type Err = ParseEnumError;
331
332 fn from_str(s: &str) -> Result<Self, Self::Err> {
333 match s {
334 "high" => Ok(Self::High),
335 "medium" => Ok(Self::Medium),
336 "low" => Ok(Self::Low),
337 "inferred" => Ok(Self::Inferred),
338 "tentative" => Ok(Self::Tentative),
339 _ => Err(ParseEnumError {
340 type_name: "Confidence",
341 value: s.to_owned(),
342 }),
343 }
344 }
345}
346
347#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
352#[serde(rename_all = "snake_case")]
353pub enum NodeStatus {
354 Active,
355 Draft,
356 Deprecated,
357 Superseded,
358}
359
360impl fmt::Display for NodeStatus {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 match self {
363 Self::Active => write!(f, "active"),
364 Self::Draft => write!(f, "draft"),
365 Self::Deprecated => write!(f, "deprecated"),
366 Self::Superseded => write!(f, "superseded"),
367 }
368 }
369}
370
371impl FromStr for NodeStatus {
372 type Err = ParseEnumError;
373
374 fn from_str(s: &str) -> Result<Self, Self::Err> {
375 match s {
376 "active" => Ok(Self::Active),
377 "draft" => Ok(Self::Draft),
378 "deprecated" => Ok(Self::Deprecated),
379 "superseded" => Ok(Self::Superseded),
380 _ => Err(ParseEnumError {
381 type_name: "NodeStatus",
382 value: s.to_owned(),
383 }),
384 }
385 }
386}
387
388#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn test_node_type_from_str_known_returns_variant() {
398 assert_eq!("facts".parse::<NodeType>().unwrap(), NodeType::Facts);
399 assert_eq!("rules".parse::<NodeType>().unwrap(), NodeType::Rules);
400 assert_eq!("workflow".parse::<NodeType>().unwrap(), NodeType::Workflow);
401 assert_eq!("entity".parse::<NodeType>().unwrap(), NodeType::Entity);
402 assert_eq!("decision".parse::<NodeType>().unwrap(), NodeType::Decision);
403 assert_eq!(
404 "exception".parse::<NodeType>().unwrap(),
405 NodeType::Exception
406 );
407 assert_eq!("example".parse::<NodeType>().unwrap(), NodeType::Example);
408 assert_eq!("glossary".parse::<NodeType>().unwrap(), NodeType::Glossary);
409 assert_eq!(
410 "anti_pattern".parse::<NodeType>().unwrap(),
411 NodeType::AntiPattern
412 );
413 assert_eq!(
414 "orchestration".parse::<NodeType>().unwrap(),
415 NodeType::Orchestration
416 );
417 assert_eq!("ticket".parse::<NodeType>().unwrap(), NodeType::Ticket);
418 }
419
420 #[test]
421 fn test_node_type_from_str_unknown_returns_custom() {
422 assert_eq!(
423 "unknown_custom".parse::<NodeType>().unwrap(),
424 NodeType::Custom("unknown_custom".to_owned())
425 );
426 }
427
428 #[test]
429 fn test_node_type_display_roundtrip() {
430 let types = [
431 NodeType::Facts,
432 NodeType::Rules,
433 NodeType::Workflow,
434 NodeType::Entity,
435 NodeType::Decision,
436 NodeType::Exception,
437 NodeType::Example,
438 NodeType::Glossary,
439 NodeType::AntiPattern,
440 NodeType::Orchestration,
441 NodeType::Ticket,
442 NodeType::Custom("my_type".to_owned()),
443 ];
444 for t in &types {
445 let s = t.to_string();
446 let parsed: NodeType = s.parse().unwrap();
447 assert_eq!(&parsed, t);
448 }
449 }
450
451 #[test]
452 fn test_priority_from_str_valid_returns_ok() {
453 assert_eq!("critical".parse::<Priority>().unwrap(), Priority::Critical);
454 assert_eq!("high".parse::<Priority>().unwrap(), Priority::High);
455 assert_eq!("normal".parse::<Priority>().unwrap(), Priority::Normal);
456 assert_eq!("low".parse::<Priority>().unwrap(), Priority::Low);
457 }
458
459 #[test]
460 fn test_priority_from_str_invalid_returns_error() {
461 let err = "invalid".parse::<Priority>().unwrap_err();
462 assert_eq!(err.type_name, "Priority");
463 assert_eq!(err.value, "invalid");
464 }
465
466 #[test]
467 fn test_priority_display_roundtrip() {
468 for p in [
469 Priority::Critical,
470 Priority::High,
471 Priority::Normal,
472 Priority::Low,
473 ] {
474 let s = p.to_string();
475 assert_eq!(s.parse::<Priority>().unwrap(), p);
476 }
477 }
478
479 #[test]
480 fn test_stability_from_str_valid_returns_ok() {
481 assert_eq!("high".parse::<Stability>().unwrap(), Stability::High);
482 assert_eq!("medium".parse::<Stability>().unwrap(), Stability::Medium);
483 assert_eq!("low".parse::<Stability>().unwrap(), Stability::Low);
484 assert_eq!(
485 "volatile".parse::<Stability>().unwrap(),
486 Stability::Volatile
487 );
488 }
489
490 #[test]
491 fn test_stability_from_str_invalid_returns_error() {
492 let err = "wrong".parse::<Stability>().unwrap_err();
493 assert_eq!(err.type_name, "Stability");
494 }
495
496 #[test]
497 fn test_stability_display_roundtrip() {
498 for s in [
499 Stability::High,
500 Stability::Medium,
501 Stability::Low,
502 Stability::Volatile,
503 ] {
504 let text = s.to_string();
505 assert_eq!(text.parse::<Stability>().unwrap(), s);
506 }
507 }
508
509 #[test]
510 fn test_confidence_from_str_valid_returns_ok() {
511 assert_eq!("high".parse::<Confidence>().unwrap(), Confidence::High);
512 assert_eq!("medium".parse::<Confidence>().unwrap(), Confidence::Medium);
513 assert_eq!("low".parse::<Confidence>().unwrap(), Confidence::Low);
514 assert_eq!(
515 "inferred".parse::<Confidence>().unwrap(),
516 Confidence::Inferred
517 );
518 assert_eq!(
519 "tentative".parse::<Confidence>().unwrap(),
520 Confidence::Tentative
521 );
522 }
523
524 #[test]
525 fn test_confidence_from_str_invalid_returns_error() {
526 let err = "maybe".parse::<Confidence>().unwrap_err();
527 assert_eq!(err.type_name, "Confidence");
528 }
529
530 #[test]
531 fn test_confidence_display_roundtrip() {
532 for c in [
533 Confidence::High,
534 Confidence::Medium,
535 Confidence::Low,
536 Confidence::Inferred,
537 Confidence::Tentative,
538 ] {
539 let text = c.to_string();
540 assert_eq!(text.parse::<Confidence>().unwrap(), c);
541 }
542 }
543
544 #[test]
545 fn test_node_status_from_str_valid_returns_ok() {
546 assert_eq!("active".parse::<NodeStatus>().unwrap(), NodeStatus::Active);
547 assert_eq!("draft".parse::<NodeStatus>().unwrap(), NodeStatus::Draft);
548 assert_eq!(
549 "deprecated".parse::<NodeStatus>().unwrap(),
550 NodeStatus::Deprecated
551 );
552 assert_eq!(
553 "superseded".parse::<NodeStatus>().unwrap(),
554 NodeStatus::Superseded
555 );
556 }
557
558 #[test]
559 fn test_node_status_from_str_invalid_returns_error() {
560 let err = "archived".parse::<NodeStatus>().unwrap_err();
561 assert_eq!(err.type_name, "NodeStatus");
562 }
563
564 #[test]
565 fn test_node_status_display_roundtrip() {
566 for ns in [
567 NodeStatus::Active,
568 NodeStatus::Draft,
569 NodeStatus::Deprecated,
570 NodeStatus::Superseded,
571 ] {
572 let text = ns.to_string();
573 assert_eq!(text.parse::<NodeStatus>().unwrap(), ns);
574 }
575 }
576
577 #[test]
578 fn test_field_value_scalar_debug() {
579 let v = FieldValue::Scalar("hello".to_owned());
580 assert!(format!("{v:?}").contains("Scalar"));
581 }
582
583 #[test]
584 fn test_field_value_list_clone() {
585 let v = FieldValue::List(vec!["a".to_owned(), "b".to_owned()]);
586 assert_eq!(v.clone(), v);
587 }
588
589 #[test]
590 fn test_field_value_serde_roundtrip_scalar() {
591 let v = FieldValue::Scalar("test".to_owned());
592 let json = serde_json::to_string(&v).unwrap();
593 let back: FieldValue = serde_json::from_str(&json).unwrap();
594 assert_eq!(v, back);
595 }
596
597 #[test]
598 fn test_field_value_serde_roundtrip_list() {
599 let v = FieldValue::List(vec!["a".to_owned(), "b".to_owned()]);
600 let json = serde_json::to_string(&v).unwrap();
601 let back: FieldValue = serde_json::from_str(&json).unwrap();
602 assert_eq!(v, back);
603 }
604
605 #[test]
606 fn test_span_new() {
607 let s = Span::new(1, 10);
608 assert_eq!(s.start_line, 1);
609 assert_eq!(s.end_line, 10);
610 }
611
612 #[test]
613 fn test_ticket_action_from_str_valid_returns_ok() {
614 assert_eq!(
615 "create".parse::<TicketAction>().unwrap(),
616 TicketAction::Create
617 );
618 assert_eq!("edit".parse::<TicketAction>().unwrap(), TicketAction::Edit);
619 assert_eq!(
620 "close".parse::<TicketAction>().unwrap(),
621 TicketAction::Close
622 );
623 assert_eq!(
624 "archive".parse::<TicketAction>().unwrap(),
625 TicketAction::Archive
626 );
627 assert_eq!(
628 "split".parse::<TicketAction>().unwrap(),
629 TicketAction::Split
630 );
631 assert_eq!("link".parse::<TicketAction>().unwrap(), TicketAction::Link);
632 }
633
634 #[test]
635 fn test_ticket_action_from_str_invalid_returns_error() {
636 let err = "delete".parse::<TicketAction>().unwrap_err();
637 assert_eq!(err.type_name, "TicketAction");
638 assert_eq!(err.value, "delete");
639 }
640
641 #[test]
642 fn test_ticket_action_display_roundtrip() {
643 for a in [
644 TicketAction::Create,
645 TicketAction::Edit,
646 TicketAction::Close,
647 TicketAction::Archive,
648 TicketAction::Split,
649 TicketAction::Link,
650 ] {
651 let s = a.to_string();
652 assert_eq!(s.parse::<TicketAction>().unwrap(), a);
653 }
654 }
655
656 #[test]
657 fn test_ticket_action_default_is_create() {
658 assert_eq!(TicketAction::default(), TicketAction::Create);
659 }
660
661 #[test]
662 fn test_sdd_phase_from_str_valid_returns_ok() {
663 assert_eq!("backlog".parse::<SddPhase>().unwrap(), SddPhase::Backlog);
664 assert_eq!("explore".parse::<SddPhase>().unwrap(), SddPhase::Explore);
665 assert_eq!("propose".parse::<SddPhase>().unwrap(), SddPhase::Propose);
666 assert_eq!("spec".parse::<SddPhase>().unwrap(), SddPhase::Spec);
667 assert_eq!("design".parse::<SddPhase>().unwrap(), SddPhase::Design);
668 assert_eq!("tasks".parse::<SddPhase>().unwrap(), SddPhase::Tasks);
669 assert_eq!("apply".parse::<SddPhase>().unwrap(), SddPhase::Apply);
670 assert_eq!("verify".parse::<SddPhase>().unwrap(), SddPhase::Verify);
671 assert_eq!("archive".parse::<SddPhase>().unwrap(), SddPhase::Archive);
672 }
673
674 #[test]
675 fn test_sdd_phase_from_str_invalid_returns_error() {
676 let err = "unknown".parse::<SddPhase>().unwrap_err();
677 assert_eq!(err.type_name, "SddPhase");
678 assert_eq!(err.value, "unknown");
679 }
680
681 #[test]
682 fn test_sdd_phase_display_roundtrip() {
683 for p in [
684 SddPhase::Backlog,
685 SddPhase::Explore,
686 SddPhase::Propose,
687 SddPhase::Spec,
688 SddPhase::Design,
689 SddPhase::Tasks,
690 SddPhase::Apply,
691 SddPhase::Verify,
692 SddPhase::Archive,
693 ] {
694 let s = p.to_string();
695 assert_eq!(s.parse::<SddPhase>().unwrap(), p);
696 }
697 }
698
699 #[test]
700 fn test_sdd_phase_default_is_backlog() {
701 assert_eq!(SddPhase::default(), SddPhase::Backlog);
702 }
703}