extern crate std;
use nanojson::{ParseErrorKind, Parser, SerializeError, Serializer, WriteError};
use nanojson::SliceWriter;
fn ser(f: impl FnOnce(&mut Serializer<&mut SliceWriter<'_>, 8>) -> Result<(), SerializeError<WriteError>>) -> std::string::String {
let mut buf = [0u8; 4096];
let mut w = SliceWriter::new(&mut buf);
let mut s = Serializer::<_, 8>::new(&mut w);
f(&mut s).expect("serialization failed");
let n = w.pos();
core::str::from_utf8(&buf[..n]).expect("output is not utf-8").to_owned()
}
fn parse(src: &[u8], buf: &mut [u8]) -> Result<std::string::String, ParseErrorKind> {
let mut p = Parser::new(src, buf);
p.string().map(|s| s.to_owned()).map_err(|e| e.kind)
}
#[test]
fn ser_all_control_chars() {
let expected: [&str; 32] = [
r#""\u0000""#, r#""\u0001""#, r#""\u0002""#, r#""\u0003""#,
r#""\u0004""#, r#""\u0005""#, r#""\u0006""#, r#""\u0007""#,
r#""\b""#, r#""\t""#, r#""\n""#, r#""\u000b""#,
r#""\f""#, r#""\r""#, r#""\u000e""#, r#""\u000f""#,
r#""\u0010""#, r#""\u0011""#, r#""\u0012""#, r#""\u0013""#,
r#""\u0014""#, r#""\u0015""#, r#""\u0016""#, r#""\u0017""#,
r#""\u0018""#, r#""\u0019""#, r#""\u001a""#, r#""\u001b""#,
r#""\u001c""#, r#""\u001d""#, r#""\u001e""#, r#""\u001f""#,
];
for (byte, &want) in (0u8..=0x1Fu8).zip(expected.iter()) {
let got = ser(|s| s.string_bytes(&[byte]));
assert_eq!(got, want, "byte 0x{byte:02x}");
}
}
#[test]
fn ser_vt_is_unicode_not_backslash_v() {
assert_eq!(ser(|s| s.string_bytes(&[0x0B])), r#""\u000b""#);
assert!(!ser(|s| s.string_bytes(&[0x0B])).contains("\\v"));
}
#[test]
fn ser_printable_ascii_passthrough() {
for byte in 0x20u8..=0x7Eu8 {
if byte == b'"' || byte == b'\\' { continue; }
let got = ser(|s| s.string_bytes(&[byte]));
let inner = &got[1..got.len() - 1]; assert_eq!(inner.as_bytes(), &[byte], "byte 0x{byte:02x} should be literal");
}
}
#[test]
fn ser_named_escapes_quote_and_backslash() {
assert_eq!(ser(|s| s.string_bytes(&[b'"'])), r#""\"""#);
assert_eq!(ser(|s| s.string_bytes(&[b'\\'])), r#""\\""#);
}
#[test]
fn ser_del_escaped() {
assert_eq!(ser(|s| s.string_bytes(&[0x7F])), r#""\u007f""#);
}
#[test]
fn ser_valid_utf8_2byte() {
assert_eq!(ser(|s| s.string("é")), "\"é\"");
assert_eq!(ser(|s| s.string("ñ")), "\"ñ\"");
}
#[test]
fn ser_valid_utf8_3byte() {
assert_eq!(ser(|s| s.string("世")), "\"世\"");
assert_eq!(ser(|s| s.string("→")), "\"→\"");
}
#[test]
fn ser_valid_utf8_4byte() {
assert_eq!(ser(|s| s.string("𝄞")), "\"𝄞\"");
assert_eq!(ser(|s| s.string("😀")), "\"😀\"");
}
#[test]
fn ser_invalid_utf8_lone_continuation() {
assert_eq!(ser(|s| s.string_bytes(&[0x80])), r#""\u0080""#);
assert_eq!(ser(|s| s.string_bytes(&[0xBF])), r#""\u00bf""#);
}
#[test]
fn ser_invalid_utf8_bad_lead() {
assert_eq!(ser(|s| s.string_bytes(&[0xFE])), r#""\u00fe""#);
assert_eq!(ser(|s| s.string_bytes(&[0xFF])), r#""\u00ff""#);
}
#[test]
fn ser_invalid_utf8_truncated_2byte() {
assert_eq!(ser(|s| s.string_bytes(&[0xC3])), r#""\u00c3""#);
assert_eq!(ser(|s| s.string_bytes(&[0xC3, b'A'])), r#""\u00c3A""#);
}
#[test]
fn ser_invalid_utf8_bad_continuation() {
assert_eq!(ser(|s| s.string_bytes(&[0xE2, 0x28, 0xA1])), r#""\u00e2(\u00a1""#);
}
#[test]
fn ser_output_is_always_valid_utf8() {
let test_cases: &[&[u8]] = &[
&[0x80],
&[0xFF],
&[0xE2, 0x28, 0xA1],
&[0xC3],
&[0x00, 0x01, 0x1F],
&[0xF8, 0x80, 0x80, 0x80, 0x80], b"hello \xFF world",
];
for &input in test_cases {
let out = ser(|s| s.string_bytes(input));
assert!(
core::str::from_utf8(out.as_bytes()).is_ok(),
"output is not valid UTF-8 for input {:?}: {:?}", input, out
);
}
}
#[test]
fn parse_all_named_escapes() {
let mut buf = [0u8; 64];
assert_eq!(parse(b"\"\\\"\"", &mut buf).unwrap(), "\""); assert_eq!(parse(b"\"\\\\\"", &mut buf).unwrap(), "\\"); assert_eq!(parse(b"\"\\/\"", &mut buf).unwrap(), "/"); assert_eq!(parse(b"\"\\b\"", &mut buf).unwrap(), "\x08"); assert_eq!(parse(b"\"\\f\"", &mut buf).unwrap(), "\x0C"); assert_eq!(parse(b"\"\\n\"", &mut buf).unwrap(), "\n"); assert_eq!(parse(b"\"\\r\"", &mut buf).unwrap(), "\r"); assert_eq!(parse(b"\"\\t\"", &mut buf).unwrap(), "\t"); }
#[test]
fn parse_u_null() {
let mut buf = [0u8; 8];
let s = parse(b"\"\\u0000\"", &mut buf).unwrap();
assert_eq!(s.as_bytes(), &[0x00]);
}
#[test]
fn parse_u_ascii() {
let mut buf = [0u8; 8];
assert_eq!(parse(b"\"\\u0041\"", &mut buf).unwrap(), "A"); assert_eq!(parse(b"\"\\u007F\"", &mut buf).unwrap(), "\x7F"); assert_eq!(parse(b"\"\\u0022\"", &mut buf).unwrap(), "\""); assert_eq!(parse(b"\"\\u005C\"", &mut buf).unwrap(), "\\"); }
#[test]
fn parse_u_latin1() {
let mut buf = [0u8; 8];
assert_eq!(parse(b"\"\\u00E9\"", &mut buf).unwrap(), "é");
assert_eq!(parse(b"\"\\u00F1\"", &mut buf).unwrap(), "ñ");
assert_eq!(parse(b"\"\\u00e9\"", &mut buf).unwrap(), "é");
}
#[test]
fn parse_u_bmp() {
let mut buf = [0u8; 8];
assert_eq!(parse(b"\"\\u4E16\"", &mut buf).unwrap(), "世");
assert_eq!(parse(b"\"\\u2192\"", &mut buf).unwrap(), "→");
}
#[test]
fn parse_u_named_equivalence() {
let mut buf = [0u8; 8];
assert_eq!(parse(b"\"\\u0008\"", &mut buf).unwrap(), "\x08"); assert_eq!(parse(b"\"\\u0009\"", &mut buf).unwrap(), "\t"); assert_eq!(parse(b"\"\\u000A\"", &mut buf).unwrap(), "\n"); assert_eq!(parse(b"\"\\u000D\"", &mut buf).unwrap(), "\r"); assert_eq!(parse(b"\"\\u000C\"", &mut buf).unwrap(), "\x0C"); }
#[test]
fn parse_u_mixed_string() {
let mut buf = [0u8; 32];
assert_eq!(
parse(b"\"\\u0048\\u0065\\u006C\\u006C\\u006F\"", &mut buf).unwrap(),
"Hello"
);
}
#[test]
fn parse_u_surrogate_pair() {
let mut buf = [0u8; 8];
assert_eq!(parse(b"\"\\uD83D\\uDE00\"", &mut buf).unwrap(), "😀");
assert_eq!(parse(b"\"\\uD834\\uDD1E\"", &mut buf).unwrap(), "𝄞");
}
#[test]
fn parse_u_surrogate_pair_max() {
let mut buf = [0u8; 8];
let s = parse(b"\"\\uDBFF\\uDFFF\"", &mut buf).unwrap();
assert_eq!(s.as_bytes(), &[0xF4, 0x8F, 0xBF, 0xBF]);
}
#[test]
fn parse_u_surrogate_pair_min() {
let mut buf = [0u8; 8];
let s = parse(b"\"\\uD800\\uDC00\"", &mut buf).unwrap();
assert_eq!(s.as_bytes(), &[0xF0, 0x90, 0x80, 0x80]);
}
#[test]
fn parse_u_hex_case_insensitive() {
let mut buf = [0u8; 8];
assert_eq!(parse(b"\"\\u004e\"", &mut buf).unwrap(), "N");
assert_eq!(parse(b"\"\\u004E\"", &mut buf).unwrap(), "N");
assert_eq!(parse(b"\"\\uD83d\\ude00\"", &mut buf).unwrap(), "😀");
assert_eq!(parse(b"\"\\uD83D\\uDE00\"", &mut buf).unwrap(), "😀");
}
#[test]
fn parse_raw_utf8_passthrough() {
let mut buf = [0u8; 64];
assert_eq!(parse("\"café\"".as_bytes(), &mut buf).unwrap(), "café");
assert_eq!(parse("\"日本語\"".as_bytes(), &mut buf).unwrap(), "日本語");
assert_eq!(parse("\"😀\"".as_bytes(), &mut buf).unwrap(), "😀");
}
#[test]
fn parse_err_unknown_escape() {
let mut buf = [0u8; 16];
assert!(matches!(parse(b"\"\\q\"", &mut buf), Err(ParseErrorKind::InvalidEscape(b'q'))));
assert!(matches!(parse(b"\"\\x41\"", &mut buf), Err(ParseErrorKind::InvalidEscape(b'x'))));
assert!(matches!(parse(b"\"\\a\"", &mut buf), Err(ParseErrorKind::InvalidEscape(b'a'))));
}
#[test]
fn parse_err_lone_high_surrogate() {
let mut buf = [0u8; 16];
assert!(matches!(
parse(b"\"\\uD800\"", &mut buf),
Err(ParseErrorKind::InvalidEscape(b'u'))
));
assert!(matches!(
parse(b"\"\\uD800\\u0041\"", &mut buf),
Err(ParseErrorKind::InvalidEscape(b'u'))
));
assert!(matches!(
parse(b"\"\\uD800X\"", &mut buf),
Err(ParseErrorKind::InvalidEscape(b'u'))
));
}
#[test]
fn parse_err_lone_low_surrogate() {
let mut buf = [0u8; 16];
assert!(matches!(
parse(b"\"\\uDC00\"", &mut buf),
Err(ParseErrorKind::InvalidEscape(b'u'))
));
assert!(matches!(
parse(b"\"\\uDFFF\"", &mut buf),
Err(ParseErrorKind::InvalidEscape(b'u'))
));
}
#[test]
fn parse_err_non_hex_in_u_escape() {
let mut buf = [0u8; 16];
assert!(matches!(
parse(b"\"\\u004g\"", &mut buf),
Err(ParseErrorKind::InvalidEscape(b'u'))
));
assert!(matches!(
parse(b"\"\\u00 0\"", &mut buf),
Err(ParseErrorKind::InvalidEscape(b'u'))
));
}
#[test]
fn parse_err_truncated_u_escape() {
let mut buf = [0u8; 16];
assert!(matches!(
parse(b"\"\\u004", &mut buf),
Err(ParseErrorKind::UnexpectedEof)
));
assert!(matches!(
parse(b"\"\\u00\"", &mut buf),
Err(ParseErrorKind::UnexpectedEof)
));
}
#[test]
fn parse_err_eof_in_string() {
let mut buf = [0u8; 16];
assert!(matches!(parse(b"\"unterminated", &mut buf), Err(ParseErrorKind::UnexpectedEof)));
assert!(matches!(parse(b"\"", &mut buf), Err(ParseErrorKind::UnexpectedEof)));
}
#[test]
fn roundtrip_control_chars() {
for byte in 0x00u8..=0x1Fu8 {
let encoded = ser(|s| s.string_bytes(&[byte]));
let mut buf = [0u8; 32];
let mut p = Parser::new(encoded.as_bytes(), &mut buf);
let decoded = p.string()
.map(|s| s.to_owned())
.expect(&std::format!("failed to parse encoded byte 0x{byte:02x}: {encoded:?}"));
assert_eq!(decoded.as_bytes(), &[byte], "round-trip failed for byte 0x{byte:02x}");
}
}
#[test]
fn roundtrip_unicode_strings() {
let inputs = ["café", "日本語", "😀", "𝄞", "→", "\u{10FFFF}"];
for &s in &inputs {
let encoded = ser(|ser| ser.string(s));
let mut buf = [0u8; 64];
let mut p = Parser::new(encoded.as_bytes(), &mut buf);
let decoded = p.string()
.map(|s| s.to_owned())
.expect(&std::format!("parse failed for {s:?}: {encoded:?}"));
assert_eq!(decoded, s, "round-trip failed for {s:?}");
}
}
#[test]
fn roundtrip_special_ascii() {
let inputs = ["\"", "\\", "/", "\"hello\\world\"", "a\nb\tc\rd"];
for &s in &inputs {
let encoded = ser(|ser| ser.string(s));
let mut buf = [0u8; 64];
let mut p = Parser::new(encoded.as_bytes(), &mut buf);
let decoded = p.string()
.map(|s| s.to_owned())
.expect(&std::format!("parse failed for {s:?}: {encoded:?}"));
assert_eq!(decoded, s, "round-trip failed for {s:?}");
}
}
#[cfg(feature = "std")]
#[test]
fn json_in_json_nested_roundtrip() {
use nanojson::{parse_as, stringify_as};
let name = "café ☕ → 𝄞";
let count = 42i64;
let tags = ["utf8", "日本語", "😀"];
let message = "\"escape test\"\n\t\x00back\\slash/forward";
let initial = stringify_as(|s| {
s.object_begin()?;
s.member("name")?; s.string(name)?;
s.member("count")?; s.integer(count)?;
s.member("tags")?;
s.array_begin()?;
for tag in &tags { s.string(tag)?; }
s.array_end()?;
s.member("message")?; s.string(message)?;
s.object_end()
}).unwrap();
let mut encoded = initial.clone();
for level in 1i64..=3 {
let next = stringify_as(|s| {
s.object_begin()?;
s.member("level")?; s.integer(level)?;
s.member("payload")?; s.string(&encoded)?;
s.object_end()
}).unwrap();
encoded = next;
}
let mut decoded = encoded;
for _ in 0..3 {
decoded = parse_as(decoded.as_bytes(), |p| {
p.object_begin()?;
let mut payload = std::string::String::new();
while let Some(key) = p.member()? {
let key = std::string::String::from(key);
if key == "payload" {
payload = std::string::String::from(p.string()?);
} else {
let _ = p.number_str()?; }
}
p.object_end()?;
Ok(payload)
}).unwrap();
}
assert_eq!(decoded, initial, "unwrapped JSON does not match initial");
parse_as(decoded.as_bytes(), |p| {
p.object_begin()?;
let mut got_name = std::string::String::new();
let mut got_count = 0i64;
let mut got_tags = std::vec::Vec::<std::string::String>::new();
let mut got_message = std::string::String::new();
while let Some(key) = p.member()? {
let key = std::string::String::from(key);
match key.as_str() {
"name" => got_name = std::string::String::from(p.string()?),
"count" => got_count = p.integer()?,
"tags" => {
p.array_begin()?;
while p.array_item()? {
got_tags.push(std::string::String::from(p.string()?));
}
p.array_end()?;
}
"message" => got_message = std::string::String::from(p.string()?),
_ => {}
}
}
p.object_end()?;
assert_eq!(got_name, name, "name field changed");
assert_eq!(got_count, count, "count field changed");
assert_eq!(got_tags, tags, "tags field changed");
assert_eq!(got_message, message, "message field changed");
Ok(())
}).unwrap();
}
#[test]
fn roundtrip_parse_unicode_escape_encoded_output() {
let mut buf = [0u8; 64];
assert_eq!(
parse(b"\"\\u0063\\u0061\\u0066\\u00E9\"", &mut buf).unwrap(),
"café"
);
assert_eq!(
parse(b"\"\\uD83D\\uDE00\"", &mut buf).unwrap(),
"😀"
);
}