use derive_more::{Display, From};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use zarrs_metadata::ConfigurationSerialize;
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display, From)]
#[non_exhaustive]
#[serde(untagged)]
pub enum ZstdCodecConfiguration {
V1(ZstdCodecConfigurationV1),
Numcodecs(ZstdCodecConfigurationNumcodecs),
}
impl ConfigurationSerialize for ZstdCodecConfiguration {}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display)]
#[serde(deny_unknown_fields)]
#[display("{}", serde_json::to_string(self).unwrap_or_default())]
pub struct ZstdCodecConfigurationV1 {
pub level: ZstdCompressionLevel,
pub checksum: bool,
}
impl ZstdCodecConfigurationV1 {
#[must_use]
pub const fn new(level: ZstdCompressionLevel, checksum: bool) -> Self {
Self { level, checksum }
}
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display)]
#[serde(deny_unknown_fields)]
#[display("{}", serde_json::to_string(self).unwrap_or_default())]
pub struct ZstdCodecConfigurationNumcodecs {
pub level: ZstdCompressionLevel,
}
#[must_use]
pub fn codec_zstd_v2_numcodecs_to_v3(zstd: &ZstdCodecConfiguration) -> ZstdCodecConfiguration {
match zstd {
ZstdCodecConfiguration::V1(zstd) => ZstdCodecConfiguration::V1(zstd.clone()),
ZstdCodecConfiguration::Numcodecs(ZstdCodecConfigurationNumcodecs { level }) => {
ZstdCodecConfiguration::V1(ZstdCodecConfigurationV1::new(*level, false))
}
}
}
#[derive(Serialize, Clone, Copy, Eq, PartialEq, Debug)]
pub struct ZstdCompressionLevel(i32);
impl<'de> serde::Deserialize<'de> for ZstdCompressionLevel {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = Value::deserialize(d)?;
match value {
Value::Number(number) => {
if let Some(number) = number.as_i64() {
if (-131_072..=22).contains(&number) {
#[allow(clippy::cast_possible_truncation)]
return Ok(Self(number as i32));
}
}
}
Value::String(string) => {
if let Ok(number) = string.parse::<i64>() {
if (-131_072..=22).contains(&number) {
#[allow(clippy::cast_possible_truncation)]
return Ok(Self(number as i32));
}
}
}
_ => {}
}
Err(serde::de::Error::custom(
"Zstd compression level must be an integer between -131072 and 22",
))
}
}
impl ZstdCompressionLevel {
#[must_use]
pub const fn new(level: i32) -> Self {
Self(level)
}
}
impl From<i32> for ZstdCompressionLevel {
fn from(value: i32) -> Self {
Self(value)
}
}
impl From<ZstdCompressionLevel> for i32 {
fn from(value: ZstdCompressionLevel) -> Self {
value.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn codec_zstd_configuration_valid() {
const JSON_VALID: &str = r#"{
"level": 22,
"checksum": false
}"#;
serde_json::from_str::<ZstdCodecConfiguration>(JSON_VALID).unwrap();
}
#[test]
fn codec_zstd_configuration_invalid2() {
const JSON_INVALID2: &str = r#"{
"level": 23,
"checksum": true
}"#;
assert!(serde_json::from_str::<ZstdCodecConfiguration>(JSON_INVALID2).is_err());
}
}