1use hpr_aero::AeroError;
4use hpr_atmos::AtmosError;
5use hpr_core::CoreError;
6use hpr_design::checks::Severity;
7use hpr_design::{DesignError, Finding};
8use hpr_motor::MotorError;
9use hpr_sim::SimError;
10use thiserror::Error;
11
12#[derive(Debug, Error)]
14#[non_exhaustive]
15pub enum Error {
16 #[error("{what} is {value}, outside its domain")]
18 Domain {
19 what: &'static str,
21 value: f64,
23 },
24 #[error(transparent)]
26 Order(Order),
27 #[error("no built-in material `{0}` (`hpr_design::materials` lists them)")]
29 UnknownMaterial(String),
30 #[error("no motor with a bundled thrust curve matches `{0}`")]
32 NoSuchMotor(String),
33 #[error("`{name}` matches several motors: {}", candidates.join(", "))]
36 AmbiguousMotor {
37 name: String,
39 candidates: Vec<String>,
41 },
42 #[error("a motor needs a designation")]
44 EmptyDesignation,
45 #[error("the motor file holds {0} motors, not one")]
47 MotorCount(usize),
48 #[error("the rocket has no motor; give it one with `Rocket::set_motor`")]
50 NoMotor,
51 #[error("the design has no configuration `{0}`")]
53 NoSuchConfiguration(String),
54 #[error(
57 "the design's checks found {} finding(s), errors among them; the first error is {}",
58 .0.len(),
59 first_error(.0)
60 )]
61 DesignChecks(Vec<Finding>),
62 #[error("catalog part {part}: {problem}")]
64 Catalog {
65 part: String,
67 problem: CatalogProblem,
69 },
70 #[error(transparent)]
72 Design(#[from] DesignError),
73 #[error(transparent)]
75 Motor(#[from] MotorError),
76 #[error(transparent)]
78 Sim(#[from] SimError),
79 #[error(transparent)]
81 Aero(#[from] AeroError),
82 #[error(transparent)]
84 Atmos(#[from] AtmosError),
85 #[error(transparent)]
87 Core(#[from] CoreError),
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
92#[non_exhaustive]
93pub enum Order {
94 #[error("the nose goes first, before any other body part")]
96 NoseNotFirst,
97 #[error("a transition needs a body part before it")]
99 NothingBeforeTransition,
100 #[error("fins, a motor tube and masses go on a body tube: add one first")]
102 NoTube,
103 #[error("the rocket has a motor tube already")]
105 SecondMotorTube,
106 #[error("a motor needs a motor tube to go in")]
108 NoMotorTube,
109 #[error("a fitting is a coupler, centering ring, bulkhead, launch lug, parachute or streamer")]
112 NotAFitting,
113 #[error("parts can't be added to a rocket read from a design")]
115 ReadFromDesign,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Error)]
120#[non_exhaustive]
121pub enum CatalogProblem {
122 #[error("it is a {found}, which `{builder}` doesn't make")]
125 Kind {
126 found: &'static str,
128 builder: &'static str,
130 },
131 #[error("its file doesn't define its material `{0}`")]
133 UndefinedMaterial(String),
134 #[error("it is neither filled nor given a wall thickness")]
136 NoWall,
137 #[error("its shape is one the builder doesn't know")]
139 Shape,
140 #[error("it states a mass, but its sizes give it no volume to hold it")]
143 NoVolume,
144}
145
146pub(crate) fn positive(what: &'static str, value: f64) -> Result<f64, Error> {
148 if value.is_finite() && value > 0.0 {
149 Ok(value)
150 } else {
151 Err(Error::Domain { what, value })
152 }
153}
154
155pub(crate) fn non_negative(what: &'static str, value: f64) -> Result<f64, Error> {
157 if value.is_finite() && value >= 0.0 {
158 Ok(value)
159 } else {
160 Err(Error::Domain { what, value })
161 }
162}
163
164pub(crate) fn finite(what: &'static str, value: f64) -> Result<f64, Error> {
166 if value.is_finite() {
167 Ok(value)
168 } else {
169 Err(Error::Domain { what, value })
170 }
171}
172
173fn first_error(findings: &[Finding]) -> String {
175 findings
176 .iter()
177 .find(|finding| finding.severity() == Severity::Error)
178 .map_or_else(|| "missing".to_owned(), |finding| format!("{finding:?}"))
179}