1use graphforge_core::SpatialValue;
4use serde::de::{self, MapAccess, Visitor};
5use serde::ser::SerializeMap;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8#[derive(Debug, Clone, PartialEq)]
26pub enum Literal {
27 Null,
29 Bool(bool),
31 Int(i64),
33 Float(f64),
38 Str(String),
40 Uuid([u8; 16]),
43 Duration {
47 months: i64,
49 days: i64,
51 seconds: i64,
54 nanos: i64,
56 },
57 DateTime(i64),
59 Date(i64),
64 LocalDateTime {
68 days: i64,
70 nanos: i64,
72 },
73 Time(i64),
76 ZonedTime {
79 nanos: i64,
81 offset: i32,
83 },
84 ZonedDateTime {
89 days: i64,
91 nanos: i64,
93 offset: i32,
95 zone: Option<String>,
97 },
98 Spatial(SpatialValue),
100 List(Vec<Literal>),
105 Map(Vec<(String, Literal)>),
109}
110
111impl Serialize for Literal {
116 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
117 match self {
120 Self::Null => LiteralSer::Null.serialize(s),
121 Self::Bool(b) => LiteralSer::Bool(*b).serialize(s),
122 Self::Int(i) => LiteralSer::Int(*i).serialize(s),
123 Self::Str(v) => LiteralSer::Str(v).serialize(s),
124 Self::Uuid(v) => LiteralSer::Uuid(v).serialize(s),
125 Self::Duration {
126 months,
127 days,
128 seconds,
129 nanos,
130 } => LiteralSer::Duration(*months, *days, *seconds, *nanos).serialize(s),
131 Self::DateTime(dt) => LiteralSer::DateTime(*dt).serialize(s),
132 Self::Date(d) => LiteralSer::Date(*d).serialize(s),
133 Self::LocalDateTime { days, nanos } => {
134 LiteralSer::LocalDateTime(*days, *nanos).serialize(s)
135 }
136 Self::Time(n) => LiteralSer::Time(*n).serialize(s),
137 Self::ZonedTime { nanos, offset } => {
138 LiteralSer::ZonedTime(*nanos, *offset).serialize(s)
139 }
140 Self::ZonedDateTime {
141 days,
142 nanos,
143 offset,
144 zone,
145 } => LiteralSer::ZonedDateTime(*days, *nanos, *offset, zone.clone()).serialize(s),
146 Self::Spatial(value) => LiteralSer::Spatial(value).serialize(s),
147 Self::List(items) => LiteralSer::List(items).serialize(s),
150 Self::Map(entries) => LiteralSer::Map(entries).serialize(s),
151 Self::Float(f) => {
152 if f.is_finite() {
153 LiteralSer::Float(*f).serialize(s)
154 } else {
155 let tag = if f.is_nan() {
157 "NaN"
158 } else if *f > 0.0 {
159 "+Infinity"
160 } else {
161 "-Infinity"
162 };
163 let mut map = s.serialize_map(Some(1))?;
164 map.serialize_entry("$float", tag)?;
165 map.end()
166 }
167 }
168 }
169 }
170}
171
172#[derive(Serialize)]
175#[serde(tag = "type", content = "value")]
176enum LiteralSer<'a> {
177 Null,
178 Bool(bool),
179 Int(i64),
180 Float(f64),
181 Str(&'a str),
182 Uuid(&'a [u8; 16]),
183 Duration(i64, i64, i64, i64),
184 DateTime(i64),
185 Date(i64),
186 LocalDateTime(i64, i64),
187 Time(i64),
188 ZonedTime(i64, i32),
189 ZonedDateTime(i64, i64, i32, Option<String>),
190 Spatial(&'a SpatialValue),
191 List(&'a [Literal]),
192 Map(&'a [(String, Literal)]),
193}
194
195impl<'de> Deserialize<'de> for Literal {
200 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
201 d.deserialize_any(LiteralVisitor)
202 }
203}
204
205struct LiteralVisitor;
206
207impl<'de> Visitor<'de> for LiteralVisitor {
208 type Value = Literal;
209
210 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 write!(
212 f,
213 "an IrLiteral (tagged object with \"type\"/\"value\" fields, \
214 or {{\"$float\": \"NaN\"/\"+Infinity\"/\"-Infinity\"}})"
215 )
216 }
217
218 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
219 let first_key: String = map
224 .next_key()?
225 .ok_or_else(|| de::Error::custom("expected at least one map key"))?;
226
227 if first_key == "$float" {
228 let tag: String = map.next_value()?;
229 let f = match tag.as_str() {
230 "NaN" => f64::NAN,
231 "+Infinity" => f64::INFINITY,
232 "-Infinity" => f64::NEG_INFINITY,
233 other => {
234 return Err(de::Error::unknown_variant(
235 other,
236 &["NaN", "+Infinity", "-Infinity"],
237 ));
238 }
239 };
240 return Ok(Literal::Float(f));
241 }
242
243 if first_key != "type" {
244 return Err(de::Error::unknown_field(&first_key, &["type", "$float"]));
245 }
246
247 let variant: String = map.next_value()?;
248 match variant.as_str() {
251 "Null" => Ok(Literal::Null),
252 "Bool" => Ok(Literal::Bool(read_value_field(&mut map)?)),
253 "Int" => Ok(Literal::Int(read_value_field(&mut map)?)),
254 "Float" => Ok(Literal::Float(read_value_field(&mut map)?)),
255 "Str" => Ok(Literal::Str(read_value_field(&mut map)?)),
256 "Uuid" => Ok(Literal::Uuid(read_value_field(&mut map)?)),
257 "Duration" => {
258 let (months, days, seconds, nanos) = read_value_field(&mut map)?;
259 Ok(Literal::Duration {
260 months,
261 days,
262 seconds,
263 nanos,
264 })
265 }
266 "DateTime" => Ok(Literal::DateTime(read_value_field(&mut map)?)),
267 "Date" => Ok(Literal::Date(read_value_field(&mut map)?)),
268 "LocalDateTime" => {
269 let (days, nanos) = read_value_field(&mut map)?;
270 Ok(Literal::LocalDateTime { days, nanos })
271 }
272 "Time" => Ok(Literal::Time(read_value_field(&mut map)?)),
273 "ZonedTime" => {
274 let (nanos, offset) = read_value_field(&mut map)?;
275 Ok(Literal::ZonedTime { nanos, offset })
276 }
277 "ZonedDateTime" => {
278 let (days, nanos, offset, zone) = read_value_field(&mut map)?;
279 Ok(Literal::ZonedDateTime {
280 days,
281 nanos,
282 offset,
283 zone,
284 })
285 }
286 "Spatial" => Ok(Literal::Spatial(read_value_field(&mut map)?)),
287 "List" => Ok(Literal::List(read_value_field(&mut map)?)),
288 "Map" => Ok(Literal::Map(read_value_field(&mut map)?)),
289 other => Err(de::Error::unknown_variant(
290 other,
291 &[
292 "Null",
293 "Bool",
294 "Int",
295 "Float",
296 "Str",
297 "Uuid",
298 "Duration",
299 "DateTime",
300 "Date",
301 "LocalDateTime",
302 "Time",
303 "ZonedTime",
304 "ZonedDateTime",
305 "Spatial",
306 "List",
307 "Map",
308 ],
309 )),
310 }
311 }
312}
313
314fn read_value_field<'de, T, A>(map: &mut A) -> Result<T, A::Error>
318where
319 T: Deserialize<'de>,
320 A: MapAccess<'de>,
321{
322 while let Some(key) = map.next_key::<String>()? {
323 if key == "value" {
324 return map.next_value();
325 }
326 let _: de::IgnoredAny = map.next_value()?;
327 }
328 Err(de::Error::missing_field("value"))
329}
330
331#[cfg(test)]
332mod tests {
333 use super::Literal;
334 use graphforge_core::{
335 SpatialCoordinates, SpatialCrs, SpatialGeometryType, SpatialType, SpatialValue,
336 };
337
338 #[test]
339 fn literal_json_golden_vectors_preserve_existing_wire_encoding() {
340 let vectors = [
343 (Literal::Null, r#"{"type":"Null"}"#),
344 (Literal::Bool(true), r#"{"type":"Bool","value":true}"#),
345 (Literal::Int(-7), r#"{"type":"Int","value":-7}"#),
346 (Literal::Float(2.5), r#"{"type":"Float","value":2.5}"#),
347 (
348 Literal::Str("line\n\"quoted\"".into()),
349 r#"{"type":"Str","value":"line\n\"quoted\""}"#,
350 ),
351 (
352 Literal::Uuid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
353 r#"{"type":"Uuid","value":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]}"#,
354 ),
355 (
356 Literal::Duration {
357 months: 1,
358 days: -2,
359 seconds: 3,
360 nanos: 4,
361 },
362 r#"{"type":"Duration","value":[1,-2,3,4]}"#,
363 ),
364 (Literal::DateTime(-1), r#"{"type":"DateTime","value":-1}"#),
365 (
366 Literal::Date(3_000_000_000),
367 r#"{"type":"Date","value":3000000000}"#,
368 ),
369 (
370 Literal::LocalDateTime {
371 days: 3_000_000_000,
372 nanos: 42,
373 },
374 r#"{"type":"LocalDateTime","value":[3000000000,42]}"#,
375 ),
376 (Literal::Time(42), r#"{"type":"Time","value":42}"#),
377 (
378 Literal::ZonedTime {
379 nanos: 42,
380 offset: -3600,
381 },
382 r#"{"type":"ZonedTime","value":[42,-3600]}"#,
383 ),
384 (
385 Literal::ZonedDateTime {
386 days: 1,
387 nanos: 42,
388 offset: 3600,
389 zone: Some("Europe/Paris".into()),
390 },
391 r#"{"type":"ZonedDateTime","value":[1,42,3600,"Europe/Paris"]}"#,
392 ),
393 (
394 Literal::ZonedDateTime {
395 days: 1,
396 nanos: 42,
397 offset: 0,
398 zone: None,
399 },
400 r#"{"type":"ZonedDateTime","value":[1,42,0,null]}"#,
401 ),
402 (
403 Literal::Spatial(SpatialValue {
404 spatial_type: SpatialType {
405 geometry: SpatialGeometryType::Point,
406 crs: SpatialCrs::Epsg4326,
407 },
408 coordinates: SpatialCoordinates::Point([1.0, 2.0]),
409 extension_name: None,
410 extension_metadata: None,
411 }),
412 r#"{"type":"Spatial","value":{"spatial_type":{"geometry":"point","crs":"EPSG:4326"},"coordinates":{"Point":[1.0,2.0]}}}"#,
413 ),
414 (
415 Literal::List(vec![Literal::Int(1), Literal::Null]),
416 r#"{"type":"List","value":[{"type":"Int","value":1},{"type":"Null"}]}"#,
417 ),
418 (
419 Literal::Map(vec![
420 ("z".into(), Literal::Bool(false)),
421 ("a".into(), Literal::List(vec![])),
422 ]),
423 r#"{"type":"Map","value":[["z",{"type":"Bool","value":false}],["a",{"type":"List","value":[]}]]}"#,
424 ),
425 ];
426 for (value, expected) in vectors {
427 assert_eq!(serde_json::to_string(&value).unwrap(), expected);
428 assert_eq!(serde_json::from_str::<Literal>(expected).unwrap(), value);
429 }
430 }
431
432 #[test]
433 fn nonfinite_literal_json_golden_vectors_preserve_nested_tags() {
434 for (number, expected) in [
435 (f64::NAN, r#"{"$float":"NaN"}"#),
436 (f64::INFINITY, r#"{"$float":"+Infinity"}"#),
437 (f64::NEG_INFINITY, r#"{"$float":"-Infinity"}"#),
438 ] {
439 assert_eq!(
440 serde_json::to_string(&Literal::Float(number)).unwrap(),
441 expected
442 );
443 let Literal::Float(decoded) = serde_json::from_str::<Literal>(expected).unwrap() else {
444 panic!("nonfinite tag must remain a float");
445 };
446 if number.is_nan() {
447 assert!(decoded.is_nan());
448 } else {
449 assert_eq!(decoded, number);
450 }
451 }
452 let nested = Literal::List(vec![Literal::Map(vec![
453 ("nan".into(), Literal::Float(f64::NAN)),
454 (
455 "infinities".into(),
456 Literal::List(vec![
457 Literal::Float(f64::INFINITY),
458 Literal::Float(f64::NEG_INFINITY),
459 ]),
460 ),
461 ])]);
462 let expected = r#"{"type":"List","value":[{"type":"Map","value":[["nan",{"$float":"NaN"}],["infinities",{"type":"List","value":[{"$float":"+Infinity"},{"$float":"-Infinity"}]}]]}]}"#;
463 assert_eq!(serde_json::to_string(&nested).unwrap(), expected);
464 let decoded: Literal = serde_json::from_str(expected).unwrap();
465 assert_eq!(serde_json::to_string(&decoded).unwrap(), expected);
466 }
467}