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