energyml_geometry/error.rs
1//! What can go wrong between an object and a set of points.
2
3use std::fmt;
4
5/// Result of a geometry operation.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Something that stopped a representation from becoming points.
9#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum Error {
12 /// A shape the caller asked for that this object cannot take.
13 ///
14 /// Not a gap in this build: the file states no connection the answer could
15 /// be derived from. Names both ends, because whoever asked wrote a rule and
16 /// needs to know which of their rules met which object.
17 #[error(
18 "{topology} cannot be drawn as {wanted}: the file states no connection this could be \
19 derived from"
20 )]
21 Shape {
22 /// What the object is, in words - "a set of polylines".
23 topology: &'static str,
24 /// What was asked of it - "a surface".
25 wanted: &'static str,
26 },
27
28 /// A form of array this crate does not evaluate yet.
29 ///
30 /// **Named, never silent.** The alternative - returning no points - looks
31 /// exactly like a working program with an empty file, and that is the
32 /// failure mode this crate exists to avoid.
33 #[error("`{form}` is not evaluated yet ({at})")]
34 Unsupported {
35 /// The concrete type met, e.g. `Point3dParametricArray`.
36 form: &'static str,
37 /// Where it was met, as a dotted path.
38 at: String,
39 },
40
41 /// The file states a shape with **no extent of its own**.
42 ///
43 /// Different news from [`Error::Unsupported`], and the difference is one a
44 /// caller acts on. *Unsupported* means this build does not evaluate that
45 /// form yet, so waiting is a strategy. This means the file **never says**
46 /// how far the shape goes: a `HorizontalPlaneGeometry` is one number, a
47 /// depth, and a plane is infinite. No release will add the extent, because
48 /// it is not a fact about the model - it is a decision about the picture,
49 /// and it belongs to whoever is drawing.
50 ///
51 /// So the refusal names what is missing rather than pretending to a gap,
52 /// and a viewer can offer the one thing that works: an extent of its own.
53 /// Drawing some box instead would put a surface of invented size into a
54 /// model, which is the failure this crate exists to avoid.
55 #[error("`{form}` has no extent of its own ({at}): {states}")]
56 Unbounded {
57 /// The concrete type met, e.g. `HorizontalPlaneGeometry`.
58 form: &'static str,
59 /// Where it was met, as a dotted path.
60 at: String,
61 /// What the file *does* state - the half that is there.
62 states: &'static str,
63 },
64
65 /// The object is not shaped the way its schema says.
66 #[error("{at}: {message}")]
67 Malformed {
68 /// Where, as a dotted path.
69 at: String,
70 /// What was expected and what was found.
71 message: String,
72 },
73
74 /// The referenced object is not in the store.
75 #[error("`{0}` is not in this store")]
76 Missing(String),
77
78 /// Reading an external array failed.
79 #[error(transparent)]
80 Storage(#[from] energyml_storage::Error),
81
82 /// Reading or walking an object failed.
83 #[error(transparent)]
84 Core(#[from] energyml_core::error::Error),
85
86 /// A unit, a coordinate system or a reprojection could not be read.
87 #[error(transparent)]
88 Domain(#[from] energyml_domain::Error),
89}
90
91impl Error {
92 /// Whether this is "the external file is not there".
93 ///
94 /// A package whose `.h5` companion was not shipped is a gap in what the
95 /// caller was given, not a defect in the model - and a viewer says so
96 /// differently from "this build cannot evaluate that form".
97 pub fn is_missing_file(&self) -> bool {
98 match self {
99 Error::Storage(energyml_storage::Error::ExternalData {
100 reason: energyml_storage::ExternalDataReason::FileMissing,
101 ..
102 }) => true,
103 Error::Domain(e) => e.is_missing_file(),
104 _ => false,
105 }
106 }
107
108 /// A shape the caller asked for that this object cannot take.
109 ///
110 /// Separate from [`Error::Unsupported`] on purpose, and the distinction is
111 /// the whole message: that one means *this build does not do it yet*, and
112 /// invites a future version. This one means *the file does not say enough
113 /// for anyone to do it* - a set of polylines is not a surface in any build.
114 pub fn shape(topology: &'static str, wanted: &'static str) -> Self {
115 Self::Shape { topology, wanted }
116 }
117
118 /// A form this crate meets but does not evaluate.
119 pub fn unsupported(form: &'static str, at: impl fmt::Display) -> Self {
120 Self::Unsupported {
121 form,
122 at: at.to_string(),
123 }
124 }
125
126 /// A shape whose extent the file does not state.
127 pub fn unbounded(form: &'static str, at: impl fmt::Display, states: &'static str) -> Self {
128 Self::Unbounded {
129 form,
130 at: at.to_string(),
131 states,
132 }
133 }
134
135 /// An object that does not match its schema.
136 pub fn malformed(at: impl fmt::Display, message: impl fmt::Display) -> Self {
137 Self::Malformed {
138 at: at.to_string(),
139 message: message.to_string(),
140 }
141 }
142}