use crate::cli::CheckArgs;
use crate::scan_cache::{CACHE_REFRESH_COMMAND, CacheStatus, ScanCache};
use crate::target::Target;
use evenframe_core::{
error::{EvenframeError, Result},
scan::ScanConfig,
};
use serde::Serialize;
pub async fn run(args: CheckArgs) -> Result<()> {
let target = Target::discover()?;
let reports = target
.focused()
.into_iter()
.map(|(project, config)| {
let scan = ScanConfig::from_config(config);
Ok(Report::new(
project,
ScanCache::path(&scan.scan_path).display().to_string(),
ScanCache::status(&scan)?,
))
})
.collect::<Result<Vec<_>>>()?;
if args.json {
let json = match reports.as_slice() {
[only] if !target.covers_workspace() => serde_json::to_string_pretty(only)?,
_ => serde_json::to_string_pretty(&reports)?,
};
println!("{json}");
} else {
for report in &reports {
report.print();
}
}
if !reports.iter().all(Report::in_sync) {
return Err(EvenframeError::Validation(
"the scan cache is not current".to_string(),
));
}
Ok(())
}
#[derive(Debug, Serialize)]
struct Report {
#[serde(skip_serializing_if = "String::is_empty")]
project: String,
cache_path: String,
#[serde(flatten)]
status: ReportStatus,
}
#[derive(Debug, Serialize)]
#[serde(tag = "status", rename_all = "snake_case")]
enum ReportStatus {
InSync { types: TypeCounts },
Missing,
Stale { reason: String },
}
#[derive(Debug, Serialize)]
struct TypeCounts {
tables: usize,
objects: usize,
enums: usize,
}
impl Report {
fn new(project: &str, cache_path: String, status: CacheStatus) -> Self {
let status = match status {
CacheStatus::Current(cache) => ReportStatus::InSync {
types: TypeCounts {
tables: cache.configs.tables.len(),
objects: cache.configs.objects.len(),
enums: cache.configs.enums.len(),
},
},
CacheStatus::Absent => ReportStatus::Missing,
CacheStatus::Stale(reason) => ReportStatus::Stale { reason },
};
Self {
project: project.to_string(),
cache_path,
status,
}
}
fn in_sync(&self) -> bool {
matches!(self.status, ReportStatus::InSync { .. })
}
fn print(&self) {
if !self.project.is_empty() {
println!("Project {}", self.project);
}
match &self.status {
ReportStatus::InSync { types } => {
println!("Types are in sync with {}", self.cache_path);
println!(
" tables: {}, objects: {}, enums: {}",
types.tables, types.objects, types.enums
);
}
ReportStatus::Missing => {
println!("No scan cache at {}", self.cache_path);
println!(" Run `{CACHE_REFRESH_COMMAND}` to write it");
}
ReportStatus::Stale { reason } => {
println!("Types are out of sync: {reason}");
println!(
" Run `{CACHE_REFRESH_COMMAND}` to refresh {}",
self.cache_path
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::{CacheStatus, Report, ScanCache};
use crate::scan_cache::CACHE_FORMAT_VERSION;
use evenframe_core::types::{AllConfigs, StructConfig};
use std::collections::BTreeMap;
fn cache_with_one_object() -> ScanCache {
ScanCache {
format_version: CACHE_FORMAT_VERSION,
evenframe_version: "0.0.0".to_string(),
stamped_at_ns: 0,
inputs: BTreeMap::new(),
configs: AllConfigs {
objects: BTreeMap::from([("Address".to_string(), StructConfig::default())]),
..AllConfigs::default()
},
}
}
fn report(status: CacheStatus) -> Report {
Report::new("", ".evenframe/cache.json".to_string(), status)
}
#[test]
fn only_a_current_cache_is_in_sync() {
assert!(report(CacheStatus::Current(cache_with_one_object())).in_sync());
assert!(!report(CacheStatus::Absent).in_sync());
assert!(!report(CacheStatus::Stale("src/lib.rs changed".to_string())).in_sync());
}
#[test]
fn json_carries_the_status_counts_and_reason() {
let json =
serde_json::to_value(report(CacheStatus::Current(cache_with_one_object()))).unwrap();
assert_eq!(json["status"], "in_sync");
assert_eq!(json.get("project"), None);
assert_eq!(json["cache_path"], ".evenframe/cache.json");
assert_eq!(json["types"]["objects"], 1);
assert_eq!(json["types"]["tables"], 0);
let json = serde_json::to_value(report(CacheStatus::Absent)).unwrap();
assert_eq!(json["status"], "missing");
let json = serde_json::to_value(Report::new(
"app",
"app/.evenframe/cache.json".to_string(),
CacheStatus::Absent,
))
.unwrap();
assert_eq!(json["project"], "app");
let json =
serde_json::to_value(report(CacheStatus::Stale("src/lib.rs changed".to_string())))
.unwrap();
assert_eq!(json["status"], "stale");
assert_eq!(json["reason"], "src/lib.rs changed");
}
}