use derive_more::{Display, From};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use zarrs_metadata::{ConfigurationSerialize, DataTypeSize};
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display, From)]
#[non_exhaustive]
#[serde(untagged)]
pub enum BloscCodecConfiguration {
V1(BloscCodecConfigurationV1),
Numcodecs(BloscCodecConfigurationNumcodecs),
}
impl ConfigurationSerialize for BloscCodecConfiguration {}
#[derive(Serialize, Copy, Clone, Debug, Eq, PartialEq)]
pub struct BloscCompressionLevel(u8);
impl From<BloscCompressionLevel> for u8 {
fn from(val: BloscCompressionLevel) -> Self {
val.0
}
}
impl TryFrom<u8> for BloscCompressionLevel {
type Error = u8;
fn try_from(level: u8) -> Result<Self, Self::Error> {
if level <= 9 {
Ok(Self(level))
} else {
Err(level)
}
}
}
impl<'de> serde::Deserialize<'de> for BloscCompressionLevel {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let level = u8::deserialize(d)?;
if level <= 9 {
Ok(Self(level))
} else {
Err(serde::de::Error::custom("clevel must be between 0 and 9"))
}
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
#[repr(u32)]
pub enum BloscShuffleMode {
#[default]
NoShuffle = 0, Shuffle = 1, BitShuffle = 2, }
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BloscCompressor {
BloscLZ,
LZ4,
LZ4HC,
Snappy,
Zlib,
Zstd,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display)]
#[serde(deny_unknown_fields)]
#[display("{}", serde_json::to_string(self).unwrap_or_default())]
pub struct BloscCodecConfigurationV1 {
pub cname: BloscCompressor,
pub clevel: BloscCompressionLevel,
#[serde(default)]
pub shuffle: BloscShuffleMode,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub typesize: Option<usize>,
pub blocksize: usize,
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display)]
#[serde(from = "BloscCodecConfigurationNumcodecs16_0")]
#[display("{}", serde_json::to_string(self).unwrap_or_default())]
pub struct BloscCodecConfigurationNumcodecs {
pub cname: BloscCompressor,
pub clevel: BloscCompressionLevel,
pub shuffle: BloscShuffleModeNumcodecs,
#[serde(default)]
pub blocksize: usize,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct BloscCodecConfigurationNumcodecs16_0 {
cname: BloscCompressor,
clevel: BloscCompressionLevel,
shuffle: BloscShuffleModeNumcodecs,
#[serde(default)]
blocksize: usize,
#[allow(dead_code)]
typesize: Option<usize>,
}
impl From<BloscCodecConfigurationNumcodecs16_0> for BloscCodecConfigurationNumcodecs {
fn from(value: BloscCodecConfigurationNumcodecs16_0) -> Self {
BloscCodecConfigurationNumcodecs {
cname: value.cname,
clevel: value.clevel,
shuffle: value.shuffle,
blocksize: value.blocksize,
}
}
}
#[derive(Serialize_repr, Deserialize_repr, Clone, Eq, PartialEq, Debug, Display)]
#[repr(i8)]
pub enum BloscShuffleModeNumcodecs {
NoShuffle = 0,
Shuffle = 1,
BitShuffle = 2,
AutoShuffle = -1,
}
#[must_use]
pub fn codec_blosc_v2_numcodecs_to_v3(
blosc: &BloscCodecConfigurationNumcodecs,
data_type_size: Option<DataTypeSize>,
) -> BloscCodecConfiguration {
let (shuffle, typesize) = match (&blosc.shuffle, data_type_size) {
(BloscShuffleModeNumcodecs::NoShuffle, _) | (_, None) => {
(BloscShuffleMode::NoShuffle, None)
}
(BloscShuffleModeNumcodecs::Shuffle, Some(DataTypeSize::Fixed(data_type_size))) => {
(BloscShuffleMode::Shuffle, Some(data_type_size))
}
(BloscShuffleModeNumcodecs::BitShuffle, Some(DataTypeSize::Fixed(data_type_size))) => {
(BloscShuffleMode::BitShuffle, Some(data_type_size))
}
(BloscShuffleModeNumcodecs::AutoShuffle, Some(DataTypeSize::Fixed(data_type_size))) => {
if data_type_size == 1 {
(BloscShuffleMode::BitShuffle, Some(data_type_size))
} else {
(BloscShuffleMode::Shuffle, Some(data_type_size))
}
}
(
BloscShuffleModeNumcodecs::Shuffle
| BloscShuffleModeNumcodecs::BitShuffle
| BloscShuffleModeNumcodecs::AutoShuffle,
Some(DataTypeSize::Variable),
) => {
(BloscShuffleMode::NoShuffle, None)
}
};
BloscCodecConfiguration::V1(BloscCodecConfigurationV1 {
cname: blosc.cname,
clevel: blosc.clevel,
shuffle,
typesize,
blocksize: blosc.blocksize,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn codec_blosc_valid1() {
serde_json::from_str::<BloscCodecConfiguration>(
r#"
{
"cname": "lz4",
"clevel": 5,
"shuffle": "shuffle",
"typesize": 4,
"blocksize": 0
}"#,
)
.unwrap();
}
#[test]
fn codec_blosc_valid2() {
serde_json::from_str::<BloscCodecConfiguration>(
r#"
{
"cname": "lz4",
"clevel": 4,
"shuffle": "bitshuffle",
"typesize": 4,
"blocksize": 0
}"#,
)
.unwrap();
}
#[test]
fn codec_blosc_invalid_no_typesize() {
serde_json::from_str::<BloscCodecConfiguration>(
r#"
{
"cname": "lz4",
"clevel": 4,
"shuffle": "bitshuffle",
"blocksize": 0
}"#,
)
.unwrap();
}
#[test]
fn codec_blosc_valid_no_shuffle() {
serde_json::from_str::<BloscCodecConfiguration>(
r#"
{
"cname": "lz4",
"clevel": 4,
"blocksize": 0
}"#,
)
.unwrap();
}
#[test]
fn codec_blosc_valid_no_typesize() {
serde_json::from_str::<BloscCodecConfiguration>(
r#"
{
"cname": "lz4",
"clevel": 4,
"shuffle": "shuffle",
"blocksize": 0
}"#,
)
.unwrap();
}
#[test]
fn codec_blosc_invalid_clevel() {
let json = r#"
{
"cname": "lz4",
"clevel": 10,
"shuffle": "shuffle",
"typesize": 4,
"blocksize": 0
}"#;
let codec_configuration = serde_json::from_str::<BloscCodecConfiguration>(json);
assert!(codec_configuration.is_err());
}
#[test]
fn codec_blosc_invalid_cname() {
let json = r#"
{
"cname": "",
"clevel": 1,
"shuffle": "shuffle",
"typesize": 4,
"blocksize": 0
}"#;
let codec_configuration = serde_json::from_str::<BloscCodecConfiguration>(json);
assert!(codec_configuration.is_err());
}
#[test]
fn codec_blosc_invalid_shuffle() {
let json = r#"
{
"cname": "lz4",
"clevel": 1,
"shuffle": "",
"typesize": 4,
"blocksize": 0
}"#;
let codec_configuration = serde_json::from_str::<BloscCodecConfiguration>(json);
assert!(codec_configuration.is_err());
}
#[test]
fn codec_blosc_v2_numcodecs() {
serde_json::from_str::<BloscCodecConfigurationNumcodecs>(
r#"
{
"cname": "lz4",
"clevel": 5,
"shuffle": 2,
"blocksize": 0
}"#,
)
.unwrap();
}
}