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
92pub fn check_latitude(latitude: f64) -> Result<()> {
97 if !(-90.0..=90.0).contains(&latitude) {
98 return Err(Error::InvalidLatitude { value: latitude });
99 }
100 Ok(())
101}
102
103pub fn check_longitude(longitude: f64) -> Result<()> {
108 if !(-180.0..=180.0).contains(&longitude) {
109 return Err(Error::InvalidLongitude { value: longitude });
110 }
111 Ok(())
112}
113
114pub fn check_coordinates(latitude: f64, longitude: f64) -> Result<()> {
119 check_latitude(latitude)?;
120 check_longitude(longitude)
121}
122
123pub fn check_elevation_angle(elevation_angle: f64) -> Result<()> {
128 if !elevation_angle.is_finite() || !(-90.0..=90.0).contains(&elevation_angle) {
129 return Err(Error::InvalidElevationAngle {
130 value: elevation_angle,
131 });
132 }
133 Ok(())
134}
135
136pub fn check_pressure(pressure: f64) -> Result<()> {
141 if !pressure.is_finite() || pressure <= 0.0 || pressure > 2000.0 {
142 return Err(Error::InvalidPressure { value: pressure });
143 }
144 Ok(())
145}
146
147pub fn check_temperature(temperature: f64) -> Result<()> {
152 if !(-273.15..=100.0).contains(&temperature) {
153 return Err(Error::InvalidTemperature { value: temperature });
154 }
155 Ok(())
156}
157
158pub fn check_azimuth(azimuth: f64) -> Result<f64> {
163 if !azimuth.is_finite() {
164 return Err(Error::ComputationError {
165 message: "azimuth is not finite",
166 });
167 }
168 Ok(normalize_degrees_0_to_360(azimuth))
169}
170
171pub fn check_zenith_angle(zenith: f64) -> Result<f64> {
176 if !zenith.is_finite() {
177 return Err(Error::ComputationError {
178 message: "zenith angle is not finite",
179 });
180 }
181 if !(0.0..=180.0).contains(&zenith) {
182 return Err(Error::ComputationError {
183 message: "zenith angle must be between 0° and 180°",
184 });
185 }
186 Ok(zenith)
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_latitude_validation() {
195 assert!(check_latitude(0.0).is_ok());
196 assert!(check_latitude(90.0).is_ok());
197 assert!(check_latitude(-90.0).is_ok());
198 assert!(check_latitude(45.5).is_ok());
199
200 assert!(check_latitude(91.0).is_err());
201 assert!(check_latitude(-91.0).is_err());
202 assert!(check_latitude(f64::NAN).is_err());
203 assert!(check_latitude(f64::INFINITY).is_err());
204 }
205
206 #[test]
207 fn test_longitude_validation() {
208 assert!(check_longitude(0.0).is_ok());
209 assert!(check_longitude(180.0).is_ok());
210 assert!(check_longitude(-180.0).is_ok());
211 assert!(check_longitude(122.5).is_ok());
212
213 assert!(check_longitude(181.0).is_err());
214 assert!(check_longitude(-181.0).is_err());
215 assert!(check_longitude(f64::NAN).is_err());
216 assert!(check_longitude(f64::INFINITY).is_err());
217 }
218
219 #[test]
220 fn test_pressure_validation() {
221 assert!(check_pressure(1013.25).is_ok());
222 assert!(check_pressure(1000.0).is_ok());
223 assert!(check_pressure(500.0).is_ok());
224
225 assert!(check_pressure(0.0).is_err());
226 assert!(check_pressure(-100.0).is_err());
227 assert!(check_pressure(3000.0).is_err());
228 assert!(check_pressure(f64::NAN).is_err());
229 assert!(check_pressure(f64::INFINITY).is_err());
230 }
231
232 #[test]
233 fn test_elevation_angle_validation() {
234 assert!(check_elevation_angle(-90.0).is_ok());
235 assert!(check_elevation_angle(0.0).is_ok());
236 assert!(check_elevation_angle(90.0).is_ok());
237
238 assert!(check_elevation_angle(-91.0).is_err());
239 assert!(check_elevation_angle(91.0).is_err());
240 assert!(check_elevation_angle(f64::NAN).is_err());
241 assert!(check_elevation_angle(f64::INFINITY).is_err());
242 }
243
244 #[test]
245 fn test_temperature_validation() {
246 assert!(check_temperature(15.0).is_ok());
247 assert!(check_temperature(0.0).is_ok());
248 assert!(check_temperature(-40.0).is_ok());
249 assert!(check_temperature(50.0).is_ok());
250
251 assert!(check_temperature(-300.0).is_err());
252 assert!(check_temperature(150.0).is_err());
253 }
254
255 #[test]
256 #[cfg(feature = "std")]
257 fn test_error_display() {
258 let err = Error::InvalidLatitude { value: 95.0 };
259 assert_eq!(
260 err.to_string(),
261 "invalid latitude 95° (must be between -90° and +90°)"
262 );
263
264 let err = Error::InvalidLongitude { value: 185.0 };
265 assert_eq!(
266 err.to_string(),
267 "invalid longitude 185° (must be between -180° and +180°)"
268 );
269
270 let err = Error::ComputationError {
271 message: "convergence failed",
272 };
273 assert_eq!(err.to_string(), "computation error: convergence failed");
274 }
275
276 #[test]
277 fn test_check_azimuth() {
278 assert!(check_azimuth(0.0).is_ok());
279 assert!(check_azimuth(180.0).is_ok());
280 assert!(check_azimuth(360.0).is_ok());
281 assert!(check_azimuth(450.0).is_ok());
282 assert!(check_azimuth(-90.0).is_ok());
283
284 assert_eq!(check_azimuth(-90.0).unwrap(), 270.0);
286 assert_eq!(check_azimuth(450.0).unwrap(), 90.0);
287
288 assert!(check_azimuth(f64::NAN).is_err());
289 assert!(check_azimuth(f64::INFINITY).is_err());
290 }
291
292 #[test]
293 fn test_check_zenith_angle() {
294 assert!(check_zenith_angle(0.0).is_ok());
295 assert!(check_zenith_angle(90.0).is_ok());
296 assert!(check_zenith_angle(180.0).is_ok());
297
298 assert!(check_zenith_angle(-1.0).is_err());
299 assert!(check_zenith_angle(181.0).is_err());
300 assert!(check_zenith_angle(f64::NAN).is_err());
301 assert!(check_zenith_angle(f64::INFINITY).is_err());
302 }
303}