use graphforge_core::SpatialValue;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
Uuid([u8; 16]),
Duration {
months: i64,
days: i64,
seconds: i64,
nanos: i64,
},
DateTime(i64),
Date(i64),
LocalDateTime {
days: i64,
nanos: i64,
},
Time(i64),
ZonedTime {
nanos: i64,
offset: i32,
},
ZonedDateTime {
days: i64,
nanos: i64,
offset: i32,
zone: Option<String>,
},
Spatial(SpatialValue),
List(Vec<Literal>),
Map(Vec<(String, Literal)>),
}
impl Serialize for Literal {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
Self::Null => LiteralSer::Null.serialize(s),
Self::Bool(b) => LiteralSer::Bool(*b).serialize(s),
Self::Int(i) => LiteralSer::Int(*i).serialize(s),
Self::Str(v) => LiteralSer::Str(v).serialize(s),
Self::Uuid(v) => LiteralSer::Uuid(v).serialize(s),
Self::Duration {
months,
days,
seconds,
nanos,
} => LiteralSer::Duration(*months, *days, *seconds, *nanos).serialize(s),
Self::DateTime(dt) => LiteralSer::DateTime(*dt).serialize(s),
Self::Date(d) => LiteralSer::Date(*d).serialize(s),
Self::LocalDateTime { days, nanos } => {
LiteralSer::LocalDateTime(*days, *nanos).serialize(s)
}
Self::Time(n) => LiteralSer::Time(*n).serialize(s),
Self::ZonedTime { nanos, offset } => {
LiteralSer::ZonedTime(*nanos, *offset).serialize(s)
}
Self::ZonedDateTime {
days,
nanos,
offset,
zone,
} => LiteralSer::ZonedDateTime(*days, *nanos, *offset, zone.clone()).serialize(s),
Self::Spatial(value) => LiteralSer::Spatial(value).serialize(s),
Self::List(items) => LiteralSer::List(items).serialize(s),
Self::Map(entries) => LiteralSer::Map(entries).serialize(s),
Self::Float(f) => {
if f.is_finite() {
LiteralSer::Float(*f).serialize(s)
} else {
let tag = if f.is_nan() {
"NaN"
} else if *f > 0.0 {
"+Infinity"
} else {
"-Infinity"
};
let mut map = s.serialize_map(Some(1))?;
map.serialize_entry("$float", tag)?;
map.end()
}
}
}
}
}
#[derive(Serialize)]
#[serde(tag = "type", content = "value")]
enum LiteralSer<'a> {
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(&'a str),
Uuid(&'a [u8; 16]),
Duration(i64, i64, i64, i64),
DateTime(i64),
Date(i64),
LocalDateTime(i64, i64),
Time(i64),
ZonedTime(i64, i32),
ZonedDateTime(i64, i64, i32, Option<String>),
Spatial(&'a SpatialValue),
List(&'a [Literal]),
Map(&'a [(String, Literal)]),
}
impl<'de> Deserialize<'de> for Literal {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
d.deserialize_any(LiteralVisitor)
}
}
struct LiteralVisitor;
impl<'de> Visitor<'de> for LiteralVisitor {
type Value = Literal;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"an IrLiteral (tagged object with \"type\"/\"value\" fields, \
or {{\"$float\": \"NaN\"/\"+Infinity\"/\"-Infinity\"}})"
)
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
let first_key: String = map
.next_key()?
.ok_or_else(|| de::Error::custom("expected at least one map key"))?;
if first_key == "$float" {
let tag: String = map.next_value()?;
let f = match tag.as_str() {
"NaN" => f64::NAN,
"+Infinity" => f64::INFINITY,
"-Infinity" => f64::NEG_INFINITY,
other => {
return Err(de::Error::unknown_variant(
other,
&["NaN", "+Infinity", "-Infinity"],
));
}
};
return Ok(Literal::Float(f));
}
if first_key != "type" {
return Err(de::Error::unknown_field(&first_key, &["type", "$float"]));
}
let variant: String = map.next_value()?;
match variant.as_str() {
"Null" => Ok(Literal::Null),
"Bool" => Ok(Literal::Bool(read_value_field(&mut map)?)),
"Int" => Ok(Literal::Int(read_value_field(&mut map)?)),
"Float" => Ok(Literal::Float(read_value_field(&mut map)?)),
"Str" => Ok(Literal::Str(read_value_field(&mut map)?)),
"Uuid" => Ok(Literal::Uuid(read_value_field(&mut map)?)),
"Duration" => {
let (months, days, seconds, nanos) = read_value_field(&mut map)?;
Ok(Literal::Duration {
months,
days,
seconds,
nanos,
})
}
"DateTime" => Ok(Literal::DateTime(read_value_field(&mut map)?)),
"Date" => Ok(Literal::Date(read_value_field(&mut map)?)),
"LocalDateTime" => {
let (days, nanos) = read_value_field(&mut map)?;
Ok(Literal::LocalDateTime { days, nanos })
}
"Time" => Ok(Literal::Time(read_value_field(&mut map)?)),
"ZonedTime" => {
let (nanos, offset) = read_value_field(&mut map)?;
Ok(Literal::ZonedTime { nanos, offset })
}
"ZonedDateTime" => {
let (days, nanos, offset, zone) = read_value_field(&mut map)?;
Ok(Literal::ZonedDateTime {
days,
nanos,
offset,
zone,
})
}
"Spatial" => Ok(Literal::Spatial(read_value_field(&mut map)?)),
"List" => Ok(Literal::List(read_value_field(&mut map)?)),
"Map" => Ok(Literal::Map(read_value_field(&mut map)?)),
other => Err(de::Error::unknown_variant(
other,
&[
"Null",
"Bool",
"Int",
"Float",
"Str",
"Uuid",
"Duration",
"DateTime",
"Date",
"LocalDateTime",
"Time",
"ZonedTime",
"ZonedDateTime",
"Spatial",
"List",
"Map",
],
)),
}
}
}
fn read_value_field<'de, T, A>(map: &mut A) -> Result<T, A::Error>
where
T: Deserialize<'de>,
A: MapAccess<'de>,
{
while let Some(key) = map.next_key::<String>()? {
if key == "value" {
return map.next_value();
}
let _: de::IgnoredAny = map.next_value()?;
}
Err(de::Error::missing_field("value"))
}
#[cfg(test)]
mod tests {
use super::Literal;
use graphforge_core::{
SpatialCoordinates, SpatialCrs, SpatialGeometryType, SpatialType, SpatialValue,
};
#[test]
fn literal_json_golden_vectors_preserve_existing_wire_encoding() {
let vectors = [
(Literal::Null, r#"{"type":"Null"}"#),
(Literal::Bool(true), r#"{"type":"Bool","value":true}"#),
(Literal::Int(-7), r#"{"type":"Int","value":-7}"#),
(Literal::Float(2.5), r#"{"type":"Float","value":2.5}"#),
(
Literal::Str("line\n\"quoted\"".into()),
r#"{"type":"Str","value":"line\n\"quoted\""}"#,
),
(
Literal::Uuid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
r#"{"type":"Uuid","value":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]}"#,
),
(
Literal::Duration {
months: 1,
days: -2,
seconds: 3,
nanos: 4,
},
r#"{"type":"Duration","value":[1,-2,3,4]}"#,
),
(Literal::DateTime(-1), r#"{"type":"DateTime","value":-1}"#),
(
Literal::Date(3_000_000_000),
r#"{"type":"Date","value":3000000000}"#,
),
(
Literal::LocalDateTime {
days: 3_000_000_000,
nanos: 42,
},
r#"{"type":"LocalDateTime","value":[3000000000,42]}"#,
),
(Literal::Time(42), r#"{"type":"Time","value":42}"#),
(
Literal::ZonedTime {
nanos: 42,
offset: -3600,
},
r#"{"type":"ZonedTime","value":[42,-3600]}"#,
),
(
Literal::ZonedDateTime {
days: 1,
nanos: 42,
offset: 3600,
zone: Some("Europe/Paris".into()),
},
r#"{"type":"ZonedDateTime","value":[1,42,3600,"Europe/Paris"]}"#,
),
(
Literal::ZonedDateTime {
days: 1,
nanos: 42,
offset: 0,
zone: None,
},
r#"{"type":"ZonedDateTime","value":[1,42,0,null]}"#,
),
(
Literal::Spatial(SpatialValue {
spatial_type: SpatialType {
geometry: SpatialGeometryType::Point,
crs: SpatialCrs::Epsg4326,
},
coordinates: SpatialCoordinates::Point([1.0, 2.0]),
extension_name: None,
extension_metadata: None,
}),
r#"{"type":"Spatial","value":{"spatial_type":{"geometry":"point","crs":"EPSG:4326"},"coordinates":{"Point":[1.0,2.0]}}}"#,
),
(
Literal::List(vec![Literal::Int(1), Literal::Null]),
r#"{"type":"List","value":[{"type":"Int","value":1},{"type":"Null"}]}"#,
),
(
Literal::Map(vec![
("z".into(), Literal::Bool(false)),
("a".into(), Literal::List(vec![])),
]),
r#"{"type":"Map","value":[["z",{"type":"Bool","value":false}],["a",{"type":"List","value":[]}]]}"#,
),
];
for (value, expected) in vectors {
assert_eq!(serde_json::to_string(&value).unwrap(), expected);
assert_eq!(serde_json::from_str::<Literal>(expected).unwrap(), value);
}
}
#[test]
fn nonfinite_literal_json_golden_vectors_preserve_nested_tags() {
for (number, expected) in [
(f64::NAN, r#"{"$float":"NaN"}"#),
(f64::INFINITY, r#"{"$float":"+Infinity"}"#),
(f64::NEG_INFINITY, r#"{"$float":"-Infinity"}"#),
] {
assert_eq!(
serde_json::to_string(&Literal::Float(number)).unwrap(),
expected
);
let Literal::Float(decoded) = serde_json::from_str::<Literal>(expected).unwrap() else {
panic!("nonfinite tag must remain a float");
};
if number.is_nan() {
assert!(decoded.is_nan());
} else {
assert_eq!(decoded, number);
}
}
let nested = Literal::List(vec![Literal::Map(vec![
("nan".into(), Literal::Float(f64::NAN)),
(
"infinities".into(),
Literal::List(vec![
Literal::Float(f64::INFINITY),
Literal::Float(f64::NEG_INFINITY),
]),
),
])]);
let expected = r#"{"type":"List","value":[{"type":"Map","value":[["nan",{"$float":"NaN"}],["infinities",{"type":"List","value":[{"$float":"+Infinity"},{"$float":"-Infinity"}]}]]}]}"#;
assert_eq!(serde_json::to_string(&nested).unwrap(), expected);
let decoded: Literal = serde_json::from_str(expected).unwrap();
assert_eq!(serde_json::to_string(&decoded).unwrap(), expected);
}
}