1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum ValidationKind {
12 NonFinite,
14 OutOfRange,
16 LengthMismatch,
18 LimitExceeded,
20 NestedMetadata,
23 DuplicateIdentifier,
25}
26
27impl ValidationKind {
28 #[must_use]
30 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::NonFinite => "non_finite",
33 Self::OutOfRange => "out_of_range",
34 Self::LengthMismatch => "length_mismatch",
35 Self::LimitExceeded => "limit_exceeded",
36 Self::NestedMetadata => "nested_metadata",
37 Self::DuplicateIdentifier => "duplicate_identifier",
38 }
39 }
40}
41
42impl fmt::Display for ValidationKind {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.write_str(self.as_str())
45 }
46}
47
48#[derive(Debug, Clone, Copy)]
51pub enum ValidationMeasure {
52 Count(u64),
54 Bytes(u64),
56 Float(f32),
58 Range { min: f32, max: f32 },
60}
61
62impl PartialEq for ValidationMeasure {
63 fn eq(&self, other: &Self) -> bool {
64 match (self, other) {
65 (Self::Count(a), Self::Count(b)) | (Self::Bytes(a), Self::Bytes(b)) => a == b,
66 (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
67 (
68 Self::Range {
69 min: a_min,
70 max: a_max,
71 },
72 Self::Range {
73 min: b_min,
74 max: b_max,
75 },
76 ) => a_min.to_bits() == b_min.to_bits() && a_max.to_bits() == b_max.to_bits(),
77 _ => false,
78 }
79 }
80}
81
82impl Eq for ValidationMeasure {}
83
84impl fmt::Display for ValidationMeasure {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 Self::Count(n) | Self::Bytes(n) => write!(f, "{n}"),
88 Self::Float(v) => write!(f, "{v}"),
89 Self::Range { min, max } => write!(f, "[{min}, {max}]"),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct ValidationError {
101 pub path: String,
103 pub kind: ValidationKind,
105 pub actual: Option<ValidationMeasure>,
107 pub limit: Option<ValidationMeasure>,
109}
110
111impl ValidationError {
112 #[must_use]
114 pub fn non_finite(path: impl Into<String>, actual: f32) -> Self {
115 Self {
116 path: path.into(),
117 kind: ValidationKind::NonFinite,
118 actual: Some(ValidationMeasure::Float(actual)),
119 limit: None,
120 }
121 }
122
123 #[must_use]
125 pub fn out_of_range(path: impl Into<String>, actual: f32, min: f32, max: f32) -> Self {
126 Self {
127 path: path.into(),
128 kind: ValidationKind::OutOfRange,
129 actual: Some(ValidationMeasure::Float(actual)),
130 limit: Some(ValidationMeasure::Range { min, max }),
131 }
132 }
133
134 #[must_use]
136 pub fn length_mismatch(path: impl Into<String>, actual: usize, expected: usize) -> Self {
137 Self {
138 path: path.into(),
139 kind: ValidationKind::LengthMismatch,
140 actual: Some(ValidationMeasure::Count(usize_as_u64(actual))),
141 limit: Some(ValidationMeasure::Count(usize_as_u64(expected))),
142 }
143 }
144
145 #[must_use]
147 pub fn limit_exceeded(path: impl Into<String>, actual: usize, limit: usize) -> Self {
148 Self {
149 path: path.into(),
150 kind: ValidationKind::LimitExceeded,
151 actual: Some(ValidationMeasure::Count(usize_as_u64(actual))),
152 limit: Some(ValidationMeasure::Count(usize_as_u64(limit))),
153 }
154 }
155
156 #[must_use]
158 pub fn byte_limit(path: impl Into<String>, actual: usize, limit: usize) -> Self {
159 Self {
160 path: path.into(),
161 kind: ValidationKind::LimitExceeded,
162 actual: Some(ValidationMeasure::Bytes(usize_as_u64(actual))),
163 limit: Some(ValidationMeasure::Bytes(usize_as_u64(limit))),
164 }
165 }
166
167 #[must_use]
169 pub fn nested_metadata(path: impl Into<String>) -> Self {
170 Self {
171 path: path.into(),
172 kind: ValidationKind::NestedMetadata,
173 actual: None,
174 limit: None,
175 }
176 }
177
178 #[must_use]
180 pub fn duplicate_identifier(path: impl Into<String>, identifier: impl fmt::Display) -> Self {
181 Self {
182 path: path.into(),
183 kind: ValidationKind::DuplicateIdentifier,
184 actual: Some(ValidationMeasure::Count(0)),
185 limit: None,
186 }
187 .with_duplicate_display(identifier)
188 }
189
190 fn with_duplicate_display(mut self, identifier: impl fmt::Display) -> Self {
191 let label = identifier.to_string();
194 if let Ok(n) = label.parse::<u64>() {
195 self.actual = Some(ValidationMeasure::Count(n));
196 } else {
197 self.actual = None;
198 if !self.path.contains(&label) {
199 self.path = format!("{}:{label}", self.path);
200 }
201 }
202 self
203 }
204}
205
206impl fmt::Display for ValidationError {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 write!(f, "{} at `{}`", self.kind, self.path)?;
209 match (&self.actual, &self.limit) {
210 (Some(actual), Some(limit)) => write!(f, ", actual={actual}, limit={limit}"),
211 (Some(actual), None) => write!(f, ", actual={actual}"),
212 (None, Some(limit)) => write!(f, ", limit={limit}"),
213 (None, None) => Ok(()),
214 }
215 }
216}
217
218impl std::error::Error for ValidationError {}
219pub(crate) fn usize_as_u64(n: usize) -> u64 {
220 u64::try_from(n).unwrap_or(u64::MAX)
221}