omf/validate/
problem.rs

1use std::{fmt::Debug, fmt::Display};
2
3use crate::{Location, colormap::NumberRange, error::InvalidData};
4
5/// Validation failure reason.
6#[derive(Debug, Clone, PartialEq, thiserror::Error)]
7pub enum Reason {
8    /// A floating-point number is NaN, Inf, or -Inf.
9    #[error("must be finite")]
10    NotFinite,
11    /// A size is zero or less.
12    #[error("must be greater than zero")]
13    NotGreaterThanZero,
14    /// Vector must have length one.
15    #[error("must be a unit vector but {0:?} length is {1}")]
16    NotUnitVector([f64; 3], f64),
17    /// Vectors must be at right angles.
18    #[error("vectors are not orthogonal: {0:?} {1:?}")]
19    NotOrthogonal([f64; 3], [f64; 3]),
20    /// A sub-blocked model says it uses octree mode but the sub-block counts are not
21    /// powers of two.
22    #[error("sub-block counts {0:?} must be powers of two for octree mode")]
23    OctreeNotPowerOfTwo([u32; 3]),
24    /// A grid or block model has size greater than 2³² in any direction.
25    #[error("grid count {0:?} exceeds maximum of 4,294,967,295")]
26    GridTooLarge(Vec<u64>),
27    /// Attribute using a location that doesn't exist on the containing geometry.
28    #[error("is {0:?} which is not valid on {1} geometry")]
29    AttrLocationWrongForGeom(Location, &'static str),
30    /// Attribute using a location that is impossible for the attribute data.
31    #[error("is {0:?} which is not valid on {1} attributes")]
32    AttrLocationWrongForAttr(Location, &'static str),
33    /// Attribute length doesn't match the geometry and location.
34    #[error("length {0} does not match geometry ({1})")]
35    AttrLengthMismatch(u64, u64),
36    /// Minimum is greater than maximum.
37    #[error("minimum is greater than maximum in {0}")]
38    MinMaxOutOfOrder(NumberRange),
39    /// The data inside an array is invalid.
40    #[error("array contains invalid data: {0}")]
41    InvalidData(InvalidData),
42    /// A data file or index is missing from the zip.
43    #[error("refers to non-existent archive member '{0}'")]
44    ZipMemberMissing(String),
45    /// A field that must be unique is duplicated.
46    #[error("must be unique but {0} is repeated")]
47    NotUnique(String),
48    /// A field that should be unique is duplicated.
49    #[error("contains duplicate of {0}")]
50    SoftNotUnique(String),
51    /// Ran into the validation message limit.
52    #[error("{0} more errors")]
53    MoreErrors(u32),
54    /// Ran into the validation message limit.
55    #[error("{0} more warnings")]
56    MoreWarnings(u32),
57}
58
59impl Reason {
60    /// True if the reason is an error, false if it is a warning.
61    pub fn is_error(&self) -> bool {
62        !matches!(self, Self::SoftNotUnique(_) | Reason::MoreWarnings(_))
63    }
64}
65
66/// A single validation problem.
67#[derive(Debug, Clone, PartialEq)]
68pub struct Problem {
69    /// Reason for the problem.
70    pub reason: Reason,
71    /// Type name of the failed object.
72    pub ty: &'static str,
73    /// Optional field name where the failure is.
74    pub field: Option<&'static str>,
75    /// Optional name of the containing object.
76    pub name: Option<String>,
77}
78
79impl Problem {
80    /// True if the reason is an error, false if it is a warning.
81    pub fn is_error(&self) -> bool {
82        self.reason.is_error()
83    }
84}
85
86impl Display for Problem {
87    /// Formats a validation problem.
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        let severity = if self.reason.is_error() {
90            "Error"
91        } else {
92            "Warning"
93        };
94        write!(f, "{severity}: '{}", self.ty)?;
95        if let Some(field) = self.field {
96            write!(f, "::{field}'")?;
97        } else {
98            write!(f, "'")?;
99        }
100        write!(f, " {}", self.reason)?;
101        if let Some(name) = &self.name {
102            write!(f, ", inside '{name}'")?;
103        }
104        Ok(())
105    }
106}
107
108/// A container of validation problems.
109#[derive(Debug, Default, Clone, PartialEq)]
110pub struct Problems(Vec<Problem>);
111
112impl Problems {
113    /// True if there are no problems.
114    pub fn is_empty(&self) -> bool {
115        self.0.is_empty()
116    }
117
118    /// The number of problems.
119    pub fn len(&self) -> usize {
120        self.0.len()
121    }
122
123    /// Converts to a vec without copying.
124    pub fn into_vec(self) -> Vec<Problem> {
125        self.0
126    }
127
128    /// Iterates over the problems.
129    pub fn iter(&self) -> impl Iterator<Item = &Problem> {
130        self.0.iter()
131    }
132
133    /// Ok if there are only warnings, Err if there are errors.
134    pub(crate) fn into_result(self) -> Result<Self, Self> {
135        if self.0.iter().any(|p| p.is_error()) {
136            Err(self)
137        } else {
138            Ok(self)
139        }
140    }
141
142    pub(crate) fn push(
143        &mut self,
144        reason: Reason,
145        ty: &'static str,
146        field: Option<&'static str>,
147        name: Option<String>,
148    ) {
149        self.0.push(Problem {
150            reason,
151            ty,
152            field,
153            name,
154        })
155    }
156}
157
158impl Display for Problems {
159    /// Formats a list of validation problems.
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        let n_errors = self.0.iter().filter(|p| p.reason.is_error()).count();
162        let n_warnings = self.0.len() - n_errors;
163        match (n_errors, n_warnings) {
164            (0, 0) => write!(f, "OMF validation passed")?,
165            (0, _) => write!(f, "OMF validation passed with warnings:")?,
166            _ => write!(f, "OMF validation failed:")?,
167        }
168        for problem in self {
169            write!(f, "\n  {problem}")?;
170        }
171        Ok(())
172    }
173}
174
175impl std::error::Error for Problems {}
176
177impl IntoIterator for Problems {
178    type Item = Problem;
179    type IntoIter = std::vec::IntoIter<Problem>;
180
181    fn into_iter(self) -> Self::IntoIter {
182        self.0.into_iter()
183    }
184}
185
186impl<'a> IntoIterator for &'a Problems {
187    type Item = &'a Problem;
188    type IntoIter = std::slice::Iter<'a, Problem>;
189
190    fn into_iter(self) -> Self::IntoIter {
191        self.0.iter()
192    }
193}
194
195impl From<Problems> for Vec<Problem> {
196    fn from(value: Problems) -> Self {
197        value.into_vec()
198    }
199}