use derive_more::Display;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use zarrs_chunk_key_encoding::{ChunkKeyEncoding, ChunkKeyEncodingPlugin, ChunkKeyEncodingTraits};
use zarrs_metadata::v3::MetadataV3;
use zarrs_metadata::{ChunkKeySeparator, Configuration, ConfigurationSerialize};
use zarrs_plugin::PluginCreateError;
use zarrs_storage::StoreKey;
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display)]
#[serde(deny_unknown_fields)]
#[display("{}", serde_json::to_string(self).unwrap_or_default())]
pub struct DefaultSuffixChunkKeyEncodingConfiguration {
#[serde(default = "default_separator")]
pub separator: ChunkKeySeparator,
pub suffix: String,
}
impl ConfigurationSerialize for DefaultSuffixChunkKeyEncodingConfiguration {}
const fn default_separator() -> ChunkKeySeparator {
ChunkKeySeparator::Slash
}
zarrs_plugin::impl_extension_aliases!(DefaultSuffixChunkKeyEncoding,
v3: "zarrs.default_suffix", []
);
inventory::submit! {
ChunkKeyEncodingPlugin::new::<DefaultSuffixChunkKeyEncoding>()
}
#[derive(Debug, Clone)]
pub struct DefaultSuffixChunkKeyEncoding {
separator: ChunkKeySeparator,
suffix: String,
}
impl DefaultSuffixChunkKeyEncoding {
#[must_use]
pub const fn new(separator: ChunkKeySeparator, suffix: String) -> Self {
Self { separator, suffix }
}
}
impl ChunkKeyEncodingTraits for DefaultSuffixChunkKeyEncoding {
fn create(metadata: &MetadataV3) -> Result<ChunkKeyEncoding, PluginCreateError> {
crate::warn_experimental_extension(metadata.name(), "chunk key encoding");
let configuration: DefaultSuffixChunkKeyEncodingConfiguration =
metadata.to_typed_configuration()?;
let default =
DefaultSuffixChunkKeyEncoding::new(configuration.separator, configuration.suffix);
Ok(default.into())
}
fn configuration(&self) -> Configuration {
DefaultSuffixChunkKeyEncodingConfiguration {
separator: self.separator,
suffix: self.suffix.clone(),
}
.into()
}
fn encode(&self, chunk_grid_indices: &[u64]) -> StoreKey {
const PREFIX: &str = "c";
let suffix: &str = &self.suffix;
let key = if chunk_grid_indices.is_empty() {
format!("{PREFIX}{suffix}")
} else {
let mut separator_str: [u8; 4] = [0; 4];
let separator_char: char = self.separator.into();
let separator_str: &str = separator_char.encode_utf8(&mut separator_str);
let mut buffers = vec![itoa::Buffer::new(); chunk_grid_indices.len()];
let iter = chunk_grid_indices
.iter()
.zip(&mut buffers)
.map(|(&n, buffer)| buffer.format(n));
#[allow(clippy::let_and_return)]
let out = [PREFIX].into_iter().chain(iter).join(separator_str) + suffix;
out
};
unsafe { StoreKey::new_unchecked(key) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::{NodePath, data_key};
#[test]
fn slash_nd() {
let chunk_key_encoding: ChunkKeyEncoding =
DefaultSuffixChunkKeyEncoding::new(ChunkKeySeparator::Slash, ".tiff".to_string())
.into();
let key = data_key(&NodePath::root(), &chunk_key_encoding.encode(&[1, 23, 45]));
assert_eq!(key, StoreKey::new("c/1/23/45.tiff").unwrap());
}
#[test]
fn dot_nd() {
let chunk_key_encoding: ChunkKeyEncoding =
DefaultSuffixChunkKeyEncoding::new(ChunkKeySeparator::Dot, ".tiff".to_string()).into();
let key = data_key(&NodePath::root(), &chunk_key_encoding.encode(&[1, 23, 45]));
assert_eq!(key, StoreKey::new("c.1.23.45.tiff").unwrap());
}
#[test]
fn slash_scalar() {
let chunk_key_encoding: ChunkKeyEncoding =
DefaultSuffixChunkKeyEncoding::new(ChunkKeySeparator::Slash, ".tiff".to_string())
.into();
let key = data_key(&NodePath::root(), &chunk_key_encoding.encode(&[]));
assert_eq!(key, StoreKey::new("c.tiff").unwrap());
}
#[test]
fn dot_scalar() {
let chunk_key_encoding: ChunkKeyEncoding =
DefaultSuffixChunkKeyEncoding::new(ChunkKeySeparator::Dot, ".tiff".to_string()).into();
let key = data_key(&NodePath::root(), &chunk_key_encoding.encode(&[]));
assert_eq!(key, StoreKey::new("c.tiff").unwrap());
}
}