use anyhow::{Context, Result, bail};
use qk_taskgraph::Task;
use serde_json::Value;
pub const FIELDS: &[&str] = &["mode", "ignoreReads"];
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Settings {
pub mode: Mode,
pub ignore_reads: Vec<String>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Mode {
#[default]
On,
Record,
}
impl Settings {
pub fn of(task: &Task) -> Result<Option<Self>> {
let Some(value) = task.definition.extra.get("qk:ultracache") else {
return Ok(None);
};
let Value::Object(object) = value else {
bail!("qk:ultracache must be an object");
};
let mode = match object.get("mode").map(Value::as_str) {
None | Some(Some("on")) => Mode::On,
Some(Some("record")) => Mode::Record,
Some(Some("off")) => return Ok(None),
Some(Some(mode @ ("warn" | "error"))) => {
bail!(
"qk:ultracache.mode {mode:?} is not supported yet; use \"on\", \"record\" or \"off\""
)
}
Some(_) => bail!("qk:ultracache.mode must be \"on\", \"record\" or \"off\""),
};
let ignore_reads: Vec<String> = match object.get("ignoreReads") {
None => Vec::new(),
Some(value) => serde_json::from_value(value.clone())
.context("qk:ultracache.ignoreReads must be an array of globs")?,
};
for glob in &ignore_reads {
crate::glob::Pattern::new(glob, false).with_context(|| {
format!("qk:ultracache.ignoreReads has an invalid glob {glob:?}")
})?;
}
Ok(Some(Self { mode, ignore_reads }))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn task(value: Value) -> Task {
let mut definition: qk_config::Target =
serde_json::from_value(serde_json::json!({})).unwrap();
definition.extra.insert("qk:ultracache".into(), value);
Task {
id: "app:build".into(),
project: "app".into(),
target: "build".into(),
configuration: None,
args: Vec::new(),
definition,
dependencies: Default::default(),
edges: Default::default(),
}
}
#[test]
fn reads_the_mode_and_ignored_reads() {
let on = Settings::of(&task(serde_json::json!({"ignoreReads": ["tmp/**"]})))
.unwrap()
.unwrap();
assert_eq!(on.ignore_reads, ["tmp/**"]);
assert_eq!(on.mode, Mode::On);
let record = Settings::of(&task(serde_json::json!({"mode": "record"})))
.unwrap()
.unwrap();
assert_eq!(record.mode, Mode::Record);
assert!(
Settings::of(&task(serde_json::json!({"mode": "off"})))
.unwrap()
.is_none()
);
for invalid in [
serde_json::json!(true),
serde_json::json!({"mode": "warn"}),
serde_json::json!({"mode": 1}),
serde_json::json!({"ignoreReads": "tmp/**"}),
serde_json::json!({"ignoreReads": ["src/[*.log"]}),
] {
assert!(Settings::of(&task(invalid.clone())).is_err(), "{invalid}");
}
}
}