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