#![cfg_attr(feature = "geo-types", doc = "```")]
#![cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
use crate::{Feature, JsonObject, JsonValue, Result};
use serde::{Serialize, Serializer, ser::Error};
use serde::de::IntoDeserializer;
use std::{convert::TryInto, io};
pub fn to_feature_string<T>(value: &T) -> Result<String>
where
T: Serialize,
{
let vec = to_feature_byte_vec(value)?;
let string = unsafe {
String::from_utf8_unchecked(vec)
};
Ok(string)
}
pub fn to_feature_collection_string<T>(values: &[T]) -> Result<String>
where
T: Serialize,
{
let vec = to_feature_collection_byte_vec(values)?;
let string = unsafe {
String::from_utf8_unchecked(vec)
};
Ok(string)
}
pub fn to_feature_byte_vec<T>(value: &T) -> Result<Vec<u8>>
where
T: Serialize,
{
let mut writer = Vec::with_capacity(128);
to_feature_writer(&mut writer, value)?;
Ok(writer)
}
pub fn to_feature_collection_byte_vec<T>(values: &[T]) -> Result<Vec<u8>>
where
T: Serialize,
{
let mut writer = Vec::with_capacity(128);
to_feature_collection_writer(&mut writer, values)?;
Ok(writer)
}
pub fn to_feature_writer<W, T>(writer: W, value: &T) -> Result<()>
where
W: io::Write,
T: Serialize,
{
let feature_serializer = FeatureWrapper::new(value);
let mut serializer = serde_json::Serializer::new(writer);
feature_serializer.serialize(&mut serializer)?;
Ok(())
}
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn to_feature<T>(value: &T) -> Result<Feature>
where
T: Serialize,
{
let feature_wrapper = FeatureWrapper::new(value);
let js_value = serde_json::to_value(feature_wrapper)?;
use serde::Deserialize;
Ok(Feature::deserialize(js_value.into_deserializer())?)
}
pub fn to_feature_collection_writer<W, T>(writer: W, features: &[T]) -> Result<()>
where
W: io::Write,
T: Serialize,
{
use serde::ser::SerializeMap;
let mut ser = serde_json::Serializer::new(writer);
let mut map = ser.serialize_map(Some(2))?;
map.serialize_entry("type", "FeatureCollection")?;
map.serialize_entry("features", &Features::new(features))?;
map.end()?;
Ok(())
}
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn serialize_geometry<IG, S>(geometry: IG, ser: S) -> std::result::Result<S::Ok, S::Error>
where
IG: TryInto<crate::Geometry>,
S: serde::Serializer,
<IG as TryInto<crate::Geometry>>::Error: std::fmt::Display,
{
geometry
.try_into()
.map_err(serialize_error_msg::<S>)?
.serialize(ser)
}
#[cfg_attr(feature = "geo-types", doc = "```")]
#[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
pub fn serialize_optional_geometry<'a, IG, S>(
geometry: &'a Option<IG>,
ser: S,
) -> std::result::Result<S::Ok, S::Error>
where
&'a IG: std::convert::TryInto<crate::Geometry>,
S: serde::Serializer,
<&'a IG as TryInto<crate::Geometry>>::Error: std::fmt::Display,
{
geometry
.as_ref()
.map(TryInto::try_into)
.transpose()
.map_err(serialize_error_msg::<S>)?
.serialize(ser)
}
fn serialize_error_msg<S: Serializer>(error: impl std::fmt::Display) -> S::Error {
Error::custom(format!("failed to convert geometry to GeoJSON: {}", error))
}
struct Features<'a, T>
where
T: Serialize,
{
features: &'a [T],
}
impl<'a, T> Features<'a, T>
where
T: Serialize,
{
fn new(features: &'a [T]) -> Self {
Self { features }
}
}
impl<T> serde::Serialize for Features<'_, T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(None)?;
for feature in self.features.iter() {
seq.serialize_element(&FeatureWrapper::new(feature))?;
}
seq.end()
}
}
struct FeatureWrapper<'t, T> {
feature: &'t T,
}
impl<'t, T> FeatureWrapper<'t, T> {
fn new(feature: &'t T) -> Self {
Self { feature }
}
}
impl<T> Serialize for FeatureWrapper<'_, T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut json_object: JsonObject = {
let value = serde_json::to_value(self.feature).map_err(|e| {
S::Error::custom(format!("Feature was not serializable as JSON - {}", e))
})?;
match value {
JsonValue::Object(object) => object,
JsonValue::Null => {
return Err(S::Error::custom("expected JSON object but found `null`"));
}
JsonValue::Bool(_) => {
return Err(S::Error::custom("expected JSON object but found `bool`"));
}
JsonValue::Number(_) => {
return Err(S::Error::custom("expected JSON object but found `number`"));
}
JsonValue::String(_) => {
return Err(S::Error::custom("expected JSON object but found `string`"));
}
JsonValue::Array(_) => {
return Err(S::Error::custom("expected JSON object but found `array`"));
}
}
};
if !json_object.contains_key("geometry") {
return Err(S::Error::custom("missing `geometry` field"));
}
let geometry = json_object.remove("geometry");
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("type", "Feature")?;
map.serialize_entry("geometry", &geometry)?;
if json_object.contains_key("id") {
map.serialize_entry("id", &json_object.remove("id"))?;
}
map.serialize_entry("properties", &json_object)?;
map.end()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::JsonValue;
use serde_json::json;
use std::str::FromStr;
#[test]
fn happy_path() {
#[derive(Serialize)]
struct MyStruct {
geometry: crate::Geometry,
name: String,
}
let my_feature = {
let geometry = crate::Geometry::new_point([0.0, 1.0]);
let name = "burbs".to_string();
MyStruct { geometry, name }
};
let expected_output_json = json!({
"type": "Feature",
"geometry": {
"coordinates":[0.0,1.0],
"type":"Point"
},
"properties": {
"name": "burbs"
}
});
let actual_output = to_feature_string(&my_feature).unwrap();
let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
assert_eq!(actual_output_json, expected_output_json);
}
mod optional_geometry {
use super::*;
#[derive(Serialize)]
struct MyStruct {
geometry: Option<crate::Geometry>,
name: String,
}
#[test]
fn with_some_geom() {
let my_feature = {
let geometry = Some(crate::Geometry::new_point([0.0, 1.0]));
let name = "burbs".to_string();
MyStruct { geometry, name }
};
let expected_output_json = json!({
"type": "Feature",
"geometry": {
"coordinates":[0.0,1.0],
"type":"Point"
},
"properties": {
"name": "burbs"
}
});
let actual_output = to_feature_string(&my_feature).unwrap();
let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
assert_eq!(actual_output_json, expected_output_json);
}
#[test]
fn with_none_geom() {
let my_feature = {
let geometry = None;
let name = "burbs".to_string();
MyStruct { geometry, name }
};
let expected_output_json = json!({
"type": "Feature",
"geometry": null,
"properties": {
"name": "burbs"
}
});
let actual_output = to_feature_string(&my_feature).unwrap();
let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
assert_eq!(actual_output_json, expected_output_json);
}
#[test]
fn without_geom_field() {
#[derive(Serialize)]
struct MyStructWithoutGeom {
name: String,
}
let my_feature = {
let name = "burbs".to_string();
MyStructWithoutGeom { name }
};
let actual_output = to_feature_string(&my_feature).unwrap_err();
let error_message = actual_output.to_string();
assert!(error_message.contains("missing"));
assert!(error_message.contains("geometry"));
}
#[test]
fn serializes_whatever_geometry() {
#[derive(Serialize)]
struct MyStructWithWeirdGeom {
geometry: Vec<u32>,
name: String,
}
let my_feature = {
let geometry = vec![1, 2, 3];
let name = "burbs".to_string();
MyStructWithWeirdGeom { geometry, name }
};
let expected_output_json = json!({
"type": "Feature",
"geometry": [1, 2, 3],
"properties": {
"name": "burbs"
}
});
let actual_output = to_feature_string(&my_feature).unwrap();
let actual_output_json = JsonValue::from_str(&actual_output).unwrap();
assert_eq!(actual_output_json, expected_output_json);
}
}
#[cfg(feature = "geo-types")]
mod geo_types_tests {
use super::*;
use crate::de::tests::feature_collection;
#[test]
fn serializes_optional_point() {
#[derive(serde::Serialize)]
struct MyStruct {
count: usize,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_geometry"
)]
geometry: Option<geo_types::Point<f64>>,
}
let my_struct = MyStruct {
count: 0,
geometry: Some(geo_types::Point::new(1.2, 0.5)),
};
let json = json! {{
"count": 0,
"geometry": {
"type": "Point",
"coordinates": [1.2, 0.5]
},
}};
assert_eq!(json, serde_json::to_value(my_struct).unwrap());
let my_struct = MyStruct {
count: 1,
geometry: None,
};
let json = json! {{
"count": 1,
}};
assert_eq!(json, serde_json::to_value(my_struct).unwrap());
}
#[test]
fn geometry_field_without_helper() {
#[derive(Serialize)]
struct MyStruct {
geometry: geo_types::Point<f64>,
name: String,
age: u64,
}
let my_struct = MyStruct {
geometry: geo_types::point!(x: 125.6, y: 10.1),
name: "Dinagat Islands".to_string(),
age: 123,
};
let expected_invalid_output = json!({
"type": "Feature",
"geometry": { "x": 125.6, "y": 10.1 },
"properties": {
"name": "Dinagat Islands",
"age": 123
}
});
let output_string = to_feature_string(&my_struct).expect("valid serialization");
let actual_output = JsonValue::from_str(&output_string).unwrap();
assert_eq!(actual_output, expected_invalid_output);
}
#[test]
fn geometry_field() {
#[derive(Serialize)]
struct MyStruct {
#[serde(serialize_with = "serialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
}
let my_struct = MyStruct {
geometry: geo_types::point!(x: 125.6, y: 10.1),
name: "Dinagat Islands".to_string(),
age: 123,
};
let expected_output = json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 123
}
});
let output_string = to_feature_string(&my_struct).expect("valid serialization");
let actual_output = JsonValue::from_str(&output_string).unwrap();
assert_eq!(actual_output, expected_output);
}
#[test]
fn with_id_field() {
#[derive(Serialize)]
struct MyStruct {
#[serde(serialize_with = "serialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
id: &'static str,
}
let my_struct = MyStruct {
geometry: geo_types::point!(x: 125.6, y: 10.1),
name: "Dinagat Islands".to_string(),
age: 123,
id: "my-id-123",
};
let expected_output = json!({
"type": "Feature",
"id": "my-id-123",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 123
}
});
let output_string = to_feature_string(&my_struct).expect("valid serialization");
let actual_output = JsonValue::from_str(&output_string).unwrap();
assert_eq!(actual_output, expected_output);
}
#[test]
fn with_numeric_id_field() {
#[derive(Serialize)]
struct MyStruct {
#[serde(serialize_with = "serialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
id: u64,
}
let my_struct = MyStruct {
geometry: geo_types::point!(x: 125.6, y: 10.1),
name: "Dinagat Islands".to_string(),
age: 123,
id: 666,
};
let expected_output = json!({
"type": "Feature",
"id": 666,
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands",
"age": 123
}
});
let output_string = to_feature_string(&my_struct).expect("valid serialization");
let actual_output = JsonValue::from_str(&output_string).unwrap();
assert_eq!(actual_output, expected_output);
}
#[test]
fn test_to_feature() {
#[derive(Serialize)]
struct MyStruct {
#[serde(serialize_with = "serialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
}
let my_struct = MyStruct {
geometry: geo_types::point!(x: 125.6, y: 10.1),
name: "Dinagat Islands".to_string(),
age: 123,
};
let actual = to_feature(&my_struct).unwrap();
let expected = Feature {
bbox: None,
geometry: Some(crate::Geometry::new_point([125.6, 10.1])),
id: None,
properties: Some(
json!({
"name": "Dinagat Islands",
"age": 123
})
.as_object()
.unwrap()
.clone(),
),
foreign_members: None,
};
assert_eq!(actual, expected)
}
#[test]
fn serialize_feature_collection() {
#[derive(Serialize)]
struct MyStruct {
#[serde(serialize_with = "serialize_geometry")]
geometry: geo_types::Point<f64>,
name: String,
age: u64,
}
let my_structs = vec![
MyStruct {
geometry: geo_types::point!(x: 125.6, y: 10.1),
name: "Dinagat Islands".to_string(),
age: 123,
},
MyStruct {
geometry: geo_types::point!(x: 2.3, y: 4.5),
name: "Neverland".to_string(),
age: 456,
},
];
let output_string =
to_feature_collection_string(&my_structs).expect("valid serialization");
let expected_output = feature_collection();
let actual_output = JsonValue::from_str(&output_string).unwrap();
assert_eq!(actual_output, expected_output);
}
}
}