aviso_validators/
point.rs1use anyhow::{Result, bail};
10use tracing::debug;
11
12pub struct PointHandler;
16
17impl PointHandler {
18 pub fn validate_and_canonicalize(value: &str, field_name: &str) -> Result<String> {
19 let (lat, lon) = Self::parse_point_coordinates(value)?;
20 debug!(
21 field = field_name,
22 lat = lat,
23 lon = lon,
24 "Point validated successfully"
25 );
26 Ok(format!("{},{}", lat, lon))
27 }
28
29 pub fn parse_point_coordinates(value: &str) -> Result<(f64, f64)> {
30 let trimmed = value.trim();
31 if trimmed.is_empty() {
32 bail!("Point coordinate string cannot be empty");
33 }
34
35 let inner = trimmed
36 .strip_prefix('(')
37 .and_then(|s| s.strip_suffix(')'))
38 .unwrap_or(trimmed)
39 .trim();
40
41 let mut parts = inner.split(',').map(str::trim);
42 let lat_str = parts
43 .next()
44 .ok_or_else(|| anyhow::anyhow!("Point must be in 'lat,lon' format"))?;
45 let lon_str = parts
46 .next()
47 .ok_or_else(|| anyhow::anyhow!("Point must be in 'lat,lon' format"))?;
48
49 if parts.next().is_some() {
50 bail!("Point must contain exactly two values: 'lat,lon'");
51 }
52
53 let lat: f64 = lat_str
54 .parse()
55 .map_err(|_| anyhow::anyhow!("Invalid latitude value: {}", lat_str))?;
56 let lon: f64 = lon_str
57 .parse()
58 .map_err(|_| anyhow::anyhow!("Invalid longitude value: {}", lon_str))?;
59
60 if !(-90.0..=90.0).contains(&lat) {
61 bail!("Latitude out of range [-90, 90]: {}", lat);
62 }
63 if !(-180.0..=180.0).contains(&lon) {
64 bail!("Longitude out of range [-180, 180]: {}", lon);
65 }
66
67 Ok((lat, lon))
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::PointHandler;
74
75 #[test]
76 fn parse_point_coordinates_accepts_basic_format() {
77 let (lat, lon) = PointHandler::parse_point_coordinates("52.55,13.5").unwrap();
78 assert_eq!(lat, 52.55);
79 assert_eq!(lon, 13.5);
80 }
81
82 #[test]
83 fn parse_point_coordinates_accepts_parenthesized_format() {
84 let (lat, lon) = PointHandler::parse_point_coordinates("(52.55,13.5)").unwrap();
85 assert_eq!(lat, 52.55);
86 assert_eq!(lon, 13.5);
87 }
88
89 #[test]
90 fn parse_point_coordinates_rejects_bad_format() {
91 assert!(PointHandler::parse_point_coordinates("52.55").is_err());
92 assert!(PointHandler::parse_point_coordinates("52.55,13.5,1.0").is_err());
93 }
94
95 #[test]
96 fn parse_point_coordinates_rejects_out_of_range() {
97 assert!(PointHandler::parse_point_coordinates("91,13.5").is_err());
98 assert!(PointHandler::parse_point_coordinates("52.5,181").is_err());
99 }
100
101 #[test]
102 fn validate_and_canonicalize_returns_canonical_form() {
103 let canonical = PointHandler::validate_and_canonicalize("(52.5500,13.5000)", "p").unwrap();
104 assert_eq!(canonical, "52.55,13.5");
105 }
106}