corpus_ipc/validation/
mod.rs1mod bounded;
29mod checks;
30mod error;
31
32pub use error::{ValidationError, ValidationKind, ValidationMeasure};
33
34pub(crate) use bounded::{
35 bounded_map, bounded_opt_string, bounded_opt_vec, bounded_string, bounded_vec, finite_f32_at,
36};
37pub(crate) use checks::{
38 add_to_total, check_count, check_finite, check_finite_slice, check_opt_string, check_range,
39 check_string, check_unique_by, check_unique_strings,
40};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct ProtocolLimits {
49 pub max_channel_values: usize,
52 pub max_spike_events: usize,
54 pub max_traces: usize,
56 pub max_gradients: usize,
58 pub max_metadata_entries: usize,
60 pub max_string_bytes: usize,
62 pub max_aggregate_records: usize,
64}
65
66impl ProtocolLimits {
67 pub const DEFAULT: Self = Self {
72 max_channel_values: 65_536,
73 max_spike_events: 65_536,
74 max_traces: 65_536,
75 max_gradients: 65_536,
76 max_metadata_entries: 256,
77 max_string_bytes: 1_024,
78 max_aggregate_records: 131_072,
79 };
80}
81
82impl Default for ProtocolLimits {
83 fn default() -> Self {
84 Self::DEFAULT
85 }
86}
87
88pub trait Validate {
90 fn validate(&self) -> Result<(), ValidationError> {
92 self.validate_with(ProtocolLimits::DEFAULT)
93 }
94
95 fn validate_with(&self, limits: ProtocolLimits) -> Result<(), ValidationError>;
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn kind_as_str_is_stable() {
105 assert_eq!(ValidationKind::NonFinite.as_str(), "non_finite");
106 assert_eq!(ValidationKind::OutOfRange.as_str(), "out_of_range");
107 assert_eq!(ValidationKind::LengthMismatch.as_str(), "length_mismatch");
108 assert_eq!(ValidationKind::LimitExceeded.as_str(), "limit_exceeded");
109 assert_eq!(ValidationKind::NestedMetadata.as_str(), "nested_metadata");
110 assert_eq!(
111 ValidationKind::DuplicateIdentifier.as_str(),
112 "duplicate_identifier"
113 );
114 }
115
116 #[test]
117 fn display_includes_path_kind_actual_and_limit() {
118 let err = ValidationError::limit_exceeded("spikes", 9, 8);
119 let text = err.to_string();
120 assert!(text.contains("limit_exceeded"));
121 assert!(text.contains("spikes"));
122 assert!(text.contains("actual=9"));
123 assert!(text.contains("limit=8"));
124 }
125
126 #[test]
127 fn default_limits_match_associated_constant() {
128 assert_eq!(ProtocolLimits::default(), ProtocolLimits::DEFAULT);
129 }
130
131 #[test]
132 fn nan_measures_compare_by_bits() {
133 let a = ValidationMeasure::Float(f32::NAN);
134 let b = ValidationMeasure::Float(f32::from_bits(f32::NAN.to_bits()));
135 assert_eq!(a, b);
136 }
137
138 #[test]
139 fn measure_count_and_bytes_are_distinct() {
140 assert_ne!(ValidationMeasure::Count(1), ValidationMeasure::Bytes(1));
141 }
142
143 #[test]
144 fn measure_range_display_is_stable() {
145 assert_eq!(
146 ValidationMeasure::Range { min: 0.5, max: 2.0 }.to_string(),
147 "[0.5, 2]"
148 );
149 }
150
151 #[test]
152 fn display_omits_missing_actual_or_limit() {
153 let only_actual = ValidationError::non_finite("x", f32::INFINITY);
154 assert!(only_actual.to_string().contains("actual="));
155 let neither = ValidationError::nested_metadata("custom");
156 assert_eq!(neither.to_string(), "nested_metadata at `custom`");
157 let only_limit = ValidationError {
158 path: "q".into(),
159 kind: ValidationKind::OutOfRange,
160 actual: None,
161 limit: Some(ValidationMeasure::Range { min: 0.0, max: 1.0 }),
162 };
163 assert!(only_limit.to_string().contains("limit="));
164 assert!(!only_limit.to_string().contains("actual="));
165 }
166}