diff --git a/lib/src/api/opt/mod.rs b/lib/src/api/opt/mod.rs index cedef246..fa77dfd5 100644 --- a/lib/src/api/opt/mod.rs +++ b/lib/src/api/opt/mod.rs @@ -194,9 +194,17 @@ fn into_json(value: Value, simplify: bool) -> JsonValue { coordinates: JsonValue, } - #[derive(Serialize)] struct GeometryCollection; + impl Serialize for GeometryCollection { + fn serialize(&self, s: S) -> Result + where + S: serde::Serializer, + { + s.serialize_str("GeometryCollection") + } + } + #[derive(Serialize)] struct Geometries { #[serde(rename = "type")] @@ -335,35 +343,48 @@ fn into_json(value: Value, simplify: bool) -> JsonValue { } match value { + // These value types are simple values which + // can be used in query responses sent to + // the client. Value::None | Value::Null => JsonValue::Null, Value::Bool(boolean) => boolean.into(), - Value::Number(Number::Int(n)) => n.into(), - Value::Number(Number::Float(n)) => n.into(), - Value::Number(Number::Decimal(n)) => json!(n), - Value::Strand(strand) => match simplify { - true => strand.to_raw().into(), - false => json!(strand), + Value::Number(number) => match number { + Number::Int(int) => int.into(), + Number::Float(float) => float.into(), + Number::Decimal(decimal) => json!(decimal), }, - Value::Duration(d) => match simplify { - true => d.to_string().into(), - false => json!(d), + Value::Strand(strand) => strand.0.into(), + Value::Duration(duration) => match simplify { + true => duration.to_raw().into(), + false => json!(duration.0), }, - Value::Datetime(d) => d.to_raw().into(), - Value::Uuid(uuid) => json!(uuid), - Value::Array(arr) => JsonValue::Array(Array::from((arr, simplify)).0), - Value::Object(obj) => JsonValue::Object(Object::from((obj, simplify)).0), + Value::Datetime(datetime) => json!(datetime.0), + Value::Uuid(uuid) => json!(uuid.0), + Value::Array(array) => JsonValue::Array(Array::from((array, simplify)).0), + Value::Object(object) => JsonValue::Object(Object::from((object, simplify)).0), Value::Geometry(geo) => match simplify { true => Geometry::from(geo).0, - false => json!(geo), + false => match geo { + sql::Geometry::Point(geo) => json!(geo), + sql::Geometry::Line(geo) => json!(geo), + sql::Geometry::Polygon(geo) => json!(geo), + sql::Geometry::MultiPoint(geo) => json!(geo), + sql::Geometry::MultiLine(geo) => json!(geo), + sql::Geometry::MultiPolygon(geo) => json!(geo), + sql::Geometry::Collection(geo) => json!(geo), + }, }, - Value::Bytes(bytes) => json!(bytes), - Value::Param(param) => json!(param), - Value::Idiom(idiom) => json!(idiom), - Value::Table(table) => json!(table), + Value::Bytes(bytes) => json!(bytes.0), Value::Thing(thing) => match simplify { true => thing.to_string().into(), false => json!(thing), }, + // These Value types are un-computed values + // and are not used in query responses sent + // to the client. + Value::Param(param) => json!(param), + Value::Idiom(idiom) => json!(idiom), + Value::Table(table) => json!(table), Value::Model(model) => json!(model), Value::Regex(regex) => json!(regex), Value::Block(block) => json!(block), @@ -372,8 +393,8 @@ fn into_json(value: Value, simplify: bool) -> JsonValue { Value::Future(future) => json!(future), Value::Constant(constant) => match simplify { true => match constant.value() { - ConstantValue::Datetime(d) => json!(d), - ConstantValue::Float(f) => f.into(), + ConstantValue::Datetime(datetime) => json!(datetime.0), + ConstantValue::Float(float) => float.into(), }, false => json!(constant), }, @@ -395,3 +416,401 @@ where error: error.to_string(), }) } + +#[cfg(test)] +mod tests { + mod into_json { + use crate::opt::from_value; + use crate::opt::into_json; + use crate::sql; + use crate::sql::Value; + use chrono::DateTime; + use chrono::Utc; + use geo::line_string; + use geo::point; + use geo::polygon; + use geo::LineString; + use geo::MultiLineString; + use geo::MultiPoint; + use geo::MultiPolygon; + use geo::Point; + use geo::Polygon; + use rust_decimal::Decimal; + use serde_json::json; + use std::collections::BTreeMap; + use std::time::Duration; + use uuid::Uuid; + + #[test] + fn none_or_null() { + for value in [Value::None, Value::Null] { + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(null)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(null)); + + let response: Option = from_value(value).unwrap(); + assert_eq!(response, None); + } + } + + #[test] + fn bool() { + for boolean in [true, false] { + let value = Value::Bool(boolean); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(boolean)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(boolean)); + + let response: bool = from_value(value).unwrap(); + assert_eq!(response, boolean); + } + } + + #[test] + fn number_int() { + for num in [i64::MIN, 0, i64::MAX] { + let value = Value::Number(sql::Number::Int(num)); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(num)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(num)); + + let response: i64 = from_value(value).unwrap(); + assert_eq!(response, num); + } + } + + #[test] + fn number_float() { + for num in [f64::NEG_INFINITY, f64::MIN, 0.0, f64::MAX, f64::INFINITY, f64::NAN] { + let value = Value::Number(sql::Number::Float(num)); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(num)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(num)); + + if num.is_finite() { + let response: f64 = from_value(value).unwrap(); + assert_eq!(response, num); + } else { + let response: Option = from_value(value).unwrap(); + assert_eq!(response, None); + } + } + } + + #[test] + fn number_decimal() { + for num in [i64::MIN, 0, i64::MAX] { + let num = Decimal::new(num, 0); + let value = Value::Number(sql::Number::Decimal(num)); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(num.to_string())); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(num)); + + let response: Decimal = from_value(value).unwrap(); + assert_eq!(response, num); + } + } + + #[test] + fn strand() { + for str in ["", "foo"] { + let value = Value::Strand(str.into()); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(str)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(str)); + + let response: String = from_value(value).unwrap(); + assert_eq!(response, str); + } + } + + #[test] + fn duration() { + for duration in [Duration::ZERO, Duration::MAX] { + let value = Value::Duration(duration.into()); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(sql::Duration(duration).to_raw())); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(duration)); + + let response: Duration = from_value(value).unwrap(); + assert_eq!(response, duration); + } + } + + #[test] + fn datetime() { + for datetime in [DateTime::::MIN_UTC, DateTime::::MAX_UTC] { + let value = Value::Datetime(datetime.into()); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(datetime)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(datetime)); + + let response: DateTime = from_value(value).unwrap(); + assert_eq!(response, datetime); + } + } + + #[test] + fn uuid() { + for uuid in [Uuid::nil(), Uuid::max()] { + let value = Value::Uuid(uuid.into()); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(uuid)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(uuid)); + + let response: Uuid = from_value(value).unwrap(); + assert_eq!(response, uuid); + } + } + + #[test] + fn array() { + for vec in [vec![], vec![true, false]] { + let value = + Value::Array(sql::Array(vec.iter().copied().map(Value::from).collect())); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(vec)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(vec)); + + let response: Vec = from_value(value).unwrap(); + assert_eq!(response, vec); + } + } + + #[test] + fn object() { + for map in [BTreeMap::new(), map!("done".to_owned() => true)] { + let value = Value::Object(sql::Object( + map.iter().map(|(key, value)| (key.clone(), Value::from(*value))).collect(), + )); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(map)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(map)); + + let response: BTreeMap = from_value(value).unwrap(); + assert_eq!(response, map); + } + } + + #[test] + fn geometry_point() { + let point = point! { x: 10., y: 20. }; + let value = Value::Geometry(sql::Geometry::Point(point)); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!({ "type": "Point", "coordinates": [10., 20.]})); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(point)); + + let response: Point = from_value(value).unwrap(); + assert_eq!(response, point); + } + + #[test] + fn geometry_line() { + let line_string = line_string![ + ( x: 0., y: 0. ), + ( x: 10., y: 0. ), + ]; + let value = Value::Geometry(sql::Geometry::Line(line_string.clone())); + + let simple_json = into_json(value.clone(), true); + assert_eq!( + simple_json, + json!({ "type": "LineString", "coordinates": [[0., 0.], [10., 0.]]}) + ); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(line_string)); + + let response: LineString = from_value(value).unwrap(); + assert_eq!(response, line_string); + } + + #[test] + fn geometry_polygon() { + let polygon = polygon![ + (x: -111., y: 45.), + (x: -111., y: 41.), + (x: -104., y: 41.), + (x: -104., y: 45.), + ]; + let value = Value::Geometry(sql::Geometry::Polygon(polygon.clone())); + + let simple_json = into_json(value.clone(), true); + assert_eq!( + simple_json, + json!({ "type": "Polygon", "coordinates": [[ + [-111., 45.], + [-111., 41.], + [-104., 41.], + [-104., 45.], + [-111., 45.], + ]]}) + ); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(polygon)); + + let response: Polygon = from_value(value).unwrap(); + assert_eq!(response, polygon); + } + + #[test] + fn geometry_multi_point() { + let multi_point: MultiPoint = + vec![point! { x: 0., y: 0. }, point! { x: 1., y: 2. }].into(); + let value = Value::Geometry(sql::Geometry::MultiPoint(multi_point.clone())); + + let simple_json = into_json(value.clone(), true); + assert_eq!( + simple_json, + json!({ "type": "MultiPoint", "coordinates": [[0., 0.], [1., 2.]]}) + ); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(multi_point)); + + let response: MultiPoint = from_value(value).unwrap(); + assert_eq!(response, multi_point); + } + + #[test] + fn geometry_multi_line() { + let multi_line = MultiLineString::new(vec![line_string![ + ( x: 0., y: 0. ), + ( x: 1., y: 2. ), + ]]); + let value = Value::Geometry(sql::Geometry::MultiLine(multi_line.clone())); + + let simple_json = into_json(value.clone(), true); + assert_eq!( + simple_json, + json!({ "type": "MultiLineString", "coordinates": [[[0., 0.], [1., 2.]]]}) + ); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(multi_line)); + + let response: MultiLineString = from_value(value).unwrap(); + assert_eq!(response, multi_line); + } + + #[test] + fn geometry_multi_polygon() { + let multi_polygon: MultiPolygon = vec![polygon![ + (x: -111., y: 45.), + (x: -111., y: 41.), + (x: -104., y: 41.), + (x: -104., y: 45.), + ]] + .into(); + let value = Value::Geometry(sql::Geometry::MultiPolygon(multi_polygon.clone())); + + let simple_json = into_json(value.clone(), true); + assert_eq!( + simple_json, + json!({ "type": "MultiPolygon", "coordinates": [[[ + [-111., 45.], + [-111., 41.], + [-104., 41.], + [-104., 45.], + [-111., 45.], + ]]]}) + ); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(multi_polygon)); + + let response: MultiPolygon = from_value(value).unwrap(); + assert_eq!(response, multi_polygon); + } + + #[test] + fn geometry_collection() { + for geometries in [vec![], vec![sql::Geometry::Point(point! { x: 10., y: 20. })]] { + let value = Value::Geometry(geometries.clone().into()); + + let simple_json = into_json(value.clone(), true); + assert_eq!( + simple_json, + json!({ + "type": "GeometryCollection", + "geometries": geometries.clone().into_iter().map(|geo| into_json(Value::from(geo), true)).collect::>(), + }) + ); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(geometries)); + + let response: Vec = from_value(value).unwrap(); + assert_eq!(response, geometries); + } + } + + #[test] + fn bytes() { + for bytes in [vec![], b"foo".to_vec()] { + let value = Value::Bytes(sql::Bytes(bytes.clone())); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(bytes)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(bytes)); + + let response: Vec = from_value(value).unwrap(); + assert_eq!(response, bytes); + } + } + + #[test] + fn thing() { + let record_id = "foo:bar"; + let thing = sql::thing(record_id).unwrap(); + let value = Value::Thing(thing.clone()); + + let simple_json = into_json(value.clone(), true); + assert_eq!(simple_json, json!(record_id)); + + let json = into_json(value.clone(), false); + assert_eq!(json, json!(thing)); + + let response: sql::Thing = from_value(value).unwrap(); + assert_eq!(response, thing); + } + } +}