use std::path::{Path, PathBuf}; use crank_core::{DescriptorId, OperationId, SampleId}; use crank_registry::{DescriptorKind, 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)?) } pub async fn write_descriptor( &self, operation_id: &OperationId, version: u32, descriptor_kind: DescriptorKind, descriptor_id: &DescriptorId, source_name: Option<&str>, payload: &[u8], ) -> Result { let file_name = match descriptor_kind { DescriptorKind::ProtoUpload => source_name.unwrap_or("schema.proto").to_owned(), DescriptorKind::DescriptorSet => source_name.unwrap_or("descriptor-set.bin").to_owned(), DescriptorKind::ReflectionSnapshot => { source_name.unwrap_or("reflection.bin").to_owned() } DescriptorKind::WsdlUpload => source_name.unwrap_or("service.wsdl").to_owned(), DescriptorKind::XsdUpload => source_name.unwrap_or("schema.xsd").to_owned(), }; let path = self .root .join("descriptors") .join(operation_id.as_str()) .join(format!("v{version}")) .join(format!("{}_{}", descriptor_id.as_str(), file_name)); write_bytes(&path, payload).await?; Ok(to_storage_ref(&path)) } pub async fn read_bytes(&self, storage_ref: &str) -> Result, StorageError> { let path = from_storage_ref(storage_ref)?; Ok(fs::read(path).await?) } } 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(()) } async fn write_bytes(path: &Path, payload: &[u8]) -> Result<(), StorageError> { if let Some(parent) = path.parent() { fs::create_dir_all(parent).await?; } fs::write(path, payload).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)) }