Skip to main content

axiolid_mesh_hidden_lines_contract/
error.rs

1//! Refusals that name the mesh at fault.
2
3use core::fmt;
4
5use axiolid_contracts::GeomError;
6
7use crate::MeshId;
8
9/// A refused hidden-line request: the [`GeomError`] and, when one input mesh
10/// is at fault, which.
11///
12/// A consumer that drops elements one at a time reads [`mesh`](Self::mesh)
13/// instead of parsing text. `mesh` is `None` for refusals of the whole
14/// request: limits, frame, near/far, crease threshold, region, an empty mesh
15/// list, memory, cancellation and a missing provider. The inner message does
16/// not repeat the mesh id; [`Display`](fmt::Display) prefixes it.
17#[derive(Debug, Clone, PartialEq)]
18#[non_exhaustive]
19pub struct HiddenLineError {
20    /// The input mesh the refusal is about, if it is about one.
21    pub mesh: Option<MeshId>,
22    /// Why the request was refused.
23    pub error: GeomError,
24}
25
26impl HiddenLineError {
27    /// Refusal of the whole request.
28    pub const fn request(error: GeomError) -> Self {
29        Self { mesh: None, error }
30    }
31
32    /// Refusal caused by one input mesh.
33    pub const fn mesh(mesh: MeshId, error: GeomError) -> Self {
34        Self {
35            mesh: Some(mesh),
36            error,
37        }
38    }
39}
40
41/// One input mesh that the per-mesh audit refuses, with the reason.
42///
43/// A registry's audit lists every such mesh of a request in request order, so
44/// a caller that leaves them out learns all refusals in one call instead of
45/// one per `hidden_lines` run. The pair is what [`HiddenLineError::mesh`]
46/// would carry for that mesh.
47#[derive(Debug, Clone, PartialEq)]
48#[non_exhaustive]
49pub struct RefusedMesh {
50    /// The refused input mesh.
51    pub mesh: MeshId,
52    /// Why it is refused.
53    pub error: GeomError,
54}
55
56impl RefusedMesh {
57    /// A refused mesh and its reason.
58    pub const fn new(mesh: MeshId, error: GeomError) -> Self {
59        Self { mesh, error }
60    }
61}
62
63impl From<GeomError> for HiddenLineError {
64    fn from(error: GeomError) -> Self {
65        Self::request(error)
66    }
67}
68
69impl fmt::Display for HiddenLineError {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self.mesh {
72            Some(MeshId(id)) => write!(f, "mesh {id}: {}", self.error),
73            None => fmt::Display::fmt(&self.error, f),
74        }
75    }
76}
77
78impl std::error::Error for HiddenLineError {
79    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
80        Some(&self.error)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use std::error::Error;
87
88    use super::*;
89
90    #[test]
91    fn display_prefixes_the_mesh_only_when_there_is_one() {
92        let inner = GeomError::NotManifold("edge 0-1 has 3 triangles".into());
93        let plain = HiddenLineError::request(inner.clone());
94        assert_eq!(plain.to_string(), inner.to_string());
95        let named = HiddenLineError::mesh(MeshId(7), inner.clone());
96        assert_eq!(named.to_string(), format!("mesh 7: {inner}"));
97    }
98
99    #[test]
100    fn conversion_and_source_keep_the_geometry_error() {
101        let error: HiddenLineError = GeomError::Cancelled.into();
102        assert_eq!(error.mesh, None);
103        assert_eq!(error.error, GeomError::Cancelled);
104        let source = error.source().expect("source").to_string();
105        assert_eq!(source, GeomError::Cancelled.to_string());
106    }
107}