use std::path::{Path, PathBuf};
use fraiseql_core::schema::CompiledSchema;
use tracing::{debug, info};
#[derive(Debug, thiserror::Error)]
pub enum SchemaLoadError {
#[error("Schema file not found: {0}")]
NotFound(PathBuf),
#[error("Failed to read schema file: {0}")]
IoError(#[from] std::io::Error),
#[error("Failed to parse schema JSON: {0}")]
ParseError(#[from] serde_json::Error),
#[error("Invalid schema: {0}")]
ValidationError(String),
}
#[derive(Debug, Clone)]
pub struct CompiledSchemaLoader {
path: PathBuf,
}
impl CompiledSchemaLoader {
#[must_use]
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self {
path: path.as_ref().to_path_buf(),
}
}
pub async fn load(&self) -> Result<CompiledSchema, SchemaLoadError> {
info!(path = %self.path.display(), "Loading compiled schema");
if !self.path.exists() {
return Err(SchemaLoadError::NotFound(self.path.clone()));
}
let contents =
tokio::fs::read_to_string(&self.path).await.map_err(SchemaLoadError::IoError)?;
debug!(
path = %self.path.display(),
size_bytes = contents.len(),
"Schema file read successfully"
);
serde_json::from_str::<serde_json::Value>(&contents)?;
let schema = CompiledSchema::from_json(&contents)
.map_err(|e| SchemaLoadError::ValidationError(e.to_string()))?;
info!(path = %self.path.display(), "Schema loaded successfully");
Ok(schema)
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use tempfile::NamedTempFile;
use super::*;
#[tokio::test]
async fn test_loader_not_found() {
let loader = CompiledSchemaLoader::new("/nonexistent/path/schema.json");
let result = loader.load().await;
assert!(matches!(result, Err(SchemaLoadError::NotFound(_))));
}
#[tokio::test]
async fn test_loader_invalid_json() {
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "{{invalid json").unwrap();
file.flush().unwrap();
let loader = CompiledSchemaLoader::new(file.path());
let result = loader.load().await;
assert!(matches!(result, Err(SchemaLoadError::ParseError(_))));
}
}