solar_positioning/
error.rs1use crate::math::normalize_degrees_0_to_360;
4use core::fmt;
5
6pub type Result<T> = core::result::Result<T, Error>;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum Error {
12 InvalidLatitude {
14 value: f64,
16 },
17 InvalidLongitude {
19 value: f64,
21 },
22 InvalidElevationAngle {
24 value: f64,
26 },
27 InvalidPressure {
29 value: f64,
31 },
32 InvalidTemperature {
34 value: f64,
36 },
37 InvalidDateTime {
39 message: &'static str,
41 },
42 ComputationError {
44 message: &'static str,
46 },
47}
48
49impl fmt::Display for Error {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::InvalidLatitude { value } => {
53 write!(
54 f,
55 "invalid latitude {value}° (must be between -90° and +90°)"
56 )
57 }
58 Self::InvalidLongitude { value } => {
59 write!(
60 f,
61 "invalid longitude {value}° (must be between -180° and +180°)"
62 )
63 }
64 Self::InvalidElevationAngle { value } => {
65 write!(
66 f,
67 "invalid elevation angle {value}° (must be between -90° and +90°)"
68 )
69 }
70 Self::InvalidPressure { value } => {
71 write!(f, "invalid pressure {value} mbar (must be positive)")
72 }
73 Self::InvalidTemperature { value } => {
74 write!(
75 f,
76 "invalid temperature {value}°C (must be above absolute zero)"
77 )
78 }
79 Self::InvalidDateTime { message } => {
80 write!(f, "invalid date/time: {message}")
81 }
82 Self::ComputationError { message } => {
83 write!(f, "computation error: {message}")
84 }
85 }
86 }
87}
88
89#[cfg(feature = "std")]
90impl std::error::Error for Error {}
91
92impl Error {
93 #[must_use]
95 pub const fn invalid_latitude(value: f64) -> Self {
96 Self::InvalidLatitude { value }
97 }
98
99 #[must_use]
101 pub const fn invalid_longitude(value: f64) -> Self {
102 Self::InvalidLongitude { value }
103 }
104
105 #[must_use]
107 pub const fn invalid_elevation_angle(value: f64) -> Self {
108 Self::InvalidElevationAngle { value }
109 }
110
111 #[must_use]
113 pub const fn invalid_pressure(value: f64) -> Self {
114 Self::InvalidPressure { value }
115 }
116
117 #[must_use]
119 pub const fn invalid_temperature(value: f64) -> Self {
120 Self::InvalidTemperature { value }
121 }
122
123 #[must_use]
125 pub const fn invalid_datetime(message: &'static str) -> Self {
126 Self::InvalidDateTime { message }
127 }
128
129 #[must_use]
131 pub const fn computation_error(message: &'static str) -> Self {
132 Self::ComputationError { message }
133 }
134}
135
136pub fn check_latitude(latitude: f64) -> Result<()> {
141 if !(-90.0..=90.0).contains(&latitude) {
142 return Err(Error::invalid_latitude(latitude));
143 }
144 Ok(())
145}
146
147pub fn check_longitude(longitude: f64) -> Result<()> {
152 if !(-180.0..=180.0).contains(&longitude) {
153 return Err(Error::invalid_longitude(longitude));
154 }
155 Ok(())
156}
157
158pub fn check_coordinates(latitude: f64, longitude: f64) -> Result<()> {
163 check_latitude(latitude)?;
164 check_longitude(longitude)?;
165 Ok(())
166}
167
168pub fn check_elevation_angle(elevation_angle: f64) -> Result<()> {
173 if !elevation_angle.is_finite() || !(-90.0..=90.0).contains(&elevation_angle) {
174 return Err(Error::invalid_elevation_angle(elevation_angle));
175 }
176 Ok(())
177}
178
179pub fn check_pressure(pressure: f64) -> Result<()> {
184 if !pressure.is_finite() || pressure <= 0.0 || pressure > 2000.0 {
185 return Err(Error::invalid_pressure(pressure));
186 }
187 Ok(())
188}
189
190pub fn check_temperature(temperature: f64) -> Result<()> {
195 if !(-273.15..=100.0).contains(&temperature) {
196 return Err(Error::invalid_temperature(temperature));
197 }
198 Ok(())
199}
200
201pub fn check_azimuth(azimuth: f64) -> Result<f64> {
206 if !azimuth.is_finite() {
207 return Err(Error::computation_error("azimuth is not finite"));
208 }
209 Ok(normalize_degrees_0_to_360(azimuth))
210}
211
212pub fn check_zenith_angle(zenith: f64) -> Result<f64> {
217 if !zenith.is_finite() {
218 return Err(Error::computation_error("zenith angle is not finite"));
219 }
220 if !(0.0..=180.0).contains(&zenith) {
221 return Err(Error::computation_error(
222 "zenith angle must be between 0° and 180°",
223 ));
224 }
225 Ok(zenith)
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_latitude_validation() {
234 assert!(check_latitude(0.0).is_ok());
235 assert!(check_latitude(90.0).is_ok());
236 assert!(check_latitude(-90.0).is_ok());
237 assert!(check_latitude(45.5).is_ok());
238
239 assert!(check_latitude(91.0).is_err());
240 assert!(check_latitude(-91.0).is_err());
241 assert!(check_latitude(f64::NAN).is_err());
242 assert!(check_latitude(f64::INFINITY).is_err());
243 }
244
245 #[test]
246 fn test_longitude_validation() {
247 assert!(check_longitude(0.0).is_ok());
248 assert!(check_longitude(180.0).is_ok());
249 assert!(check_longitude(-180.0).is_ok());
250 assert!(check_longitude(122.5).is_ok());
251
252 assert!(check_longitude(181.0).is_err());
253 assert!(check_longitude(-181.0).is_err());
254 assert!(check_longitude(f64::NAN).is_err());
255 assert!(check_longitude(f64::INFINITY).is_err());
256 }
257
258 #[test]
259 fn test_pressure_validation() {
260 assert!(check_pressure(1013.25).is_ok());
261 assert!(check_pressure(1000.0).is_ok());
262 assert!(check_pressure(500.0).is_ok());
263
264 assert!(check_pressure(0.0).is_err());
265 assert!(check_pressure(-100.0).is_err());
266 assert!(check_pressure(3000.0).is_err());
267 assert!(check_pressure(f64::NAN).is_err());
268 assert!(check_pressure(f64::INFINITY).is_err());
269 }
270
271 #[test]
272 fn test_elevation_angle_validation() {
273 assert!(check_elevation_angle(-90.0).is_ok());
274 assert!(check_elevation_angle(0.0).is_ok());
275 assert!(check_elevation_angle(90.0).is_ok());
276
277 assert!(check_elevation_angle(-91.0).is_err());
278 assert!(check_elevation_angle(91.0).is_err());
279 assert!(check_elevation_angle(f64::NAN).is_err());
280 assert!(check_elevation_angle(f64::INFINITY).is_err());
281 }
282
283 #[test]
284 fn test_temperature_validation() {
285 assert!(check_temperature(15.0).is_ok());
286 assert!(check_temperature(0.0).is_ok());
287 assert!(check_temperature(-40.0).is_ok());
288 assert!(check_temperature(50.0).is_ok());
289
290 assert!(check_temperature(-300.0).is_err());
291 assert!(check_temperature(150.0).is_err());
292 }
293
294 #[test]
295 #[cfg(feature = "std")]
296 fn test_error_display() {
297 let err = Error::invalid_latitude(95.0);
298 assert_eq!(
299 err.to_string(),
300 "invalid latitude 95° (must be between -90° and +90°)"
301 );
302
303 let err = Error::invalid_longitude(185.0);
304 assert_eq!(
305 err.to_string(),
306 "invalid longitude 185° (must be between -180° and +180°)"
307 );
308
309 let err = Error::computation_error("convergence failed");
310 assert_eq!(err.to_string(), "computation error: convergence failed");
311 }
312
313 #[test]
314 fn test_check_azimuth() {
315 assert!(check_azimuth(0.0).is_ok());
316 assert!(check_azimuth(180.0).is_ok());
317 assert!(check_azimuth(360.0).is_ok());
318 assert!(check_azimuth(450.0).is_ok());
319 assert!(check_azimuth(-90.0).is_ok());
320
321 assert_eq!(check_azimuth(-90.0).unwrap(), 270.0);
323 assert_eq!(check_azimuth(450.0).unwrap(), 90.0);
324
325 assert!(check_azimuth(f64::NAN).is_err());
326 assert!(check_azimuth(f64::INFINITY).is_err());
327 }
328
329 #[test]
330 fn test_check_zenith_angle() {
331 assert!(check_zenith_angle(0.0).is_ok());
332 assert!(check_zenith_angle(90.0).is_ok());
333 assert!(check_zenith_angle(180.0).is_ok());
334
335 assert!(check_zenith_angle(-1.0).is_err());
336 assert!(check_zenith_angle(181.0).is_err());
337 assert!(check_zenith_angle(f64::NAN).is_err());
338 assert!(check_zenith_angle(f64::INFINITY).is_err());
339 }
340}