use super::ConfigError;
use crate::error::ErrorPolicy;
use crate::ops::ChunkConfig;
use bytesize::ByteSize;
use serde::{Deserialize, Deserializer};
fn de_byte_size<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
ByteSize::deserialize(d).map(|b| b.as_u64())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
enum EncodePolicyWire {
Skip,
Fail,
}
impl From<EncodePolicyWire> for ErrorPolicy {
fn from(w: EncodePolicyWire) -> Self {
match w {
EncodePolicyWire::Skip => ErrorPolicy::Skip,
EncodePolicyWire::Fail => ErrorPolicy::Fail,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub(crate) struct ChunkSection {
#[serde(deserialize_with = "de_byte_size")]
target_bytes: u64,
encode_policy: EncodePolicyWire,
}
impl Default for ChunkSection {
fn default() -> Self {
let ChunkConfig {
target_bytes,
encode_policy,
} = ChunkConfig::default();
ChunkSection {
target_bytes: target_bytes as u64,
encode_policy: match encode_policy {
ErrorPolicy::Skip => EncodePolicyWire::Skip,
ErrorPolicy::Fail => EncodePolicyWire::Fail,
},
}
}
}
impl ChunkSection {
pub(crate) fn resolve(&self) -> Result<ChunkConfig, ConfigError> {
if self.target_bytes == 0 {
return Err(ConfigError::Validation(
"chunk.target_bytes must be greater than zero".into(),
));
}
let target_bytes = usize::try_from(self.target_bytes).map_err(|_| {
ConfigError::Validation(format!(
"chunk.target_bytes ({}) exceeds the addressable range on this platform",
self.target_bytes
))
})?;
Ok(ChunkConfig {
target_bytes,
encode_policy: self.encode_policy.into(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(yaml: &str) -> Result<ChunkSection, serde_yaml::Error> {
serde_yaml::from_str(yaml)
}
#[test]
fn empty_block_is_the_runtime_default_including_skip() {
let cfg = parse("{}").unwrap().resolve().unwrap();
assert_eq!(cfg.target_bytes, 64 * 1024);
assert_eq!(cfg.encode_policy, ErrorPolicy::Skip);
}
#[test]
fn partial_block_keeps_the_skip_default() {
let cfg = parse("target_bytes: 512KiB").unwrap().resolve().unwrap();
assert_eq!(cfg.target_bytes, 512 * 1024);
assert_eq!(cfg.encode_policy, ErrorPolicy::Skip);
}
#[test]
fn parses_bytesize_and_encode_policy() {
let cfg = parse("target_bytes: 128KiB\nencode_policy: fail")
.unwrap()
.resolve()
.unwrap();
assert_eq!(cfg.target_bytes, 128 * 1024);
assert_eq!(cfg.encode_policy, ErrorPolicy::Fail);
let cfg = parse("target_bytes: 4096").unwrap().resolve().unwrap();
assert_eq!(cfg.target_bytes, 4096);
}
#[test]
fn zero_target_is_a_field_named_error() {
let err = parse("target_bytes: 0B").unwrap().resolve().unwrap_err();
assert!(err.to_string().contains("chunk.target_bytes"), "{err}");
}
#[test]
fn unknown_key_and_unknown_policy_are_rejected() {
assert!(parse("bogus: 1").is_err());
assert!(parse("encode_policy: retry").is_err());
}
}