use std::path::Path;
use std::sync::{OnceLock, RwLock};
use std::time::SystemTime;
use crate::schema::Schema;
fn cell() -> &'static RwLock<Option<CachedSchema>> {
static INSTANCE: OnceLock<RwLock<Option<CachedSchema>>> = OnceLock::new();
INSTANCE.get_or_init(|| RwLock::new(initial_load()))
}
#[derive(Debug, Clone)]
pub struct CachedSchema {
pub schema: Schema,
pub loaded_at: SystemTime,
}
fn initial_load() -> Option<CachedSchema> {
read_current_schema_file().ok().map(|schema| CachedSchema {
schema,
loaded_at: SystemTime::now(),
})
}
fn read_current_schema_file() -> Result<Schema, String> {
let path = Path::new("rustio.schema.json");
if !path.exists() {
return Err("rustio.schema.json not found".into());
}
let raw = std::fs::read_to_string(path).map_err(|e| format!("read error: {e}"))?;
Schema::parse(&raw).map_err(|e| format!("parse error: {e}"))
}
pub fn snapshot() -> Option<CachedSchema> {
cell().read().ok().and_then(|g| g.clone())
}
pub fn refresh() -> Result<CachedSchema, String> {
let fresh = read_current_schema_file()?;
let mut guard = cell()
.write()
.map_err(|_| "schema cache lock is poisoned — restart the server to recover".to_string())?;
let cached = CachedSchema {
schema: fresh,
loaded_at: SystemTime::now(),
};
*guard = Some(cached.clone());
Ok(cached)
}
pub fn refresh_best_effort() {
let _ = refresh();
}
pub fn format_loaded_at(t: SystemTime) -> String {
let datetime: chrono::DateTime<chrono::Utc> = t.into();
datetime.format("%Y-%m-%d %H:%M:%S UTC").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_loaded_at_produces_stable_shape() {
let t = SystemTime::UNIX_EPOCH;
let s = format_loaded_at(t);
assert_eq!(s, "1970-01-01 00:00:00 UTC");
}
#[test]
fn snapshot_returns_same_value_when_not_refreshed() {
let a = snapshot();
let b = snapshot();
assert_eq!(a.map(|c| c.schema), b.map(|c| c.schema),);
}
}