#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct VadSegment {
start_sample: u64,
end_sample: u64,
}
impl VadSegment {
pub const fn new(start_sample: u64, end_sample: u64) -> Self {
if end_sample <= start_sample {
panic!("VadSegment::new requires end_sample > start_sample");
}
Self {
start_sample,
end_sample,
}
}
pub const fn start_sample(&self) -> u64 {
self.start_sample
}
pub const fn end_sample(&self) -> u64 {
self.end_sample
}
pub const fn sample_count(&self) -> u64 {
self.end_sample - self.start_sample
}
}
#[cfg(feature = "serde")]
const _: () = {
impl<'de> serde::Deserialize<'de> for VadSegment {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error as _;
#[derive(serde::Deserialize)]
struct Raw {
start_sample: u64,
end_sample: u64,
}
let r = Raw::deserialize(deserializer)?;
if r.end_sample <= r.start_sample {
return Err(D::Error::custom(format!(
"VadSegment requires end_sample > start_sample (got start_sample={}, end_sample={})",
r.start_sample, r.end_sample
)));
}
Ok(Self {
start_sample: r.start_sample,
end_sample: r.end_sample,
})
}
}
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let s = VadSegment::new(100, 250);
assert_eq!(s.start_sample(), 100);
assert_eq!(s.end_sample(), 250);
assert_eq!(s.sample_count(), 150);
}
#[test]
#[should_panic(expected = "end_sample > start_sample")]
fn zero_duration_panics() {
VadSegment::new(100, 100);
}
#[test]
#[should_panic(expected = "end_sample > start_sample")]
fn negative_duration_panics() {
VadSegment::new(200, 100);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_rejects_reversed_range() {
let json = r#"{"start_sample":200,"end_sample":100}"#;
let res: Result<VadSegment, _> = serde_json::from_str(json);
assert!(
res.is_err(),
"reversed range must fail deserialization; got {res:?}"
);
let err = res.err().unwrap().to_string();
assert!(
err.contains("end_sample > start_sample"),
"expected invariant in error message, got {err:?}"
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_rejects_zero_duration() {
let json = r#"{"start_sample":100,"end_sample":100}"#;
let res: Result<VadSegment, _> = serde_json::from_str(json);
assert!(res.is_err(), "zero-duration must fail; got {res:?}");
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_accepts_valid_range() {
let json = r#"{"start_sample":100,"end_sample":250}"#;
let s: VadSegment = serde_json::from_str(json).expect("valid range");
assert_eq!(s.start_sample(), 100);
assert_eq!(s.end_sample(), 250);
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trip() {
let original = VadSegment::new(100, 250);
let json = serde_json::to_string(&original).expect("serialize");
let back: VadSegment = serde_json::from_str(&json).expect("deserialize");
assert_eq!(original, back);
}
}