spec_driven_docs/services/
known_issues.rs1use camino::Utf8Path;
10use serde::Serialize;
11
12use crate::error::AppError;
13use crate::gates::front_matter_values;
14use crate::gates::paths::ki_records;
15use crate::gates::{GateCtx, read_text};
16
17#[derive(Debug, Serialize)]
19pub struct Case {
20 pub id: String,
22 pub state: Option<String>,
24 pub checked: Option<String>,
26 pub filing: Option<String>,
28 pub upstream: Option<String>,
30 pub path: String,
32}
33
34pub fn cases(target: &Utf8Path) -> Result<Vec<Case>, AppError> {
40 let ctx = GateCtx::new(target);
41 let mut cases = Vec::new();
42 for record in ki_records(&ctx, &[])? {
43 let text = read_text(&ctx, &record)?;
44 cases.push(Case {
45 id: record.file_stem().unwrap_or_default().to_string(),
46 state: first(&text, "state"),
47 checked: first(&text, "checked"),
48 filing: first(&text, "filing"),
49 upstream: first(&text, "upstream"),
50 path: record.to_string(),
51 });
52 }
53 Ok(cases)
54}
55
56fn first(text: &str, key: &str) -> Option<String> {
58 front_matter_values(text, key)
59 .into_iter()
60 .find(|value| !value.is_empty())
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 fn target_with(record: &str) -> tempfile::TempDir {
68 let dir = tempfile::tempdir().unwrap();
69 let records = dir.path().join("_docs/reference/known-issues");
70 std::fs::create_dir_all(&records).unwrap();
71 std::fs::write(records.join("KI-vendor-replays.md"), record).unwrap();
72 dir
73 }
74
75 #[test]
76 fn reads_both_axes_and_the_upstream_reference() {
77 let dir = target_with(
78 "---\nupstream: https://example.invalid/issues/1\nstate: masked\nchecked: 2026-06-18\nfiling: filed\n---\n# V\n",
79 );
80 let target = camino::Utf8Path::from_path(dir.path()).unwrap();
81 let cases = cases(target).unwrap();
82 assert_eq!(cases.len(), 1);
83 assert_eq!(cases[0].id, "KI-vendor-replays");
84 assert_eq!(cases[0].state.as_deref(), Some("masked"));
85 assert_eq!(cases[0].checked.as_deref(), Some("2026-06-18"));
86 assert_eq!(cases[0].filing.as_deref(), Some("filed"));
87 assert_eq!(
88 cases[0].path,
89 "_docs/reference/known-issues/KI-vendor-replays.md"
90 );
91 }
92
93 #[test]
94 fn a_record_stating_no_axis_reports_none_rather_than_failing() {
95 let dir = target_with("---\naffects: client\n---\n# V\n");
96 let target = camino::Utf8Path::from_path(dir.path()).unwrap();
97 let cases = cases(target).unwrap();
98 assert_eq!(cases.len(), 1);
99 assert!(cases[0].state.is_none());
100 assert!(cases[0].checked.is_none());
101 assert!(cases[0].filing.is_none());
102 assert!(cases[0].upstream.is_none());
103 }
104
105 #[test]
106 fn a_record_stating_an_empty_checked_reports_none() {
107 let dir = target_with("---\nstate: masked\nchecked:\n---\n# V\n");
108 let target = camino::Utf8Path::from_path(dir.path()).unwrap();
109 let cases = cases(target).unwrap();
110 assert_eq!(cases.len(), 1);
111 assert!(cases[0].checked.is_none());
112 }
113
114 #[test]
115 fn a_target_with_no_zone_lists_nothing() {
116 let dir = tempfile::tempdir().unwrap();
117 let target = camino::Utf8Path::from_path(dir.path()).unwrap();
118 assert!(cases(target).unwrap().is_empty());
119 }
120}