1use crate::codecs::shared;
4use crate::codecs::{Codec, CodecError};
5use crate::data::{HCol, HDict, HGrid};
6use crate::kinds::*;
7use chrono::{NaiveDate, NaiveTime, Timelike};
8use serde_json::{Map, Value};
9
10pub struct Json3Codec;
14
15impl Codec for Json3Codec {
16 fn mime_type(&self) -> &str {
17 "application/json;v=3"
18 }
19
20 fn encode_grid(&self, grid: &HGrid) -> Result<String, CodecError> {
21 let val = encode_grid_value(grid)?;
22 serde_json::to_string(&val).map_err(|e| CodecError::Encode(e.to_string()))
23 }
24
25 fn decode_grid(&self, input: &str) -> Result<HGrid, CodecError> {
26 let val: Value = serde_json::from_str(input).map_err(|e| CodecError::Parse {
27 pos: 0,
28 message: e.to_string(),
29 })?;
30 decode_grid_value(&val)
31 }
32
33 fn encode_scalar(&self, val: &Kind) -> Result<String, CodecError> {
34 let json = encode_kind(val)?;
35 serde_json::to_string(&json).map_err(|e| CodecError::Encode(e.to_string()))
36 }
37
38 fn decode_scalar(&self, input: &str) -> Result<Kind, CodecError> {
39 let val: Value = serde_json::from_str(input).map_err(|e| CodecError::Parse {
40 pos: 0,
41 message: e.to_string(),
42 })?;
43 decode_kind(&val)
44 }
45}
46
47pub fn encode_kind(val: &Kind) -> Result<Value, CodecError> {
51 match val {
52 Kind::Null => Ok(Value::Null),
53 Kind::Bool(b) => Ok(Value::Bool(*b)),
54 Kind::Marker => Ok(Value::String("m:".into())),
55 Kind::NA => Ok(Value::String("z:".into())),
56 Kind::Remove => Ok(Value::String("-:".into())),
57 Kind::Number(n) => Ok(encode_number(n)),
58 Kind::Str(s) => Ok(Value::String(format!("s:{s}"))),
59 Kind::Ref(r) => Ok(encode_ref(r)),
60 Kind::Uri(u) => Ok(Value::String(format!("u:{}", u.val()))),
61 Kind::Symbol(s) => Ok(Value::String(format!("y:{}", s.val()))),
62 Kind::Date(d) => Ok(Value::String(format!("d:{}", d.format("%Y-%m-%d")))),
63 Kind::Time(t) => Ok(Value::String(format!("h:{}", encode_time_str(t)))),
64 Kind::DateTime(hdt) => Ok(encode_datetime(hdt)),
65 Kind::Coord(c) => Ok(Value::String(format!("c:{},{}", c.lat, c.lng))),
66 Kind::XStr(x) => Ok(Value::String(format!("x:{}:{}", x.type_name, x.val))),
67 Kind::List(items) => {
68 let arr: Result<Vec<Value>, CodecError> = items.iter().map(encode_kind).collect();
69 Ok(Value::Array(arr?))
70 }
71 Kind::Dict(d) => encode_dict(d),
72 Kind::Grid(g) => {
73 let val = encode_grid_value(g)?;
74 Ok(Value::Object(val))
75 }
76 }
77}
78
79fn encode_number(n: &Number) -> Value {
81 let val_str = shared::format_number_val(n.val);
82 match &n.unit {
83 Some(u) => Value::String(format!("n:{val_str} {u}")),
84 None => Value::String(format!("n:{val_str}")),
85 }
86}
87
88fn encode_ref(r: &HRef) -> Value {
90 match &r.dis {
91 Some(dis) => Value::String(format!("r:{} {}", r.val, dis)),
92 None => Value::String(format!("r:{}", r.val)),
93 }
94}
95
96fn encode_datetime(hdt: &HDateTime) -> Value {
98 let dt_str = hdt.dt.format("%Y-%m-%dT%H:%M:%S").to_string();
99 let frac = shared::format_frac_seconds(hdt.dt.nanosecond());
100 let offset_str = hdt.dt.format("%:z").to_string();
101 if hdt.tz_name.is_empty() {
102 Value::String(format!("t:{dt_str}{frac}{offset_str}"))
103 } else {
104 Value::String(format!("t:{dt_str}{frac}{offset_str} {}", hdt.tz_name))
105 }
106}
107
108fn encode_time_str(t: &NaiveTime) -> String {
110 shared::format_time(t)
111}
112
113fn encode_dict(d: &HDict) -> Result<Value, CodecError> {
115 let mut m = Map::new();
116 for (k, v) in d.sorted_tags() {
117 m.insert(k.to_string(), encode_kind(v)?);
118 }
119 Ok(Value::Object(m))
120}
121
122fn encode_grid_value(grid: &HGrid) -> Result<Map<String, Value>, CodecError> {
124 let mut m = Map::new();
125
126 let mut meta_map = Map::new();
128 meta_map.insert("ver".into(), Value::String("3.0".into()));
129 for (k, v) in grid.meta.sorted_tags() {
130 meta_map.insert(k.to_string(), encode_kind(v)?);
131 }
132 m.insert("meta".into(), Value::Object(meta_map));
133
134 let cols: Result<Vec<Value>, CodecError> = grid
136 .cols
137 .iter()
138 .map(|col| {
139 let mut cm = Map::new();
140 cm.insert("name".into(), Value::String(col.name.clone()));
141 if !col.meta.is_empty()
143 && let Value::Object(meta_map) = encode_dict(&col.meta)?
144 {
145 for (k, v) in meta_map {
146 cm.insert(k, v);
147 }
148 }
149 Ok(Value::Object(cm))
150 })
151 .collect();
152 m.insert("cols".into(), Value::Array(cols?));
153
154 let rows: Result<Vec<Value>, CodecError> = grid.rows.iter().map(encode_dict).collect();
156 m.insert("rows".into(), Value::Array(rows?));
157
158 Ok(m)
159}
160
161const MAX_NESTING_DEPTH: usize = 64;
165
166pub fn decode_kind(val: &Value) -> Result<Kind, CodecError> {
168 decode_kind_depth(val, 0)
169}
170
171fn decode_kind_depth(val: &Value, depth: usize) -> Result<Kind, CodecError> {
173 if depth > MAX_NESTING_DEPTH {
174 return Err(CodecError::Parse {
175 pos: 0,
176 message: "maximum nesting depth exceeded".into(),
177 });
178 }
179 match val {
180 Value::Null => Ok(Kind::Null),
181 Value::Bool(b) => Ok(Kind::Bool(*b)),
182 Value::Number(n) => {
183 let v = n.as_f64().ok_or_else(|| CodecError::Parse {
185 pos: 0,
186 message: format!("cannot convert JSON number to f64: {n}"),
187 })?;
188 Ok(Kind::Number(Number::unitless(v)))
189 }
190 Value::String(s) => decode_prefixed_string(s),
191 Value::Array(arr) => {
192 let items: Result<Vec<Kind>, CodecError> = arr
193 .iter()
194 .map(|v| decode_kind_depth(v, depth + 1))
195 .collect();
196 Ok(Kind::List(items?))
197 }
198 Value::Object(m) => decode_object_depth(m, depth + 1),
199 }
200}
201
202fn decode_prefixed_string(s: &str) -> Result<Kind, CodecError> {
204 if s.len() >= 2 {
206 let prefix = &s[..2];
207 let rest = &s[2..];
208 match prefix {
209 "m:" => {
210 if rest.is_empty() {
211 return Ok(Kind::Marker);
212 }
213 }
214 "z:" => {
215 if rest.is_empty() {
216 return Ok(Kind::NA);
217 }
218 }
219 "-:" => {
220 if rest.is_empty() {
221 return Ok(Kind::Remove);
222 }
223 }
224 "s:" => return Ok(Kind::Str(rest.to_string())),
225 "n:" => return decode_number_str(rest),
226 "r:" => return decode_ref_str(rest),
227 "u:" => return Ok(Kind::Uri(Uri::new(rest))),
228 "y:" => return Ok(Kind::Symbol(Symbol::new(rest))),
229 "d:" => return decode_date_str(rest),
230 "h:" => return decode_time_str(rest),
231 "t:" => return decode_datetime_str(rest),
232 "c:" => return decode_coord_str(rest),
233 "x:" => return decode_xstr_str(rest),
234 _ => {}
235 }
236 }
237 Ok(Kind::Str(s.to_string()))
242}
243
244fn decode_number_str(s: &str) -> Result<Kind, CodecError> {
246 let (val_str, unit) = match s.find(' ') {
249 Some(pos) => {
250 let val_part = &s[..pos];
251 let unit_part = &s[pos + 1..];
252 (
253 val_part,
254 if unit_part.is_empty() {
255 None
256 } else {
257 Some(unit_part.to_string())
258 },
259 )
260 }
261 None => (s, None),
262 };
263
264 let v = match val_str {
265 "INF" => f64::INFINITY,
266 "-INF" => f64::NEG_INFINITY,
267 "NaN" => f64::NAN,
268 _ => val_str.parse::<f64>().map_err(|e| CodecError::Parse {
269 pos: 0,
270 message: format!("invalid v3 number: {e}"),
271 })?,
272 };
273 Ok(Kind::Number(Number::new(v, unit)))
274}
275
276fn decode_ref_str(s: &str) -> Result<Kind, CodecError> {
278 match s.find(' ') {
280 Some(pos) => {
281 let val = &s[..pos];
282 let dis = &s[pos + 1..];
283 Ok(Kind::Ref(HRef::new(val, Some(dis.to_string()))))
284 }
285 None => Ok(Kind::Ref(HRef::from_val(s))),
286 }
287}
288
289fn decode_date_str(s: &str) -> Result<Kind, CodecError> {
291 let d = NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(|e| CodecError::Parse {
292 pos: 0,
293 message: format!("invalid v3 date: {e}"),
294 })?;
295 Ok(Kind::Date(d))
296}
297
298fn decode_time_str(s: &str) -> Result<Kind, CodecError> {
300 NaiveTime::parse_from_str(s, "%H:%M:%S%.f")
301 .or_else(|_| NaiveTime::parse_from_str(s, "%H:%M:%S"))
302 .map(Kind::Time)
303 .map_err(|e| CodecError::Parse {
304 pos: 0,
305 message: format!("invalid v3 time: {e}"),
306 })
307}
308
309fn decode_datetime_str(s: &str) -> Result<Kind, CodecError> {
312 let (dt_str, tz_name) = split_datetime_tz(s);
318
319 let dt = chrono::DateTime::parse_from_rfc3339(dt_str)
320 .or_else(|_| chrono::DateTime::parse_from_str(dt_str, "%Y-%m-%dT%H:%M:%S%:z"))
321 .or_else(|_| chrono::DateTime::parse_from_str(dt_str, "%Y-%m-%dT%H:%M:%S%.f%:z"))
322 .map_err(|e| CodecError::Parse {
323 pos: 0,
324 message: format!("invalid v3 datetime: {e}"),
325 })?;
326
327 Ok(Kind::DateTime(HDateTime::new(dt, tz_name)))
328}
329
330fn split_datetime_tz(s: &str) -> (&str, &str) {
336 if let Some(t_pos) = s.find('T') {
341 let after_t = &s[t_pos..];
342 let offset_end = find_offset_end(after_t);
345 if let Some(end) = offset_end {
346 let abs_end = t_pos + end;
347 if abs_end < s.len() {
348 let rest = &s[abs_end..];
349 if let Some(space_pos) = rest.find(' ') {
350 let dt_part = &s[..abs_end + space_pos];
351 let tz_part = &s[abs_end + space_pos + 1..];
352 return (dt_part, tz_part);
353 }
354 }
355 return (s, "");
356 }
357 }
358 (s, "")
359}
360
361fn find_offset_end(s: &str) -> Option<usize> {
364 if s.ends_with('Z') {
366 return Some(s.len());
367 }
368 for (i, c) in s.char_indices().rev() {
371 if (c == '+' || c == '-') && i + 6 <= s.len() {
372 let candidate = &s[i..];
374 if candidate.len() >= 6 {
375 let hh = &candidate[1..3];
376 let colon = &candidate[3..4];
377 let mm = &candidate[4..6];
378 if colon == ":"
379 && hh.chars().all(|c| c.is_ascii_digit())
380 && mm.chars().all(|c| c.is_ascii_digit())
381 {
382 return Some(i + 6);
383 }
384 }
385 }
386 }
387 None
388}
389
390fn decode_coord_str(s: &str) -> Result<Kind, CodecError> {
392 let parts: Vec<&str> = s.splitn(2, ',').collect();
393 if parts.len() != 2 {
394 return Err(CodecError::Parse {
395 pos: 0,
396 message: format!("invalid v3 coord: expected 'lat,lng', got '{s}'"),
397 });
398 }
399 let lat = parts[0].parse::<f64>().map_err(|e| CodecError::Parse {
400 pos: 0,
401 message: format!("invalid v3 coord lat: {e}"),
402 })?;
403 let lng = parts[1].parse::<f64>().map_err(|e| CodecError::Parse {
404 pos: 0,
405 message: format!("invalid v3 coord lng: {e}"),
406 })?;
407 Ok(Kind::Coord(Coord::new(lat, lng)))
408}
409
410fn decode_xstr_str(s: &str) -> Result<Kind, CodecError> {
412 match s.find(':') {
413 Some(pos) => {
414 let type_name = &s[..pos];
415 let val = &s[pos + 1..];
416 Ok(Kind::XStr(XStr::new(type_name, val)))
417 }
418 None => Err(CodecError::Parse {
419 pos: 0,
420 message: format!("invalid v3 xstr: expected 'Type:value', got '{s}'"),
421 }),
422 }
423}
424
425fn decode_object_depth(m: &Map<String, Value>, depth: usize) -> Result<Kind, CodecError> {
427 if depth > MAX_NESTING_DEPTH {
428 return Err(CodecError::Parse {
429 pos: 0,
430 message: "maximum nesting depth exceeded".into(),
431 });
432 }
433 if m.contains_key("meta") && m.contains_key("cols") {
435 let grid = decode_grid_from_map_depth(m, depth)?;
436 return Ok(Kind::Grid(Box::new(grid)));
437 }
438 let mut dict = HDict::new();
440 for (key, val) in m {
441 dict.set(key.clone(), decode_kind_depth(val, depth + 1)?);
442 }
443 Ok(Kind::Dict(Box::new(dict)))
444}
445
446pub fn decode_grid_value(val: &Value) -> Result<HGrid, CodecError> {
448 let m = match val {
449 Value::Object(m) => m,
450 _ => {
451 return Err(CodecError::Parse {
452 pos: 0,
453 message: "grid must be a JSON object".into(),
454 });
455 }
456 };
457 decode_grid_from_map_depth(m, 0)
458}
459
460fn decode_grid_from_map_depth(m: &Map<String, Value>, depth: usize) -> Result<HGrid, CodecError> {
462 if depth > MAX_NESTING_DEPTH {
463 return Err(CodecError::Parse {
464 pos: 0,
465 message: "maximum nesting depth exceeded".into(),
466 });
467 }
468
469 let meta = match m.get("meta") {
471 Some(Value::Object(meta_map)) => {
472 let mut dict = HDict::new();
473 for (key, val) in meta_map {
474 if key == "ver" {
475 continue;
476 }
477 dict.set(key.clone(), decode_kind_depth(val, depth + 1)?);
478 }
479 dict
480 }
481 _ => HDict::new(),
482 };
483
484 let cols = match m.get("cols") {
486 Some(Value::Array(arr)) => {
487 let mut cols = Vec::with_capacity(arr.len());
488 for col_val in arr {
489 let col_obj = col_val.as_object().ok_or_else(|| CodecError::Parse {
490 pos: 0,
491 message: "col must be a JSON object".into(),
492 })?;
493 let name = match col_obj.get("name") {
494 Some(Value::String(n)) => n.clone(),
495 _ => {
496 return Err(CodecError::Parse {
497 pos: 0,
498 message: "col missing 'name' field".into(),
499 });
500 }
501 };
502 let mut col_meta = HDict::new();
503 for (key, val) in col_obj {
504 if key != "name" {
505 col_meta.set(key.clone(), decode_kind_depth(val, depth + 1)?);
506 }
507 }
508 cols.push(HCol::with_meta(name, col_meta));
509 }
510 cols
511 }
512 _ => Vec::new(),
513 };
514
515 let rows = match m.get("rows") {
517 Some(Value::Array(arr)) => {
518 let mut rows = Vec::with_capacity(arr.len());
519 for row_val in arr {
520 let row_obj = row_val.as_object().ok_or_else(|| CodecError::Parse {
521 pos: 0,
522 message: "row must be a JSON object".into(),
523 })?;
524 let mut dict = HDict::new();
525 for (key, val) in row_obj {
526 dict.set(key.clone(), decode_kind_depth(val, depth + 1)?);
527 }
528 rows.push(dict);
529 }
530 rows
531 }
532 _ => Vec::new(),
533 };
534
535 Ok(HGrid::from_parts(meta, cols, rows))
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541 use crate::data::{HCol, HDict, HGrid};
542 use chrono::{FixedOffset, NaiveDate, NaiveTime, TimeZone};
543
544 fn roundtrip_scalar(kind: Kind) -> Kind {
545 let codec = Json3Codec;
546 let encoded = codec.encode_scalar(&kind).unwrap();
547 codec.decode_scalar(&encoded).unwrap()
548 }
549
550 #[test]
553 fn null_roundtrip() {
554 assert_eq!(roundtrip_scalar(Kind::Null), Kind::Null);
555 }
556
557 #[test]
558 fn null_encodes_to_json_null() {
559 let codec = Json3Codec;
560 assert_eq!(codec.encode_scalar(&Kind::Null).unwrap(), "null");
561 }
562
563 #[test]
566 fn bool_true_roundtrip() {
567 assert_eq!(roundtrip_scalar(Kind::Bool(true)), Kind::Bool(true));
568 }
569
570 #[test]
571 fn bool_false_roundtrip() {
572 assert_eq!(roundtrip_scalar(Kind::Bool(false)), Kind::Bool(false));
573 }
574
575 #[test]
576 fn bool_encodes_to_json_bool() {
577 let codec = Json3Codec;
578 assert_eq!(codec.encode_scalar(&Kind::Bool(true)).unwrap(), "true");
579 assert_eq!(codec.encode_scalar(&Kind::Bool(false)).unwrap(), "false");
580 }
581
582 #[test]
585 fn marker_roundtrip() {
586 assert_eq!(roundtrip_scalar(Kind::Marker), Kind::Marker);
587 }
588
589 #[test]
590 fn marker_encodes_as_prefix() {
591 let codec = Json3Codec;
592 assert_eq!(codec.encode_scalar(&Kind::Marker).unwrap(), "\"m:\"");
593 }
594
595 #[test]
598 fn na_roundtrip() {
599 assert_eq!(roundtrip_scalar(Kind::NA), Kind::NA);
600 }
601
602 #[test]
603 fn na_encodes_as_prefix() {
604 let codec = Json3Codec;
605 assert_eq!(codec.encode_scalar(&Kind::NA).unwrap(), "\"z:\"");
606 }
607
608 #[test]
611 fn remove_roundtrip() {
612 assert_eq!(roundtrip_scalar(Kind::Remove), Kind::Remove);
613 }
614
615 #[test]
616 fn remove_encodes_as_prefix() {
617 let codec = Json3Codec;
618 assert_eq!(codec.encode_scalar(&Kind::Remove).unwrap(), "\"-:\"");
619 }
620
621 #[test]
624 fn number_unitless_roundtrip() {
625 let k = Kind::Number(Number::unitless(72.5));
626 assert_eq!(roundtrip_scalar(k.clone()), k);
627 }
628
629 #[test]
630 fn number_with_unit_roundtrip() {
631 let k = Kind::Number(Number::new(72.5, Some("\u{00B0}F".into())));
632 assert_eq!(roundtrip_scalar(k.clone()), k);
633 }
634
635 #[test]
636 fn number_zero_roundtrip() {
637 let k = Kind::Number(Number::unitless(0.0));
638 assert_eq!(roundtrip_scalar(k.clone()), k);
639 }
640
641 #[test]
642 fn number_negative_roundtrip() {
643 let k = Kind::Number(Number::new(-23.45, Some("m\u{00B2}".into())));
644 assert_eq!(roundtrip_scalar(k.clone()), k);
645 }
646
647 #[test]
648 fn number_integer_roundtrip() {
649 let k = Kind::Number(Number::unitless(42.0));
650 assert_eq!(roundtrip_scalar(k.clone()), k);
651 }
652
653 #[test]
654 fn number_inf_roundtrip() {
655 let k = Kind::Number(Number::unitless(f64::INFINITY));
656 assert_eq!(roundtrip_scalar(k.clone()), k);
657 }
658
659 #[test]
660 fn number_neg_inf_roundtrip() {
661 let k = Kind::Number(Number::unitless(f64::NEG_INFINITY));
662 assert_eq!(roundtrip_scalar(k.clone()), k);
663 }
664
665 #[test]
666 fn number_nan_roundtrip() {
667 let codec = Json3Codec;
668 let k = Kind::Number(Number::unitless(f64::NAN));
669 let encoded = codec.encode_scalar(&k).unwrap();
670 let decoded = codec.decode_scalar(&encoded).unwrap();
671 match decoded {
672 Kind::Number(n) => {
673 assert!(n.val.is_nan());
674 assert_eq!(n.unit, None);
675 }
676 other => panic!("expected Number, got {other:?}"),
677 }
678 }
679
680 #[test]
681 fn number_encoding_format() {
682 let codec = Json3Codec;
683 let k = Kind::Number(Number::new(72.5, Some("\u{00B0}F".into())));
684 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"n:72.5 \u{00B0}F\"");
685 }
686
687 #[test]
688 fn number_unitless_encoding_format() {
689 let codec = Json3Codec;
690 let k = Kind::Number(Number::unitless(42.0));
691 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"n:42\"");
692 }
693
694 #[test]
695 fn number_inf_encoding_format() {
696 let codec = Json3Codec;
697 let k = Kind::Number(Number::unitless(f64::INFINITY));
698 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"n:INF\"");
699 }
700
701 #[test]
702 fn plain_json_number_decodes_as_number() {
703 let codec = Json3Codec;
704 let decoded = codec.decode_scalar("42.5").unwrap();
705 assert_eq!(decoded, Kind::Number(Number::unitless(42.5)));
706 }
707
708 #[test]
711 fn string_simple_roundtrip() {
712 let k = Kind::Str("hello".into());
713 assert_eq!(roundtrip_scalar(k.clone()), k);
714 }
715
716 #[test]
717 fn string_empty_roundtrip() {
718 let k = Kind::Str(String::new());
719 assert_eq!(roundtrip_scalar(k.clone()), k);
720 }
721
722 #[test]
723 fn string_with_special_chars_roundtrip() {
724 let k = Kind::Str("line1\nline2\ttab".into());
725 assert_eq!(roundtrip_scalar(k.clone()), k);
726 }
727
728 #[test]
729 fn string_encodes_with_s_prefix() {
730 let codec = Json3Codec;
731 assert_eq!(
732 codec.encode_scalar(&Kind::Str("hello".into())).unwrap(),
733 "\"s:hello\""
734 );
735 }
736
737 #[test]
738 fn string_empty_encodes_with_s_prefix() {
739 let codec = Json3Codec;
740 assert_eq!(
741 codec.encode_scalar(&Kind::Str(String::new())).unwrap(),
742 "\"s:\""
743 );
744 }
745
746 #[test]
749 fn ref_simple_roundtrip() {
750 let k = Kind::Ref(HRef::from_val("site-1"));
751 assert_eq!(roundtrip_scalar(k.clone()), k);
752 }
753
754 #[test]
755 fn ref_with_dis_roundtrip() {
756 let k = Kind::Ref(HRef::new("site-1", Some("Main Site".into())));
757 let rt = roundtrip_scalar(k);
758 match rt {
759 Kind::Ref(r) => {
760 assert_eq!(r.val, "site-1");
761 assert_eq!(r.dis, Some("Main Site".into()));
762 }
763 other => panic!("expected Ref, got {other:?}"),
764 }
765 }
766
767 #[test]
768 fn ref_encoding_format() {
769 let codec = Json3Codec;
770 let k = Kind::Ref(HRef::new("abc", Some("Display Name".into())));
771 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"r:abc Display Name\"");
772 }
773
774 #[test]
777 fn uri_roundtrip() {
778 let k = Kind::Uri(Uri::new("http://example.com/api"));
779 assert_eq!(roundtrip_scalar(k.clone()), k);
780 }
781
782 #[test]
783 fn uri_encoding_format() {
784 let codec = Json3Codec;
785 let k = Kind::Uri(Uri::new("http://example.com"));
786 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"u:http://example.com\"");
787 }
788
789 #[test]
792 fn symbol_roundtrip() {
793 let k = Kind::Symbol(Symbol::new("hot-water"));
794 assert_eq!(roundtrip_scalar(k.clone()), k);
795 }
796
797 #[test]
798 fn symbol_encoding_format() {
799 let codec = Json3Codec;
800 let k = Kind::Symbol(Symbol::new("hot-water"));
801 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"y:hot-water\"");
802 }
803
804 #[test]
807 fn date_roundtrip() {
808 let k = Kind::Date(NaiveDate::from_ymd_opt(2024, 3, 13).unwrap());
809 assert_eq!(roundtrip_scalar(k.clone()), k);
810 }
811
812 #[test]
813 fn date_encoding_format() {
814 let codec = Json3Codec;
815 let k = Kind::Date(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
816 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"d:2024-01-01\"");
817 }
818
819 #[test]
822 fn time_roundtrip() {
823 let k = Kind::Time(NaiveTime::from_hms_opt(8, 12, 5).unwrap());
824 assert_eq!(roundtrip_scalar(k.clone()), k);
825 }
826
827 #[test]
828 fn time_with_frac_roundtrip() {
829 let k = Kind::Time(NaiveTime::from_hms_milli_opt(14, 30, 0, 123).unwrap());
830 assert_eq!(roundtrip_scalar(k.clone()), k);
831 }
832
833 #[test]
834 fn time_encoding_format() {
835 let codec = Json3Codec;
836 let k = Kind::Time(NaiveTime::from_hms_opt(12, 30, 45).unwrap());
837 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"h:12:30:45\"");
838 }
839
840 #[test]
843 fn datetime_roundtrip() {
844 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
845 let dt = offset.with_ymd_and_hms(2024, 1, 1, 8, 12, 5).unwrap();
846 let k = Kind::DateTime(HDateTime::new(dt, "New_York"));
847 assert_eq!(roundtrip_scalar(k.clone()), k);
848 }
849
850 #[test]
851 fn datetime_utc_roundtrip() {
852 let offset = FixedOffset::east_opt(0).unwrap();
853 let dt = offset.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();
854 let k = Kind::DateTime(HDateTime::new(dt, "UTC"));
855 assert_eq!(roundtrip_scalar(k.clone()), k);
856 }
857
858 #[test]
859 fn datetime_encoding_format() {
860 let codec = Json3Codec;
861 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
862 let dt = offset.with_ymd_and_hms(2024, 1, 1, 12, 30, 45).unwrap();
863 let k = Kind::DateTime(HDateTime::new(dt, "New_York"));
864 assert_eq!(
865 codec.encode_scalar(&k).unwrap(),
866 "\"t:2024-01-01T12:30:45-05:00 New_York\""
867 );
868 }
869
870 #[test]
873 fn coord_roundtrip() {
874 let k = Kind::Coord(Coord::new(37.5458266, -77.4491888));
875 assert_eq!(roundtrip_scalar(k.clone()), k);
876 }
877
878 #[test]
879 fn coord_encoding_format() {
880 let codec = Json3Codec;
881 let k = Kind::Coord(Coord::new(40.7, -74.0));
882 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"c:40.7,-74\"");
883 }
884
885 #[test]
888 fn xstr_roundtrip() {
889 let k = Kind::XStr(XStr::new("Color", "red"));
890 assert_eq!(roundtrip_scalar(k.clone()), k);
891 }
892
893 #[test]
894 fn xstr_encoding_format() {
895 let codec = Json3Codec;
896 let k = Kind::XStr(XStr::new("Color", "red"));
897 assert_eq!(codec.encode_scalar(&k).unwrap(), "\"x:Color:red\"");
898 }
899
900 #[test]
903 fn list_empty_roundtrip() {
904 let k = Kind::List(vec![]);
905 assert_eq!(roundtrip_scalar(k.clone()), k);
906 }
907
908 #[test]
909 fn list_mixed_roundtrip() {
910 let k = Kind::List(vec![
911 Kind::Number(Number::unitless(1.0)),
912 Kind::Str("two".into()),
913 Kind::Marker,
914 Kind::Bool(true),
915 Kind::Null,
916 ]);
917 assert_eq!(roundtrip_scalar(k.clone()), k);
918 }
919
920 #[test]
921 fn list_nested_roundtrip() {
922 let k = Kind::List(vec![
923 Kind::List(vec![Kind::Number(Number::unitless(1.0))]),
924 Kind::List(vec![Kind::Str("inner".into())]),
925 ]);
926 assert_eq!(roundtrip_scalar(k.clone()), k);
927 }
928
929 #[test]
932 fn dict_empty_roundtrip() {
933 let k = Kind::Dict(Box::new(HDict::new()));
934 assert_eq!(roundtrip_scalar(k.clone()), k);
935 }
936
937 #[test]
938 fn dict_with_values_roundtrip() {
939 let mut d = HDict::new();
940 d.set("site", Kind::Marker);
941 d.set("dis", Kind::Str("Main".into()));
942 d.set(
943 "area",
944 Kind::Number(Number::new(4500.0, Some("ft\u{00B2}".into()))),
945 );
946 let k = Kind::Dict(Box::new(d));
947 assert_eq!(roundtrip_scalar(k.clone()), k);
948 }
949
950 #[test]
953 fn grid_empty_roundtrip() {
954 let codec = Json3Codec;
955 let g = HGrid::new();
956 let encoded = codec.encode_grid(&g).unwrap();
957 let decoded = codec.decode_grid(&encoded).unwrap();
958 assert!(decoded.is_empty());
959 assert_eq!(decoded.num_cols(), 0);
960 }
961
962 #[test]
963 fn grid_with_data_roundtrip() {
964 let codec = Json3Codec;
965
966 let cols = vec![HCol::new("dis"), HCol::new("area")];
967 let mut row1 = HDict::new();
968 row1.set("dis", Kind::Str("Site One".into()));
969 row1.set("area", Kind::Number(Number::unitless(4500.0)));
970 let mut row2 = HDict::new();
971 row2.set("dis", Kind::Str("Site Two".into()));
972 row2.set("area", Kind::Number(Number::unitless(3200.0)));
973
974 let g = HGrid::from_parts(HDict::new(), cols, vec![row1, row2]);
975 let encoded = codec.encode_grid(&g).unwrap();
976 let decoded = codec.decode_grid(&encoded).unwrap();
977
978 assert_eq!(decoded.num_cols(), 2);
979 assert_eq!(decoded.len(), 2);
980 assert_eq!(decoded.col_names().collect::<Vec<_>>(), vec!["dis", "area"]);
981 assert_eq!(
982 decoded.row(0).unwrap().get("dis"),
983 Some(&Kind::Str("Site One".into()))
984 );
985 assert_eq!(
986 decoded.row(1).unwrap().get("dis"),
987 Some(&Kind::Str("Site Two".into()))
988 );
989 }
990
991 #[test]
992 fn grid_with_meta_roundtrip() {
993 let codec = Json3Codec;
994
995 let mut meta = HDict::new();
996 meta.set("err", Kind::Marker);
997 meta.set("dis", Kind::Str("some error".into()));
998
999 let g = HGrid::from_parts(meta, vec![], vec![]);
1000 let encoded = codec.encode_grid(&g).unwrap();
1001 let decoded = codec.decode_grid(&encoded).unwrap();
1002
1003 assert!(decoded.is_err());
1004 assert_eq!(
1005 decoded.meta.get("dis"),
1006 Some(&Kind::Str("some error".into()))
1007 );
1008 }
1009
1010 #[test]
1011 fn grid_with_col_meta_roundtrip() {
1012 let codec = Json3Codec;
1013
1014 let mut col_meta = HDict::new();
1015 col_meta.set("unit", Kind::Str("kW".into()));
1016
1017 let cols = vec![HCol::new("name"), HCol::with_meta("power", col_meta)];
1018 let g = HGrid::from_parts(HDict::new(), cols, vec![]);
1019 let encoded = codec.encode_grid(&g).unwrap();
1020 let decoded = codec.decode_grid(&encoded).unwrap();
1021
1022 assert_eq!(decoded.num_cols(), 2);
1023 let power_col = decoded.col("power").unwrap();
1024 assert_eq!(power_col.meta.get("unit"), Some(&Kind::Str("kW".into())));
1025 }
1026
1027 #[test]
1028 fn grid_encoding_has_ver() {
1029 let codec = Json3Codec;
1030 let g = HGrid::new();
1031 let encoded = codec.encode_grid(&g).unwrap();
1032 let val: Value = serde_json::from_str(&encoded).unwrap();
1033 let meta = val.get("meta").unwrap().as_object().unwrap();
1034 assert_eq!(meta.get("ver").unwrap(), "3.0");
1035 }
1036
1037 #[test]
1038 fn grid_missing_cells() {
1039 let codec = Json3Codec;
1040
1041 let cols = vec![HCol::new("a"), HCol::new("b")];
1042 let mut row1 = HDict::new();
1043 row1.set("a", Kind::Number(Number::unitless(1.0)));
1044 let g = HGrid::from_parts(HDict::new(), cols, vec![row1]);
1047 let encoded = codec.encode_grid(&g).unwrap();
1048 let decoded = codec.decode_grid(&encoded).unwrap();
1049
1050 let r = decoded.row(0).unwrap();
1051 assert!(r.has("a"));
1052 assert!(r.missing("b"));
1053 }
1054
1055 #[test]
1058 fn disambiguation_str_vs_marker() {
1059 let codec = Json3Codec;
1061 let decoded = codec.decode_scalar("\"m:\"").unwrap();
1062 assert_eq!(decoded, Kind::Marker);
1063 }
1064
1065 #[test]
1066 fn disambiguation_str_with_colon() {
1067 let codec = Json3Codec;
1069 let decoded = codec.decode_scalar("\"s:hello:world\"").unwrap();
1070 assert_eq!(decoded, Kind::Str("hello:world".into()));
1071 }
1072
1073 #[test]
1074 fn string_that_looks_like_prefix() {
1075 let k = Kind::Str("m:".into());
1077 let rt = roundtrip_scalar(k.clone());
1078 assert_eq!(rt, k);
1079 }
1080
1081 #[test]
1082 fn list_with_all_types() {
1083 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
1084 let dt = offset.with_ymd_and_hms(2024, 1, 1, 8, 0, 0).unwrap();
1085 let k = Kind::List(vec![
1086 Kind::Null,
1087 Kind::Marker,
1088 Kind::NA,
1089 Kind::Remove,
1090 Kind::Bool(true),
1091 Kind::Number(Number::new(42.0, Some("kW".into()))),
1092 Kind::Str("hello".into()),
1093 Kind::Ref(HRef::new("x", Some("Dis".into()))),
1094 Kind::Uri(Uri::new("http://a.com")),
1095 Kind::Symbol(Symbol::new("tag")),
1096 Kind::Date(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
1097 Kind::Time(NaiveTime::from_hms_opt(12, 0, 0).unwrap()),
1098 Kind::DateTime(HDateTime::new(dt, "New_York")),
1099 Kind::Coord(Coord::new(37.5, -77.4)),
1100 Kind::XStr(XStr::new("Color", "red")),
1101 ]);
1102 assert_eq!(roundtrip_scalar(k.clone()), k);
1103 }
1104
1105 #[test]
1106 fn nested_dict_with_typed_values() {
1107 let mut inner = HDict::new();
1108 inner.set(
1109 "temp",
1110 Kind::Number(Number::new(72.5, Some("\u{00B0}F".into()))),
1111 );
1112 inner.set("site", Kind::Ref(HRef::from_val("s1")));
1113 let k = Kind::Dict(Box::new(inner));
1114 assert_eq!(roundtrip_scalar(k.clone()), k);
1115 }
1116
1117 #[test]
1118 fn grid_nested_in_scalar() {
1119 let codec = Json3Codec;
1120 let cols = vec![HCol::new("x")];
1121 let mut row = HDict::new();
1122 row.set("x", Kind::Number(Number::unitless(42.0)));
1123 let g = HGrid::from_parts(HDict::new(), cols, vec![row]);
1124
1125 let k = Kind::Grid(Box::new(g));
1126 let encoded = codec.encode_scalar(&k).unwrap();
1127 let decoded = codec.decode_scalar(&encoded).unwrap();
1128 match decoded {
1129 Kind::Grid(g) => {
1130 assert_eq!(g.len(), 1);
1131 assert_eq!(g.num_cols(), 1);
1132 }
1133 other => panic!("expected Grid, got {other:?}"),
1134 }
1135 }
1136}