use std::sync::Arc;
use zarrs::storage::byte_range::ByteRangeIterator;
use zarrs::storage::{
Bytes, MaybeBytesIterator, ReadableStorageTraits, StorageError, StoreKey,
};
use crate::cache::{Cache, FetchRequest};
pub(crate) fn sanitize_v2_array_meta(data: Vec<u8>) -> Vec<u8> {
let Ok(serde_json::Value::Object(mut map)) =
serde_json::from_slice::<serde_json::Value>(&data)
else {
return data;
};
if map.get("dimension_separator") != Some(&serde_json::Value::Null) {
return data;
}
map.remove("dimension_separator");
serde_json::to_vec(&serde_json::Value::Object(map)).unwrap_or(data)
}
#[cfg(feature = "object-store")]
pub(crate) struct SanitizedV2<S>(Arc<S>);
#[cfg(feature = "object-store")]
impl<S> SanitizedV2<S> {
pub(crate) fn new(inner: Arc<S>) -> Self {
Self(inner)
}
fn is_v2_array_meta(key: &StoreKey) -> bool {
key.as_str().ends_with(".zarray")
}
}
#[cfg(feature = "object-store")]
impl<S: ReadableStorageTraits> SanitizedV2<S> {
fn sanitized_whole(&self, key: &StoreKey) -> Result<Option<Bytes>, StorageError> {
let Some(raw) = self.0.get(key)? else {
return Ok(None);
};
Ok(Some(Bytes::from(sanitize_v2_array_meta(raw.to_vec()))))
}
}
#[cfg(feature = "object-store")]
impl<S: ReadableStorageTraits> ReadableStorageTraits for SanitizedV2<S> {
fn get_partial_many<'a>(
&'a self,
key: &StoreKey,
byte_ranges: ByteRangeIterator<'a>,
) -> Result<MaybeBytesIterator<'a>, StorageError> {
if !Self::is_v2_array_meta(key) {
return self.0.get_partial_many(key, byte_ranges);
}
let Some(whole) = self.sanitized_whole(key)? else {
return Ok(None);
};
let size = whole.len() as u64;
let mut out: Vec<Result<Bytes, StorageError>> = Vec::new();
for br in byte_ranges {
let start = br.start(size) as usize;
let end = start + br.length(size) as usize;
if end > whole.len() {
out.push(Err(StorageError::Other(format!(
"zarr byte range {start}..{end} out of bounds for object of {} bytes",
whole.len()
))));
} else {
out.push(Ok(whole.slice(start..end)));
}
}
Ok(Some(Box::new(out.into_iter())))
}
fn size_key(&self, key: &StoreKey) -> Result<Option<u64>, StorageError> {
if !Self::is_v2_array_meta(key) {
return self.0.size_key(key);
}
Ok(self.sanitized_whole(key)?.map(|b| b.len() as u64))
}
fn supports_get_partial(&self) -> bool {
self.0.supports_get_partial()
}
}
pub(crate) struct CacheStorage {
cache: Arc<Cache>,
base: String,
}
impl CacheStorage {
pub(crate) fn new(cache: Arc<Cache>, base_url: &str) -> Self {
Self {
cache,
base: base_url.trim_end_matches('/').to_string(),
}
}
fn fetch_whole(&self, key: &StoreKey) -> Result<Option<Bytes>, StorageError> {
let url = format!("{}/{}", self.base, key.as_str());
match self.cache.fetch(&FetchRequest::new(&url)) {
Ok(blob) => {
let data = std::fs::read(&blob.path).map_err(|e| {
StorageError::Other(format!(
"reading cached zarr object {}: {e}",
blob.path.display()
))
})?;
let data = if key.as_str().ends_with(".zarray") {
sanitize_v2_array_meta(data)
} else {
data
};
Ok(Some(Bytes::from(data)))
}
Err(e) if e.is_cache_miss() || e.is_not_found() => Ok(None),
Err(e) => Err(StorageError::Other(format!(
"cache fetch of zarr object {url}: {e}"
))),
}
}
}
impl ReadableStorageTraits for CacheStorage {
fn get_partial_many<'a>(
&'a self,
key: &StoreKey,
byte_ranges: ByteRangeIterator<'a>,
) -> Result<MaybeBytesIterator<'a>, StorageError> {
let Some(whole) = self.fetch_whole(key)? else {
return Ok(None);
};
let size = whole.len() as u64;
let mut out: Vec<Result<Bytes, StorageError>> = Vec::new();
for br in byte_ranges {
let start = br.start(size) as usize;
let len = br.length(size) as usize;
let end = start + len;
if end > whole.len() {
out.push(Err(StorageError::Other(format!(
"zarr byte range {start}..{end} out of bounds for object of {} bytes",
whole.len()
))));
} else {
out.push(Ok(whole.slice(start..end)));
}
}
Ok(Some(Box::new(out.into_iter())))
}
fn size_key(&self, key: &StoreKey) -> Result<Option<u64>, StorageError> {
Ok(self.fetch_whole(key)?.map(|b| b.len() as u64))
}
fn supports_get_partial(&self) -> bool {
false
}
}