Compare commits

..

11 Commits

Author SHA1 Message Date
Paul van Tilburg 6030134a34
Bump the version to 0.2.0 2022-10-01 15:47:21 +02:00
Paul van Tilburg 79a6f7c25f
Update the changelog 2022-10-01 15:45:11 +02:00
Paul van Tilburg 7fbef8ddf9
Add some more fields 2022-10-01 15:41:19 +02:00
Paul van Tilburg 56a888d5a3
Add support for serde (closes: #2)
* Introduce and document the `serde` feature
* Implement `serde::Deserialize` and `serde::Serialize` for `GeoUri`
* Add tests
2022-10-01 15:41:19 +02:00
Paul van Tilburg f2cb2788ab
Ignore editor-specific files of VS Code and Vim 2022-10-01 15:41:19 +02:00
Paul van Tilburg ff32105fce
Enable all features and document them for docs.rs 2022-10-01 15:41:19 +02:00
Paul van Tilburg 758a3f8072
Add support for converting from/to Url structs (closes: #1)
* Introduce and document the `url` feature
* Implement `From<&GeoUri>` and `From<GeoUri>` for `Url`
* Implement `TryFrom<&Url>` and `TryFrom<Url>` for `GeoUri`
* Add and extend tests
2022-10-01 15:41:18 +02:00
Paul van Tilburg 2628e96740
Add support for conversion from coordinate tuples
* Implement the `TryFrom` trait for `GeoUri` for `(f64, f64)` and
  `(f64, f64, f64)`
* Extend the tests
* Update the documentation
2022-10-01 15:20:05 +02:00
Paul van Tilburg 76ff1c95da
Use a single validator function; update tests 2022-10-01 15:20:05 +02:00
Paul van Tilburg 8ad3d5dea7
Fix typos and improve examples 2022-10-01 15:19:59 +02:00
Paul van Tilburg 7f3d4128b8
Make crate adhere to Rust API guidelines
* Don't set the `homepage` field in `Cargo.toml` if it is the same as
  the repository (C-METADATA)
* Don't use `expect` but `?` in examples (C-QUESTION-MARK)
  (This cannot be fixed for the README for now, unfortunately)
* Document the errors for all methods which return a `Result`
  (C-FAILURE)
2022-10-01 13:53:02 +02:00
5 changed files with 414 additions and 33 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
/target
Cargo.lock
.vim
.vscode

View File

@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.0] - 2022-10-01
### Added
* Add support for converting from/to `Url` structs (#1)
* Add support for (de)serializing via serde (#2)
### Fixed
* Fix documentation and comment types and improve examples
* Make the crate adhere to the [Rust API guidelines](https://rust-lang.github.io/api-guidelines/)
## [0.1.1] - 2022-09-30
### Added
@ -18,5 +30,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Fix some small errors in the documentation
[Unreleased]: https://git.luon.net/paul/geo-uri-rs/compare/v0.1.1...HEAD
[Unreleased]: https://git.luon.net/paul/geo-uri-rs/compare/v0.2.0...HEAD
[0.2.0]: https://git.luon.net/paul/geo-uri-rs/compare/v0.1.1..v0.2.0
[0.1.1]: https://git.luon.net/paul/geo-uri-rs/commits/tag/v0.1.1

View File

@ -1,18 +1,30 @@
[package]
name = "geo-uri"
version = "0.1.1"
version = "0.2.0"
authors = ["Paul van Tilburg <paul@luon.net>"]
edition = "2021"
rust-version = "1.60.0"
description = "A crate for parsing and generating uniform resource identifiers for geographic locations (geo URIs)"
homepage = "https://git.luon.net/paul/geo-uri-rs"
repository = "https://git.luon.net/paul/geo-uri-rs.git"
readme = "README.md"
repository = "https://git.luon.net/paul/geo-uri-rs"
license = "MIT"
keywords = ["geolocation", "uri", "parser", "rfc5870"]
categories = ["parser-implementations", "web-programming", "encoding"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[features]
url = ["dep:url"]
serde = ["dep:serde"]
[dependencies]
derive_builder = "0.11.2"
serde = { version = "1.0.145", optional = true }
thiserror = "1.0.35"
url = { version = "2.3.1", optional = true }
[dev-dependencies]
float_eq = "1.0.0"
serde_test = "1.0.145"

View File

@ -17,13 +17,14 @@ Just run the following to add this library to your project:
```sh
$ cargo add geo-uri
Updating crates.io index
Adding thiserror vX.Y.Z to dependencies.
Adding geo-uri vX.Y.Z to dependencies.
```
### Parsing
Use either the [`FromStr`](std::str::FromStr) or
[`TryFrom`](std::convert::TryFrom) traits to parse a geo URI string:
Use either the [`TryFrom`](std::convert::TryFrom) trait or the
[`parse`](str::parse) method on strings to parse a geo URI string into a
[`GeoUri`] struct:
```rust
use geo_uri::GeoUri;
@ -34,8 +35,7 @@ assert_eq!(geo_uri.longitude(), 5.134);
assert_eq!(geo_uri.altitude(), Some(3.6));
assert_eq!(geo_uri.uncertainty(), Some(1000.0));
use std::str::FromStr;
let geo_uri = GeoUri::from_str("geo:52.107,5.134;u=2000.0").expect("valid geo URI");
let geo_uri: GeoUri = "geo:52.107,5.134;u=2000.0".parse().expect("valid geo URI");
assert_eq!(geo_uri.latitude(), 52.107);
assert_eq!(geo_uri.longitude(), 5.134);
assert_eq!(geo_uri.altitude(), None);
@ -56,9 +56,9 @@ assert_eq!(geo_uri.uncertainty(), None);
### Generating
Use the `GeoUriBuilder` to construct a `GeoUri` struct.
Use the [`GeoUriBuilder`] to construct a [`GeoUri`] struct.
Then, use either the [`ToString`](std::string::ToString) or
[`Display`](std::fmt::Display) trait to generate an geo URI string:
[`Display`](std::fmt::Display) trait to generate a geo URI string:
```rust
use geo_uri::GeoUri;
@ -80,6 +80,56 @@ assert_eq!(
);
```
It is also possible to construct a [`GeoUri`] struct from coordinate tuples
using the [`TryFrom`](std::convert::TryFrom) trait:
```rust
use geo_uri::GeoUri;
let geo_uri = GeoUri::try_from((52.107, 5.134)).expect("valid coordinates");
let geo_uri = GeoUri::try_from((52.107, 5.134, 3.6)).expect("valid coordinates");
```
### Feature: `url`
You can enable the `url` feature to convert from and to
[`Url`](https://docs.rs/url/2/url/struct.Url.html) structs from the
[`url`](https://docs.rs/url/2/url) crate.
Enable the feature in your `Cargo.toml` first:
```toml
geo-uri = { version = "X.Y.Z", features = ["url"] }
```
Then you can do:
```rust
use geo_uri::GeoUri;
use url::Url;
let url = Url::parse("geo:52.107,5.134,3.6").expect("valid URL");
let geo_uri = GeoUri::try_from(&url).expect("valid geo URI");
let geo_url = Url::from(geo_uri);
assert_eq!(url, geo_url);
```
Note that it is always possible to transform a [`GeoUri`] into an [`Url`], but
not always the other way around! This is because the format of the coordinates
and parameters after the URI scheme "geo:" may be invalid!
### Feature: `serde`
If you enable the `serde` feature, [`GeoUri`] will implement
[`serde::Serialize`](https://docs.rs/serde/1/serde/trait.Serialize.html) and
[`serde::Deserialize`](https://docs.rs/serde/1/serde/trait.Deserialize.html).
See the [serde](https://serde.rs) documentation for more information.
```toml
geo-uri = { version = "X.Y.Z", features = ["serde"] }
```
## License
geo-uri-rs is licensed under the MIT license (see the `LICENSE` file or

View File

@ -1,3 +1,4 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![warn(
clippy::all,
@ -20,7 +21,14 @@ use std::num::ParseFloatError;
use std::str::FromStr;
use derive_builder::Builder;
#[cfg(feature = "serde")]
use serde::{
de::{Deserialize, Visitor},
ser::Serialize,
};
use thiserror::Error;
#[cfg(feature = "url")]
use url::Url;
/// The scheme name of a geo URI.
const URI_SCHEME_NAME: &str = "geo";
@ -90,7 +98,7 @@ pub enum CoordRefSystem {
}
impl CoordRefSystem {
/// Validates geo location coordinates against the selected coordinate reference system.
/// Validates geolocation coordinates against the selected coordinate reference system.
///
/// # Examples
///
@ -107,6 +115,11 @@ impl CoordRefSystem {
/// Err(Error::OutOfRangeLongitude)
/// );
/// ```
///
/// # Errors
///
/// An error is returned if the latitude/longitude is out of range with respect to the
/// coordinate reference system.
pub fn validate(&self, latitude: f64, longitude: f64) -> Result<(), Error> {
// This holds only for WGS-84, but it is the only one supported right now!
if !(-90.0..=90.0).contains(&latitude) {
@ -138,36 +151,47 @@ impl Default for CoordRefSystem {
///
/// ```rust
/// use geo_uri::GeoUri;
/// # use geo_uri::Error;
///
/// let geo_uri = GeoUri::try_from("geo:52.107,5.134,3.6;u=1000").expect("valid geo URI");
/// # fn main() -> Result<(), Error> {
/// let geo_uri = GeoUri::try_from("geo:52.107,5.134,3.6;u=1000")?;
/// 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.0));
/// # Ok(())
/// # }
/// ```
///
/// or by using the [`TryFrom`] trait:
/// or by calling the [`parse`](str::parse) method on a string (using the [`TryFrom`] trait):
/// ```
/// use geo_uri::GeoUri;
/// use std::str::FromStr;
/// # use geo_uri::Error;
///
/// let geo_uri = GeoUri::from_str("geo:52.107,5.134;u=2000.0").expect("valid geo URI");
/// # fn main() -> Result<(), Error> {
/// let geo_uri: GeoUri = "geo:52.107,5.134;u=2000.0".parse()?;
/// 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.0));
/// # Ok(())
/// # }
/// ```
///
/// It is also possible to call the parse function directly:
///
/// ```rust
/// use geo_uri::GeoUri;
/// # use geo_uri::Error;
///
/// let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6").expect("valid geo URI");
/// # fn main() -> Result<(), Error> {
/// let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6")?;
/// 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(), None);
/// # Ok(())
/// # }
/// ```
///
/// ## Generating
@ -176,13 +200,14 @@ impl Default for CoordRefSystem {
///
/// ```rust
/// use geo_uri::GeoUri;
/// # use geo_uri::GeoUriBuilderError;
///
/// # fn main() -> Result<(), GeoUriBuilderError> {
/// let geo_uri = GeoUri::builder()
/// .latitude(52.107)
/// .longitude(5.134)
/// .uncertainty(1_000.0)
/// .build()
/// .unwrap();
/// .build()?;
/// assert_eq!(
/// geo_uri.to_string(),
/// String::from("geo:52.107,5.134;u=1000")
@ -191,6 +216,18 @@ impl Default for CoordRefSystem {
/// format!("{geo_uri}"),
/// String::from("geo:52.107,5.134;u=1000")
/// );
/// # Ok(())
/// # }
/// ```
///
/// It is also possible to construct a [`GeoUri`] struct from coordinate tuples
/// using the [`TryFrom`](std::convert::TryFrom) trait:
///
/// ```rust
/// use geo_uri::GeoUri;
///
/// let geo_uri = GeoUri::try_from((52.107, 5.134)).expect("valid coordinates");
/// let geo_uri = GeoUri::try_from((52.107, 5.134, 3.6)).expect("valid coordinates");
/// ```
///
/// # See also
@ -236,6 +273,10 @@ impl GeoUri {
///
/// For the geo URI scheme syntax, see the propsed IEEE standard
/// [RFC 5870](https://www.rfc-editor.org/rfc/rfc5870#section-3.3).
///
/// # Errors
///
/// Will return an error if the parsing fails in any way.
pub fn parse(uri: &str) -> Result<Self, Error> {
let uri = uri.to_ascii_lowercase();
let uri_path = uri.strip_prefix("geo:").ok_or(Error::MissingScheme)?;
@ -293,22 +334,17 @@ impl GeoUri {
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 {
// Validate the geo URI before returning it.
let geo_uri = GeoUri {
crs,
latitude,
longitude,
altitude,
uncertainty,
})
};
geo_uri.validate()?;
Ok(geo_uri)
}
/// Returns the latitude coordinate.
@ -318,7 +354,10 @@ impl GeoUri {
/// Changes the latitude coordinate.
///
/// The latitude may be out of range for the coordinate reference system.
/// # Errors
///
/// If the latitude is out of range for the coordinate reference system, an error will be
/// returned.
pub fn set_latitude(&mut self, latitude: f64) -> Result<(), Error> {
self.crs.validate(latitude, self.longitude)?;
self.latitude = latitude;
@ -333,7 +372,10 @@ impl GeoUri {
/// Changes the longitude coordinate.
///
/// The longitude may be out of range for the coordinate reference system.
/// # Errors
///
/// If the longitude is out of range for the coordinate reference system, an error will be
/// returned.
pub fn set_longitude(&mut self, longitude: f64) -> Result<(), Error> {
self.crs.validate(self.latitude, longitude)?;
self.longitude = longitude;
@ -358,7 +400,9 @@ impl GeoUri {
/// Changes the uncertainty around the location.
///
/// The uncertainty distance must be positive.
/// # Errors
///
/// If the uncertainty distance is not zero or positive, an error will be returned.
pub fn set_uncertainty(&mut self, uncertainty: Option<f64>) -> Result<(), Error> {
if let Some(unc) = uncertainty {
if unc < 0.0 {
@ -369,6 +413,59 @@ impl GeoUri {
Ok(())
}
/// Validates the coordinates.
///
/// This is only meant for internal use to prevent returning [`GeoUri`] objects that are
/// actually invalid.
///
/// # Errors
///
/// Returns an error if the current latitude/longitude is invalid with respect to the current
/// coordinate reference system, or if the uncertainy, if set, is not zero or positive.
fn validate(&self) -> Result<(), Error> {
// Validate the latitude/longitude against the coordinate refrence system.
self.crs.validate(self.latitude, self.longitude)?;
// Ensure that the uncertainty is not negatify, if set.
if let Some(unc) = self.uncertainty {
if unc < 0.0 {
return Err(Error::OutOfRangeUncertainty);
}
}
Ok(())
}
}
#[cfg(feature = "serde")]
struct GeoUriVisitor;
#[cfg(feature = "serde")]
impl<'de> Visitor<'de> for GeoUriVisitor {
type Value = GeoUri;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "a string starting with {URI_SCHEME_NAME}:")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
GeoUri::parse(v).map_err(E::custom)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> Deserialize<'de> for GeoUri {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(GeoUriVisitor)
}
}
impl fmt::Display for GeoUri {
@ -393,6 +490,22 @@ impl fmt::Display for GeoUri {
}
}
#[cfg(feature = "url")]
#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
impl From<&GeoUri> for Url {
fn from(geo_uri: &GeoUri) -> Self {
Url::parse(&geo_uri.to_string()).expect("valid URL")
}
}
#[cfg(feature = "url")]
#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
impl From<GeoUri> for Url {
fn from(geo_uri: GeoUri) -> Self {
Url::from(&geo_uri)
}
}
impl FromStr for GeoUri {
type Err = Error;
@ -401,6 +514,17 @@ impl FromStr for GeoUri {
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl Serialize for GeoUri {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl TryFrom<&str> for GeoUri {
type Error = Error;
@ -409,6 +533,57 @@ impl TryFrom<&str> for GeoUri {
}
}
impl TryFrom<(f64, f64)> for GeoUri {
type Error = Error;
fn try_from((latitude, longitude): (f64, f64)) -> Result<Self, Self::Error> {
let geo_uri = GeoUri {
latitude,
longitude,
..Default::default()
};
geo_uri.validate()?;
Ok(geo_uri)
}
}
impl TryFrom<(f64, f64, f64)> for GeoUri {
type Error = Error;
fn try_from((latitude, longitude, altitude): (f64, f64, f64)) -> Result<Self, Self::Error> {
let geo_uri = GeoUri {
latitude,
longitude,
altitude: Some(altitude),
..Default::default()
};
geo_uri.validate()?;
Ok(geo_uri)
}
}
#[cfg(feature = "url")]
#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
impl TryFrom<&Url> for GeoUri {
type Error = Error;
fn try_from(url: &Url) -> Result<Self, Self::Error> {
GeoUri::parse(url.as_str())
}
}
#[cfg(feature = "url")]
#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
impl TryFrom<Url> for GeoUri {
type Error = Error;
fn try_from(url: Url) -> Result<Self, Self::Error> {
GeoUri::try_from(&url)
}
}
impl PartialEq for GeoUri {
fn eq(&self, other: &Self) -> bool {
// In the WGS-84 CRS the the longitude is ignored for the poles.
@ -424,6 +599,10 @@ impl PartialEq for GeoUri {
impl GeoUriBuilder {
/// Validates the coordinates against the
///
/// # Errors
///
/// Returns an error if the the currently configured coordinate values are invalid.
fn validate(&self) -> Result<(), String> {
self.crs
.unwrap_or_default()
@ -446,6 +625,8 @@ impl GeoUriBuilder {
#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
#[cfg(feature = "serde")]
use serde_test::{assert_de_tokens_error, assert_tokens, Token};
use super::*;
@ -498,6 +679,12 @@ mod tests {
Err(GeoUriBuilderError::ValidationError(_))
));
builder.longitude(5.134).uncertainty(-200.0);
assert!(matches!(
builder.build(),
Err(GeoUriBuilderError::ValidationError(_))
));
Ok(())
}
@ -610,6 +797,29 @@ mod tests {
Ok(())
}
#[test]
fn geo_uri_validate() {
let mut geo_uri = GeoUri {
crs: CoordRefSystem::Wgs84,
latitude: 52.107,
longitude: 5.134,
altitude: None,
uncertainty: None,
};
assert_eq!(geo_uri.validate(), Ok(()));
geo_uri.latitude = 100.0;
assert_eq!(geo_uri.validate(), Err(Error::OutOfRangeLatitude));
geo_uri.latitude = 52.107;
geo_uri.longitude = -200.0;
assert_eq!(geo_uri.validate(), Err(Error::OutOfRangeLongitude));
geo_uri.longitude = 5.134;
geo_uri.uncertainty = Some(-2000.0);
assert_eq!(geo_uri.validate(), Err(Error::OutOfRangeUncertainty));
}
#[test]
fn geo_uri_get_set() {
let mut geo_uri = GeoUri {
@ -664,6 +874,25 @@ mod tests {
assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6;u=25000");
}
#[cfg(feature = "url")]
#[test]
fn geo_uri_from() {
let geo_uri = GeoUri {
crs: CoordRefSystem::Wgs84,
latitude: 52.107,
longitude: 5.134,
altitude: Some(3.6),
uncertainty: Some(1000.0),
};
let url = Url::from(&geo_uri);
assert_eq!(url.scheme(), "geo");
assert_eq!(url.path(), "52.107,5.134,3.6;u=1000");
let url = Url::from(geo_uri);
assert_eq!(url.scheme(), "geo");
assert_eq!(url.path(), "52.107,5.134,3.6;u=1000");
}
#[test]
fn geo_uri_from_str() -> Result<(), Error> {
let geo_uri = GeoUri::from_str("geo:52.107,5.134")?;
@ -675,14 +904,89 @@ mod tests {
Ok(())
}
#[cfg(feature = "serde")]
#[test]
fn geo_uri_serde() {
let geo_uri = GeoUri {
crs: CoordRefSystem::Wgs84,
latitude: 52.107,
longitude: 5.134,
altitude: Some(3.6),
uncertainty: Some(1000.0),
};
assert_tokens(&geo_uri, &[Token::String("geo:52.107,5.134,3.6;u=1000")]);
assert_de_tokens_error::<GeoUri>(
&[Token::I32(0)],
"invalid type: integer `0`, expected a string starting with geo:",
);
assert_de_tokens_error::<GeoUri>(
&[Token::String("geo:100.0,5.134,3.6")],
&format!("{}", Error::OutOfRangeLatitude),
);
}
#[test]
fn geo_uri_try_from() -> Result<(), Error> {
// &str
let geo_uri = GeoUri::try_from("geo:52.107,5.134")?;
assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001);
assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001);
assert_eq!(geo_uri.altitude, None);
assert_eq!(geo_uri.uncertainty, None);
// (f64, f64)
let geo_uri = GeoUri::try_from((51.107, 5.134))?;
assert_float_eq!(geo_uri.latitude, 51.107, abs <= 0.001);
assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001);
assert_eq!(geo_uri.altitude, None);
assert_eq!(geo_uri.uncertainty, None);
assert_eq!(
GeoUri::try_from((100.0, 5.134)),
Err(Error::OutOfRangeLatitude)
);
assert_eq!(
GeoUri::try_from((51.107, -200.0)),
Err(Error::OutOfRangeLongitude)
);
// (f64, f64, f64)
let geo_uri = GeoUri::try_from((51.107, 5.134, 3.6))?;
assert_float_eq!(geo_uri.latitude, 51.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.1);
assert_eq!(geo_uri.uncertainty, None);
assert_eq!(
GeoUri::try_from((100.0, 5.134, 3.6)),
Err(Error::OutOfRangeLatitude)
);
assert_eq!(
GeoUri::try_from((51.107, -200.0, 3.6)),
Err(Error::OutOfRangeLongitude)
);
Ok(())
}
#[cfg(feature = "url")]
#[test]
fn geo_uri_try_from_url() -> Result<(), Error> {
// Url
let url = Url::parse("geo:51.107,5.134,3.6;crs=wgs84;u=1000;foo=bar").expect("valid URL");
let geo_uri = GeoUri::try_from(&url)?;
assert_float_eq!(geo_uri.latitude, 51.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.1);
assert_eq!(geo_uri.uncertainty, Some(1000.0));
let geo_uri = GeoUri::try_from(url)?;
assert_float_eq!(geo_uri.latitude, 51.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.1);
assert_eq!(geo_uri.uncertainty, Some(1000.0));
Ok(())
}