1use crate::store::Store;
5use serde_json::Value;
6
7#[derive(Debug, Clone, PartialEq, Default)]
8pub struct SelectedList {
9 pub commit: String, pub changed: Vec<String>, pub selected: Vec<String>, }
13
14fn str_array(obj: &serde_json::Map<String, Value>, k: &str) -> Result<Vec<String>, String> {
15 match obj.get(k) {
16 None => Ok(Vec::new()),
17 Some(Value::Array(a)) => a
18 .iter()
19 .map(|e| {
20 e.as_str()
21 .map(|s| s.to_string())
22 .ok_or(format!("selected-list.{k} element is not a string"))
23 })
24 .collect(),
25 Some(_) => Err(format!("selected-list.{k} must be an array")),
26 }
27}
28
29pub fn from_value(v: &Value) -> Result<SelectedList, String> {
31 let obj = v.as_object().ok_or("selected-list is not an object")?;
32 crate::tick::only_keys(obj, &["commit", "changed", "selected"], "selected-list")?;
33 let commit = obj
34 .get("commit")
35 .and_then(|x| x.as_str())
36 .unwrap_or("")
37 .to_string();
38 Ok(SelectedList {
39 commit,
40 changed: str_array(obj, "changed")?,
41 selected: str_array(obj, "selected")?,
42 })
43}
44
45pub fn read(store: &Store) -> std::io::Result<Option<SelectedList>> {
47 let path = store.root.join("results").join("selected.json");
48 let text = match std::fs::read_to_string(&path) {
49 Ok(t) => t,
50 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
51 Err(e) => return Err(e),
52 };
53 let v: Value = serde_json::from_str(&text)
54 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
55 from_value(&v)
56 .map(Some)
57 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use crate::store::Store;
64
65 fn store() -> (std::path::PathBuf, Store) {
66 use std::sync::atomic::{AtomicU64, Ordering};
67 static N: AtomicU64 = AtomicU64::new(0);
68 let p = std::env::temp_dir().join(format!(
69 "ev-selected-{}-{}",
70 std::process::id(),
71 N.fetch_add(1, Ordering::Relaxed)
72 ));
73 let _ = std::fs::remove_dir_all(&p);
74 std::fs::create_dir_all(&p).unwrap();
75 let s = Store::at(&p);
76 s.init().unwrap();
77 (p, s)
78 }
79
80 #[test]
81 fn read_should_parse_the_list_when_results_selected_json_exists() {
82 let (_p, s) = store();
84 std::fs::write(
85 s.root.join("results").join("selected.json"),
86 r#"{"commit":"d308afac1b2c3d4e5f60718293a4b5c6d7e8f901","changed":["pyproject.toml"],"selected":["pytest x"]}"#,
87 )
88 .unwrap();
89
90 let sl = read(&s).unwrap().expect("present");
92
93 assert_eq!(sl.changed, vec!["pyproject.toml".to_string()]);
95 assert_eq!(sl.selected, vec!["pytest x".to_string()]);
96 }
97
98 #[test]
99 fn read_should_be_none_when_no_selected_list_exists() {
100 let (_p, s) = store();
102
103 let sl = read(&s).unwrap();
105
106 assert!(sl.is_none());
108 }
109
110 #[test]
111 fn from_value_should_reject_the_list_when_it_has_an_unknown_field() {
112 let v = serde_json::json!({ "commit": "x", "selected": [], "health": 1 });
114
115 let parsed = from_value(&v);
117
118 assert!(parsed.is_err());
120 }
121}