use serde::de::DeserializeOwned;
use std::{fs, io, path::PathBuf};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CachedJsonReport<T> {
pub path: PathBuf,
pub report: T,
}
pub trait JsonCacheReport {
fn schema_version(&self) -> u32;
fn network(&self) -> &str;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LoadJsonCacheRequest<'a> {
pub path: PathBuf,
pub network: &'a str,
pub expected_schema_version: u32,
}
pub trait LoadJsonCacheErrorMapper {
type Error;
fn missing_cache(&self, path: PathBuf) -> Self::Error;
fn read_cache(&self, path: PathBuf, source: io::Error) -> Self::Error;
fn parse_cache(&self, path: PathBuf, source: serde_json::Error) -> Self::Error;
fn unsupported_schema(&self, version: u32, expected: u32) -> Self::Error;
fn network_mismatch(&self, requested: String, actual: String) -> Self::Error;
}
pub fn announce_cache_refresh(component: &str, path: &std::path::Path, source_endpoint: &str) {
eprintln!(
"{component} cache missing at {}; calling {source_endpoint} to refresh/create cache",
path.display()
);
}
pub fn load_json_cache<T, Errors>(
request: LoadJsonCacheRequest<'_>,
errors: Errors,
) -> Result<CachedJsonReport<T>, Errors::Error>
where
T: DeserializeOwned + JsonCacheReport,
Errors: LoadJsonCacheErrorMapper,
{
let path = request.path;
if !path.is_file() {
return Err(errors.missing_cache(path));
}
let data =
fs::read_to_string(&path).map_err(|source| errors.read_cache(path.clone(), source))?;
let report = serde_json::from_str::<T>(&data)
.map_err(|source| errors.parse_cache(path.clone(), source))?;
let actual_schema_version = report.schema_version();
if actual_schema_version != request.expected_schema_version {
return Err(
errors.unsupported_schema(actual_schema_version, request.expected_schema_version)
);
}
let actual_network = report.network();
if actual_network != request.network {
return Err(
errors.network_mismatch(request.network.to_string(), actual_network.to_string())
);
}
Ok(CachedJsonReport { path, report })
}