1use std::{
4 collections::BTreeSet,
5 fmt::{Debug, Display},
6 ops::Deref,
7};
8
9use crate::offsets::{NullableOffsetMarker, OffsetMarker};
10
11pub trait Validate {
18 fn validate(&self) -> Result<(), ValidationReport> {
23 let mut ctx = Default::default();
24 self.validate_impl(&mut ctx);
25 if ctx.errors.is_empty() {
26 Ok(())
27 } else {
28 Err(ValidationReport { errors: ctx.errors })
29 }
30 }
31
32 #[allow(unused_variables)]
55 fn validate_impl(&self, ctx: &mut ValidationCtx);
56}
57
58#[derive(Clone, Debug, Default)]
68pub struct ValidationCtx {
69 cur_location: Vec<LocationElem>,
70 errors: Vec<ValidationError>,
71}
72
73#[derive(Debug, Clone)]
74struct ValidationError {
75 error: String,
76 location: Vec<LocationElem>,
77}
78
79#[derive(Clone)]
81pub struct ValidationReport {
82 errors: Vec<ValidationError>,
83}
84
85#[derive(Debug, Clone)]
86enum LocationElem {
87 Table(&'static str),
88 Field(&'static str),
89 Index(usize),
90}
91
92impl ValidationCtx {
93 pub fn in_table(&mut self, name: &'static str, f: impl FnOnce(&mut ValidationCtx)) {
98 self.with_elem(LocationElem::Table(name), f);
99 }
100
101 pub fn in_field(&mut self, name: &'static str, f: impl FnOnce(&mut ValidationCtx)) {
105 self.with_elem(LocationElem::Field(name), f);
106 }
107
108 pub fn with_array_items<'a, T: 'a>(
113 &mut self,
114 iter: impl Iterator<Item = &'a T>,
115 mut f: impl FnMut(&mut ValidationCtx, &T),
116 ) {
117 for (i, item) in iter.enumerate() {
118 self.with_elem(LocationElem::Index(i), |ctx| f(ctx, item))
119 }
120 }
121
122 pub fn report(&mut self, msg: impl Display) {
124 self.errors.push(ValidationError {
125 location: self.cur_location.clone(),
126 error: msg.to_string(),
127 });
128 }
129
130 fn with_elem(&mut self, elem: LocationElem, f: impl FnOnce(&mut ValidationCtx)) {
131 self.cur_location.push(elem);
132 f(self);
133 self.cur_location.pop();
134 }
135}
136
137impl Display for ValidationReport {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 if self.errors.len() == 1 {
140 return writeln!(f, "Validation error:\n{}", self.errors.first().unwrap());
141 }
142
143 writeln!(f, "{} validation errors:", self.errors.len())?;
144 for (i, error) in self.errors.iter().enumerate() {
145 writeln!(f, "#{}\n{error}", i + 1)?;
146 }
147 Ok(())
148 }
149}
150
151impl Debug for ValidationReport {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 <Self as Display>::fmt(self, f)
154 }
155}
156
157static MANY_SPACES: &str = " ";
158
159impl Display for ValidationError {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 writeln!(f, "\"{}\"", self.error)?;
162 let mut indent = 0;
163 if self.location.len() < 2 {
164 return f.write_str("no parent location available");
165 }
166
167 for (i, window) in self.location.windows(2).enumerate() {
168 let prev = &window[0];
169 let current = &window[1];
170 if i == 0 {
171 if let LocationElem::Table(name) = prev {
172 write!(f, "in: {name}")?;
173 } else {
174 panic!("first item always table");
175 }
176 }
177
178 match current {
179 LocationElem::Table(name) => {
180 indent += 1;
181 let indent_str = &MANY_SPACES[..indent * 2];
182 write!(f, "\n{indent_str}{name}")
183 }
184 LocationElem::Field(name) => write!(f, ".{name}"),
185 LocationElem::Index(idx) => write!(f, "[{idx}]"),
186 }?;
187 }
188 writeln!(f)
189 }
190}
191
192impl<T: Validate> Validate for Vec<T> {
193 fn validate_impl(&self, ctx: &mut ValidationCtx) {
194 ctx.with_array_items(self.iter(), |ctx, item| item.validate_impl(ctx))
195 }
196}
197
198impl<const N: usize, T: Validate> Validate for OffsetMarker<T, N> {
199 fn validate_impl(&self, ctx: &mut ValidationCtx) {
200 self.deref().validate_impl(ctx)
201 }
202}
203
204impl<const N: usize, T: Validate> Validate for NullableOffsetMarker<T, N> {
205 fn validate_impl(&self, ctx: &mut ValidationCtx) {
206 if let Some(b) = self.as_ref() {
207 b.validate_impl(ctx);
208 }
209 }
210}
211
212impl<T: Validate> Validate for Option<T> {
213 fn validate_impl(&self, ctx: &mut ValidationCtx) {
214 if let Some(t) = self {
215 t.validate_impl(ctx)
216 }
217 }
218}
219
220impl<T: Validate> Validate for BTreeSet<T> {
221 fn validate_impl(&self, ctx: &mut ValidationCtx) {
222 ctx.with_array_items(self.iter(), |ctx, item| item.validate_impl(ctx))
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn sanity_check_array_validation() {
232 #[derive(Clone, Debug, Copy)]
233 struct Derp(i16);
234
235 struct DerpStore {
236 derps: Vec<Derp>,
237 }
238
239 impl Validate for Derp {
240 fn validate_impl(&self, ctx: &mut ValidationCtx) {
241 if self.0 > 7 {
242 ctx.report("this derp is too big!!");
243 }
244 }
245 }
246
247 impl Validate for DerpStore {
248 fn validate_impl(&self, ctx: &mut ValidationCtx) {
249 ctx.in_table("DerpStore", |ctx| {
250 ctx.in_field("derps", |ctx| self.derps.validate_impl(ctx))
251 })
252 }
253 }
254
255 let my_derps = DerpStore {
256 derps: [1i16, 0, 3, 4, 12, 7, 6].into_iter().map(Derp).collect(),
257 };
258
259 let report = my_derps.validate().err().unwrap();
260 assert_eq!(report.errors.len(), 1);
261 assert!(report.to_string().contains(".derps[4]"));
263 }
264}