mod bounded;
mod checks;
mod error;
pub use error::{ValidationError, ValidationKind, ValidationMeasure};
pub(crate) use bounded::{
bounded_map, bounded_opt_string, bounded_opt_vec, bounded_string, bounded_vec, finite_f32_at,
};
pub(crate) use checks::{
add_to_total, check_count, check_finite, check_finite_slice, check_opt_string, check_range,
check_string, check_unique_by, check_unique_strings,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProtocolLimits {
pub max_channel_values: usize,
pub max_spike_events: usize,
pub max_traces: usize,
pub max_gradients: usize,
pub max_metadata_entries: usize,
pub max_string_bytes: usize,
pub max_aggregate_records: usize,
}
impl ProtocolLimits {
pub const DEFAULT: Self = Self {
max_channel_values: 65_536,
max_spike_events: 65_536,
max_traces: 65_536,
max_gradients: 65_536,
max_metadata_entries: 256,
max_string_bytes: 1_024,
max_aggregate_records: 131_072,
};
}
impl Default for ProtocolLimits {
fn default() -> Self {
Self::DEFAULT
}
}
pub trait Validate {
fn validate(&self) -> Result<(), ValidationError> {
self.validate_with(ProtocolLimits::DEFAULT)
}
fn validate_with(&self, limits: ProtocolLimits) -> Result<(), ValidationError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_as_str_is_stable() {
assert_eq!(ValidationKind::NonFinite.as_str(), "non_finite");
assert_eq!(ValidationKind::OutOfRange.as_str(), "out_of_range");
assert_eq!(ValidationKind::LengthMismatch.as_str(), "length_mismatch");
assert_eq!(ValidationKind::LimitExceeded.as_str(), "limit_exceeded");
assert_eq!(ValidationKind::NestedMetadata.as_str(), "nested_metadata");
assert_eq!(
ValidationKind::DuplicateIdentifier.as_str(),
"duplicate_identifier"
);
}
#[test]
fn display_includes_path_kind_actual_and_limit() {
let err = ValidationError::limit_exceeded("spikes", 9, 8);
let text = err.to_string();
assert!(text.contains("limit_exceeded"));
assert!(text.contains("spikes"));
assert!(text.contains("actual=9"));
assert!(text.contains("limit=8"));
}
#[test]
fn default_limits_match_associated_constant() {
assert_eq!(ProtocolLimits::default(), ProtocolLimits::DEFAULT);
}
#[test]
fn nan_measures_compare_by_bits() {
let a = ValidationMeasure::Float(f32::NAN);
let b = ValidationMeasure::Float(f32::from_bits(f32::NAN.to_bits()));
assert_eq!(a, b);
}
#[test]
fn measure_count_and_bytes_are_distinct() {
assert_ne!(ValidationMeasure::Count(1), ValidationMeasure::Bytes(1));
}
#[test]
fn measure_range_display_is_stable() {
assert_eq!(
ValidationMeasure::Range { min: 0.5, max: 2.0 }.to_string(),
"[0.5, 2]"
);
}
#[test]
fn display_omits_missing_actual_or_limit() {
let only_actual = ValidationError::non_finite("x", f32::INFINITY);
assert!(only_actual.to_string().contains("actual="));
let neither = ValidationError::nested_metadata("custom");
assert_eq!(neither.to_string(), "nested_metadata at `custom`");
let only_limit = ValidationError {
path: "q".into(),
kind: ValidationKind::OutOfRange,
actual: None,
limit: Some(ValidationMeasure::Range { min: 0.0, max: 1.0 }),
};
assert!(only_limit.to_string().contains("limit="));
assert!(!only_limit.to_string().contains("actual="));
}
}