From 6a9c13d0998fbcbcc18d7d1c60d5fdee733fa442 Mon Sep 17 00:00:00 2001 From: Paul van Tilburg Date: Thu, 29 Sep 2022 22:15:28 +0200 Subject: [PATCH] Change uncertainty distance to be of type f64 The uncertainty distance is a positive floating number. * Validate this while parsing/building/using the setter * Add the error variant `Error::OutOfRangeUncertainty` and change `Error::InvalidDistance` to `Error::InvalidUncertainty` * Update the tests --- README.md | 12 +++---- src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 73 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index fc471b8..7db9d09 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ let geo_uri = GeoUri::try_from("geo:52.107,5.134,3.6;u=1000"); assert!(geo_uri.is_ok()); use std::str::FromStr; -let geo_uri2 = GeoUri::from_str("geo:52.107,5.134;u=2000"); +let geo_uri2 = GeoUri::from_str("geo:52.107,5.134;u=2000.0"); assert!(geo_uri2.is_ok()); ``` @@ -55,11 +55,11 @@ Use either the [`ToString`](std::string::ToString) or use geo_uri::GeoUri; let geo_uri = GeoUri::builder() - .latitude(52.107) - .longitude(5.134) - .uncertainty(1_000) - .build() - .unwrap(); + .latitude(52.107) + .longitude(5.134) + .uncertainty(1_000.0) + .build() + .unwrap(); assert_eq!( geo_uri.to_string(), String::from("geo:52.107,5.134;u=1000") diff --git a/src/lib.rs b/src/lib.rs index 05f34b0..c658a9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ #![deny(missing_docs)] use std::fmt; -use std::num::{ParseFloatError, ParseIntError}; +use std::num::ParseFloatError; use std::str::FromStr; use derive_builder::Builder; @@ -36,9 +36,9 @@ pub enum Error { #[error("Invalid coordinate reference system")] InvalidCoordRefSystem, - /// The geo URI contains an unparsable/invalid (uncertainty) distance. + /// The geo URI contains an unparsable/invalid uncertainty distance. #[error("Invalid distance in geo URI: {0}")] - InvalidDistance(ParseIntError), + InvalidUncertainty(ParseFloatError), /// The geo URI contains no coordinates. #[error("Missing coordinates in geo URI")] @@ -62,11 +62,15 @@ pub enum Error { #[error("Latitude coordinate is out of range")] OutOfRangeLatitude, - /// The longitude coordinate is out of range of `-180.0..=180.0` degrees + /// The longitude coordinate is out of range of `-180.0..=180.0` degrees. /// /// This can only fail for the WGS-84 coordinate reference system. #[error("Longitude coordinate is out of range")] OutOfRangeLongitude, + + /// The uncertainty distance is not positive. + #[error("Uncertainty distance not positive")] + OutOfRangeUncertainty, } /// The reference system of the provided coordinates. @@ -139,7 +143,7 @@ impl Default for CoordRefSystem { /// assert_eq!(geo_uri.latitude(), 52.107); /// assert_eq!(geo_uri.longitude(), 5.134); /// assert_eq!(geo_uri.altitude(), Some(3.6)); -/// assert_eq!(geo_uri.uncertainty(), Some(1000)); +/// assert_eq!(geo_uri.uncertainty(), Some(1000.0)); /// ``` /// /// or by using the [`TryFrom`] trait: @@ -147,11 +151,11 @@ impl Default for CoordRefSystem { /// use geo_uri::GeoUri; /// use std::str::FromStr; /// -/// let geo_uri = GeoUri::from_str("geo:52.107,5.134;u=2000").expect("valid geo URI"); +/// let geo_uri = GeoUri::from_str("geo:52.107,5.134;u=2000.0").expect("valid geo URI"); /// assert_eq!(geo_uri.latitude(), 52.107); /// assert_eq!(geo_uri.longitude(), 5.134); /// assert_eq!(geo_uri.altitude(), None); -/// assert_eq!(geo_uri.uncertainty(), Some(2000)); +/// assert_eq!(geo_uri.uncertainty(), Some(2000.0)); /// ``` /// /// It is also possible to call the parse function directly: @@ -176,7 +180,7 @@ impl Default for CoordRefSystem { /// let geo_uri = GeoUri::builder() /// .latitude(52.107) /// .longitude(5.134) -/// .uncertainty(1_000) +/// .uncertainty(1_000.0) /// .build() /// .unwrap(); /// assert_eq!( @@ -217,7 +221,9 @@ pub struct GeoUri { #[builder(default, setter(strip_option))] /// The uncertainty around the location as a radius (distance) in meters. - uncertainty: Option, + /// + /// This distance needs to be positive. + uncertainty: Option, } impl GeoUri { @@ -275,19 +281,26 @@ impl GeoUri { match param_parts.next() { Some(("u", value)) => ( CoordRefSystem::Wgs84, - Some(value.parse().map_err(Error::InvalidDistance)?), + Some(value.parse().map_err(Error::InvalidUncertainty)?), ), Some(_) | None => (CoordRefSystem::Wgs84, None), } } Some(("u", value)) => ( CoordRefSystem::default(), - Some(value.parse().map_err(Error::InvalidDistance)?), + Some(value.parse().map_err(Error::InvalidUncertainty)?), ), Some(_) | None => (CoordRefSystem::default(), None), }; + // Validate the parsed values. crs.validate(latitude, longitude)?; + // FIXME: Move this into the validator? This code is duplicate now. + if let Some(unc) = uncertainty { + if unc < 0.0 { + return Err(Error::OutOfRangeUncertainty); + } + } Ok(GeoUri { crs, @@ -339,13 +352,22 @@ impl GeoUri { } /// Returns the uncertainty around the location. - pub fn uncertainty(&self) -> Option { + pub fn uncertainty(&self) -> Option { self.uncertainty } /// Changes the uncertainty around the location. - pub fn set_uncertainty(&mut self, uncertainty: Option) { + /// + /// The uncertainty distance must be positive. + pub fn set_uncertainty(&mut self, uncertainty: Option) -> Result<(), Error> { + if let Some(unc) = uncertainty { + if unc < 0.0 { + return Err(Error::OutOfRangeUncertainty); + } + } self.uncertainty = uncertainty; + + Ok(()) } } @@ -409,7 +431,15 @@ impl GeoUriBuilder { self.latitude.unwrap_or_default(), self.longitude.unwrap_or_default(), ) - .map_err(|e| format!("{e}")) + .map_err(|e| format!("{e}"))?; + + if let Some(unc) = self.uncertainty.unwrap_or_default() { + if unc < 0.0 { + return Err(format!("{}", Error::OutOfRangeUncertainty)); + } + } + + Ok(()) } } @@ -482,6 +512,12 @@ mod tests { let geo_uri = GeoUri::parse("52.107,5.134"); assert!(matches!(geo_uri, Err(Error::MissingScheme))); + let geo_uri = GeoUri::parse("geo:100.0,5.134"); + assert!(matches!(geo_uri, Err(Error::OutOfRangeLatitude))); + + let geo_uri = GeoUri::parse("geo:62.107,-200.0"); + assert!(matches!(geo_uri, Err(Error::OutOfRangeLongitude))); + let geo_uri = GeoUri::parse("geo:geo:52.107,5.134"); assert!(matches!(geo_uri, Err(Error::InvalidCoord(_)))); @@ -510,34 +546,37 @@ mod tests { assert_eq!(geo_uri.uncertainty, None); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u="); - assert!(matches!(geo_uri, Err(Error::InvalidDistance(_)))); + assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_)))); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=foo"); - assert!(matches!(geo_uri, Err(Error::InvalidDistance(_)))); + assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_)))); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=wgs84;u=foo"); - assert!(matches!(geo_uri, Err(Error::InvalidDistance(_)))); + assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_)))); + + let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=-10.0"); + assert!(matches!(geo_uri, Err(Error::OutOfRangeUncertainty))); let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;u=25000")?; assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001); assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001); assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001); - assert_eq!(geo_uri.uncertainty, Some(25_000)); + assert_eq!(geo_uri.uncertainty, Some(25_000.0)); let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000")?; assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001); assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001); assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001); - assert_eq!(geo_uri.uncertainty, Some(25_000)); + assert_eq!(geo_uri.uncertainty, Some(25_000.0)); let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;CRS=wgs84;U=25000")?; - assert_eq!(geo_uri.uncertainty, Some(25_000)); + assert_eq!(geo_uri.uncertainty, Some(25_000.0)); let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000;foo=bar")?; assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001); assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001); assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001); - assert_eq!(geo_uri.uncertainty, Some(25_000)); + assert_eq!(geo_uri.uncertainty, Some(25_000.0)); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=foo"); assert!(matches!(geo_uri, Err(Error::InvalidCoordRefSystem))); @@ -578,8 +617,12 @@ mod tests { geo_uri.set_altitude(Some(3.6)); assert_eq!(geo_uri.altitude(), Some(3.6)); - geo_uri.set_uncertainty(Some(25_000)); - assert_eq!(geo_uri.uncertainty(), Some(25_000)); + assert_eq!(geo_uri.set_uncertainty(Some(25_000.0)), Ok(())); + assert_eq!( + geo_uri.set_uncertainty(Some(-100.0)), + Err(Error::OutOfRangeUncertainty) + ); + assert_eq!(geo_uri.uncertainty(), Some(25_000.0)); } #[test] @@ -596,7 +639,7 @@ mod tests { geo_uri.altitude = Some(3.6); assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6"); - geo_uri.uncertainty = Some(25_000); + geo_uri.uncertainty = Some(25_000.0); assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6;u=25000"); }