use std::path::Path; use serde::Deserialize; use crate::models::{Catalog, CatalogObject}; /// In-memory catalog store, shared (via `Arc`) across routes. Populated either /// from a DESI data file loaded at startup or from `placeholder`. #[derive(Debug, Clone, Default)] pub struct CatalogStore { pub catalogs: Vec, pub objects: Vec, } /// JSON layout of the DESI data file referenced by `API_DESI_DATA`. #[derive(Debug, Deserialize)] pub struct DataFile { pub catalogs: Vec, #[serde(default)] pub objects: Vec, } impl CatalogStore { /// Static fallback catalogs used when no `API_DESI_DATA` file is given /// (and by the `routes::app()` test helper). pub fn placeholder() -> Self { Self { catalogs: vec![ Catalog { name: "edr".to_string(), release: "EDR".to_string(), description: "DESI Early Data Release".to_string(), object_count: None, }, Catalog { name: "dr1".to_string(), release: "DR1".to_string(), description: "DESI Data Release 1".to_string(), object_count: None, }, ], objects: Vec::new(), } } /// Loads catalogs and objects from a JSON data file. Errors on unreadable /// files or malformed JSON so the caller can fail loudly instead of /// silently serving empty data. pub fn load(path: &Path) -> anyhow::Result { let text = std::fs::read_to_string(path)?; let file: DataFile = serde_json::from_str(&text)?; Ok(Self { catalogs: file.catalogs, objects: file.objects, }) } } #[cfg(test)] mod tests { use super::*; #[test] fn load_parses_catalogs_and_objects() { let json = r#"{ "catalogs": [ {"name":"edr","release":"EDR","description":"test","object_count":2} ], "objects": [ {"id":"o1","catalog":"edr","object_type":"GALAXY","ra":1.5,"dec":2.5,"redshift":0.8} ] }"#; let dir = std::env::temp_dir().join(format!( "desi_explorer_store_{}_{}", std::process::id(), line!() )); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("data.json"); std::fs::write(&path, json).unwrap(); let store = CatalogStore::load(&path).unwrap(); let _ = std::fs::remove_dir_all(&dir); assert_eq!(store.catalogs.len(), 1); assert_eq!(store.catalogs[0].name, "edr"); assert_eq!(store.catalogs[0].object_count, Some(2)); assert_eq!(store.objects.len(), 1); assert_eq!(store.objects[0].id, "o1"); assert_eq!(store.objects[0].ra, 1.5); } #[test] fn load_rejects_malformed_json() { let dir = std::env::temp_dir().join(format!( "desi_explorer_store_{}_{}", std::process::id(), line!() )); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("data.json"); std::fs::write(&path, "not json").unwrap(); let result = CatalogStore::load(&path); let _ = std::fs::remove_dir_all(&dir); assert!(result.is_err()); } }