diff --git a/CHANGES.md b/CHANGES.md index 1fc2d7a7..3748c34f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,9 @@ * * Overhauled front page documentation. * +* Parse `Geometry`/`Feature`/`FeatureCollection` directly from str rather than + via `GeoJson` when you know what you're expecting. + * * `Feature` now derives `Default` * diff --git a/src/feature.rs b/src/feature.rs index d0891cab..3062198f 100644 --- a/src/feature.rs +++ b/src/feature.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::convert::TryFrom; +use std::str::FromStr; use crate::errors::Error; use crate::json::{json, Deserialize, Deserializer, JsonObject, JsonValue, Serialize, Serializer}; @@ -42,6 +43,14 @@ impl From for Feature { } } +impl FromStr for Feature { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::try_from(crate::GeoJson::from_str(s)?) + } +} + impl<'a> From<&'a Feature> for JsonObject { fn from(feature: &'a Feature) -> JsonObject { let mut map = JsonObject::new(); @@ -211,8 +220,11 @@ impl Serialize for Id { #[cfg(test)] mod tests { + use crate::json::json; use crate::{feature, Error, Feature, GeoJson, Geometry, Value}; + use std::str::FromStr; + fn feature_json_str() -> &'static str { "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"properties\":{},\"type\":\ \"Feature\"}" @@ -460,4 +472,40 @@ mod tests { assert_eq!(feature.contains_property("foo"), false); assert_eq!(feature.properties_iter().collect::>(), vec![]); } + + #[test] + fn test_from_str_ok() { + let feature_json = json!({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [125.6, 10.1] + }, + "properties": { + "name": "Dinagat Islands" + } + }) + .to_string(); + + let feature = Feature::from_str(&feature_json).unwrap(); + assert_eq!("Dinagat Islands", feature.property("name").unwrap()); + } + + #[test] + fn test_from_str_with_unexpected_type() { + let geometry_json = json!({ + "type": "Point", + "coordinates": [125.6, 10.1] + }) + .to_string(); + + let actual_failure = Feature::from_str(&geometry_json).unwrap_err(); + match actual_failure { + Error::ExpectedType { actual, expected } => { + assert_eq!(actual, "Geometry"); + assert_eq!(expected, "Feature"); + } + e => panic!("unexpected error: {}", e), + }; + } } diff --git a/src/feature_collection.rs b/src/feature_collection.rs index 9435d885..fdad6036 100644 --- a/src/feature_collection.rs +++ b/src/feature_collection.rs @@ -14,6 +14,7 @@ use std::convert::TryFrom; use std::iter::FromIterator; +use std::str::FromStr; use crate::errors::Error; use crate::json::{json, Deserialize, Deserializer, JsonObject, JsonValue, Serialize, Serializer}; @@ -132,6 +133,14 @@ impl TryFrom for FeatureCollection { } } +impl FromStr for FeatureCollection { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::try_from(crate::GeoJson::from_str(s)?) + } +} + impl Serialize for FeatureCollection { fn serialize(&self, serializer: S) -> Result where @@ -222,7 +231,10 @@ impl FromIterator for FeatureCollection { #[cfg(test)] mod tests { - use crate::{Feature, FeatureCollection, Value}; + use crate::json::json; + use crate::{Error, Feature, FeatureCollection, Value}; + + use std::str::FromStr; #[test] fn test_fc_from_iterator() { @@ -244,4 +256,52 @@ mod tests { assert_eq!(fc.features.len(), 2); assert_eq!(fc.bbox, Some(vec![-1., -1., -1., 11., 11., 11.])); } + + #[test] + fn test_from_str_ok() { + let fc_json = json!({ "type": "FeatureCollection", "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [125.6, 10.1] + }, + "properties": { + "name": "Dinagat Islands" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [125.6, 10.1] + }, + "properties": { + "name": "Dinagat Islands" + } + }, + ]}) + .to_string(); + + let feature_collection = FeatureCollection::from_str(&fc_json).unwrap(); + assert_eq!(2, feature_collection.features.len()); + } + + #[test] + fn test_from_str_with_unexpected_type() { + let geometry_json = json!({ + "type": "Point", + "coordinates": [125.6, 10.1] + }) + .to_string(); + + let actual_failure = FeatureCollection::from_str(&geometry_json).unwrap_err(); + match actual_failure { + Error::ExpectedType { actual, expected } => { + assert_eq!(actual, "Geometry"); + assert_eq!(expected, "FeatureCollection"); + } + e => panic!("unexpected error: {}", e), + }; + } } diff --git a/src/geometry.rs b/src/geometry.rs index 3546ca1e..60c9c915 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::str::FromStr; use std::{convert::TryFrom, fmt}; use crate::errors::Error; @@ -300,6 +301,14 @@ impl TryFrom for Geometry { } } +impl FromStr for Geometry { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::try_from(crate::GeoJson::from_str(s)?) + } +} + impl Serialize for Geometry { fn serialize(&self, serializer: S) -> Result where @@ -333,9 +342,10 @@ where #[cfg(test)] mod tests { + use std::str::FromStr; - use crate::json::JsonObject; - use crate::{GeoJson, Geometry, Value}; + use crate::json::{json, JsonObject}; + use crate::{Error, GeoJson, Geometry, Value}; fn encode(geometry: &Geometry) -> String { serde_json::to_string(&geometry).unwrap() @@ -466,4 +476,40 @@ mod tests { }; assert_eq!(decoded_geometry, geometry_collection); } + + #[test] + fn test_from_str_ok() { + let geometry_json = json!({ + "type": "Point", + "coordinates": [125.6f64, 10.1] + }) + .to_string(); + + let geometry = Geometry::from_str(&geometry_json).unwrap(); + assert!(matches!(geometry.value, Value::Point(_))); + } + + #[test] + fn test_from_str_with_unexpected_type() { + let feature_json = json!({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [125.6, 10.1] + }, + "properties": { + "name": "Dinagat Islands" + } + }) + .to_string(); + + let actual_failure = Geometry::from_str(&feature_json).unwrap_err(); + match actual_failure { + Error::ExpectedType { actual, expected } => { + assert_eq!(actual, "Feature"); + assert_eq!(expected, "Geometry"); + } + e => panic!("unexpected error: {}", e), + }; + } }