diff --git a/Cargo.toml b/Cargo.toml index 6beef49..5dccbe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,11 +15,14 @@ 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" diff --git a/README.md b/README.md index 6741228..b0c2abc 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,17 @@ 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 diff --git a/src/lib.rs b/src/lib.rs index efb6963..bda865a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,11 +19,16 @@ use std::fmt; use std::num::ParseFloatError; use std::str::FromStr; -#[cfg(feature = "url")] -use url::Url; 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"; @@ -433,6 +438,36 @@ impl GeoUri { } } +#[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(self, v: &str) -> Result + 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(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(GeoUriVisitor) + } +} + impl fmt::Display for GeoUri { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { @@ -479,6 +514,17 @@ impl FromStr for GeoUri { } } +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl Serialize for GeoUri { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + impl TryFrom<&str> for GeoUri { type Error = Error; @@ -579,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::*; @@ -856,6 +904,28 @@ 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::( + &[Token::I32(0)], + "invalid type: integer `0`, expected a string starting with geo:", + ); + assert_de_tokens_error::( + &[Token::String("geo:100.0,5.134,3.6")], + &format!("{}", Error::OutOfRangeLatitude), + ); + } + #[test] fn geo_uri_try_from() -> Result<(), Error> { // &str