fj_kernel/validate/
mod.rs

1//! Infrastructure for validating objects
2
3mod cycle;
4mod edge;
5mod face;
6mod shell;
7mod sketch;
8mod solid;
9mod surface;
10mod vertex;
11
12pub use self::{
13    cycle::CycleValidationError, edge::HalfEdgeValidationError,
14    face::FaceValidationError, shell::ShellValidationError,
15    solid::SolidValidationError,
16};
17
18use std::convert::Infallible;
19
20use fj_math::Scalar;
21
22/// Assert that some object has a validation error which matches a specifc pattern.
23/// This is preferred to matching on [`Validate::validate_and_return_first_error`], since usually we don't care about the order.
24#[macro_export]
25macro_rules! assert_contains_err {
26    ($o:tt,$p:pat) => {
27        assert!({
28            let mut errors = Vec::new();
29            $o.validate(&mut errors);
30            errors.iter().any(|e| matches!(e, $p))
31        })
32    };
33}
34
35/// Validate an object
36///
37/// This trait is used automatically when inserting an object into a store.
38pub trait Validate: Sized {
39    /// Validate the object using default config and return on first error
40    #[allow(clippy::result_large_err)]
41    fn validate_and_return_first_error(&self) -> Result<(), ValidationError> {
42        let mut errors = Vec::new();
43        self.validate(&mut errors);
44
45        if let Some(err) = errors.into_iter().next() {
46            return Err(err);
47        }
48
49        Ok(())
50    }
51
52    /// Validate the object using default configuration
53    fn validate(&self, errors: &mut Vec<ValidationError>) {
54        self.validate_with_config(&ValidationConfig::default(), errors)
55    }
56
57    /// Validate the object
58    fn validate_with_config(
59        &self,
60        config: &ValidationConfig,
61        errors: &mut Vec<ValidationError>,
62    );
63}
64
65/// Configuration required for the validation process
66#[derive(Debug, Clone, Copy)]
67pub struct ValidationConfig {
68    /// The minimum distance between distinct objects
69    ///
70    /// Objects whose distance is less than the value defined in this field, are
71    /// considered identical.
72    pub distinct_min_distance: Scalar,
73
74    /// The maximum distance between identical objects
75    ///
76    /// Objects that are considered identical might still have a distance
77    /// between them, due to inaccuracies of the numerical representation. If
78    /// that distance is less than the one defined in this field, can not be
79    /// considered identical.
80    pub identical_max_distance: Scalar,
81}
82
83impl Default for ValidationConfig {
84    fn default() -> Self {
85        Self {
86            distinct_min_distance: Scalar::from_f64(5e-7), // 0.5 µm,
87
88            // This value was chosen pretty arbitrarily. Seems small enough to
89            // catch errors. If it turns out it's too small (because it produces
90            // false positives due to floating-point accuracy issues), we can
91            // adjust it.
92            identical_max_distance: Scalar::from_f64(5e-14),
93        }
94    }
95}
96
97/// An error that can occur during a validation
98#[derive(Clone, Debug, thiserror::Error)]
99pub enum ValidationError {
100    /// `Cycle` validation error
101    #[error("`Cycle` validation error:\n    {0}")]
102    Cycle(#[from] CycleValidationError),
103
104    /// `Face` validation error
105    #[error("`Face` validation error\n    {0}")]
106    Face(#[from] FaceValidationError),
107
108    /// `HalfEdge` validation error
109    #[error("`HalfEdge` validation error\n    {0}")]
110    HalfEdge(#[from] HalfEdgeValidationError),
111
112    /// `Shell` validation error
113    #[error("`Shell` validation error\n    {0}")]
114    Shell(#[from] ShellValidationError),
115
116    /// `Solid` validation error
117    #[error("`Solid` validation error\n    {0}")]
118    Solid(#[from] SolidValidationError),
119}
120
121impl From<Infallible> for ValidationError {
122    fn from(infallible: Infallible) -> Self {
123        match infallible {}
124    }
125}