use std::path::{Path, PathBuf}; use crank_core::{OperationId, SampleId}; use crank_registry::SampleKind; use serde_json::Value; use thiserror::Error; use tokio::fs; #[derive(Clone)] pub struct LocalArtifactStorage { root: PathBuf, } #[derive(Debug, Error)] pub enum StorageError { #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] Serialization(#[from] serde_json::Error), #[error("invalid storage reference: {details}")] InvalidStorageRef { details: String }, } impl LocalArtifactStorage { pub fn new(root: PathBuf) -> Self { Self { root } } pub async fn write_json_sample( &self, operation_id: &OperationId, version: u32, sample_kind: SampleKind, sample_id: &SampleId, payload: &Value, ) -> Result { let file_name = match sample_kind { SampleKind::InputJson => "input.json", SampleKind::OutputJson => "output.json", SampleKind::YamlImportSource => "source.json", }; let path = self .root .join("samples") .join(operation_id.as_str()) .join(format!("v{version}")) .join(format!("{}_{}", sample_id.as_str(), file_name)); write_json_file(&path, payload).await?; Ok(to_storage_ref(&path)) } pub async fn read_json(&self, storage_ref: &str) -> Result { let path = from_storage_ref(storage_ref)?; let bytes = fs::read(path).await?; Ok(serde_json::from_slice(&bytes)?) } } async fn write_json_file(path: &Path, payload: &Value) -> Result<(), StorageError> { if let Some(parent) = path.parent() { fs::create_dir_all(parent).await?; } let bytes = serde_json::to_vec_pretty(payload)?; fs::write(path, bytes).await?; Ok(()) } fn to_storage_ref(path: &Path) -> String { format!("file://{}", path.display()) } fn from_storage_ref(storage_ref: &str) -> Result { let Some(path) = storage_ref.strip_prefix("file://") else { return Err(StorageError::InvalidStorageRef { details: storage_ref.to_owned(), }); }; Ok(PathBuf::from(path)) }