use nextjson::formats::{self, Format};
struct MissingArraySeparator;
impl nextjson::NsonSchema for MissingArraySeparator {
const SCHEMA: nextjson::TypeSchema = nextjson::TypeSchema::Opaque;
}
struct CsvReorderedObjectRows;
impl nextjson::NsonSchema for CsvReorderedObjectRows {
const SCHEMA: nextjson::TypeSchema = nextjson::TypeSchema::Opaque;
}
impl nextjson::NsonSerialize for CsvReorderedObjectRows {
fn nextencode<E: nextjson::FormatEncoder>(
&self,
encoder: &mut E,
) -> core::result::Result<(), E::Error> {
encoder.begin_array()?;
encoder.separator()?;
encoder.begin_object()?;
encoder.key("left")?;
encoder.write_i64(1)?;
encoder.key("right")?;
encoder.write_i64(2)?;
encoder.end_object()?;
encoder.separator()?;
encoder.begin_object()?;
encoder.key("right")?;
encoder.write_i64(4)?;
encoder.key("left")?;
encoder.write_i64(3)?;
encoder.end_object()?;
encoder.end_array()
}
}
impl nextjson::NsonSerialize for MissingArraySeparator {
fn nextencode<E: nextjson::FormatEncoder>(
&self,
encoder: &mut E,
) -> core::result::Result<(), E::Error> {
encoder.begin_array()?;
encoder.write_null()?;
encoder.end_array()
}
}
#[test]
fn format_entry_points_reject_invalid_serialization_events() {
assert!(formats::MsgPack.encode(&MissingArraySeparator).is_err());
assert!(formats::Ron.encode(&MissingArraySeparator).is_err());
assert!(formats::Yaml.encode(&MissingArraySeparator).is_err());
assert!(formats::Bencode.encode(&MissingArraySeparator).is_err());
}
#[test]
fn ron_roundtrips() {
roundtrip(&vec![1_i64, 2, 3], formats::Ron);
roundtrip(&(1_i64, "two".to_string(), 3.5_f64), formats::Ron);
roundtrip(&"hello".to_string(), formats::Ron);
roundtrip(&true, formats::Ron);
roundtrip(&Option::<i64>::None, formats::Ron);
roundtrip(&Some(7_i64), formats::Ron);
let mut m = nextjson::Map::new();
m.insert("name".to_string(), nextjson::Value::from("NextJson"));
m.insert("n".to_string(), nextjson::Value::from(42_i64));
roundtrip(&m, formats::Ron);
}
#[test]
fn ron_wire() {
assert_eq!(formats::Ron.encode(&42_i64).unwrap(), b"42");
assert_eq!(formats::Ron.encode(&true).unwrap(), b"true");
assert_eq!(formats::Ron.encode(&"hi".to_string()).unwrap(), b"\"hi\"");
assert_eq!(formats::Ron.encode(&vec![1_i64, 2]).unwrap(), b"[1, 2]");
assert_eq!(formats::Ron.encode(&Option::<i64>::None).unwrap(), b"None");
assert_eq!(formats::Ron.encode(&Some(5_i64)).unwrap(), b"5");
}
#[test]
fn ron_decodes_foreign_syntax() {
let value: nextjson::Value = formats::Ron.decode(b"(name: \"x\", count: 3)").unwrap();
assert_eq!(value["name"], nextjson::Value::from("x"));
assert_eq!(value["count"], nextjson::Value::from(3_i64));
let value: nextjson::Value = formats::Ron.decode(b"Some([1, 2])").unwrap();
assert_eq!(value[1], nextjson::Value::from(2_i64));
}
#[test]
fn json5_roundtrips_and_lenient() {
roundtrip(&vec![1_i64, 2, 3], formats::Json5);
roundtrip(&"héllo ✓".to_string(), formats::Json5);
let mut m = nextjson::Map::new();
m.insert("a".to_string(), nextjson::Value::from(1_i64));
roundtrip(&m, formats::Json5);
let value: nextjson::Value = formats::Json5
.decode(
br#"{ // comment
unquotedKey: 'value',
hex: 0x1F,
trailing: [1, 2, 3,],
}"#,
)
.unwrap();
assert_eq!(value["unquotedKey"], nextjson::Value::from("value"));
assert_eq!(value["hex"], nextjson::Value::from(31_i64));
assert_eq!(value["trailing"][2], nextjson::Value::from(3_i64));
}
#[test]
fn hjson_roundtrips_and_lenient() {
roundtrip(&vec![1_i64, 2, 3], formats::Hjson);
roundtrip(&"hi".to_string(), formats::Hjson);
let mut m = nextjson::Map::new();
m.insert("a".to_string(), nextjson::Value::from(1_i64));
roundtrip(&m, formats::Hjson);
let value: nextjson::Value = formats::Hjson
.decode(
br#"{
# a comment
name: NextJson
nums: [1, 2, 3,]
}"#,
)
.unwrap();
assert_eq!(value["name"], nextjson::Value::from("NextJson"));
assert_eq!(value["nums"][0], nextjson::Value::from(1_i64));
}
#[test]
fn sexpr_roundtrips() {
roundtrip(&vec![1_i64, 2, 3], formats::Sexpr);
roundtrip(&"hello".to_string(), formats::Sexpr);
roundtrip(&true, formats::Sexpr);
roundtrip(&Option::<i64>::None, formats::Sexpr);
roundtrip(&vec!["a".to_string(), "b".to_string()], formats::Sexpr);
roundtrip(&vec![vec![1_i64], vec![2_i64]], formats::Sexpr);
let mut m = nextjson::Map::new();
m.insert("a".to_string(), nextjson::Value::from(1_i64));
m.insert("b".to_string(), nextjson::Value::from("two"));
roundtrip(&m, formats::Sexpr);
}
#[test]
fn sexpr_wire() {
assert_eq!(formats::Sexpr.encode(&vec![1_i64, 2]).unwrap(), b"(1 2)");
assert_eq!(formats::Sexpr.encode(&true).unwrap(), b"#t");
assert_eq!(
formats::Sexpr.encode(&"a b".to_string()).unwrap(),
b"\"a b\""
);
assert_eq!(formats::Sexpr.encode(&42_i64).unwrap(), b"42");
}
#[test]
fn urlform_roundtrips() {
let mut m = std::collections::BTreeMap::new();
m.insert("name".to_string(), "Next Json".to_string());
m.insert("count".to_string(), "42".to_string());
roundtrip(&m, formats::UrlForm);
let mut m = std::collections::BTreeMap::new();
m.insert("q".to_string(), "a+b/c d%".to_string());
let bytes = formats::UrlForm.encode(&m).unwrap();
assert_eq!(bytes, b"q=a%2Bb%2Fc+d%25");
let back: std::collections::BTreeMap<String, String> = formats::UrlForm.decode(&bytes).unwrap();
assert_eq!(back.get("q").unwrap(), "a+b/c d%");
}
#[test]
fn csv_roundtrips() {
let rows = vec![
vec!["a".to_string(), "b".to_string()],
vec!["1".to_string(), "2".to_string()],
];
let bytes = formats::Csv.encode(&rows).unwrap();
assert_eq!(bytes, b"a,b\n1,2\n");
let back: Vec<Vec<String>> = formats::Csv.decode(&bytes).unwrap();
assert_eq!(back, rows);
let rows = vec![vec!["x,y".to_string(), "line\nbreak".to_string()]];
let bytes = formats::Csv.encode(&rows).unwrap();
assert_eq!(bytes, b"\"x,y\",\"line\nbreak\"\n");
let back: Vec<Vec<String>> = formats::Csv.decode(&bytes).unwrap();
assert_eq!(back, rows);
let spaced: Vec<Vec<String>> = formats::Csv.decode(b" left,\tright\n").unwrap();
assert_eq!(
spaced,
vec![vec![" left".to_string(), "\tright".to_string()]]
);
assert!(formats::Csv
.decode::<Vec<Vec<String>>>(b"a\"b,c\n")
.is_err());
assert!(formats::Csv
.decode::<Vec<Vec<String>>>(b"\"a\"x,c\n")
.is_err());
}
#[test]
fn csv_object_rows_with_header() {
let mut r1 = nextjson::Map::new();
r1.insert("name".to_string(), nextjson::Value::from("a"));
r1.insert("n".to_string(), nextjson::Value::from(1_i64));
let mut r2 = nextjson::Map::new();
r2.insert("name".to_string(), nextjson::Value::from("b"));
r2.insert("n".to_string(), nextjson::Value::from(2_i64));
let rows = vec![r1, r2];
let bytes = formats::Csv.encode(&rows).unwrap();
assert_eq!(bytes, b"name,n\na,1\nb,2\n");
let back: Vec<nextjson::Map> = formats::Csv.decode(&bytes).unwrap();
assert_eq!(back.len(), 2);
assert_eq!(back[0]["name"], nextjson::Value::from("a"));
assert_eq!(back[1]["n"], nextjson::Value::from(2_i64));
let reordered = formats::Csv.encode(&CsvReorderedObjectRows).unwrap();
assert_eq!(reordered, b"left,right\n1,2\n3,4\n");
assert!(formats::Csv
.decode::<Vec<nextjson::Map>>(b"a,a\n1,2\n")
.is_err());
assert!(formats::Csv
.decode::<Vec<nextjson::Map>>(b"a,b\n1\n")
.is_err());
assert!(formats::Csv
.decode::<Vec<nextjson::Map>>(b"a\n1,2\n")
.is_err());
assert!(formats::Csv.encode(&42_i64).is_err());
}
#[test]
fn toml_roundtrips() {
let mut m = nextjson::Map::new();
m.insert("title".to_string(), nextjson::Value::from("NextJson"));
m.insert("version".to_string(), nextjson::Value::from(1_i64));
m.insert("enabled".to_string(), nextjson::Value::from(true));
m.insert(
"list".to_string(),
nextjson::Value::from(vec![
nextjson::Value::from(1_i64),
nextjson::Value::from(2_i64),
]),
);
let mut nested = nextjson::Map::new();
nested.insert("key".to_string(), nextjson::Value::from("val"));
m.insert("table".to_string(), nextjson::Value::Object(nested));
roundtrip(&m, formats::Toml);
let mut encoder = formats::TomlEncoder::new(Vec::new());
nextjson::NsonSerialize::nextencode(&m, &mut encoder).unwrap();
let encoded = encoder.finish().unwrap();
assert!(!encoded.is_empty());
let direct: nextjson::Map = formats::Toml.decode(&encoded).unwrap();
assert_eq!(direct, m);
}
#[test]
fn toml_decodes_foreign() {
let input = br#"
title = "NextJson"
[owner]
name = "blueokanna"
[[products]]
name = "Hammer"
[products.details]
color = "red"
"#;
let value: nextjson::Value = formats::Toml.decode(input).unwrap();
assert_eq!(value["title"], nextjson::Value::from("NextJson"));
assert_eq!(value["owner"]["name"], nextjson::Value::from("blueokanna"));
assert_eq!(
value["products"][0]["name"],
nextjson::Value::from("Hammer")
);
assert_eq!(
value["products"][0]["details"]["color"],
nextjson::Value::from("red")
);
}
#[test]
fn toml_rejects_excessive_nesting() {
let mut input = String::from("value = ");
input.push_str(&"[".repeat(129));
input.push('0');
input.push_str(&"]".repeat(129));
assert!(formats::Toml
.decode::<nextjson::Value>(input.as_bytes())
.is_err());
}
#[test]
fn yaml_roundtrips() {
let mut m = nextjson::Map::new();
m.insert("name".to_string(), nextjson::Value::from("NextJson"));
m.insert("count".to_string(), nextjson::Value::from(3_i64));
m.insert("ok".to_string(), nextjson::Value::from(true));
m.insert(
"tags".to_string(),
nextjson::Value::from(vec![
nextjson::Value::from("fast"),
nextjson::Value::from("safe"),
]),
);
let mut nested = nextjson::Map::new();
nested.insert("deep".to_string(), nextjson::Value::from(1_i64));
m.insert("config".to_string(), nextjson::Value::Object(nested));
roundtrip(&m, formats::Yaml);
let mut encoder = formats::YamlEncoder::new(Vec::new());
nextjson::NsonSerialize::nextencode(&m, &mut encoder).unwrap();
let encoded = encoder.finish().unwrap();
assert!(!encoded.is_empty());
let direct: nextjson::Map = formats::Yaml.decode(&encoded).unwrap();
assert_eq!(direct, m);
}
#[test]
fn yaml_decodes_foreign() {
let input = br#"
name: NextJson
count: 3
ok: true
tags:
- fast
- safe
config:
deep: 1
"#;
let value: nextjson::Value = formats::Yaml.decode(input).unwrap();
assert_eq!(value["name"], nextjson::Value::from("NextJson"));
assert_eq!(value["count"], nextjson::Value::from(3_i64));
assert_eq!(value["ok"], nextjson::Value::from(true));
assert_eq!(value["tags"][1], nextjson::Value::from("safe"));
assert_eq!(value["config"]["deep"], nextjson::Value::from(1_i64));
}
#[test]
fn yaml_flow_style() {
let value: nextjson::Value = formats::Yaml
.decode(br#"{a: 1, b: [true, null], c: {x: y}}"#)
.unwrap();
assert_eq!(value["a"], nextjson::Value::from(1_i64));
assert_eq!(value["b"][0], nextjson::Value::from(true));
assert_eq!(value["b"][1], nextjson::Value::Null);
assert_eq!(value["c"]["x"], nextjson::Value::from("y"));
}
#[test]
fn yaml_rejects_excessive_flow_nesting() {
let mut input = "[".repeat(129);
input.push('0');
input.push_str(&"]".repeat(129));
assert!(formats::Yaml
.decode::<nextjson::Value>(input.as_bytes())
.is_err());
}
#[test]
fn formats_registry_count() {
let all = formats::all();
assert!(all.len() >= 14);
for info in all {
assert!(!info.name.is_empty());
}
let mut names: Vec<&str> = all.iter().map(|f| f.name).collect();
names.sort_unstable();
names.dedup();
assert_eq!(
names.len(),
all.len(),
"registry contains duplicate formats"
);
}
#[test]
fn transcode_between_formats() {
let json = br#"{"name":"NextJson","values":[1,2,3],"ok":true}"#;
let msgpack = formats::transcode(json, formats::Json, formats::MsgPack).unwrap();
let back = formats::transcode(&msgpack, formats::MsgPack, formats::Json).unwrap();
assert_eq!(back, json);
let yaml = formats::transcode(json, formats::Json, formats::Yaml).unwrap();
let back = formats::transcode(&yaml, formats::Yaml, formats::Json).unwrap();
assert_eq!(back, json);
let cbor = formats::transcode(json, formats::Json, formats::Cbor).unwrap();
let back = formats::transcode(&cbor, formats::Cbor, formats::Json).unwrap();
assert_eq!(back, json);
let ron = formats::transcode(json, formats::Json, formats::Ron).unwrap();
let back = formats::transcode(&ron, formats::Ron, formats::Json).unwrap();
assert_eq!(back, json);
}
#[test]
fn full_matrix_roundtrips() {
let mut m = nextjson::Map::new();
m.insert("name".to_string(), nextjson::Value::from("NextJson"));
m.insert("count".to_string(), nextjson::Value::from(7_i64));
m.insert("ok".to_string(), nextjson::Value::from(true));
m.insert(
"items".to_string(),
nextjson::Value::from(vec![
nextjson::Value::from("a"),
nextjson::Value::from("b"),
nextjson::Value::from(3_i64),
]),
);
let mut nested = nextjson::Map::new();
nested.insert("deep".to_string(), nextjson::Value::from(1_i64));
m.insert("config".to_string(), nextjson::Value::Object(nested));
roundtrip(&m, formats::Json);
roundtrip(&m, formats::Cbor);
roundtrip(&m, formats::Json5);
roundtrip(&m, formats::Hjson);
roundtrip(&m, formats::MsgPack);
roundtrip(&m, formats::Pickle);
roundtrip(&m, formats::Ron);
roundtrip(&m, formats::Yaml);
roundtrip(&m, formats::Toml);
roundtrip(&m, formats::Bson);
assert!(formats::Sexpr.encode(&m).is_ok());
assert!(formats::Bencode.encode(&m).is_ok());
let f = 3.25_f64;
roundtrip(&f, formats::Json);
roundtrip(&f, formats::Cbor);
roundtrip(&f, formats::MsgPack);
roundtrip(&f, formats::Pickle);
roundtrip(&f, formats::Ron);
roundtrip(&f, formats::Sexpr);
roundtrip(&f, formats::Yaml);
assert!(formats::Toml.encode(&f).is_err());
assert!(formats::Bson.encode(&f).is_err());
assert!(formats::Bencode.encode(&f).is_err());
assert!(formats::Postcard.encode(&f).is_err());
}
#[test]
fn cross_language_wire_compatibility() {
let foreign: &[u8] = &[
0x82, 0xa3, b'f', b'o', b'o', 0x2a, 0xa3, b'b', b'a', b'r', 0xa3, b'b', b'a', b'z', ];
let value: nextjson::Value = formats::MsgPack.decode(foreign).unwrap();
assert_eq!(value["foo"], nextjson::Value::from(42_u8));
assert_eq!(value["bar"], nextjson::Value::from("baz"));
let cbor_foreign: &[u8] = &[
0xa2, 0x63, b'f', b'o', b'o', 0x18, 0x2a, 0x63, b'b', b'a', b'r', 0x63, b'b', b'a', b'z', ];
let value: nextjson::Value = formats::Cbor.decode(cbor_foreign).unwrap();
assert_eq!(value["foo"], nextjson::Value::from(42_u8));
let bson_foreign: &[u8] = &[
0x1a, 0x00, 0x00, 0x00, 0x10, b'n', 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, b'b', 0x00, 0x01, 0x02, b's', 0x00, 0x03, 0x00, 0x00, 0x00, b'h', b'i', 0x00, 0x00,
];
let value: nextjson::Value = formats::Bson.decode(bson_foreign).unwrap();
assert_eq!(value["n"], nextjson::Value::from(42_i32));
assert_eq!(value["b"], nextjson::Value::from(true));
assert_eq!(value["s"], nextjson::Value::from("hi"));
let toml_foreign = br#"[package]
name = "nextjson"
version = "0.1.0"
edition = "2021"
"#;
let value: nextjson::Value = formats::Toml.decode(toml_foreign).unwrap();
assert_eq!(value["package"]["name"], nextjson::Value::from("nextjson"));
let yaml_foreign = br#"apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
key: value
"#;
let value: nextjson::Value = formats::Yaml.decode(yaml_foreign).unwrap();
assert_eq!(value["kind"], nextjson::Value::from("ConfigMap"));
assert_eq!(
value["metadata"]["name"],
nextjson::Value::from("app-config")
);
}
fn roundtrip<F: Format, T>(value: &T, format: F) -> T
where
T: nextjson::NsonSerialize
+ for<'de> nextjson::NsonDeserialize<'de>
+ Clone
+ PartialEq
+ core::fmt::Debug,
{
let bytes = format.encode(value).expect("encode");
let back: T = format.decode(&bytes).expect("decode");
assert_eq!(&back, value, "round-trip failed for {}", F::NAME);
back
}
#[test]
fn msgpack_scalars() {
assert_eq!(formats::MsgPack.encode(&true).unwrap(), &[0xc3], "true");
assert_eq!(formats::MsgPack.encode(&false).unwrap(), &[0xc2]);
assert_eq!(
formats::MsgPack.encode(&Option::<u8>::None).unwrap(),
&[0xc0]
);
assert_eq!(formats::MsgPack.encode(&42_u8).unwrap(), &[0x2a]);
assert_eq!(formats::MsgPack.encode(&-1_i8).unwrap(), &[0xff]);
assert_eq!(
formats::MsgPack.encode(&300_u16).unwrap(),
&[0xcd, 0x01, 0x2c]
);
assert_eq!(
formats::MsgPack.encode(&"hello").unwrap(),
&[0xa5, b'h', b'e', b'l', b'l', b'o']
);
assert_eq!(
formats::MsgPack.encode(&1.5_f64).unwrap(),
&[0xcb, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
);
}
#[test]
fn msgpack_containers_wire() {
assert_eq!(
formats::MsgPack.encode(&vec![1_u8, 2, 3]).unwrap(),
&[0x93, 0x01, 0x02, 0x03]
);
let mut map = std::collections::BTreeMap::new();
map.insert("a".to_string(), 1_u8);
assert_eq!(
formats::MsgPack.encode(&map).unwrap(),
&[0x81, 0xa1, b'a', 0x01]
);
}
#[test]
fn msgpack_roundtrips() {
roundtrip(
&(7_u64, "NextJson".to_string(), vec![1_i32, 2, 3]),
formats::MsgPack,
);
roundtrip(
&[
("name".to_string(), "NextJson".to_string()),
("kind".to_string(), "serde-free".to_string()),
("ok".to_string(), "yes".to_string()),
],
formats::MsgPack,
);
roundtrip(&vec![0_u16, 1, 15, 16, 255, 256, 65535], formats::MsgPack);
roundtrip(&[-128_i16, -1, 0, 127, 128, 32767], formats::MsgPack);
roundtrip(&3.25_f64, formats::MsgPack);
roundtrip(&-0.5_f32, formats::MsgPack);
roundtrip(&"héllo wörld ✓".to_string(), formats::MsgPack);
roundtrip(&Option::<String>::None, formats::MsgPack);
roundtrip(&Some("x".to_string()), formats::MsgPack);
roundtrip(&[true, false, true], formats::MsgPack);
roundtrip(&Vec::<i64>::new(), formats::MsgPack);
roundtrip(
&[
["a".to_string(), "b".to_string()],
["c".to_string(), "d".to_string()],
],
formats::MsgPack,
);
}
#[test]
fn msgpack_large_containers_need_wide_headers() {
let v: Vec<u8> = (0..20).collect();
let bytes = formats::MsgPack.encode(&v).unwrap();
assert_eq!(&bytes[..3], &[0xdc, 0x00, 0x14]);
let back: Vec<u8> = formats::MsgPack.decode(&bytes).unwrap();
assert_eq!(back, v);
let mut m = std::collections::BTreeMap::new();
for i in 0..20_u8 {
m.insert(i.to_string(), i);
}
let bytes = formats::MsgPack.encode(&m).unwrap();
assert_eq!(&bytes[..3], &[0xde, 0x00, 0x14]);
let back: std::collections::BTreeMap<String, u8> = formats::MsgPack.decode(&bytes).unwrap();
assert_eq!(back, m);
}
#[test]
fn msgpack_decodes_foreign_wire_bytes() {
let foreign: &[u8] = &[
0x84, 0xa4, b'l', b'i', b's', b't', 0x93, 0x01, 0x02, 0x03, 0xa1, b's', 0xa1, b'x', 0xa1,
b'n', 0xc0, 0xa1, b'b', 0xc3,
];
let value: nextjson::Value = formats::MsgPack.decode(foreign).unwrap();
assert_eq!(value["list"][2], nextjson::Value::from(3_u8));
assert_eq!(value["s"], nextjson::Value::from("x"));
assert_eq!(value["n"], nextjson::Value::Null);
assert_eq!(value["b"], nextjson::Value::from(true));
}
#[test]
fn msgpack_rejects_bad_input() {
assert!(formats::MsgPack
.decode::<String>(&[0xda, 0x01, 0x00, b'a'])
.is_err());
assert!(formats::MsgPack.decode::<String>(&[0xa1, 0xff]).is_err());
assert!(formats::MsgPack.decode::<u8>(&[0x2a, 0x2b]).is_err());
assert!(formats::MsgPack.encode(&u128::MAX).is_err());
}
#[test]
fn registry_and_detection() {
assert!(formats::all().iter().any(|f| f.name == "msgpack"));
assert_eq!(formats::by_name("JSON"), Some(formats::FormatKind::Json));
assert_eq!(
formats::by_extension("yml"),
Some(formats::FormatKind::Yaml)
);
assert_eq!(
formats::detect(&[0x93, 0x01, 0x02, 0x03]),
Some(formats::FormatKind::MsgPack)
);
assert_eq!(
formats::detect(b"{\"a\":1}"),
Some(formats::FormatKind::Json)
);
}
#[test]
fn postcard_wire() {
assert_eq!(formats::Postcard.encode(&42_u64).unwrap(), &[0x2a]);
assert_eq!(formats::Postcard.encode(&0_u64).unwrap(), &[0x00]);
assert_eq!(
formats::Postcard.encode(&"abc".to_string()).unwrap(),
&[0x03, b'a', b'b', b'c']
);
assert_eq!(
formats::Postcard.encode(&vec![1_u8, 2, 3]).unwrap(),
&[0x03, 0x01, 0x02, 0x03]
);
assert_eq!(formats::Postcard.encode(&300_u64).unwrap(), &[0xac, 0x02]);
}
#[test]
fn postcard_typed_roundtrips() {
roundtrip(&42_u64, formats::Postcard);
roundtrip(&vec![1_u64, 2, 3], formats::Postcard);
roundtrip(&"hello".to_string(), formats::Postcard);
roundtrip(&["a".to_string(), "b".to_string()], formats::Postcard);
let mut m = std::collections::BTreeMap::new();
m.insert("x".to_string(), 7_u64);
m.insert("y".to_string(), 8_u64);
roundtrip(&m, formats::Postcard);
roundtrip(&(), formats::Postcard);
}
#[test]
fn postcard_rejects_non_self_describing() {
assert!(formats::Postcard.encode(&-1_i64).is_err());
assert!(formats::Postcard.encode(&1.5_f64).is_err());
assert!(formats::Postcard
.decode::<nextjson::Value>(&[0x2a])
.is_err());
assert!(formats::Postcard.decode::<Option<u64>>(&[0x2a]).is_err());
assert!(formats::Postcard.decode::<u64>(&[0x80, 0x00]).is_err());
}
#[test]
fn bencode_wire() {
assert_eq!(formats::Bencode.encode(&42_i64).unwrap(), b"i42e");
assert_eq!(formats::Bencode.encode(&-5_i64).unwrap(), b"i-5e");
assert_eq!(
formats::Bencode.encode(&"spam".to_string()).unwrap(),
b"4:spam"
);
assert_eq!(
formats::Bencode.encode(&vec![1_i64, 2, 3]).unwrap(),
b"li1ei2ei3ee"
);
let mut m = nextjson::Map::new();
m.insert("bar".to_string(), nextjson::Value::from("spam"));
m.insert("foo".to_string(), nextjson::Value::from(42_i64));
assert_eq!(
formats::Bencode.encode(&m).unwrap(),
b"d3:bar4:spam3:fooi42ee"
);
}
#[test]
fn bencode_roundtrips() {
roundtrip(&42_i64, formats::Bencode);
roundtrip(&-1000_i128, formats::Bencode);
roundtrip(&i128::MIN, formats::Bencode);
roundtrip(&"spam eggs".to_string(), formats::Bencode);
roundtrip(&vec![1_i64, 2, 3], formats::Bencode);
roundtrip(&vec![vec![1_i64], vec![2_i64, 3_i64]], formats::Bencode);
let mut m = nextjson::Map::new();
m.insert("a".to_string(), nextjson::Value::from(1_i64));
m.insert("b".to_string(), nextjson::Value::from("two"));
m.insert(
"c".to_string(),
nextjson::Value::from(vec![
nextjson::Value::from(10_i64),
nextjson::Value::from(20_i64),
]),
);
roundtrip(&m, formats::Bencode);
let mut unsorted = nextjson::Map::new();
unsorted.insert("z".to_string(), nextjson::Value::from(1_i64));
unsorted.insert("a".to_string(), nextjson::Value::from(2_i64));
assert_eq!(
formats::Bencode.encode(&unsorted).unwrap(),
b"d1:ai2e1:zi1ee"
);
let flag = true;
let bytes = formats::Bencode.encode(&flag).unwrap();
assert_eq!(bytes, b"i1e");
assert!(formats::Bencode.decode::<bool>(&bytes).unwrap());
}
#[test]
fn bencode_decodes_foreign_wire_bytes() {
let foreign = b"d8:announce13:udp://tracker6:piecesli1ei2ei3eee";
let value: nextjson::Value = formats::Bencode.decode(foreign).unwrap();
assert_eq!(value["announce"], nextjson::Value::from("udp://tracker"));
assert_eq!(value["pieces"][2], nextjson::Value::from(3_i64));
}
#[test]
fn bencode_rejects_unsupported() {
assert!(formats::Bencode.encode(&1.5_f64).is_err());
assert!(formats::Bencode.encode(&Option::<u8>::None).is_err());
assert!(formats::Bencode.encode(&u128::MAX).is_err());
assert!(formats::Bencode.decode::<i64>(b"i03e").is_err());
assert!(formats::Bencode.decode::<i64>(b"i-0e").is_err());
assert!(formats::Bencode.decode::<String>(b"03:abc").is_err());
}
#[test]
fn pickle_wire() {
assert_eq!(
formats::Pickle.encode(&42_i64).unwrap(),
&[0x80, 0x02, 0x4b, 0x2a, 0x2e]
);
assert_eq!(
formats::Pickle.encode(&"abc".to_string()).unwrap(),
&[0x80, 0x02, 0x58, 0x03, 0, 0, 0, b'a', b'b', b'c', 0x2e]
);
}
#[test]
fn pickle_roundtrips() {
roundtrip(&42_i64, formats::Pickle);
roundtrip(&-300_i64, formats::Pickle);
roundtrip(&1.5_f64, formats::Pickle);
roundtrip(&true, formats::Pickle);
roundtrip(&"héllo ✓".to_string(), formats::Pickle);
roundtrip(&vec![1_i64, 2, 3], formats::Pickle);
roundtrip(&(1_i64, "two".to_string(), 3.0_f64), formats::Pickle);
let mut m = nextjson::Map::new();
m.insert("a".to_string(), nextjson::Value::from(1_i64));
m.insert(
"b".to_string(),
nextjson::Value::from(vec![
nextjson::Value::from(1_i64),
nextjson::Value::from(2_i64),
]),
);
roundtrip(&m, formats::Pickle);
roundtrip(&Option::<i64>::None, formats::Pickle);
roundtrip(&Some(7_i64), formats::Pickle);
roundtrip(&i128::MAX, formats::Pickle);
roundtrip(&i128::MIN, formats::Pickle);
}
#[test]
fn pickle_decodes_real_python_bytes() {
let foreign: &[u8] = &[
0x80, 0x02, 0x7d, 0x28, 0x58, 0x01, 0x00, 0x00, 0x00, b'a', 0x5d, 0x28, 0x4b, 0x01, 0x4b, 0x02, 0x4b, 0x03, 0x65, 0x58, 0x02, 0x00, 0x00, 0x00, b's', b'p', 0x4e, 0x58, 0x01, 0x00, 0x00, 0x00, b'b', 0x88, 0x75, 0x2e, ];
let value: nextjson::Value = formats::Pickle.decode(foreign).unwrap();
assert_eq!(value["a"][0], nextjson::Value::from(1_i64));
assert_eq!(value["a"][2], nextjson::Value::from(3_i64));
assert_eq!(value["sp"], nextjson::Value::Null);
assert_eq!(value["b"], nextjson::Value::from(true));
}
#[test]
fn pickle_rejects_bad_input() {
assert!(formats::Pickle.decode::<i64>(&[0x80, 0x02, 0xff]).is_err());
assert!(formats::Pickle.decode::<i64>(&[0x80, 0x02]).is_err());
assert!(formats::Pickle
.decode::<i64>(&[0x80, 0x05, 0x4b, 0x01, 0x2e])
.is_err());
}
#[test]
fn pickle_rejects_non_finite_wire_floats() {
for value in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] {
let mut wire = vec![0x80, 0x02, 0x47];
wire.extend_from_slice(&value.to_be_bytes());
wire.push(0x2e);
assert!(formats::Pickle.decode::<nextjson::Value>(&wire).is_err());
}
}
#[test]
fn bson_wire() {
let mut m = nextjson::Map::new();
m.insert("a".to_string(), nextjson::Value::from(1_i32));
let bytes = formats::Bson.encode(&m).unwrap();
assert_eq!(
bytes,
&[0x0c, 0x00, 0x00, 0x00, 0x10, b'a', 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]
);
let arr = vec![1_i64, 2_i64, 3_i64];
let bytes = formats::Bson.encode(&arr).unwrap();
assert_eq!(
bytes,
&[
0x1a, 0x00, 0x00, 0x00, 0x10, b'0', 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, b'1', 0x00, 0x02, 0x00, 0x00, 0x00,
0x10, b'2', 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
]
);
let mut m = nextjson::Map::new();
m.insert(
"a".to_string(),
nextjson::Value::from(vec![
nextjson::Value::from(true),
nextjson::Value::from(false),
]),
);
m.insert("b".to_string(), nextjson::Value::from("hi"));
m.insert("c".to_string(), nextjson::Value::Null);
let bytes = formats::Bson.encode(&m).unwrap();
let back: nextjson::Value = formats::Bson.decode(&bytes).unwrap();
assert_eq!(back["a"][0], nextjson::Value::from(true));
assert_eq!(back["a"][1], nextjson::Value::from(false));
assert_eq!(back["b"], nextjson::Value::from("hi"));
assert_eq!(back["c"], nextjson::Value::Null);
}
#[test]
fn bson_roundtrips() {
roundtrip(&vec![1_i64, 2, 3], formats::Bson);
roundtrip(&vec!["a".to_string(), "b".to_string()], formats::Bson);
roundtrip(&vec![vec![1_i64], vec![2_i64, 3_i64]], formats::Bson);
roundtrip(&(1_i64, "two".to_string(), 3.0_f64), formats::Bson);
let mut m = nextjson::Map::new();
m.insert("name".to_string(), nextjson::Value::from("NextJson"));
m.insert("count".to_string(), nextjson::Value::from(7_i64));
m.insert("ok".to_string(), nextjson::Value::from(true));
m.insert(
"tags".to_string(),
nextjson::Value::from(vec![
nextjson::Value::from("fast"),
nextjson::Value::from("safe"),
]),
);
roundtrip(&m, formats::Bson);
}
#[test]
fn bson_rejects_unsupported() {
assert!(formats::Bson.encode(&42_i64).is_err());
assert!(formats::Bson.encode(&"hi".to_string()).is_err());
assert!(formats::Bson.encode(&true).is_err());
assert!(formats::Bson.encode(&Option::<u8>::None).is_err());
assert!(formats::Bson.encode(&u128::MAX).is_err());
assert!(formats::Bson.encode(&i128::MIN).is_err());
let mut nul_key = nextjson::Map::new();
nul_key.insert("bad\0key".to_string(), nextjson::Value::Null);
assert!(formats::Bson.encode(&nul_key).is_err());
let mut non_finite = nextjson::Map::new();
non_finite.insert("value".to_string(), nextjson::Value::from(f64::INFINITY));
assert!(formats::Bson.encode(&non_finite).is_err());
let invalid_bool = [9, 0, 0, 0, 0x08, b'a', 0, 2, 0];
assert!(formats::Bson
.decode::<nextjson::Value>(&invalid_bool)
.is_err());
let invalid_string_terminator = [14, 0, 0, 0, 0x02, b's', 0, 2, 0, 0, 0, b'a', 1, 0];
assert!(formats::Bson
.decode::<nextjson::Value>(&invalid_string_terminator)
.is_err());
let negative_document_length = [0xff, 0xff, 0xff, 0xff, 0];
assert!(formats::Bson
.decode::<nextjson::Value>(&negative_document_length)
.is_err());
}
#[test]
fn bson_rejects_truncated_documents() {
let mut m = nextjson::Map::new();
m.insert("a".to_string(), nextjson::Value::from(1_i32));
let bytes = formats::Bson.encode(&m).unwrap();
let mut bad = bytes.clone();
bad[0] = 100;
assert!(formats::Bson.decode::<nextjson::Value>(&bad).is_err());
}
#[test]
fn urlform_truncated_percent_escape_is_error_not_panic() {
for input in [
&b"x=%1"[..],
&b"x=%F"[..],
&b"a=%a&b=2"[..],
&b"%"[..],
&b"x=%"[..],
] {
assert!(
formats::UrlForm.decode::<nextjson::Value>(input).is_err(),
"input {input:?} must error, not panic"
);
}
}
#[test]
fn urlform_percent_utf8_roundtrip() {
let bytes = b"q=%C3%A9";
let value: nextjson::Value = formats::UrlForm.decode(bytes).unwrap();
assert_eq!(value["q"], nextjson::Value::from("é"));
let mut m = std::collections::BTreeMap::new();
m.insert("q".to_string(), "é✓".to_string());
roundtrip(&m, formats::UrlForm);
}
#[test]
fn urlform_option_and_value_decode() {
let bytes = b"a=1&b=2";
let value: nextjson::Value = formats::UrlForm.decode(bytes).unwrap();
assert_eq!(value["a"], nextjson::Value::from("1"));
assert_eq!(value["b"], nextjson::Value::from("2"));
let opt: std::collections::BTreeMap<String, Option<i64>> =
formats::UrlForm.decode(bytes).unwrap();
assert_eq!(opt.get("a"), Some(&Some(1)));
assert_eq!(opt.get("b"), Some(&Some(2)));
}
#[test]
fn pickle_large_unsigned_roundtrips() {
roundtrip(&0x8000_0000_u64, formats::Pickle); roundtrip(&0xFFFF_FFFF_u64, formats::Pickle); roundtrip(&0x8000_0000_0000_0000_u64, formats::Pickle); roundtrip(&u64::MAX, formats::Pickle);
roundtrip(&i128::MAX, formats::Pickle);
roundtrip(&(1_i128 << 63), formats::Pickle);
roundtrip(&(-(1_i128 << 63) - 1), formats::Pickle);
}
#[test]
fn pickle_rejects_deep_nesting() {
let mut bytes = vec![0x80, 0x02];
for _ in 0..400 {
bytes.extend_from_slice(&[0x5d, 0x28]); }
bytes.extend_from_slice(&[0x65, 0x2e]); assert!(
formats::Pickle.decode::<nextjson::Value>(&bytes).is_err(),
"deeply nested pickle must be rejected"
);
}
#[test]
fn ron_rejects_deep_some_nesting() {
let mut input = String::new();
for _ in 0..400 {
input.push_str("Some(");
}
input.push('1');
for _ in 0..400 {
input.push(')');
}
assert!(
formats::Ron
.decode::<nextjson::Value>(input.as_bytes())
.is_err(),
"deeply nested Some must be rejected"
);
}
#[test]
fn json5_negative_hex_preserves_sign() {
let value: nextjson::Value = formats::Json5.decode(b"{a: -0x1F, b: +0x10}").unwrap();
assert_eq!(value["a"], nextjson::Value::from(-31_i64));
assert_eq!(value["b"], nextjson::Value::from(16_i64));
}
#[test]
fn csv_scalar_and_utf8_decode() {
let v: i32 = formats::Csv.decode(b"42").unwrap();
assert_eq!(v, 42);
let s: String = formats::Csv.decode("héllo".as_bytes()).unwrap();
assert_eq!(s, "héllo");
let rows = vec![vec!["café".to_string(), "✓".to_string()]];
let bytes = formats::Csv.encode(&rows).unwrap();
let back: Vec<Vec<String>> = formats::Csv.decode(&bytes).unwrap();
assert_eq!(back, rows);
}
#[test]
fn hjson_inline_comment_in_unquoted_value() {
let value: nextjson::Value = formats::Hjson.decode(b"{ a: hello # comment\n}\n").unwrap();
assert_eq!(value["a"], nextjson::Value::from("hello"));
}
#[test]
fn yaml_quoted_scalar_keeps_hash() {
let value: nextjson::Value = formats::Yaml.decode(br#"a: "hello # world""#).unwrap();
assert_eq!(value["a"], nextjson::Value::from("hello # world"));
}
#[test]
fn yaml_dash_mapping_key_not_swallowed() {
let value: nextjson::Value = formats::Yaml.decode(b"a:\n ---: x\n b: y\n").unwrap();
assert_eq!(value["a"]["---"], nextjson::Value::from("x"));
assert_eq!(value["a"]["b"], nextjson::Value::from("y"));
}
#[test]
fn yaml_sequence_item_nested_block() {
let input = b"- name: x\n details:\n a: 1\n- name: y\n";
let value: nextjson::Value = formats::Yaml.decode(&input[..]).unwrap();
assert_eq!(value[0]["name"], nextjson::Value::from("x"));
assert_eq!(value[0]["details"]["a"], nextjson::Value::from(1_i64));
assert_eq!(value[1]["name"], nextjson::Value::from("y"));
}