#![cfg_attr(not(feature = "std"), no_std)]
#![allow(unsafe_code)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "derive")]
extern crate self as json_bourne;
mod casing;
mod de;
mod error;
mod event;
#[cfg(feature = "alloc")]
mod float;
mod lexer;
mod parser;
mod ser;
#[cfg(all(test, feature = "std"))]
mod teju_gen;
#[doc(hidden)]
pub use casing::{Casing as __Casing, Renamed as __Renamed};
pub use de::{FromJson, parse, parse_str};
#[cfg(feature = "alloc")]
pub use de::{MapKey, key_to_cow};
pub use error::{Error, ErrorKind, LineColumn, Position};
pub use event::{Event, JsonNum, JsonStr, MAX_INPUT_LEN};
pub use lexer::{Checkpoint, DEFAULT_MAX_DEPTH, Lexer, ValueKind};
pub use parser::Parser;
#[cfg(feature = "alloc")]
pub use ser::{
ByteSink, FmtWriteSink, MapKeyOut, PrettyStringSink, StringSink, to_fmt, to_string,
to_string_pretty, to_vec,
};
#[cfg(feature = "std")]
pub use ser::{IoWriteSink, to_writer};
pub use ser::{JsonWrite, ToJson};
#[cfg(feature = "derive")]
pub use bourne_derive::{FromJson, ToJson};
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn object_first_key_then_next_event_for_value() {
let mut p: Parser<'_> = Parser::new(br#"{"flag":true}"#);
let start = p.next_event().unwrap().unwrap();
assert!(matches!(start, Event::StartObject));
let key = p.object_first_key().unwrap().unwrap();
assert_eq!(key, "flag");
let val = p.next_event().unwrap().unwrap();
assert_eq!(val, Event::Bool(true));
let close = p.next_event().unwrap().unwrap();
assert!(matches!(close, Event::EndObject));
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn object_next_key_handoff_to_next_event() {
let mut p: Parser<'_> = Parser::new(br#"{"a":1,"b":true}"#);
let _ = p.next_event().unwrap().unwrap();
let k1 = p.object_first_key().unwrap().unwrap();
assert_eq!(k1, "a");
let v1 = p.parse_i64_value().unwrap();
assert_eq!(v1, 1);
let k2 = p.object_next_key().unwrap().unwrap();
assert_eq!(k2, "b");
let v2 = p.next_event().unwrap().unwrap();
assert_eq!(v2, Event::Bool(true));
assert!(p.object_next_key().unwrap().is_none());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn primitives_round_through_typed_parse() {
assert!(parse_str::<bool>("true").unwrap());
assert!(!parse_str::<bool>("false").unwrap());
assert_eq!(parse_str::<i32>("-7").unwrap(), -7);
assert_eq!(parse_str::<u64>("9999999999").unwrap(), 9_999_999_999);
let f: f64 = parse_str("1.5e2").unwrap();
assert!((f - 150.0).abs() < f64::EPSILON);
assert_eq!(parse_str::<&str>("\"hello\"").unwrap(), "hello");
}
#[test]
fn integer_overflow_is_rejected_at_parse_site() {
let r = parse_str::<u8>("256");
assert_eq!(r.unwrap_err().kind, ErrorKind::NumberOutOfRange);
}
#[test]
fn type_mismatch_is_rejected_at_parse_site() {
let r = parse_str::<u32>("\"five\"");
assert_eq!(r.unwrap_err().kind, ErrorKind::ExpectedNumber);
}
#[test]
fn option_handles_null_and_value() {
let none: Option<u32> = parse_str("null").unwrap();
assert_eq!(none, None);
let some: Option<u32> = parse_str("17").unwrap();
assert_eq!(some, Some(17));
}
#[test]
fn fixed_array_exact_length() {
let arr: [i32; 3] = parse_str("[1,2,3]").unwrap();
assert_eq!(arr, [1, 2, 3]);
}
#[test]
fn fixed_array_too_short_errors() {
let r = parse_str::<[i32; 3]>("[1,2]");
assert!(r.is_err());
}
#[test]
fn fixed_array_too_long_errors() {
let r = parse_str::<[i32; 3]>("[1,2,3,4]");
assert!(r.is_err());
}
#[test]
fn tuple_heterogeneous() {
let v: (i32, &str, bool) = parse_str(r#"[1, "hi", true]"#).unwrap();
assert_eq!(v, (1, "hi", true));
}
#[test]
fn borrowed_str_lifetime() {
let input = String::from(r#""borrowed""#);
let s: &str = parse_str(&input).unwrap();
assert_eq!(s, "borrowed");
}
#[test]
fn rejects_trailing_data_at_typed_layer() {
let r = parse_str::<u32>("1 2");
assert!(r.is_err());
}
#[cfg(feature = "std")]
#[test]
fn duration_round_trips_fractional_seconds() {
use std::time::Duration;
let d: Duration = parse_str("1.5").unwrap();
assert_eq!(d, Duration::new(1, 500_000_000));
let d: Duration = parse_str("0").unwrap();
assert_eq!(d, Duration::ZERO);
assert!(parse_str::<Duration>("-1").is_err());
}
#[cfg(feature = "std")]
#[test]
fn system_time_round_trips_around_epoch() {
use std::time::{Duration, SystemTime, UNIX_EPOCH};
let t: SystemTime = parse_str("0").unwrap();
assert_eq!(t, UNIX_EPOCH);
let t: SystemTime = parse_str("1.5").unwrap();
assert_eq!(t, UNIX_EPOCH + Duration::new(1, 500_000_000));
let t: SystemTime = parse_str("-2").unwrap();
assert_eq!(t, UNIX_EPOCH - Duration::from_secs(2));
let s = to_string(&(UNIX_EPOCH + Duration::from_secs(42))).unwrap();
let back: SystemTime = parse_str(&s).unwrap();
assert_eq!(back, UNIX_EPOCH + Duration::from_secs(42));
}
#[cfg(feature = "std")]
#[test]
fn ip_and_socket_addrs_parse_from_strings() {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
let v4: Ipv4Addr = parse_str(r#""127.0.0.1""#).unwrap();
assert_eq!(v4, Ipv4Addr::LOCALHOST);
let v6: Ipv6Addr = parse_str(r#""::1""#).unwrap();
assert_eq!(v6, Ipv6Addr::LOCALHOST);
let ip: IpAddr = parse_str(r#""10.0.0.1""#).unwrap();
assert!(matches!(ip, IpAddr::V4(_)));
let sa: SocketAddr = parse_str(r#""127.0.0.1:8080""#).unwrap();
assert_eq!(sa.port(), 8080);
assert!(parse_str::<Ipv4Addr>(r#""not-an-ip""#).is_err());
}
#[cfg(feature = "std")]
#[test]
fn pathbuf_parses_from_string() {
use std::path::PathBuf;
let p: PathBuf = parse_str(r#""/etc/hosts""#).unwrap();
assert_eq!(p, PathBuf::from("/etc/hosts"));
}
#[cfg(feature = "std")]
#[test]
fn hashmap_string_key() {
use std::collections::HashMap;
let m: HashMap<String, i32> = parse_str(r#"{"a":1,"b":2}"#).unwrap();
assert_eq!(m.get("a"), Some(&1));
assert_eq!(m.get("b"), Some(&2));
assert_eq!(m.len(), 2);
}
#[cfg(feature = "std")]
#[test]
fn hashmap_borrowed_key_zero_copy() {
use std::collections::HashMap;
let input = String::from(r#"{"alpha":1,"beta":2}"#);
let m: HashMap<&str, i32> = parse_str(&input).unwrap();
let input_start = input.as_ptr() as usize;
let input_end = input_start + input.len();
for k in m.keys() {
let p = k.as_ptr() as usize;
assert!((input_start..input_end).contains(&p), "key was copied");
}
}
#[cfg(feature = "alloc")]
#[test]
fn btreemap_round_trips() {
use std::collections::BTreeMap;
let m: BTreeMap<String, Vec<i32>> = parse_str(r#"{"x":[1,2],"y":[3]}"#).unwrap();
assert_eq!(m["x"], vec![1, 2]);
assert_eq!(m["y"], vec![3]);
}
#[cfg(feature = "std")]
#[test]
fn map_rejects_duplicate_key() {
use std::collections::HashMap;
let r: Result<HashMap<String, i32>, _> = parse_str(r#"{"a":1,"a":2}"#);
assert_eq!(r.unwrap_err().kind, ErrorKind::DuplicateKey);
}
#[cfg(feature = "std")]
#[test]
fn map_rejects_non_object() {
use std::collections::HashMap;
let r: Result<HashMap<String, i32>, _> = parse_str("[1,2]");
assert!(r.is_err());
}
#[cfg(feature = "std")]
#[test]
fn hashset_round_trips() {
use std::collections::HashSet;
let s: HashSet<i32> = parse_str("[1,2,3,2,1]").unwrap();
assert_eq!(s.len(), 3);
assert!(s.contains(&1));
assert!(s.contains(&2));
assert!(s.contains(&3));
}
#[cfg(feature = "alloc")]
#[test]
fn btreeset_round_trips() {
use std::collections::BTreeSet;
let s: BTreeSet<i32> = parse_str("[3,1,2]").unwrap();
assert_eq!(s.into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
}
#[cfg(feature = "alloc")]
#[test]
fn box_rc_arc_are_transparent_wrappers() {
let b: Box<u32> = parse_str("42").unwrap();
assert_eq!(*b, 42);
let r: std::rc::Rc<&str> = parse_str(r#""hello""#).unwrap();
assert_eq!(*r, "hello");
let a: std::sync::Arc<Vec<i32>> = parse_str("[1,2,3]").unwrap();
assert_eq!(*a, vec![1, 2, 3]);
}
#[cfg(feature = "alloc")]
#[test]
fn char_accepts_single_scalar() {
assert_eq!(parse_str::<char>(r#""a""#).unwrap(), 'a');
assert_eq!(parse_str::<char>(r#""中""#).unwrap(), '中');
assert_eq!(parse_str::<char>(r#""\n""#).unwrap(), '\n');
assert_eq!(parse_str::<char>(r#""😀""#).unwrap(), '😀');
}
#[cfg(feature = "alloc")]
#[test]
#[allow(clippy::unicode_not_nfc)] fn char_rejects_empty_or_multiple() {
assert!(parse_str::<char>(r#""""#).is_err());
assert!(parse_str::<char>(r#""ab""#).is_err());
assert!(parse_str::<char>(r#""é""#).is_err());
assert!(parse_str::<char>("42").is_err());
}
#[test]
fn i128_round_trips_full_range() {
assert_eq!(parse_str::<i128>("0").unwrap(), 0);
assert_eq!(
parse_str::<i128>("170141183460469231731687303715884105727").unwrap(),
i128::MAX,
);
assert_eq!(
parse_str::<i128>("-170141183460469231731687303715884105728").unwrap(),
i128::MIN,
);
assert!(parse_str::<i128>("170141183460469231731687303715884105728").is_err());
}
#[test]
fn u128_round_trips_full_range() {
assert_eq!(parse_str::<u128>("0").unwrap(), 0);
assert_eq!(
parse_str::<u128>("340282366920938463463374607431768211455").unwrap(),
u128::MAX,
);
assert!(parse_str::<u128>("340282366920938463463374607431768211456").is_err());
assert!(parse_str::<u128>("-1").is_err());
}
#[test]
fn i128_38_vs_39_digit_boundary() {
let thirty_eight_nines = "9".repeat(38);
let v: i128 = parse_str(&thirty_eight_nines).unwrap();
assert_eq!(v.to_string(), thirty_eight_nines);
let ten_pow_38 = format!("1{}", "0".repeat(38));
let v: i128 = parse_str(&ten_pow_38).unwrap();
assert_eq!(v.to_string(), ten_pow_38);
let thirty_nine_nines = "9".repeat(39);
assert!(parse_str::<i128>(&thirty_nine_nines).is_err());
}
#[test]
fn vec_i128_round_trips() {
let v: Vec<i128> = parse_str(
"[0,1,-1,170141183460469231731687303715884105727,-170141183460469231731687303715884105728]",
)
.unwrap();
assert_eq!(v, vec![0, 1, -1, i128::MAX, i128::MIN]);
}
#[test]
fn vec_u128_round_trips() {
let v: Vec<u128> = parse_str("[0,1,2,340282366920938463463374607431768211455]").unwrap();
assert_eq!(v, vec![0, 1, 2, u128::MAX]);
}
#[test]
fn f32_rejects_overflow_to_infinity() {
let r = parse_str::<f32>("1e40");
assert_eq!(r.unwrap_err().kind, ErrorKind::NumberOutOfRange);
let r = parse_str::<f32>("-1e40");
assert_eq!(r.unwrap_err().kind, ErrorKind::NumberOutOfRange);
let v: f32 = parse_str("3.4028235e38").unwrap();
assert!(v.is_finite());
}
#[test]
fn f64_rejects_overflow_to_infinity() {
let r = parse_str::<f64>("1e400");
assert_eq!(r.unwrap_err().kind, ErrorKind::NumberOutOfRange);
let r = parse_str::<f64>("-1e400");
assert_eq!(r.unwrap_err().kind, ErrorKind::NumberOutOfRange);
}
#[test]
fn i64_min_is_parseable() {
assert_eq!(parse_str::<i64>("-9223372036854775808").unwrap(), i64::MIN);
assert_eq!(
parse_str::<i64>("-9223372036854775807").unwrap(),
i64::MIN + 1,
);
assert_eq!(parse_str::<i64>("9223372036854775807").unwrap(), i64::MAX);
assert!(parse_str::<i64>("-9223372036854775809").is_err());
assert!(parse_str::<i64>("9223372036854775808").is_err());
let v: Vec<i64> = parse_str("[-9223372036854775808]").unwrap();
assert_eq!(v, vec![i64::MIN]);
}
#[cfg(feature = "alloc")]
#[test]
fn vec_and_string() {
let v: Vec<i32> = parse_str("[10, 20, 30]").unwrap();
assert_eq!(v, vec![10, 20, 30]);
let s: String = parse_str(r#""owned""#).unwrap();
assert_eq!(s, "owned");
}
#[cfg(feature = "alloc")]
#[test]
fn nested_vec() {
let v: Vec<Vec<i32>> = parse_str("[[1,2],[3]]").unwrap();
assert_eq!(v, vec![vec![1, 2], vec![3]]);
}
#[cfg(feature = "alloc")]
#[test]
fn string_decodes_simple_escapes() {
let s: String = parse_str(r#""line1\nline2\ttab\"quote\\back\/slash""#).unwrap();
assert_eq!(s, "line1\nline2\ttab\"quote\\back/slash");
}
#[cfg(feature = "alloc")]
#[test]
fn string_decodes_b_and_f_escapes() {
let s: String = parse_str(r#""\b\f""#).unwrap();
assert_eq!(s, "\u{0008}\u{000C}");
}
#[cfg(feature = "alloc")]
#[test]
fn string_decodes_unicode_bmp_escape() {
let s: String = parse_str(r#""café 中文 ~""#).unwrap();
assert_eq!(s, "café 中文 ~");
}
#[cfg(feature = "alloc")]
#[test]
fn string_decodes_surrogate_pair() {
let s: String = parse_str(r#""hi 😀 there""#).unwrap();
assert_eq!(s, "hi 😀 there");
}
#[cfg(feature = "alloc")]
#[test]
fn string_rejects_lone_surrogate() {
let r = parse_str::<String>(r#""\uD800""#);
assert_eq!(r.unwrap_err().kind, ErrorKind::UnpairedSurrogate);
let r = parse_str::<String>(r#""\uDC00""#);
assert_eq!(r.unwrap_err().kind, ErrorKind::UnpairedSurrogate);
}
#[cfg(feature = "alloc")]
#[test]
fn string_rejects_high_surrogate_then_non_low() {
let r = parse_str::<String>(r#""\uD800A""#);
assert_eq!(r.unwrap_err().kind, ErrorKind::UnpairedSurrogate);
}
#[cfg(feature = "alloc")]
#[test]
fn string_decodes_long_mixed_input() {
let json = r#""one\ttwo\nthreeAfour\\five\"six""#;
let s: String = parse_str(json).unwrap();
assert_eq!(s, "one\ttwo\nthreeAfour\\five\"six");
}
#[cfg(feature = "alloc")]
#[test]
fn vec_string_with_escapes_roundtrips() {
let json = r#"["","a","\n","mixéd","\\\"\/"]"#;
let v: Vec<String> = parse_str(json).unwrap();
assert_eq!(v, vec!["", "a", "\n", "mixéd", "\\\"/"]);
}
#[cfg(feature = "alloc")]
#[test]
fn cow_borrows_when_no_escapes() {
use std::borrow::Cow;
let input = String::from(r#""borrowed""#);
let c: Cow<'_, str> = parse_str(&input).unwrap();
assert_eq!(c, "borrowed");
match c {
Cow::Borrowed(s) => {
let input_start = input.as_ptr() as usize;
let input_end = input_start + input.len();
let s_ptr = s.as_ptr() as usize;
assert!(
(input_start..input_end).contains(&s_ptr),
"Cow::Borrowed pointer should be inside input",
);
}
Cow::Owned(_) => panic!("expected Borrowed for un-escaped input"),
}
}
#[cfg(feature = "alloc")]
#[test]
fn cow_owns_when_escapes_present() {
use std::borrow::Cow;
let c: Cow<'_, str> = parse_str(r#""line\nwrap""#).unwrap();
assert_eq!(c, "line\nwrap");
assert!(matches!(c, Cow::Owned(_)));
}
#[test]
fn parser_object_key_lex_with_escapes() {
let input = br#"{"a\nb":1,"c":2}"#;
let mut p: Parser<'_> = Parser::new(input);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let k1 = p.object_first_key_lex().unwrap().unwrap();
assert!(k1.has_escapes());
assert_eq!(p.parse_i64_value().unwrap(), 1);
let k2 = p.object_next_key_lex().unwrap().unwrap();
assert!(!k2.has_escapes());
assert_eq!(p.parse_i64_value().unwrap(), 2);
assert!(p.object_next_key_lex().unwrap().is_none());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_object_key_lex_empty() {
let mut p: Parser<'_> = Parser::new(b"{}");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(p.object_first_key_lex().unwrap().is_none());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_array_start_continue() {
let mut p: Parser<'_> = Parser::new(b"[1,2,3]");
assert!(!p.array_start().unwrap());
assert_eq!(p.parse_i64_value().unwrap(), 1);
assert!(!p.array_continue(b']').unwrap());
assert_eq!(p.parse_i64_value().unwrap(), 2);
assert!(!p.array_continue(b']').unwrap());
assert_eq!(p.parse_i64_value().unwrap(), 3);
assert!(p.array_continue(b']').unwrap());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_array_start_empty() {
let mut p: Parser<'_> = Parser::new(b"[]");
assert!(p.array_start().unwrap());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_array_nested_in_object() {
let mut p: Parser<'_> = Parser::new(br#"{"v":[1,2]}"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let key = p.object_first_key().unwrap().unwrap();
assert_eq!(key, "v");
assert!(!p.array_start().unwrap());
assert_eq!(p.parse_i64_value().unwrap(), 1);
assert!(!p.array_continue(b']').unwrap());
assert_eq!(p.parse_i64_value().unwrap(), 2);
assert!(p.array_continue(b']').unwrap());
assert!(p.object_next_key().unwrap().is_none());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn error_kind_display_covers_all_variants() {
use alloc::format;
let cases: &[(ErrorKind, &str)] = &[
(ErrorKind::UnexpectedEof, "unexpected end of input"),
(ErrorKind::InvalidEscape, "invalid string escape"),
(ErrorKind::InvalidUnicodeEscape, "invalid \\u escape"),
(
ErrorKind::UnpairedSurrogate,
"unpaired UTF-16 surrogate in \\u escape",
),
(ErrorKind::InvalidUtf8, "invalid UTF-8"),
(ErrorKind::InvalidNumber, "invalid number literal"),
(
ErrorKind::NumberOutOfRange,
"number does not fit target type",
),
(
ErrorKind::ControlCharInString,
"control character in string literal",
),
(ErrorKind::TrailingData, "trailing data after JSON value"),
(
ErrorKind::DepthLimitExceeded,
"nesting depth limit exceeded",
),
(ErrorKind::ExpectedValue, "expected JSON value"),
(ErrorKind::ExpectedString, "expected string"),
(ErrorKind::ExpectedNumber, "expected number"),
(ErrorKind::ExpectedBool, "expected boolean"),
(ErrorKind::ExpectedNull, "expected null"),
(ErrorKind::ExpectedArray, "expected array"),
(ErrorKind::ExpectedObject, "expected object"),
(ErrorKind::TypeMismatch, "type mismatch"),
(ErrorKind::DuplicateKey, "duplicate object key"),
(ErrorKind::MissingField, "missing required field"),
(ErrorKind::UnknownField, "unknown field"),
(
ErrorKind::NonFiniteFloat,
"non-finite float not representable in JSON",
),
(ErrorKind::UnexpectedByte(0x7B), "unexpected byte 0x7b"),
];
for (kind, expected) in cases {
assert_eq!(format!("{kind}"), *expected, "mismatch for {kind:?}");
}
}
#[test]
fn utf8_f4_leading_byte_valid() {
let s = "\"\u{100000}\"";
let v: &str = parse_str(s).unwrap();
assert_eq!(v, "\u{100000}");
}
#[test]
fn utf8_f4_leading_byte_invalid_continuation() {
let mut bytes = b"\"".to_vec();
bytes.push(0xF4);
bytes.push(0x90); bytes.push(0x80);
bytes.push(0x80);
bytes.push(b'"');
let r = parse::<&str>(&bytes);
assert_eq!(r.unwrap_err().kind, ErrorKind::InvalidUtf8);
}
#[test]
fn utf8_multibyte_all_leading_ranges() {
let cases: &[&str] = &[
"\"\u{0080}\"", "\"\u{0800}\"", "\"\u{1000}\"", "\"\u{D7FF}\"", "\"\u{E000}\"", "\"\u{10000}\"", "\"\u{40000}\"", "\"\u{100000}\"", ];
for input in cases {
let v: &str = parse_str(input).unwrap();
let expected = &input[1..input.len() - 1];
assert_eq!(v, expected);
}
}
#[test]
fn serialize_all_control_bytes() {
for b in 0x00_u8..=0x1F {
let s = alloc::string::String::from(b as char);
let json = to_string(&s).unwrap();
assert!(json.starts_with('"') && json.ends_with('"'));
let inner = &json[1..json.len() - 1];
match b {
b'"' | b'\\' => unreachable!(),
b'\n' => assert_eq!(inner, "\\n"),
b'\r' => assert_eq!(inner, "\\r"),
b'\t' => assert_eq!(inner, "\\t"),
0x08 => assert_eq!(inner, "\\b"),
0x0C => assert_eq!(inner, "\\f"),
_ => {
let expected = alloc::format!("\\u00{b:02x}");
assert_eq!(inner, expected, "byte 0x{b:02x}");
}
}
}
}
#[test]
fn next_event_rejects_trailing_comma_in_array() {
let mut p: Parser<'_> = Parser::new(b"[1,]");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
let _ = p.next_event().unwrap().unwrap(); let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b']'));
}
#[test]
fn next_event_rejects_trailing_comma_in_object() {
let mut p: Parser<'_> = Parser::new(br#"{"a":1,}"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_)));
let _ = p.next_event().unwrap().unwrap(); let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b'}'));
}
#[test]
fn next_event_object_colon_eof() {
let mut p: Parser<'_> = Parser::new(br#"{"a""#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_)));
let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn next_event_object_colon_wrong_byte() {
let mut p: Parser<'_> = Parser::new(br#"{"a";"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_)));
let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b';'));
}
#[test]
fn next_event_empty_input() {
let mut p: Parser<'_> = Parser::new(b"");
let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn next_event_eof_inside_array() {
let mut p: Parser<'_> = Parser::new(b"[");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn next_event_eof_after_array_comma() {
let mut p: Parser<'_> = Parser::new(b"[1,");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
let _ = p.next_event().unwrap().unwrap(); let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn next_event_bad_byte_after_array_value() {
let mut p: Parser<'_> = Parser::new(b"[1;");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
let _ = p.next_event().unwrap().unwrap(); let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b';'));
}
#[test]
fn next_event_eof_in_object_key_position() {
let mut p: Parser<'_> = Parser::new(b"{");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn next_event_bad_byte_in_object_key_position() {
let mut p: Parser<'_> = Parser::new(b"{1");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b'1'));
}
#[test]
fn next_event_eof_after_object_value() {
let mut p: Parser<'_> = Parser::new(br#"{"a":1"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_)));
let _ = p.next_event().unwrap().unwrap(); let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn next_event_bad_byte_after_object_value() {
let mut p: Parser<'_> = Parser::new(br#"{"a":1;"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_)));
let _ = p.next_event().unwrap().unwrap(); let err = p.next_event().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b';'));
}
#[test]
fn next_event_object_value_state_via_fast_path() {
let mut p: Parser<'_> = Parser::new(br#"{"x":42,"y":99}"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let k1 = p.object_first_key().unwrap().unwrap();
assert_eq!(k1, "x");
let v1 = p.next_event().unwrap().unwrap();
assert!(matches!(v1, Event::Number(_)));
let k2 = p.object_next_key().unwrap().unwrap();
assert_eq!(k2, "y");
let v2 = p.next_event().unwrap().unwrap();
assert!(matches!(v2, Event::Number(_)));
assert!(p.object_next_key().unwrap().is_none());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn next_event_nested_containers_close_correctly() {
let mut p: Parser<'_> = Parser::new(br#"{"a":[1],"b":{"c":2}}"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_))); assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Number(_)));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_))); assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Key(_))); assert!(matches!(p.next_event().unwrap().unwrap(), Event::Number(_)));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndObject));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndObject));
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn next_event_nested_array_in_array() {
let input = b"[[],[1,2]]";
let mut p: Parser<'_> = Parser::new(input);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Number(_)));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::Number(_)));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn serialize_string_with_quote_and_backslash() {
let s = "a\"b\\c";
let json = to_string(&s).unwrap();
assert_eq!(json, r#""a\"b\\c""#);
}
#[test]
fn serialize_string_with_all_named_escapes() {
let s = "\x08\x0C\n\r\t";
let json = to_string(&s).unwrap();
assert_eq!(json, r#""\b\f\n\r\t""#);
}
#[test]
fn parser_object_first_key_empty_inside_array() {
let mut p: Parser<'_> = Parser::new(b"[{}]");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(p.object_first_key().unwrap().is_none());
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_object_first_key_empty_inside_object() {
let mut p: Parser<'_> = Parser::new(br#"{"a":{}}"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let k = p.object_first_key().unwrap().unwrap();
assert_eq!(k, "a");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(p.object_first_key().unwrap().is_none());
assert!(p.object_next_key().unwrap().is_none());
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_object_first_key_lex_empty_inside_array() {
let mut p: Parser<'_> = Parser::new(b"[{}]");
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
assert!(p.object_first_key_lex().unwrap().is_none());
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_object_next_key_close_inside_array() {
let mut p: Parser<'_> = Parser::new(br#"[{"a":1}]"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let k = p.object_first_key().unwrap().unwrap();
assert_eq!(k, "a");
assert_eq!(p.parse_i64_value().unwrap(), 1);
assert!(p.object_next_key().unwrap().is_none());
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn parser_object_next_key_lex_close_inside_array() {
let mut p: Parser<'_> = Parser::new(br#"[{"a":1}]"#);
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartArray
));
assert!(matches!(
p.next_event().unwrap().unwrap(),
Event::StartObject
));
let k = p.object_first_key_lex().unwrap().unwrap();
assert!(!k.has_escapes());
assert_eq!(p.parse_i64_value().unwrap(), 1);
assert!(p.object_next_key_lex().unwrap().is_none());
assert!(matches!(p.next_event().unwrap().unwrap(), Event::EndArray));
assert!(p.next_event().unwrap().is_none());
}
#[test]
fn lexer_object_first_key_bad_byte() {
let mut lex: Lexer<'_> = Lexer::new(b"{1");
lex.object_start().unwrap();
let err = lex.object_first_key().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b'1'));
}
#[test]
fn lexer_object_first_key_eof() {
let mut lex: Lexer<'_> = Lexer::new(b"{");
lex.object_start().unwrap();
let err = lex.object_first_key().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn lexer_object_first_key_lex_bad_byte() {
let mut lex: Lexer<'_> = Lexer::new(b"{1");
lex.object_start().unwrap();
let err = lex.object_first_key_lex().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b'1'));
}
#[test]
fn lexer_object_first_key_lex_eof() {
let mut lex: Lexer<'_> = Lexer::new(b"{");
lex.object_start().unwrap();
let err = lex.object_first_key_lex().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn lexer_object_next_key_bad_byte() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a":1;"#);
lex.object_start().unwrap();
let _ = lex.object_first_key().unwrap();
let _ = lex.parse_i64_value().unwrap();
let err = lex.object_next_key().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b';'));
}
#[test]
fn lexer_object_next_key_eof() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a":1"#);
lex.object_start().unwrap();
let _ = lex.object_first_key().unwrap();
let _ = lex.parse_i64_value().unwrap();
let err = lex.object_next_key().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn lexer_object_next_key_bad_byte_after_comma() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a":1,2"#);
lex.object_start().unwrap();
let _ = lex.object_first_key().unwrap();
let _ = lex.parse_i64_value().unwrap();
let err = lex.object_next_key().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b'2'));
}
#[test]
fn lexer_object_next_key_lex_bad_byte() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a":1;"#);
lex.object_start().unwrap();
let _ = lex.object_first_key_lex().unwrap();
let _ = lex.parse_i64_value().unwrap();
let err = lex.object_next_key_lex().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b';'));
}
#[test]
fn lexer_object_next_key_lex_eof() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a":1"#);
lex.object_start().unwrap();
let _ = lex.object_first_key_lex().unwrap();
let _ = lex.parse_i64_value().unwrap();
let err = lex.object_next_key_lex().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn lexer_object_next_key_lex_bad_byte_after_comma() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a":1,2"#);
lex.object_start().unwrap();
let _ = lex.object_first_key_lex().unwrap();
let _ = lex.parse_i64_value().unwrap();
let err = lex.object_next_key_lex().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b'2'));
}
#[test]
fn lexer_expect_colon_bad_byte() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a";"#);
lex.object_start().unwrap();
let err = lex.object_first_key().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedByte(b';'));
}
#[test]
fn lexer_expect_colon_eof() {
let mut lex: Lexer<'_> = Lexer::new(br#"{"a""#);
lex.object_start().unwrap();
let err = lex.object_first_key().unwrap_err();
assert_eq!(err.kind, ErrorKind::UnexpectedEof);
}
#[test]
fn fixed_array_zero_length() {
let arr: [i32; 0] = parse_str("[]").unwrap();
assert_eq!(arr, [0_i32; 0]);
}
#[test]
fn fixed_array_empty_but_expected_nonempty() {
let r = parse_str::<[i32; 3]>("[]");
assert!(r.is_err());
}
#[test]
fn fixed_array_closed_early() {
let r = parse_str::<[i32; 3]>("[1]");
assert!(r.is_err());
}
#[test]
fn static_msg_cross_category() {
use alloc::format;
let kind = ErrorKind::UnexpectedEof;
let msg = format!("{kind}");
assert_eq!(msg, "unexpected end of input");
let kind = ErrorKind::ExpectedNull;
let msg = format!("{kind}");
assert_eq!(msg, "expected null");
}
#[test]
fn serialize_string_containing_only_quote() {
let s = "\"";
let json = to_string(&s).unwrap();
assert_eq!(json, r#""\"""#);
}
#[test]
fn serialize_string_containing_only_backslash() {
let s = "\\";
let json = to_string(&s).unwrap();
assert_eq!(json, r#""\\""#);
}
#[test]
fn utf8_invalid_leading_byte() {
let bytes = b"\"\xFF\"";
let r = parse::<&str>(bytes);
assert_eq!(r.unwrap_err().kind, ErrorKind::InvalidUtf8);
}
#[test]
fn utf8_truncated_3byte_sequence() {
let mut bytes = b"\"".to_vec();
bytes.push(0xE1);
bytes.push(0x80);
let r = parse::<&str>(&bytes);
assert_eq!(r.unwrap_err().kind, ErrorKind::InvalidUtf8);
}
#[test]
fn utf8_bad_third_continuation_byte() {
let mut bytes = b"\"".to_vec();
bytes.push(0xE1);
bytes.push(0x80);
bytes.push(0x00); let r = parse::<&str>(&bytes);
assert!(r.is_err());
}
#[test]
fn utf8_4byte_bad_third_continuation() {
let mut bytes = b"\"".to_vec();
bytes.push(0xF0);
bytes.push(0x90);
bytes.push(0x80);
bytes.push(0x00); let r = parse::<&str>(&bytes);
assert!(r.is_err());
}
#[test]
fn utf8_e0_bad_second_byte() {
let mut bytes = b"\"".to_vec();
bytes.push(0xE0);
bytes.push(0x80); bytes.push(0x80);
bytes.push(b'"');
let r = parse::<&str>(&bytes);
assert_eq!(r.unwrap_err().kind, ErrorKind::InvalidUtf8);
}
#[test]
fn utf8_ed_bad_second_byte() {
let mut bytes = b"\"".to_vec();
bytes.push(0xED);
bytes.push(0xA0); bytes.push(0x80);
bytes.push(b'"');
let r = parse::<&str>(&bytes);
assert_eq!(r.unwrap_err().kind, ErrorKind::InvalidUtf8);
}
#[test]
fn utf8_f0_bad_second_byte() {
let mut bytes = b"\"".to_vec();
bytes.push(0xF0);
bytes.push(0x80); bytes.push(0x80);
bytes.push(0x80);
bytes.push(b'"');
let r = parse::<&str>(&bytes);
assert_eq!(r.unwrap_err().kind, ErrorKind::InvalidUtf8);
}
#[test]
fn fixed_array_non_array_input() {
let r = parse_str::<[i32; 2]>("42");
assert!(r.is_err());
}
}
#[cfg(all(test, feature = "std"))]
mod ser_roundtrip {
use super::{parse_str, to_string};
#[allow(clippy::needless_pass_by_value)]
fn rt<T>(v: T)
where
T: crate::ToJson + for<'a> crate::FromJson<'a> + PartialEq + core::fmt::Debug,
{
let s = to_string(&v).expect("serialize");
let v2: T = parse_str(&s).expect("parse back");
assert_eq!(v, v2, "round-trip diverged via {s:?}");
}
#[test]
fn bool_roundtrips() {
rt(true);
rt(false);
}
#[test]
fn unit_roundtrips() {
rt(());
}
#[test]
fn signed_ints_roundtrip() {
rt(0_i8);
rt(i8::MIN);
rt(i8::MAX);
rt(0_i16);
rt(i16::MIN);
rt(i16::MAX);
rt(0_i32);
rt(i32::MIN);
rt(i32::MAX);
rt(0_i64);
rt(i64::MIN);
rt(i64::MAX);
rt(0_isize);
rt(isize::MIN);
rt(isize::MAX);
}
#[test]
fn unsigned_ints_roundtrip() {
rt(0_u8);
rt(u8::MAX);
rt(0_u16);
rt(u16::MAX);
rt(0_u32);
rt(u32::MAX);
rt(0_u64);
rt(u64::MAX);
rt(0_usize);
rt(usize::MAX);
}
#[test]
fn wide_ints_roundtrip() {
rt(0_i128);
rt(i128::MIN);
rt(i128::MAX);
rt(0_u128);
rt(u128::MAX);
}
#[test]
fn strings_roundtrip() {
for s in [
"",
"hello",
"a\\b",
"with \"quotes\"",
"tab\tnewline\nreturn\rbs\x08ff\x0cnul\x00ctl\x1f",
"café 中文 😀",
] {
rt(String::from(s));
}
}
#[test]
fn cow_roundtrips() {
use std::borrow::Cow;
for src in ["owned", "with\nescape", ""] {
let v = Cow::<str>::Owned(String::from(src));
let s = to_string(&v).unwrap();
let back: Cow<'_, str> = parse_str(&s).unwrap();
assert_eq!(back, src);
}
}
#[test]
fn char_roundtrips() {
for c in ['a', 'Z', '中', '😀', '\n', '\t', '"', '\\'] {
rt(c);
}
}
#[test]
fn option_roundtrips() {
rt(Option::<u32>::None);
rt(Some(42_u32));
rt(Option::<String>::None);
rt(Some(String::from("x")));
}
#[test]
fn reference_forwarding() {
let v: u32 = 7;
let owned = to_string(&v).unwrap();
let by_ref = to_string(&&v).unwrap();
assert_eq!(owned, by_ref);
}
#[test]
fn wrapper_types_transparent() {
use std::rc::Rc;
use std::sync::Arc;
let v: u32 = 9;
assert_eq!(to_string(&v).unwrap(), to_string(&Box::new(v)).unwrap());
assert_eq!(to_string(&v).unwrap(), to_string(&Rc::new(v)).unwrap());
assert_eq!(to_string(&v).unwrap(), to_string(&Arc::new(v)).unwrap());
}
#[test]
fn pinned_wire_format() {
assert_eq!(to_string(&true).unwrap(), "true");
assert_eq!(to_string(&false).unwrap(), "false");
assert_eq!(to_string(&()).unwrap(), "null");
assert_eq!(to_string(&0_i64).unwrap(), "0");
assert_eq!(to_string(&-1_i64).unwrap(), "-1");
assert_eq!(to_string(&i64::MIN).unwrap(), "-9223372036854775808");
assert_eq!(to_string(&u64::MAX).unwrap(), "18446744073709551615");
assert_eq!(to_string(&"hi").unwrap(), "\"hi\"");
assert_eq!(to_string(&"\x01").unwrap(), "\"\\u0001\"");
}
#[test]
fn vec_roundtrips() {
rt(Vec::<i32>::new());
rt(vec![1_i32, 2, 3]);
rt(vec![String::from("a"), String::from("b")]);
rt(vec![Some(1_u32), None, Some(3)]);
}
#[test]
fn nested_vec_roundtrips() {
rt(vec![vec![1_i32, 2], vec![3], Vec::new()]);
}
#[test]
fn fixed_array_roundtrips() {
rt([1_i32, 2, 3]);
rt([true, false, true]);
let zero: [i32; 0] = [];
rt(zero);
}
#[test]
fn tuple_roundtrips() {
rt((1_i32, String::from("hi"), true));
rt((1_u8, 2_u16, 3_u32, 4_u64));
}
#[test]
fn slice_pinned() {
let s: &[i32] = &[10, 20, 30];
assert_eq!(to_string(&s).unwrap(), "[10,20,30]");
let empty: &[u8] = &[];
assert_eq!(to_string(&empty).unwrap(), "[]");
}
#[test]
fn btreemap_roundtrips() {
use std::collections::BTreeMap;
let mut m = BTreeMap::new();
m.insert(String::from("a"), 1_i32);
m.insert(String::from("b"), 2);
rt(m);
}
#[test]
fn btreemap_with_escape_in_key() {
use std::collections::BTreeMap;
let mut m = BTreeMap::new();
m.insert(String::from("a\nb"), 1_i32);
rt(m);
}
#[test]
fn btreeset_roundtrips() {
use std::collections::BTreeSet;
let mut s = BTreeSet::new();
s.insert(1_i32);
s.insert(2);
s.insert(3);
rt(s);
}
#[cfg(feature = "std")]
#[test]
fn hashmap_roundtrips() {
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert(String::from("alpha"), 1_i32);
m.insert(String::from("beta"), 2);
let s = to_string(&m).unwrap();
let back: HashMap<String, i32> = parse_str(&s).unwrap();
assert_eq!(back, m);
}
#[cfg(feature = "std")]
#[test]
fn hashset_roundtrips() {
use std::collections::HashSet;
let mut s = HashSet::new();
s.insert(1_i32);
s.insert(2);
s.insert(3);
let json = to_string(&s).unwrap();
let back: HashSet<i32> = parse_str(&json).unwrap();
assert_eq!(back, s);
}
#[cfg(feature = "std")]
#[test]
fn ip_addrs_roundtrip() {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
rt(Ipv4Addr::LOCALHOST);
rt(Ipv6Addr::LOCALHOST);
rt(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)));
rt(SocketAddr::from(([127, 0, 0, 1], 8080)));
}
#[cfg(feature = "std")]
#[test]
fn pathbuf_roundtrips() {
use std::path::PathBuf;
rt(PathBuf::from("/etc/hosts"));
}
#[test]
fn empty_collections_pinned() {
use std::collections::BTreeMap;
assert_eq!(to_string(&Vec::<i32>::new()).unwrap(), "[]");
let empty: [i32; 0] = [];
assert_eq!(to_string(&empty).unwrap(), "[]");
let m: BTreeMap<String, i32> = BTreeMap::new();
assert_eq!(to_string(&m).unwrap(), "{}");
}
#[test]
fn finite_f64_roundtrips() {
for v in [
0.0_f64,
-0.0,
1.0,
-1.0,
1.5,
-1.5,
1.5e2,
1.5e-2,
f64::MIN_POSITIVE,
f64::MAX,
f64::MIN,
std::f64::consts::PI,
] {
rt(v);
}
}
#[test]
fn finite_f32_roundtrips() {
for v in [0.0_f32, 1.5, -1.5, f32::MIN_POSITIVE, f32::MAX] {
rt(v);
}
}
#[test]
fn nonfinite_f64_rejected() {
use crate::ErrorKind;
let r = to_string(&f64::INFINITY);
assert_eq!(r.unwrap_err().kind, ErrorKind::NonFiniteFloat);
let r = to_string(&f64::NEG_INFINITY);
assert_eq!(r.unwrap_err().kind, ErrorKind::NonFiniteFloat);
let r = to_string(&f64::NAN);
assert_eq!(r.unwrap_err().kind, ErrorKind::NonFiniteFloat);
}
#[cfg(feature = "std")]
#[test]
fn duration_roundtrips() {
use std::time::Duration;
rt(Duration::from_secs(0));
rt(Duration::from_millis(1500));
rt(Duration::new(42, 750_000_000));
}
}
#[cfg(all(test, feature = "std"))]
mod to_json_macro_tests {
use super::{parse_str, to_string};
use crate::{FromJson, ToJson};
#[derive(Debug, PartialEq, ToJson)]
struct Plain {
id: u32,
name: String,
}
#[derive(Debug, PartialEq, FromJson)]
struct PlainParse {
id: u32,
name: String,
}
#[test]
fn plain_struct_emits_object() {
let v = Plain {
id: 7,
name: String::from("alice"),
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"id":7,"name":"alice"}"#);
let back: PlainParse = parse_str(&s).unwrap();
assert_eq!(
back,
PlainParse {
id: 7,
name: String::from("alice")
}
);
}
#[derive(Debug, PartialEq, ToJson)]
struct Borrowed<'input> {
tag: &'input str,
count: u32,
}
#[test]
fn struct_with_lifetime() {
let v = Borrowed {
tag: "hi",
count: 3,
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"tag":"hi","count":3}"#);
}
#[derive(Debug, PartialEq, ToJson)]
struct Decorated {
#[bourne(rename = "user-id")]
user_id: u32,
#[bourne(skip)]
cached: u32,
#[bourne(skip_if_none)]
note: Option<String>,
value: u32,
}
#[test]
fn rename_emits_new_key() {
let v = Decorated {
user_id: 1,
cached: 99,
note: None,
value: 42,
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"user-id":1,"value":42}"#);
}
#[test]
fn skip_if_none_emits_when_some() {
let v = Decorated {
user_id: 1,
cached: 0,
note: Some(String::from("hi")),
value: 7,
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"user-id":1,"note":"hi","value":7}"#);
}
#[derive(Debug, PartialEq, ToJson)]
struct Empty {}
#[test]
fn empty_struct_emits_empty_object() {
assert_eq!(to_string(&Empty {}).unwrap(), "{}");
}
#[derive(Debug, PartialEq, ToJson)]
struct WithEscape {
text: String,
}
#[test]
fn macro_passes_strings_to_escape_path() {
let v = WithEscape {
text: String::from("a\nb\"c"),
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"text":"a\nb\"c"}"#);
}
#[derive(Debug, PartialEq, ToJson)]
struct UserId(u64);
#[test]
fn newtype_emits_bare_value() {
let v = UserId(42);
assert_eq!(to_string(&v).unwrap(), "42");
}
#[derive(Debug, PartialEq, ToJson)]
struct BorrowedTag<'input>(&'input str);
#[test]
fn newtype_with_lifetime() {
let v = BorrowedTag("hello");
assert_eq!(to_string(&v).unwrap(), r#""hello""#);
}
#[derive(Debug, PartialEq, ToJson)]
struct Point(i32, i32);
#[test]
fn tuple_struct_emits_array() {
let v = Point(3, -7);
assert_eq!(to_string(&v).unwrap(), "[3,-7]");
}
#[derive(Debug, PartialEq, ToJson)]
struct Triple(i32, String, bool);
#[test]
fn three_field_tuple_struct() {
let v = Triple(1, String::from("hi"), true);
assert_eq!(to_string(&v).unwrap(), r#"[1,"hi",true]"#);
}
#[derive(Debug, PartialEq, ToJson)]
enum Shape {
Circle,
Wrapper(u32),
Pair(u32, String),
Box {
w: u32,
h: u32,
},
#[bourne(rename = "tri")]
Triangle,
}
#[test]
fn enum_unit_emits_string() {
assert_eq!(to_string(&Shape::Circle).unwrap(), r#""Circle""#);
}
#[test]
fn enum_renamed_unit() {
assert_eq!(to_string(&Shape::Triangle).unwrap(), r#""tri""#);
}
#[test]
fn enum_newtype_emits_object() {
assert_eq!(to_string(&Shape::Wrapper(7)).unwrap(), r#"{"Wrapper":7}"#);
}
#[test]
fn enum_tuple_emits_object_with_array() {
let v = Shape::Pair(1, String::from("x"));
assert_eq!(to_string(&v).unwrap(), r#"{"Pair":[1,"x"]}"#);
}
#[test]
fn enum_struct_variant_emits_nested_object() {
let v = Shape::Box { w: 10, h: 20 };
assert_eq!(to_string(&v).unwrap(), r#"{"Box":{"w":10,"h":20}}"#);
}
#[derive(Debug, PartialEq, ToJson)]
#[bourne(tag = "type")]
enum Event {
Heartbeat,
#[bourne(rename = "click")]
Click {
x: u32,
y: u32,
},
}
#[test]
fn internal_tag_unit_emits_object_with_tag() {
assert_eq!(
to_string(&Event::Heartbeat).unwrap(),
r#"{"type":"Heartbeat"}"#
);
}
#[test]
fn internal_tag_struct_inlines_fields() {
let v = Event::Click { x: 1, y: 2 };
assert_eq!(to_string(&v).unwrap(), r#"{"type":"click","x":1,"y":2}"#);
}
#[derive(Debug, PartialEq, ToJson)]
#[bourne(tag = "t", content = "c")]
enum Msg {
Ping,
Echo(String),
Pair(u32, u32),
Body { text: String },
}
#[test]
fn adjacent_unit_emits_only_tag() {
assert_eq!(to_string(&Msg::Ping).unwrap(), r#"{"t":"Ping"}"#);
}
#[test]
fn adjacent_newtype_emits_content() {
let v = Msg::Echo(String::from("hi"));
assert_eq!(to_string(&v).unwrap(), r#"{"t":"Echo","c":"hi"}"#);
}
#[test]
fn adjacent_tuple_emits_content_array() {
let v = Msg::Pair(1, 2);
assert_eq!(to_string(&v).unwrap(), r#"{"t":"Pair","c":[1,2]}"#);
}
#[test]
fn adjacent_struct_emits_content_object() {
let v = Msg::Body {
text: String::from("ok"),
};
assert_eq!(to_string(&v).unwrap(), r#"{"t":"Body","c":{"text":"ok"}}"#);
}
#[derive(Debug, PartialEq, ToJson)]
#[bourne(untagged)]
enum Mixed {
Nothing,
One(u32),
Two(u32, u32),
Body { name: String },
}
#[test]
fn untagged_unit_emits_null() {
assert_eq!(to_string(&Mixed::Nothing).unwrap(), "null");
}
#[test]
fn untagged_newtype_emits_inner() {
assert_eq!(to_string(&Mixed::One(42)).unwrap(), "42");
}
#[test]
fn untagged_tuple_emits_array() {
assert_eq!(to_string(&Mixed::Two(1, 2)).unwrap(), "[1,2]");
}
#[test]
fn untagged_struct_emits_object() {
let v = Mixed::Body {
name: String::from("x"),
};
assert_eq!(to_string(&v).unwrap(), r#"{"name":"x"}"#);
}
}
#[cfg(all(test, feature = "std"))]
mod sink_adapter_tests {
use super::{to_fmt, to_string};
#[test]
fn fmt_sink_matches_to_string_for_struct() {
let m = vec![("a", 1_i32), ("b", 2)]
.into_iter()
.collect::<std::collections::BTreeMap<_, _>>();
let canonical = to_string(&m).unwrap();
let mut out = String::new();
to_fmt(&m, &mut out).unwrap();
assert_eq!(out, canonical);
}
#[test]
fn fmt_sink_handles_floats_with_grisu3() {
let canonical = to_string(&1.5_f64).unwrap();
let mut out = String::new();
to_fmt(&1.5_f64, &mut out).unwrap();
assert_eq!(out, canonical);
}
#[test]
fn fmt_sink_rejects_non_finite() {
let mut out = String::new();
let err = to_fmt(&f64::INFINITY, &mut out).unwrap_err();
assert_eq!(err.kind, crate::ErrorKind::NonFiniteFloat);
}
#[cfg(feature = "std")]
#[test]
fn io_writer_matches_to_string_for_struct() {
use super::to_writer;
let v = vec![1_i32, 2, 3];
let canonical = to_string(&v).unwrap();
let mut buf = Vec::<u8>::new();
to_writer(&v, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), canonical);
}
#[cfg(feature = "std")]
#[test]
fn io_writer_handles_floats() {
use super::to_writer;
let canonical = to_string(&-2.7e-5_f64).unwrap();
let mut buf = Vec::<u8>::new();
to_writer(&-2.7e-5_f64, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), canonical);
}
#[cfg(feature = "std")]
#[test]
fn io_writer_rejects_non_finite() {
use super::to_writer;
let mut buf = Vec::<u8>::new();
let err = to_writer(&f64::NAN, &mut buf).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
}
#[test]
fn pretty_empty_object_compact() {
use super::to_string_pretty;
let m: std::collections::BTreeMap<String, i32> = std::collections::BTreeMap::new();
assert_eq!(to_string_pretty(&m).unwrap(), "{}");
}
#[test]
fn pretty_empty_array_compact() {
use super::to_string_pretty;
let v: Vec<i32> = Vec::new();
assert_eq!(to_string_pretty(&v).unwrap(), "[]");
}
#[test]
fn pretty_array_indents_two_spaces() {
use super::to_string_pretty;
let v = vec![1_i32, 2, 3];
assert_eq!(to_string_pretty(&v).unwrap(), "[\n 1,\n 2,\n 3\n]");
}
#[test]
fn pretty_object_keys_have_one_space_after_colon() {
use super::to_string_pretty;
let m: std::collections::BTreeMap<&str, i32> = [("a", 1), ("b", 2)].into_iter().collect();
assert_eq!(
to_string_pretty(&m).unwrap(),
"{\n \"a\": 1,\n \"b\": 2\n}",
);
}
#[test]
fn pretty_nested_indents_proportionally() {
use super::to_string_pretty;
let v: Vec<Vec<i32>> = vec![vec![1, 2], vec![3]];
assert_eq!(
to_string_pretty(&v).unwrap(),
"[\n [\n 1,\n 2\n ],\n [\n 3\n ]\n]",
);
}
#[cfg(feature = "indexmap")]
#[test]
fn indexmap_preserves_insertion_order_on_parse() {
use crate::parse_str;
let json = r#"{"zebra":1,"alpha":2,"mango":3}"#;
let m: indexmap::IndexMap<String, i32> = parse_str(json).unwrap();
let keys: Vec<&str> = m.keys().map(String::as_str).collect();
assert_eq!(keys, ["zebra", "alpha", "mango"]);
assert_eq!(m["alpha"], 2);
}
#[cfg(feature = "indexmap")]
#[test]
fn indexmap_round_trips_preserving_order() {
use super::to_string;
use crate::parse_str;
let mut m = indexmap::IndexMap::<String, i32>::new();
m.insert("z".into(), 1);
m.insert("a".into(), 2);
m.insert("m".into(), 3);
let s = to_string(&m).unwrap();
assert_eq!(s, r#"{"z":1,"a":2,"m":3}"#);
let back: indexmap::IndexMap<String, i32> = parse_str(&s).unwrap();
assert_eq!(
back.keys().map(String::as_str).collect::<Vec<_>>(),
["z", "a", "m"],
);
}
#[cfg(feature = "indexmap")]
#[test]
fn indexmap_rejects_duplicate_keys() {
use crate::parse_str;
let r: Result<indexmap::IndexMap<String, i32>, _> = parse_str(r#"{"a":1,"a":2}"#);
assert_eq!(r.unwrap_err().kind, crate::ErrorKind::DuplicateKey);
}
#[cfg(feature = "indexmap")]
#[test]
fn indexset_round_trips() {
use super::to_string;
use crate::parse_str;
let mut s = indexmap::IndexSet::<i32>::new();
s.insert(3);
s.insert(1);
s.insert(2);
let json = to_string(&s).unwrap();
assert_eq!(json, "[3,1,2]");
let back: indexmap::IndexSet<i32> = parse_str(&json).unwrap();
assert_eq!(back.iter().copied().collect::<Vec<_>>(), [3, 1, 2]);
}
#[test]
fn pretty_round_trips_through_compact_parse() {
use super::{parse_str, to_string_pretty};
let m: std::collections::BTreeMap<String, Vec<i32>> = [
(String::from("a"), vec![1, 2]),
(String::from("b"), vec![3]),
]
.into_iter()
.collect();
let pretty = to_string_pretty(&m).unwrap();
let back: std::collections::BTreeMap<String, Vec<i32>> = parse_str(&pretty).unwrap();
assert_eq!(back, m);
}
#[cfg(feature = "std")]
#[test]
fn io_writer_propagates_underlying_error() {
use super::to_writer;
struct FailingWriter;
impl std::io::Write for FailingWriter {
fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("disk full"))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut w = FailingWriter;
let err = to_writer(&"hi", &mut w).unwrap_err();
assert_eq!(err.to_string(), "disk full");
}
}
#[cfg(all(test, feature = "std"))]
mod json_macro_tests {
use super::{parse_str, to_string};
use crate::{FromJson, ToJson};
#[derive(Debug, PartialEq, FromJson, ToJson)]
struct Plain {
id: u32,
name: String,
}
#[test]
fn plain_struct_round_trips() {
let v = Plain {
id: 7,
name: String::from("alice"),
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"id":7,"name":"alice"}"#);
let back: Plain = parse_str(&s).unwrap();
assert_eq!(back, v);
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
struct Borrowed<'input> {
tag: &'input str,
count: u32,
}
#[test]
fn struct_with_lifetime_round_trips() {
let json = r#"{"tag":"hi","count":3}"#;
let v: Borrowed<'_> = parse_str(json).unwrap();
assert_eq!(
v,
Borrowed {
tag: "hi",
count: 3
}
);
assert_eq!(to_string(&v).unwrap(), json);
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
struct Decorated {
#[bourne(rename = "user-id")]
user_id: u32,
#[bourne(skip)]
cached: u32,
#[bourne(skip_if_none)]
note: Option<String>,
value: u32,
}
#[test]
fn rename_skip_skip_if_none() {
let v = Decorated {
user_id: 1,
cached: 99,
note: None,
value: 42,
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"user-id":1,"value":42}"#);
let back: Decorated = parse_str(&s).unwrap();
assert_eq!(back.user_id, 1);
assert_eq!(back.value, 42);
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
struct UserId(u64);
#[test]
fn newtype_round_trips() {
let v = UserId(42);
let s = to_string(&v).unwrap();
assert_eq!(s, "42");
let back: UserId = parse_str(&s).unwrap();
assert_eq!(back, v);
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
struct Pair(i32, i32);
#[test]
fn tuple_struct_round_trips() {
let v = Pair(3, -7);
let s = to_string(&v).unwrap();
assert_eq!(s, "[3,-7]");
let back: Pair = parse_str(&s).unwrap();
assert_eq!(back, v);
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
enum Shape {
Circle,
Wrapper(u32),
Pair(u32, String),
Box {
w: u32,
h: u32,
},
#[bourne(rename = "tri")]
Triangle,
}
#[test]
fn externally_tagged_enum_round_trips() {
let cases: Vec<(Shape, &str)> = vec![
(Shape::Circle, r#""Circle""#),
(Shape::Wrapper(7), r#"{"Wrapper":7}"#),
(Shape::Pair(1, String::from("x")), r#"{"Pair":[1,"x"]}"#),
(Shape::Box { w: 10, h: 20 }, r#"{"Box":{"w":10,"h":20}}"#),
(Shape::Triangle, r#""tri""#),
];
for (val, expected) in cases {
let s = to_string(&val).unwrap();
assert_eq!(s, expected);
let back: Shape = parse_str(&s).unwrap();
assert_eq!(back, val);
}
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
#[bourne(tag = "type")]
enum Event {
Heartbeat,
#[bourne(rename = "click")]
Click {
x: u32,
y: u32,
},
}
#[test]
fn internally_tagged_enum_round_trips() {
let hb = Event::Heartbeat;
let s = to_string(&hb).unwrap();
assert_eq!(s, r#"{"type":"Heartbeat"}"#);
let back: Event = parse_str(&s).unwrap();
assert_eq!(back, hb);
let click = Event::Click { x: 1, y: 2 };
let s = to_string(&click).unwrap();
assert_eq!(s, r#"{"type":"click","x":1,"y":2}"#);
let back: Event = parse_str(&s).unwrap();
assert_eq!(back, click);
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
#[bourne(tag = "t", content = "c")]
enum Msg {
Ping,
Echo(String),
Pair(u32, u32),
Body { text: String },
}
#[test]
fn adjacently_tagged_enum_round_trips() {
let cases: Vec<(Msg, &str)> = vec![
(Msg::Ping, r#"{"t":"Ping"}"#),
(Msg::Echo(String::from("hi")), r#"{"t":"Echo","c":"hi"}"#),
(Msg::Pair(1, 2), r#"{"t":"Pair","c":[1,2]}"#),
(
Msg::Body {
text: String::from("ok"),
},
r#"{"t":"Body","c":{"text":"ok"}}"#,
),
];
for (val, expected) in cases {
let s = to_string(&val).unwrap();
assert_eq!(s, expected);
let back: Msg = parse_str(&s).unwrap();
assert_eq!(back, val);
}
}
#[derive(Debug, PartialEq, FromJson, ToJson)]
#[bourne(untagged)]
enum Mixed {
Nothing,
One(u32),
Two(u32, u32),
Body { name: String },
}
#[derive(Debug, PartialEq, Eq, FromJson, ToJson)]
pub struct PubFields {
pub id: u32,
pub(crate) name: String,
value: u32,
}
#[test]
fn pub_fields_round_trip() {
let v = PubFields {
id: 1,
name: String::from("hi"),
value: 2,
};
let s = to_string(&v).unwrap();
assert_eq!(s, r#"{"id":1,"name":"hi","value":2}"#);
let back: PubFields = parse_str(&s).unwrap();
assert_eq!(back, v);
}
#[test]
fn untagged_enum_serializes() {
assert_eq!(to_string(&Mixed::Nothing).unwrap(), "null");
assert_eq!(to_string(&Mixed::One(42)).unwrap(), "42");
assert_eq!(to_string(&Mixed::Two(1, 2)).unwrap(), "[1,2]");
assert_eq!(
to_string(&Mixed::Body {
name: String::from("x")
})
.unwrap(),
r#"{"name":"x"}"#,
);
}
}
#[cfg(all(test, feature = "std"))]
mod unsafe_boundary_tests {
use super::*;
use alloc::string::String;
use alloc::vec::Vec;
#[test]
fn bytesink_slice_floats_exact_capacity() {
for n in [0usize, 1, 2, 3, 7, 16, 17, 32, 33] {
#[allow(clippy::cast_precision_loss)]
let data: Vec<f64> = (0..n).map(|i| i as f64 + 0.5).collect();
let mut out: Vec<u8> = Vec::with_capacity(2 + n * 33);
let mut sink = ByteSink::new(&mut out);
(data.as_slice()).write_json(&mut sink).expect("ser");
let s = core::str::from_utf8(&out).expect("ASCII");
let back: Vec<f64> = parse_str(s).expect("parse back");
assert_eq!(back.len(), n, "n={n}");
#[allow(clippy::cast_precision_loss, clippy::float_cmp)]
for (i, &v) in back.iter().enumerate() {
assert_eq!(v, i as f64 + 0.5, "n={n} i={i}");
}
}
}
#[test]
fn bytesink_slice_f32_exact_capacity() {
for n in [0usize, 1, 2, 16, 17] {
#[allow(clippy::cast_precision_loss)]
let data: Vec<f32> = (0..n).map(|i| i as f32 + 0.25).collect();
let mut out: Vec<u8> = Vec::with_capacity(2 + n * 33);
let mut sink = ByteSink::new(&mut out);
(data.as_slice()).write_json(&mut sink).expect("ser");
let s = core::str::from_utf8(&out).expect("ASCII");
let back: Vec<f32> = parse_str(s).expect("parse back");
assert_eq!(back.len(), n);
#[allow(clippy::cast_precision_loss, clippy::float_cmp)]
for (i, &v) in back.iter().enumerate() {
assert_eq!(v, i as f32 + 0.25, "n={n} i={i}");
}
}
}
#[test]
fn bytesink_slice_nonfinite_taint_path() {
for (name, bad_idx, bad_val) in [
("inf at 0", 0usize, f64::INFINITY),
("nan at end", 4usize, f64::NAN),
("neg_inf middle", 2usize, f64::NEG_INFINITY),
] {
let mut data: Vec<f64> = (0..5).map(f64::from).collect();
data[bad_idx] = bad_val;
let r = to_string(data.as_slice());
assert!(r.is_err(), "{name}: expected non-finite error");
}
}
#[test]
fn bytesink_slice_ints_exact_capacity() {
for n in [0usize, 1, 5, 16, 17] {
#[allow(clippy::cast_possible_wrap)]
let n_i64 = n as i64;
#[allow(clippy::cast_possible_wrap)]
let data: Vec<i64> = (0..n).map(|i| (i as i64) - n_i64 / 2).collect();
let mut out: Vec<u8> = Vec::with_capacity(2 + n * 21);
let mut sink = ByteSink::new(&mut out);
(data.as_slice()).write_json(&mut sink).expect("ser");
let s = core::str::from_utf8(&out).expect("ASCII");
let back: Vec<i64> = parse_str(s).expect("parse back");
assert_eq!(back, data, "n={n}");
}
}
#[test]
fn decode_escapes_multibyte_in_literal_chunk() {
let cases: &[(&str, &str)] = &[
(r#""café\nfin""#, "café\nfin"),
(r#""price: 5€\tea""#, "price: 5€\tea"),
(r#""note 𝄞\nplayed""#, "note 𝄞\nplayed"),
(r#""éé\nçç\tüü""#, "éé\nçç\tüü"),
(r#""\n𝄞café\t""#, "\n𝄞café\t"),
(
r#""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaéé\n""#,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaéé\n",
),
];
for (json, expected) in cases {
let s: String = parse_str(json).expect("parse");
assert_eq!(s, *expected, "case {json}");
}
}
#[test]
fn bytesink_empty_slice_writes_brackets_only() {
let empty: Vec<i64> = Vec::new();
let s = to_string(empty.as_slice()).expect("ser");
assert_eq!(s, "[]");
let empty: Vec<f64> = Vec::new();
let s = to_string(empty.as_slice()).expect("ser");
assert_eq!(s, "[]");
}
#[test]
fn bytesink_single_element_slice() {
let one = [42_i64];
assert_eq!(to_string(one.as_slice()).unwrap(), "[42]");
let one = [1.5_f64];
assert_eq!(to_string(one.as_slice()).unwrap(), "[1.5]");
let one = [1.5_f32];
assert_eq!(to_string(one.as_slice()).unwrap(), "[1.5]");
}
#[test]
fn decode_escapes_empty_string() {
let s: String = parse_str(r#""""#).expect("parse");
assert_eq!(s, "");
}
#[test]
fn decode_escapes_no_escapes_pure_ascii() {
let v: Vec<String> = parse_str(r#"["hello world","no escapes here"]"#).expect("parse");
assert_eq!(
v,
[String::from("hello world"), String::from("no escapes here")]
);
}
#[test]
fn decode_escapes_escape_at_start() {
let v: Vec<String> = parse_str(r#"["\nhello","\tworld"]"#).expect("parse");
assert_eq!(v, [String::from("\nhello"), String::from("\tworld")]);
}
#[test]
fn decode_escapes_escape_at_end() {
let v: Vec<String> = parse_str(r#"["hello\n","world\t"]"#).expect("parse");
assert_eq!(v, [String::from("hello\n"), String::from("world\t")]);
}
#[test]
fn decode_escapes_consecutive_escapes() {
let v: Vec<String> = parse_str(r#"["\n\t\r\\","\"\"\""]"#).expect("parse");
assert_eq!(v, [String::from("\n\t\r\\"), String::from(r#"""""#)]);
}
#[test]
fn vec_borrowed_str_fast_path() {
let v: Vec<&str> = parse_str("[]").expect("empty");
assert_eq!(v, Vec::<&str>::new());
let v: Vec<&str> = parse_str(r#"["only"]"#).expect("single");
assert_eq!(v, ["only"]);
let v: Vec<&str> = parse_str(r#"["a","b","c","d","e"]"#).expect("many");
assert_eq!(v, ["a", "b", "c", "d", "e"]);
let r: Result<Vec<&str>, _> = parse_str(r#"["plain","esc\nbad"]"#);
assert!(r.is_err(), "borrowed path must reject escape");
}
#[cfg(feature = "std")]
#[test]
fn vec_duration_fast_path() {
use std::time::Duration;
let v: Vec<Duration> = parse_str("[]").expect("empty");
assert!(v.is_empty());
let v: Vec<Duration> = parse_str("[1.5]").expect("single");
assert_eq!(v, [Duration::from_secs_f64(1.5)]);
let v: Vec<Duration> = parse_str("[0.0,1.0,1.5,2.25,100.125]").expect("many");
assert_eq!(v.len(), 5);
assert_eq!(v[0], Duration::ZERO);
assert_eq!(v[4], Duration::from_secs_f64(100.125));
let r: Result<Vec<Duration>, _> = parse_str("[1.0,-2.0]");
assert!(r.is_err());
}
#[test]
fn vec_i64_fast_path() {
let v: Vec<i64> = parse_str("[]").expect("empty");
assert!(v.is_empty());
let v: Vec<i64> = parse_str("[42]").expect("single");
assert_eq!(v, [42]);
let v: Vec<i64> =
parse_str("[1,-1,9223372036854775807,-9223372036854775808]").expect("many");
assert_eq!(v, [1, -1, i64::MAX, i64::MIN]);
let r: Result<Vec<u32>, _> = parse_str("[1,99999999999]");
assert!(r.is_err(), "u32 should overflow");
}
#[test]
fn vec_u128_fast_path() {
let v: Vec<u128> = parse_str("[]").expect("empty");
assert!(v.is_empty());
let v: Vec<u128> = parse_str("[0,170141183460469231731687303715884105727]").expect("many");
assert_eq!(v, [0u128, i128::MAX as u128]);
}
#[test]
fn decode_all_simple_escapes() {
let cases: &[(&str, &str)] = &[
(r#""\b""#, "\u{0008}"), (r#""\f""#, "\u{000C}"), (r#""\/""#, "/"), (r#""\\""#, "\\"), (r#""\"""#, "\""), (r#""\n""#, "\n"),
(r#""\r""#, "\r"),
(r#""\t""#, "\t"),
];
for &(json, expected) in cases {
let s: String = parse_str(json).expect(json);
assert_eq!(s, expected, "case {json}");
}
}
#[test]
fn decode_unknown_escape_errors() {
let r: Result<String, _> = parse_str(r#""\x""#);
assert!(r.is_err(), "unknown escape should error");
let r: Result<String, _> = parse_str("\"\\\"");
assert!(r.is_err(), "lone trailing backslash should error");
}
#[test]
fn decode_unicode_escape_short_input_errors() {
let r: Result<String, _> = parse_str(r#""\u00""#);
assert!(r.is_err(), "short \\u should error");
let r: Result<String, _> = parse_str(r#""\u""#);
assert!(r.is_err());
}
#[test]
fn decode_unicode_lone_low_surrogate_errors() {
let r: Result<String, _> = parse_str(r#""\uDC00""#);
assert!(r.is_err(), "lone low surrogate should error");
}
#[test]
fn decode_unicode_high_surrogate_then_invalid_low_errors() {
let json = "\"\\uD800\\u0041\""; let r: Result<String, _> = parse_str(json);
assert!(
r.is_err(),
"high surrogate then non-low-surrogate \\u must error"
);
}
#[test]
fn decode_unicode_high_surrogate_then_short_second_escape_errors() {
let json = r#""\uD800\u""#;
let r: Result<String, _> = parse_str(json);
assert!(r.is_err(), "high surrogate then truncated \\u must error");
}
#[test]
fn decode_unicode_high_surrogate_then_invalid_hex_errors() {
let json = "\"\\uD800\\uZZZZ\"";
let r: Result<String, _> = parse_str(json);
assert!(r.is_err(), "high surrogate then bad hex must error");
}
#[test]
fn decode_unicode_high_surrogate_then_non_u_escape_errors() {
let r: Result<String, _> = parse_str(r#""\uD800\n""#);
assert!(
r.is_err(),
"high surrogate not followed by \\u should error"
);
let r: Result<String, _> = parse_str(r#""\uD800A""#);
assert!(
r.is_err(),
"high surrogate not followed by escape should error"
);
}
#[test]
fn decode_unicode_surrogate_pair_round_trips() {
let json = "\"\\uD83D\\uDE00\"";
let s: String = parse_str(json).expect("parse");
assert_eq!(s, "\u{1F600}");
let json = "\"a\\uD83D\\uDE00b\\uD83D\\uDE01c\"";
let s: String = parse_str(json).expect("parse");
assert_eq!(s, "a\u{1F600}b\u{1F601}c");
}
#[test]
fn decode_unicode_bmp_escape_round_trips() {
let json = "\"\\u00E9\"";
let s: String = parse_str(json).expect("parse");
assert_eq!(s, "é");
let json = "\"A\\u00A3\\u20AC\""; let s: String = parse_str(json).expect("parse");
assert_eq!(s, "A£€");
}
#[test]
fn decode_invalid_hex_in_u_escape_errors() {
let r: Result<String, _> = parse_str(r#""\u00ZX""#);
assert!(r.is_err(), "invalid hex should error");
}
#[test]
fn to_decimal_uncentred_powers_of_two_round_trip() {
let cases = [
2.0_f64,
4.0,
8.0,
16.0,
32.0,
64.0,
128.0,
256.0,
512.0,
1024.0,
2.0_f64.powi(20),
2.0_f64.powi(50),
2.0_f64.powi(100),
2.0_f64.powi(500),
2.0_f64.powi(1023), 2.0_f64.powi(-1), 2.0_f64.powi(-2), 2.0_f64.powi(-10),
2.0_f64.powi(-50),
2.0_f64.powi(-100),
2.0_f64.powi(-1000),
];
for &v in &cases {
let s = to_string(&v).expect("ser");
let back: f64 = parse_str(&s).expect("parse back");
#[allow(clippy::float_cmp)]
{
assert_eq!(back, v, "round-trip for {v:e}: emitted {s:?}");
}
}
}
#[test]
fn to_decimal_uncentred_negative_powers_round_trip() {
for k in [-30, -10, -1, 1, 10, 30, 100, 500] {
let v = -(2.0_f64.powi(k));
let s = to_string(&v).expect("ser");
let back: f64 = parse_str(&s).expect("parse back");
#[allow(clippy::float_cmp)]
{
assert_eq!(back, v, "round-trip for {v:e}: emitted {s:?}");
}
}
}
#[test]
fn to_decimal_uncentred_subnormals_round_trip() {
let cases = [
5e-324_f64, f64::MIN_POSITIVE, -f64::MIN_POSITIVE,
f64::MAX,
-f64::MAX,
];
for &v in &cases {
let s = to_string(&v).expect("ser");
let back: f64 = parse_str(&s).expect("parse back");
#[allow(clippy::float_cmp)]
{
assert_eq!(back, v, "round-trip for {v:e}: emitted {s:?}");
}
}
}
#[test]
fn string_sink_writes_directly() {
let mut out = String::new();
let mut sink = StringSink::new(&mut out);
sink.write_byte(b'[').unwrap();
sink.write_str_raw("1").unwrap();
sink.write_byte(b',').unwrap();
sink.write_float_f64(2.5).unwrap();
sink.write_byte(b']').unwrap();
assert_eq!(out, "[1,2.5]");
let mut out = String::new();
let mut sink = StringSink::new(&mut out);
assert!(sink.write_float_f64(f64::NAN).is_err());
}
#[test]
fn pretty_sink_with_indent_uses_custom_indent() {
let mut out = String::new();
let mut sink = PrettyStringSink::with_indent(&mut out, "\t");
let data = (1_i32, 2_i32, 3_i32);
data.write_json(&mut sink).unwrap();
assert!(out.contains("\n\t"), "got: {out:?}");
}
#[test]
fn pretty_sink_handles_floats() {
let v: alloc::vec::Vec<f64> = alloc::vec![1.5, 2.5, 3.0];
let s = to_string_pretty(v.as_slice()).unwrap();
assert!(s.contains("1.5"));
assert!(s.contains("3.0"));
}
#[test]
fn bytesink_unchecked_float_nonfinite_returns_error() {
let mut out: Vec<u8> = Vec::with_capacity(64);
let mut sink = ByteSink::new(&mut out);
#[allow(unsafe_code)]
let r = unsafe { sink.write_float_f64_unchecked(f64::INFINITY) };
assert!(r.is_err(), "non-finite must error through cold arm");
}
#[test]
fn bytesink_unchecked_finite_writes_value() {
let mut out: Vec<u8> = Vec::with_capacity(64);
let mut sink = ByteSink::new(&mut out);
let value = 2.5_f64;
#[allow(unsafe_code)]
unsafe {
sink.write_float_f64_unchecked_finite(value).unwrap();
}
let s = core::str::from_utf8(&out).unwrap();
let parsed: f64 = s.parse().unwrap();
assert_eq!(parsed.to_bits(), value.to_bits());
}
#[cfg(feature = "std")]
#[test]
fn path_serializes_via_to_string() {
use std::path::Path;
let p = Path::new("/tmp/file.txt");
let s = to_string(p).unwrap();
assert_eq!(s, "\"/tmp/file.txt\"");
}
#[test]
fn map_with_cow_str_keys_serializes() {
use alloc::borrow::Cow;
use alloc::collections::BTreeMap;
let mut m: BTreeMap<Cow<'_, str>, i32> = BTreeMap::new();
m.insert(Cow::Borrowed("a"), 1);
m.insert(Cow::Owned(String::from("b")), 2);
let s = to_string(&m).unwrap();
assert!(s.contains(r#""a":1"#));
assert!(s.contains(r#""b":2"#));
}
#[test]
fn f64_pre_validate_slice_is_noop() {
let slice: &[f64] = &[1.0, 2.0, f64::INFINITY];
let r = <f64 as ToJson>::pre_validate_slice(slice);
assert!(r.is_ok());
}
#[test]
fn to_fmt_writes_finite_and_rejects_nonfinite() {
let mut s = String::new();
to_fmt(&1.5_f64, &mut s).unwrap();
assert_eq!(s, "1.5");
let mut s = String::new();
assert!(to_fmt(&f64::INFINITY, &mut s).is_err());
}
#[test]
fn to_fmt_propagates_underlying_write_failure() {
use core::fmt;
struct AlwaysFail;
impl fmt::Write for AlwaysFail {
fn write_str(&mut self, _s: &str) -> fmt::Result {
Err(fmt::Error)
}
}
let mut sink = AlwaysFail;
let r = to_fmt(&42_i64, &mut sink);
assert!(r.is_err(), "expected propagated error from failing writer");
let mut sink = AlwaysFail;
let r = to_fmt(&1.5_f64, &mut sink);
assert!(r.is_err());
}
}