Compare commits

...

31 Commits
v0.1.0 ... main

Author SHA1 Message Date
Paul van Tilburg 0ef589a5ac Add a full Gitea Actions release workflow
Check, lint and test using Cargo / Check, lint and test (push) Successful in 1m37s Details
2024-04-03 21:45:05 +02:00
Paul van Tilburg 8b69470cff Bump the version to 0.2.2
Check, lint and test using Cargo / Check, lint and test (push) Successful in 1m29s Details
2024-04-03 21:40:04 +02:00
Paul van Tilburg 071473ffcf Specify which files to package for publishing 2024-04-03 21:39:48 +02:00
Paul van Tilburg 867397d2db Update the changelog 2024-04-03 21:34:47 +02:00
Paul van Tilburg 54e3540946 Bump the dependency on derived_builder to 0.20.0 2024-04-03 21:31:50 +02:00
Paul van Tilburg c63bdceb01
Simplify Gitea Actions check and lint workflow
Check, lint and test using Cargo / Check, lint and test (push) Successful in 1m42s Details
2023-04-25 16:34:08 +02:00
Paul van Tilburg 00856f4dd9
Speed up workflow by using sparce Cargo index for crates.io
Check Details
Lints Details
Test Suite Details
2023-03-21 11:45:16 +01:00
Paul van Tilburg e6f02664b7
Tweak the cargo workflow a bit more
Check Details
Lints Details
Test Suite Details
- Run using the `debian-latest` image
- Use local `actions/checkout` where possible
- Small reordering
2023-03-21 10:48:11 +01:00
Paul van Tilburg bbd061f0d3
Enable all features during CI test step
Check Details
Lints Details
Test Suite Details
2023-03-20 21:18:50 +01:00
Paul van Tilburg ca86701ee8
Add CI workflow for Cargo using Gitea Actions
Check Details
Lints Details
Test Suite Details
2023-03-20 21:06:26 +01:00
Paul van Tilburg aa9d2b88f7
Bump the version to 0.2.1 2023-03-11 19:54:59 +01:00
Paul van Tilburg 0c158e13b7
Update the changelog 2023-03-11 19:54:38 +01:00
Paul van Tilburg abf3234a25
Fix typo in docs.rs metadata section name 2023-03-11 19:43:06 +01:00
Paul van Tilburg be4ebc26e1
Bump dependency on derive_builder 2023-03-07 14:55:51 +01:00
Paul van Tilburg f00fbc76ab
Fix links in README
In rustdoc, the crate's own types don't need to be linked, but for some
web view that uses plain Markdown, the links looked weird. Fix this by
linkin the crates' own types too.
2022-10-13 19:30:40 +02:00
Paul van Tilburg 129b29da26
Use assert_eq for float tests; drop dev depend on float_eq crate 2022-10-08 15:03:16 +02:00
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
Paul van Tilburg 8201b4d17b
Bump the version to 0.1.1 2022-09-30 20:52:08 +02:00
Paul van Tilburg eba77d57f8
Add a changelog 2022-09-30 20:50:57 +02:00
Paul van Tilburg 93cc6a8501
Add some more extra fields 2022-09-30 20:44:11 +02:00
Paul van Tilburg 3a9bc96289
Update the examples in README.md; tweak text 2022-09-30 08:08:03 +02:00
7 changed files with 630 additions and 71 deletions

View File

@ -0,0 +1,48 @@
name: "Check, lint and test using Cargo"
on:
- pull_request
- push
- workflow_dispatch
jobs:
check_lint:
name: Check, lint and test
runs-on: debian-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install stable toolchain
uses: https://github.com/actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Use sparse Cargo index for crates.io
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
- name: Run cargo check
uses: https://github.com/actions-rs/cargo@v1
with:
command: check
- name: Run cargo clippy
uses: https://github.com/actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Run cargo fmt
uses: https://github.com/actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Run cargo test
uses: https://github.com/actions-rs/cargo@v1
with:
command: test
args: --all-features

View File

