fj_core/validate/mod.rs
1//! # Infrastructure for validating objects
2//!
3//! ## Structure
4//!
5//! Every single requirement is checked by a dedicated function. These functions
6//! are called **validation checks**. Validation checks are currently not
7//! visible in the public API, but their structure is reflected in the variants
8//! of the different enums that make up [`ValidationError`] (each validation
9//! check produces one of the types of validation error, that these nested enums
10//! represent).
11//!
12//! In principle, the absence of validation errors should guarantee, that an
13//! object can be exported to an external file format without problems (which
14//! falls under the purview of the [`fj-export`] crate). This has not yet been
15//! achieved, as some validation checks are still missing.
16//!
17//! The [issue tracker] has open issues for some of those missing checks, but
18//! others are not currently tracked (or not even known). Please feel free to
19//! open a new issue (or comment on an existing one), if you encounter an object
20//! that you believe should be invalid, but is not.
21//!
22//!
23//! ## Use
24//!
25//! All objects implement the [`Validate`] trait, which users can use to
26//! validate objects manually. This might be useful for debugging, but is
27//! otherwise not recommended.
28//!
29//! Experience has shown, that stopping the construction of a shape on the first
30//! validation failure can make it hard to understand what actually went wrong.
31//! For that reason, objects are validated as they are constructed, but
32//! validation errors are collected in the background, to be processed when the
33//! whole shape has been finished.
34//!
35//! This is set up within the [`Layers`] API, and validation errors result in a
36//! panic, when the [`Layers`] instance is dropped. Unless you want to handle
37//! validation errors in a different way, you don't have to do anything special
38//! to use the validation infrastructure.
39//!
40//!
41//! ## Configuration
42//!
43//! Fornjot's geometry representation is set up to prioritize correctness, which
44//! is achieved by making the relations between different objects *explicit*.
45//! This means, for example, that coincident objects of the same type that don't
46//! have the same *identity* are generally considered invalid.
47//!
48//! Coincidence checks must use a tolerance value to be useful, meaning objects
49//! that are very close together can be considered coincident. What should be
50//! considered "very close" is dependent on the scale that your model operates
51//! on, and this fact is taken into account by allowing for configuration via
52//! [`Validate::validate`] and [`ValidationConfig`].
53//!
54//!
55//! ## Implementation Note
56//!
57//! This module is in the process of being replaced. See [`crate::validation`].
58//!
59//!
60//! [`fj-export`]: https://crates.io/crates/fj-export
61//! [issue tracker]: https://github.com/hannobraun/fornjot/issues
62//! [`Layers`]: crate::layers::Layers
63
64mod curve;
65mod cycle;
66mod edge;
67mod face;
68mod references;
69mod region;
70mod shell;
71mod sketch;
72mod solid;
73mod surface;
74mod vertex;
75
76use crate::{
77 geometry::Geometry,
78 validation::{ValidationConfig, ValidationError},
79};
80
81pub use self::{
82 edge::EdgeValidationError, face::FaceValidationError,
83 shell::ShellValidationError, sketch::SketchValidationError,
84 solid::SolidValidationError,
85};
86
87/// Assert that some object has a validation error which matches a specific
88/// pattern. This is preferred to matching on [`Validate::validate_and_return_first_error`], since usually we don't care about the order.
89#[macro_export]
90macro_rules! assert_contains_err {
91 ($core:expr, $o:expr, $p:pat) => {
92 assert!({
93 let mut errors = Vec::new();
94 $o.validate(
95 &$crate::validation::ValidationConfig::default(),
96 &mut errors,
97 &$core.layers.geometry,
98 );
99 errors.iter().any(|e| matches!(e, $p))
100 })
101 };
102}
103
104/// Validate an object
105///
106/// This trait is used automatically when inserting an object into a store.
107pub trait Validate: Sized {
108 /// Validate the object using default config and return on first error
109 #[allow(clippy::result_large_err)]
110 fn validate_and_return_first_error(
111 &self,
112 geometry: &Geometry,
113 ) -> Result<(), ValidationError> {
114 let mut errors = Vec::new();
115 self.validate(&ValidationConfig::default(), &mut errors, geometry);
116
117 if let Some(err) = errors.into_iter().next() {
118 return Err(err);
119 }
120
121 Ok(())
122 }
123
124 /// Validate the object
125 fn validate(
126 &self,
127 config: &ValidationConfig,
128 errors: &mut Vec<ValidationError>,
129 geometry: &Geometry,
130 );
131}