@ -0,0 +1,75 @@
name: "Release"
on:
push:
tags:
- "v*"
jobs:
release:
name: "Release"
runs-on: debian-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Determine the version of the release
run: |
VERSION=${GITHUB_REF_NAME#v}
echo "Releasing version: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Get the release notes from the changelog
run: |
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
RELEASE_NOTES=$(sed -n -e "/^## \[$VERSION\]/,/^## \[/{//"'!'"p;}" CHANGELOG.md | sed -e '1d;$d')
echo "Release notes:"
echo
echo "$RELEASE_NOTES"
echo "RELEASE_NOTES<<$EOF" >> "$GITHUB_ENV"
echo "$RELEASE_NOTES" >> "$GITHUB_ENV"
echo "$EOF" >> "$GITHUB_ENV"
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Release to Gitea
uses: actions/release-action@main
with:
# This is available by default.
api_key: '${{ secrets.RELEASE_TOKEN }}'
files: FIXME
title: 'Release ${{ env.VERSION }}'
body: '${{ env.RELEASE_NOTES }}'
release-crate:
name: "Release crate"
runs-on: debian-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Rust stable toolchain
uses: https://github.com/actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Register the Gitea crate registry with Cargo
run: echo -e '[registries.luon]\nindex = "https://git.luon.net/paul/_cargo-index.git"' >> /root/.cargo/config.toml
- name: Run cargo publish
uses: https://github.com/actions-rs/cargo@v1
env:
# This needs to be provided for the repository; no login necessary as a result.
CARGO_REGISTRIES_LUON_TOKEN: '${{ secrets.CARGO_TOKEN }}'
with:
command: publish
args: --registry luon

2
.gitignore vendored
View File

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

55
CHANGELOG.md Normal file
View File

@ -0,0 +1,55 @@
# Changelog
All notable changes to geo-uri-rs will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.2.2] - 2024-04-03
### Changed
* Bumped dependency on `derive_builder` crate
## [0.2.1] - 2023-03-11
### Changed
* Bumped dependency on `derive_builder` crate
* Use `assert_eq` for float tests; drop dev depend on `float_eq` crate
### Fixed
* Fix doclinks in README
* Fix docs.rs metadata section name in `Cargo.toml`
## [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
* Update examples in `README.md`
* Add some more fields to `Cargo.toml`
### Fixed
* Fix some small errors in the documentation
[Unreleased]: https://git.luon.net/paul/geo-uri-rs/compare/v0.2.2...HEAD
[0.2.2]: https://git.luon.net/paul/geo-uri-rs/compare/v0.2.1..v0.2.2
[0.2.1]: https://git.luon.net/paul/geo-uri-rs/compare/v0.2.0..v0.2.1
[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,15 +1,30 @@
[package]
name = "geo-uri"
version = "0.1.0"
version = "0.2.2"
authors = ["Paul van Tilburg <paul@luon.net>"]
edition = "2021"
description = "A Rust crate for parsing uniform resource identifiers for geographic locations (geo URIs)"
rust-version = "1.60.0"
description = "A crate for parsing and generating uniform resource identifiers for geographic locations (geo URIs)"
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"]
include = ["CHANGELOG.md", "LICENSE", "README.md", "src/*.rs"]
[package.metadata."docs.rs"]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[features]
url = ["dep:url"]
serde = ["dep:serde"]
[dependencies]
derive_builder = "0.11.2"
derive_builder = "0.20.0"
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

@ -1,9 +1,8 @@
t# geo-uri-rs
# geo-uri-rs
A Rust crate (`geo-uri`) for uniform resource identifiers for geographic
locations (geo URIs) according to
IEEE RFC [5870](https://www.rfc-editor.org/rfc/rfc5870).
This crate allows for parsing and generating geo URIs in the correct format.
A Rust crate for uniform resource identifiers for geographic locations (geo
URIs) according to IEEE [RFC 5870](https://www.rfc-editor.org/rfc/rfc5870).
This crate supports parsing and generating geo URIs in the correct format.
Its parser is currently somewhat more liberal than the proposed standard.
It supports geolocations specified by latitude and longitude, but also
@ -18,23 +17,29 @@ Just run the following to add this library to your project:
```sh
$ cargo add geo-uri
Updating crates.io index
Adding thiserror v??? 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`](GeoUri) struct:
```rust
use geo_uri::GeoUri;
let geo_uri = GeoUri::try_from("geo:52.107,5.134,3.6;u=1000");
assert!(geo_uri.is_ok());
let geo_uri = GeoUri::try_from("geo:52.107,5.134,3.6;u=1000").expect("valid geo URI");
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));
use std::str::FromStr;
let geo_uri2 = GeoUri::from_str("geo:52.107,5.134;u=2000.0");
assert!(geo_uri2.is_ok());
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);
assert_eq!(geo_uri.uncertainty(), Some(2000.0));
```
It is also possible to call the parse function directly:
@ -42,14 +47,18 @@ It is also possible to call the parse function directly:
```rust
use geo_uri::GeoUri;
let geo_uri3 = GeoUri::parse("geo:52.107,5.134,3.6;u=1000");
assert!(geo_uri3.is_ok());
let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6").expect("valid geo URI");
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);
```
### Generating
Use either the [`ToString`](std::string::ToString) or
[`Display`](std::fmt::Display) trait to generate an geo URI after building it:
Use the [`GeoUriBuilder`](GeoUriBuilder) to construct a [`GeoUri`](GeoUri)
struct. Then, use either the [`ToString`](std::string::ToString) or
[`Display`](std::fmt::Display) trait to generate a geo URI string:
```rust
use geo_uri::GeoUri;
@ -60,6 +69,7 @@ let geo_uri = GeoUri::builder()
.uncertainty(1_000.0)
.build()
.unwrap();
assert_eq!(
geo_uri.to_string(),
String::from("geo:52.107,5.134;u=1000")
@ -70,6 +80,57 @@ assert_eq!(
);
```
It is also possible to construct a [`GeoUri`](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`](GeoUri) into an
[`Url`](https://docs.rs/url/2/url/struct.Url.html), 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`](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()
@ -445,7 +624,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::*;
@ -481,8 +661,8 @@ mod tests {
builder.longitude(5.134);
let geo_uri = builder.build()?;
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.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude, None);
assert_eq!(geo_uri.uncertainty, None);
@ -498,14 +678,20 @@ mod tests {
Err(GeoUriBuilderError::ValidationError(_))
));
builder.longitude(5.134).uncertainty(-200.0);
assert!(matches!(
builder.build(),
Err(GeoUriBuilderError::ValidationError(_))
));
Ok(())
}
#[test]
fn geo_uri_parse() -> Result<(), Error> {
let geo_uri = GeoUri::parse("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.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude, None);
assert_eq!(geo_uri.uncertainty, None);
@ -540,9 +726,9 @@ mod tests {
assert!(matches!(geo_uri, Err(Error::InvalidCoord(_))));
let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6")?;
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.1);
assert_eq!(geo_uri.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude.unwrap(), 3.6);
assert_eq!(geo_uri.uncertainty, None);
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=");
@ -558,24 +744,24 @@ mod tests {
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.1);
assert_eq!(geo_uri.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude.unwrap(), 3.6);
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.1);
assert_eq!(geo_uri.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude.unwrap(), 3.6);
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.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.1);
assert_eq!(geo_uri.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude.unwrap(), 3.6);
assert_eq!(geo_uri.uncertainty, Some(25_000.0));
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=foo");
@ -586,21 +772,21 @@ mod tests {
// Examples from RFC 5870 (sections 1, 6.1, 6.2 and 9.4)!
let geo_uri = GeoUri::parse("geo:13.4125,103.8667")?;
assert_float_eq!(geo_uri.latitude, 13.4125, abs <= 0.0001);
assert_float_eq!(geo_uri.longitude, 103.8667, abs <= 0.0001);
assert_eq!(geo_uri.latitude, 13.4125);
assert_eq!(geo_uri.longitude, 103.8667);
assert_eq!(geo_uri.altitude, None);
assert_eq!(geo_uri.uncertainty, None);
let geo_uri = GeoUri::parse("geo:48.2010,16.3695,183")?;
assert_float_eq!(geo_uri.latitude, 48.2010, abs <= 0.0001);
assert_float_eq!(geo_uri.longitude, 16.3695, abs <= 0.0001);
assert_float_eq!(geo_uri.altitude.unwrap(), 183.0, abs <= 0.1);
assert_eq!(geo_uri.latitude, 48.2010);
assert_eq!(geo_uri.longitude, 16.3695);
assert_eq!(geo_uri.altitude.unwrap(), 183.0);
assert_eq!(geo_uri.uncertainty, None);
let geo_uri = GeoUri::parse("geo:48.198634,16.371648;crs=wgs84;u=40")?;
assert_eq!(geo_uri.crs, CoordRefSystem::Wgs84);
assert_float_eq!(geo_uri.latitude, 48.198634, abs <= 0.000001);
assert_float_eq!(geo_uri.longitude, 16.371648, abs <= 0.000001);
assert_eq!(geo_uri.latitude, 48.198634);
assert_eq!(geo_uri.longitude, 16.371648);
assert_eq!(geo_uri.altitude, None);
assert_eq!(geo_uri.uncertainty, Some(40.0));
@ -610,6 +796,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,25 +873,119 @@ 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")?;
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.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude, None);
assert_eq!(geo_uri.uncertainty, None);
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.latitude, 52.107);
assert_eq!(geo_uri.longitude, 5.134);
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_eq!(geo_uri.latitude, 51.107);
assert_eq!(geo_uri.longitude, 5.134);
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_eq!(geo_uri.latitude, 51.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude.unwrap(), 3.6);
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_eq!(geo_uri.latitude, 51.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude.unwrap(), 3.6);
assert_eq!(geo_uri.uncertainty, Some(1000.0));
let geo_uri = GeoUri::try_from(url)?;
assert_eq!(geo_uri.latitude, 51.107);
assert_eq!(geo_uri.longitude, 5.134);
assert_eq!(geo_uri.altitude.unwrap(), 3.6);
assert_eq!(geo_uri.uncertainty, Some(1000.0));
Ok(())
}