use std::collections::BTreeMap;
use std::net::{IpAddr, SocketAddr};
use jsonx::{Bytes, DateTime, Datetime, Int, Ip, IpPort, Map, Uint, Value};
use serde::{Deserialize, Serialize};
#[test]
fn decode_primitives() {
assert_eq!(jsonx::from_str::<Value>("null").unwrap(), Value::Null);
assert_eq!(jsonx::from_str::<Value>("true").unwrap(), Value::Bool(true));
assert_eq!(jsonx::from_str::<Value>("false").unwrap(), Value::Bool(false));
assert_eq!(jsonx::from_str::<Value>("5").unwrap(), Value::Number(5.0));
assert_eq!(jsonx::from_str::<Value>("-5").unwrap(), Value::Number(-5.0));
assert_eq!(jsonx::from_str::<Value>("5.5").unwrap(), Value::Number(5.5));
assert_eq!(jsonx::from_str::<Value>("1e-3").unwrap(), Value::Number(0.001));
assert_eq!(
jsonx::from_str::<Value>(r#""hello""#).unwrap(),
Value::String("hello".into())
);
}
#[test]
fn decode_whitespace_around_value() {
assert_eq!(jsonx::from_str::<Value>("\n true ").unwrap(), Value::Bool(true));
assert_eq!(jsonx::from_str::<f64>("\t -5 \n").unwrap(), -5.0);
}
#[test]
fn decode_nested_json() {
let v: Value = jsonx::from_str(r#"{"X": [1], "Y": 4}"#).unwrap();
let mut expected = Map::new();
expected.insert("X".into(), Value::Array(vec![Value::Number(1.0)]));
expected.insert("Y".into(), Value::Number(4.0));
assert_eq!(v, Value::Object(expected));
}
#[test]
fn unquoted_keys_and_trailing_commas() {
let v: Value = jsonx::from_str("{ __: true, _a_b : false, x9: 1, }").unwrap();
let obj = v.as_object().unwrap();
assert_eq!(obj.get("__"), Some(&Value::Bool(true)));
assert_eq!(obj.get("_a_b"), Some(&Value::Bool(false)));
assert_eq!(obj.get("x9"), Some(&Value::Number(1.0)));
let arr: Value = jsonx::from_str(r#"["test", int64(-123),]"#).unwrap();
assert_eq!(
arr,
Value::Array(vec![Value::String("test".into()), Value::Int64(-123)])
);
}
#[test]
fn string_escapes() {
assert_eq!(jsonx::from_str::<String>(r#""aሴ""#).unwrap(), "a\u{1234}");
assert_eq!(jsonx::from_str::<String>(r#""http:\/\/""#).unwrap(), "http://");
assert_eq!(
jsonx::from_str::<String>(r#""tab\tnewline\n""#).unwrap(),
"tab\tnewline\n"
);
assert_eq!(
jsonx::from_str::<String>(r#""g-clef: 𝄞""#).unwrap(),
"g-clef: \u{1D11E}"
);
}
#[test]
fn rejects_lone_surrogate() {
assert!(jsonx::from_str::<String>(r#""\uD834""#).is_err());
assert!(jsonx::from_str::<String>(r#""\uDD1E""#).is_err());
}
#[test]
fn borrows_strings_when_possible() {
let s: &str = jsonx::from_str(r#""borrowed""#).unwrap();
assert_eq!(s, "borrowed");
}
#[test]
fn typed_integers_to_value() {
assert_eq!(jsonx::from_str::<Value>("int(64)").unwrap(), Value::Int(64));
assert_eq!(jsonx::from_str::<Value>("uint(64)").unwrap(), Value::Uint(64));
assert_eq!(jsonx::from_str::<Value>("int8(-128)").unwrap(), Value::Int8(-128));
assert_eq!(jsonx::from_str::<Value>("uint8(255)").unwrap(), Value::Uint8(255));
assert_eq!(jsonx::from_str::<Value>("int16(-4567)").unwrap(), Value::Int16(-4567));
assert_eq!(jsonx::from_str::<Value>("int32(5364564)").unwrap(), Value::Int32(5364564));
assert_eq!(
jsonx::from_str::<Value>(r#"int64("9223372036854775807")"#).unwrap(),
Value::Int64(i64::MAX)
);
assert_eq!(
jsonx::from_str::<Value>(r#"uint64("18446744073709551615")"#).unwrap(),
Value::Uint64(u64::MAX)
);
}
#[test]
fn typed_integers_into_rust_ints() {
assert_eq!(jsonx::from_str::<i32>("int32(5)").unwrap(), 5);
assert_eq!(jsonx::from_str::<i32>("5").unwrap(), 5);
assert_eq!(jsonx::from_str::<i32>("int(5)").unwrap(), 5);
assert_eq!(jsonx::from_str::<u8>("uint8(200)").unwrap(), 200);
assert_eq!(jsonx::from_str::<i64>(r#"int64("123")"#).unwrap(), 123);
}
#[test]
fn integer_range_errors() {
assert!(jsonx::from_str::<Value>("int8(-500)").is_err());
assert!(jsonx::from_str::<Value>("uint8(256)").is_err());
assert!(jsonx::from_str::<i8>("int32(1000)").is_err());
}
#[test]
fn extended_types_to_value() {
assert_eq!(
jsonx::from_str::<Value>(r#"datetime("2017-01-01T12:00:00Z")"#).unwrap(),
Value::DateTime(DateTime::parse_from_rfc3339("2017-01-01T12:00:00Z").unwrap())
);
assert_eq!(
jsonx::from_str::<Value>(r#"ip("192.168.100.19")"#).unwrap(),
Value::Ip("192.168.100.19".parse().unwrap())
);
assert_eq!(
jsonx::from_str::<Value>(r#"ip("fd00::abc:1")"#).unwrap(),
Value::Ip("fd00::abc:1".parse().unwrap())
);
assert_eq!(
jsonx::from_str::<Value>(r#"ipport("192.168.1.2:65000")"#).unwrap(),
Value::IpPort("192.168.1.2:65000".parse().unwrap())
);
assert_eq!(
jsonx::from_str::<Value>(r#"ipport("[fd00::abc:1]:65000")"#).unwrap(),
Value::IpPort("[fd00::abc:1]:65000".parse().unwrap())
);
assert_eq!(
jsonx::from_str::<Value>(r#"bytes("YWJjZA==")"#).unwrap(),
Value::Bytes(b"abcd".to_vec())
);
}
fn ctor(name: &str, arg: Value) -> Value {
Value::Constructor {
name: name.to_owned(),
arg: Box::new(arg),
}
}
#[test]
fn unknown_constructor_to_value() {
assert_eq!(
jsonx::from_str::<Value>(r#"duration("5s")"#).unwrap(),
ctor("duration", Value::String("5s".into()))
);
assert_eq!(
jsonx::from_str::<Value>(r#"ipnet("10.0.0.0/8")"#).unwrap(),
ctor("ipnet", Value::String("10.0.0.0/8".into()))
);
}
#[test]
fn custom_constructor_round_trips() {
for text in [
r#"duration("5s")"#,
r#"ipnet("10.0.0.0/8")"#,
r#"uuid("550e8400-e29b-41d4-a716-446655440000")"#,
r#"scaled(1.5)"#,
r#"wrapped(int(5))"#,
r#"outer(inner("x"))"#,
r#"point([1.0,2.0])"#,
] {
let value: Value = jsonx::from_str(text).unwrap();
assert_eq!(jsonx::to_string(&value).unwrap(), text, "round-trip of {text}");
assert_eq!(jsonx::from_str::<Value>(&jsonx::to_string(&value).unwrap()).unwrap(), value);
}
}
#[test]
fn custom_constructor_nested_in_containers() {
let value: Value =
jsonx::from_str(r#"{a: duration("5s"), b: [cidr("0.0.0.0/0")],}"#).unwrap();
assert_eq!(value.get("a"), Some(&ctor("duration", Value::String("5s".into()))));
assert_eq!(
value.get("b").and_then(Value::as_array),
Some(&[ctor("cidr", Value::String("0.0.0.0/0".into()))][..])
);
}
#[test]
fn constructor_helper_builds_and_encodes() {
assert_eq!(
jsonx::to_string(&Value::constructor("duration", "5s")).unwrap(),
r#"duration("5s")"#
);
assert_eq!(
jsonx::to_string(&Value::constructor("scaled", 1.5)).unwrap(),
"scaled(1.5)"
);
}
#[test]
fn deeply_nested_constructors_are_bounded() {
let deep = format!("{}1{}", "a(".repeat(500), ")".repeat(500));
assert!(jsonx::from_str::<Value>(&deep).is_err());
}
#[test]
fn invalid_constructor_name_fails_to_serialize() {
for bad in ["bad name", "3lead", "with-dash", "", "a(b", "💥"] {
let v = ctor(bad, Value::String("x".into()));
assert!(
jsonx::to_string(&v).is_err(),
"expected error serializing constructor name {bad:?}, got {:?}",
jsonx::to_string(&v)
);
}
}
#[test]
fn valid_constructor_names_round_trip() {
for good in ["good", "_private", "x", "snake_case", "with9digits", "_"] {
let v = ctor(good, Value::String("x".into()));
let text = jsonx::to_string(&v).unwrap();
assert_eq!(text, format!(r#"{good}("x")"#));
assert_eq!(jsonx::from_str::<Value>(&text).unwrap(), v);
}
}
#[test]
fn to_jsonx_arg_yields_canonical_text() {
let dt: Value = jsonx::from_str(r#"datetime("2017-12-25T15:00:00Z")"#).unwrap();
assert_eq!(dt.to_jsonx_arg().as_deref(), Some("2017-12-25T15:00:00Z"));
assert_eq!(
Value::Ip("10.0.0.1".parse().unwrap()).to_jsonx_arg().as_deref(),
Some("10.0.0.1")
);
assert_eq!(Value::bytes(b"abcd".to_vec()).to_jsonx_arg().as_deref(), Some("YWJjZA=="));
assert_eq!(Value::int(7).to_jsonx_arg().as_deref(), Some("7"));
assert_eq!(Value::Int8(-128).to_jsonx_arg().as_deref(), Some("-128"));
assert_eq!(
Value::constructor("ipnet", "10.0.0.0/8").to_jsonx_arg().as_deref(),
Some("10.0.0.0/8")
);
assert_eq!(
Value::constructor("point", Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]))
.to_jsonx_arg()
.as_deref(),
Some("[1.0,2.0]")
);
assert_eq!(Value::Null.to_jsonx_arg(), None);
assert_eq!(Value::String("x".into()).to_jsonx_arg(), None);
assert_eq!(Value::Number(1.5).to_jsonx_arg(), None);
}
#[test]
fn datetime_helpers_are_public() {
let dt = jsonx::datetime::parse("2017-12-25T15:00:00+00:00").unwrap();
assert_eq!(jsonx::datetime::to_jsonx_string(&dt), "2017-12-25T15:00:00Z");
}
#[test]
fn value_builders_encode() {
assert_eq!(jsonx::to_string(&Value::int(-5)).unwrap(), "int(-5)");
assert_eq!(jsonx::to_string(&Value::uint(5)).unwrap(), "uint(5)");
assert_eq!(jsonx::to_string(&Value::bytes(b"hi".to_vec())).unwrap(), r#"bytes("aGk=")"#);
assert_eq!(jsonx::to_string(&Value::string("x")).unwrap(), r#""x""#);
}
#[test]
fn object_key_ordering_matches_backend() {
let v: Value = jsonx::from_str("{b: 1, a: 2, c: 3}").unwrap();
let text = jsonx::to_string(&v).unwrap();
#[cfg(feature = "preserve_order")]
assert_eq!(text, "{b:1.0,a:2.0,c:3.0}");
#[cfg(not(feature = "preserve_order"))]
assert_eq!(text, "{a:2.0,b:1.0,c:3.0}");
}
fn reference_value() -> Value {
let mut m = Map::new();
m.insert("k01".into(), Value::Null);
m.insert("k02".into(), Value::Bool(false));
m.insert("k03".into(), Value::Bool(true));
m.insert("k04".into(), Value::String("test".into()));
m.insert("k05".into(), Value::Number(1.45678e-98));
m.insert("k06".into(), Value::Int(-454365464));
m.insert("k07".into(), Value::Uint(455645765));
m.insert("k08".into(), Value::Int8(-128));
m.insert("k09".into(), Value::Uint8(255));
m.insert("k10".into(), Value::Int16(32767));
m.insert("k11".into(), Value::Uint16(65535));
m.insert("k12".into(), Value::Int32(i32::MAX));
m.insert("k13".into(), Value::Uint32(u32::MAX));
m.insert("k14".into(), Value::Int64(i64::MAX));
m.insert("k15".into(), Value::Uint64(u64::MAX));
m.insert(
"k16".into(),
Value::DateTime(DateTime::parse_from_rfc3339("2017-12-25T15:00:00Z").unwrap()),
);
m.insert("k17".into(), Value::Ip("192.168.1.2".parse().unwrap()));
m.insert("k18".into(), Value::IpPort("192.168.1.2:65000".parse().unwrap()));
m.insert("k19".into(), Value::Ip("::1".parse().unwrap()));
m.insert("k20".into(), Value::IpPort("[::1]:65000".parse().unwrap()));
m.insert(
"k21".into(),
Value::Array(vec![Value::String("test".into()), Value::Int(123)]),
);
let mut inner = Map::new();
inner.insert("test".into(), Value::Bool(true));
m.insert("k22".into(), Value::Object(inner));
Value::Object(m)
}
#[test]
fn encode_compact() {
let expected = r#"{k01:null,k02:false,k03:true,k04:"test",k05:1.45678e-98,k06:int(-454365464),k07:uint(455645765),k08:int8(-128),k09:uint8(255),k10:int16(32767),k11:uint16(65535),k12:int32(2147483647),k13:uint32(4294967295),k14:int64("9223372036854775807"),k15:uint64("18446744073709551615"),k16:datetime("2017-12-25T15:00:00Z"),k17:ip("192.168.1.2"),k18:ipport("192.168.1.2:65000"),k19:ip("::1"),k20:ipport("[::1]:65000"),k21:["test",int(123)],k22:{test:true}}"#;
assert_eq!(jsonx::to_string(&reference_value()).unwrap(), expected);
}
#[test]
fn encode_pretty() {
let expected = r#"{
k01: null,
k02: false,
k03: true,
k04: "test",
k05: 1.45678e-98,
k06: int(-454365464),
k07: uint(455645765),
k08: int8(-128),
k09: uint8(255),
k10: int16(32767),
k11: uint16(65535),
k12: int32(2147483647),
k13: uint32(4294967295),
k14: int64("9223372036854775807"),
k15: uint64("18446744073709551615"),
k16: datetime("2017-12-25T15:00:00Z"),
k17: ip("192.168.1.2"),
k18: ipport("192.168.1.2:65000"),
k19: ip("::1"),
k20: ipport("[::1]:65000"),
k21: [
"test",
int(123)
],
k22: {
test: true
}
}"#;
assert_eq!(jsonx::to_string_pretty(&reference_value()).unwrap(), expected);
}
#[test]
fn value_round_trips_through_text() {
let value = reference_value();
let text = jsonx::to_string(&value).unwrap();
let back: Value = jsonx::from_str(&text).unwrap();
assert_eq!(value, back);
let pretty = jsonx::to_string_pretty(&value).unwrap();
let back_pretty: Value = jsonx::from_str(&pretty).unwrap();
assert_eq!(value, back_pretty);
}
#[test]
fn empty_containers_encode_tightly() {
assert_eq!(jsonx::to_string(&Value::Array(vec![])).unwrap(), "[]");
assert_eq!(jsonx::to_string(&Value::Object(Map::new())).unwrap(), "{}");
assert_eq!(jsonx::to_string_pretty(&Value::Array(vec![])).unwrap(), "[]");
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Server {
name: String,
port: u16,
weight: i32,
tags: Vec<String>,
nick: Option<String>,
}
#[test]
fn derive_struct_round_trip() {
let server = Server {
name: "db".into(),
port: 5432,
weight: -1,
tags: vec!["a".into(), "b".into()],
nick: None,
};
let text = jsonx::to_string(&server).unwrap();
assert_eq!(
text,
r#"{name:"db",port:uint16(5432),weight:int32(-1),tags:["a","b"],nick:null}"#
);
assert_eq!(jsonx::from_str::<Server>(&text).unwrap(), server);
}
#[test]
fn derive_struct_accepts_quoted_and_unquoted_keys() {
let from_relaxed: Server =
jsonx::from_str(r#"{ name: "db", port: 5432, weight: -1, tags: [], nick: "d", }"#).unwrap();
assert_eq!(from_relaxed.name, "db");
assert_eq!(from_relaxed.port, 5432);
assert_eq!(from_relaxed.nick.as_deref(), Some("d"));
}
#[test]
fn derive_with_wrappers() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Record {
#[serde(with = "jsonx::ip")]
addr: IpAddr,
#[serde(with = "jsonx::ipport")]
listen: SocketAddr,
blob: Bytes,
big: Int,
count: Uint,
#[serde(with = "jsonx::datetime")]
ts: DateTime,
}
let record = Record {
addr: "10.0.0.1".parse::<IpAddr>().unwrap(),
listen: "[::1]:8080".parse::<SocketAddr>().unwrap(),
blob: Bytes(b"hello".to_vec()),
big: Int(-9000000000),
count: Uint(42),
ts: DateTime::parse_from_rfc3339("2020-06-01T08:30:00Z").unwrap(),
};
let text = jsonx::to_string(&record).unwrap();
assert_eq!(
text,
r#"{addr:ip("10.0.0.1"),listen:ipport("[::1]:8080"),blob:bytes("aGVsbG8="),big:int(-9000000000),count:uint(42),ts:datetime("2020-06-01T08:30:00Z")}"#
);
assert_eq!(jsonx::from_str::<Record>(&text).unwrap(), record);
}
#[test]
fn derive_with_attribute_free_wrappers() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Record {
addr: Ip,
listen: IpPort,
blob: Bytes,
big: Int,
count: Uint,
ts: Datetime,
}
let record = Record {
addr: Ip("10.0.0.1".parse().unwrap()),
listen: IpPort("[::1]:8080".parse().unwrap()),
blob: Bytes(b"hello".to_vec()),
big: Int(-9000000000),
count: Uint(42),
ts: Datetime(DateTime::parse_from_rfc3339("2020-06-01T08:30:00Z").unwrap()),
};
let text = jsonx::to_string(&record).unwrap();
assert_eq!(
text,
r#"{addr:ip("10.0.0.1"),listen:ipport("[::1]:8080"),blob:bytes("aGVsbG8="),big:int(-9000000000),count:uint(42),ts:datetime("2020-06-01T08:30:00Z")}"#
);
assert_eq!(jsonx::from_str::<Record>(&text).unwrap(), record);
}
#[test]
fn wrappers_are_transparent_to_other_formats() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Record {
addr: Ip,
ts: Datetime,
big: Int,
}
let record = Record {
addr: Ip("10.0.0.1".parse().unwrap()),
ts: Datetime(DateTime::parse_from_rfc3339("2020-06-01T08:30:00Z").unwrap()),
big: Int(7),
};
let json = serde_json::to_string(&record).unwrap();
assert_eq!(json, r#"{"addr":"10.0.0.1","ts":"2020-06-01T08:30:00Z","big":7}"#);
assert_eq!(serde_json::from_str::<Record>(&json).unwrap(), record);
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct Version {
major: u16,
minor: u16,
}
impl jsonx::JsonxConstructor for Version {
const TOKEN: &'static str = jsonx::ctor!("semver");
fn to_jsonx_arg(&self) -> String {
format!("{}.{}", self.major, self.minor)
}
fn from_jsonx_arg(arg: &str) -> Result<Self, String> {
let (major, minor) = arg.split_once('.').ok_or("expected MAJOR.MINOR")?;
Ok(Version {
major: major.parse().map_err(|_| "bad major")?,
minor: minor.parse().map_err(|_| "bad minor")?,
})
}
}
impl Serialize for Version {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
jsonx::constructor::serialize(self, s)
}
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
jsonx::constructor::deserialize(d)
}
}
#[test]
fn custom_constructor_round_trips_in_jsonx() {
let v = Version { major: 1, minor: 4 };
let text = jsonx::to_string(&v).unwrap();
assert_eq!(text, r#"semver("1.4")"#);
assert_eq!(jsonx::from_str::<Version>(&text).unwrap(), v);
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct App {
version: Version,
bind: IpPort,
}
let app = App {
version: Version { major: 2, minor: 0 },
bind: IpPort("0.0.0.0:80".parse().unwrap()),
};
let text = jsonx::to_string(&app).unwrap();
assert_eq!(text, r#"{version:semver("2.0"),bind:ipport("0.0.0.0:80")}"#);
assert_eq!(jsonx::from_str::<App>(&text).unwrap(), app);
}
#[test]
fn custom_constructor_is_transparent_to_other_formats() {
let v = Version { major: 1, minor: 4 };
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, r#""1.4""#);
assert_eq!(serde_json::from_str::<Version>(&json).unwrap(), v);
}
#[cfg(feature = "derive")]
mod derive_tests {
#[derive(jsonx::JsonxConstructor, Clone, Copy, PartialEq, Eq, Debug)]
#[jsonx(name = "semver")]
struct DerivedVersion {
major: u16,
minor: u16,
}
impl std::fmt::Display for DerivedVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
impl std::str::FromStr for DerivedVersion {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
let (a, b) = s.split_once('.').ok_or("expected MAJOR.MINOR")?;
Ok(DerivedVersion {
major: a.parse().map_err(|_| "bad major")?,
minor: b.parse().map_err(|_| "bad minor")?,
})
}
}
#[derive(jsonx::JsonxConstructor, Clone, Copy, PartialEq, Eq, Debug)]
struct Mac([u8; 6]);
impl std::fmt::Display for Mac {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let b = self.0;
write!(
f,
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
b[0], b[1], b[2], b[3], b[4], b[5]
)
}
}
impl std::str::FromStr for Mac {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
let mut out = [0u8; 6];
let mut parts = s.split(':');
for slot in &mut out {
let p = parts.next().ok_or("too few octets")?;
*slot = u8::from_str_radix(p, 16).map_err(|_| "bad octet")?;
}
if parts.next().is_some() {
return Err("too many octets".into());
}
Ok(Mac(out))
}
}
#[test]
fn derived_constructor_round_trips_and_is_transparent() {
let v = DerivedVersion { major: 1, minor: 4 };
let text = jsonx::to_string(&v).unwrap();
assert_eq!(text, r#"semver("1.4")"#);
assert_eq!(jsonx::from_str::<DerivedVersion>(&text).unwrap(), v);
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, r#""1.4""#);
assert_eq!(serde_json::from_str::<DerivedVersion>(&json).unwrap(), v);
}
#[test]
fn derived_constructor_defaults_name_to_lowercased_type() {
let mac = Mac([0xde, 0xad, 0xbe, 0xef, 0x00, 0x01]);
let text = jsonx::to_string(&mac).unwrap();
assert_eq!(text, r#"mac("de:ad:be:ef:00:01")"#);
assert_eq!(jsonx::from_str::<Mac>(&text).unwrap(), mac);
}
}
#[test]
fn custom_constructor_via_with_attribute() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Release {
#[serde(with = "jsonx::constructor")]
at: Version,
}
let r = Release { at: Version { major: 3, minor: 7 } };
let text = jsonx::to_string(&r).unwrap();
assert_eq!(text, r#"{at:semver("3.7")}"#);
assert_eq!(jsonx::from_str::<Release>(&text).unwrap(), r);
}
#[test]
fn derive_enums() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
enum E {
Unit,
New(i32),
Tuple(i32, i32),
Struct { a: i32 },
}
for (value, text) in [
(E::Unit, r#""Unit""#),
(E::New(5), r#"{New:int32(5)}"#),
(E::Tuple(1, 2), r#"{Tuple:[int32(1),int32(2)]}"#),
(E::Struct { a: 7 }, r#"{Struct:{a:int32(7)}}"#),
] {
assert_eq!(jsonx::to_string(&value).unwrap(), text, "encoding {value:?}");
assert_eq!(jsonx::from_str::<E>(text).unwrap(), value, "decoding {text}");
}
}
#[test]
fn map_with_integer_keys() {
let mut m: BTreeMap<u32, String> = BTreeMap::new();
m.insert(1, "one".into());
m.insert(2, "two".into());
let text = jsonx::to_string(&m).unwrap();
assert_eq!(text, r#"{"1":"one","2":"two"}"#);
assert_eq!(jsonx::from_str::<BTreeMap<u32, String>>(&text).unwrap(), m);
}
#[test]
fn non_greedy_decoding() {
let (value, offset): (Value, usize) = jsonx::from_str_partial("{test: 1} blah").unwrap();
let mut expected = Map::new();
expected.insert("test".into(), Value::Number(1.0));
assert_eq!(value, Value::Object(expected));
assert_eq!(&"{test: 1} blah"[offset..], "blah");
}
#[test]
fn trailing_data_is_an_error_for_from_str() {
let err = jsonx::from_str::<Value>("{a:1} blah").unwrap_err();
assert!(matches!(err, jsonx::Error::TrailingData { .. }));
}
#[test]
fn syntax_errors() {
assert!(jsonx::from_str::<Value>("[,]").is_err());
assert!(jsonx::from_str::<Value>("{").is_err());
assert!(jsonx::from_str::<Value>(r#"{"X": "foo", "Y"}"#).is_err());
assert!(jsonx::from_str::<Value>("nul").is_err());
assert!(jsonx::from_str::<Value>("[1, 2, 3+]").is_err());
}
#[test]
fn non_finite_float_is_rejected_on_encode() {
assert!(jsonx::to_string(&f64::NAN).is_err());
assert!(jsonx::to_string(&f64::INFINITY).is_err());
}
#[test]
fn display_matches_compact_encoding() {
let value = reference_value();
assert_eq!(value.to_string(), jsonx::to_string(&value).unwrap());
}
#[test]
fn malformed_input_never_panics() {
let inputs = [
"", " ", "[", "]", "{", "}", "{:}", "[,]", "{,}", ",", ":",
"int", "int(", "int()", "int8(", "ip(", "ip()", "datetime()",
"bytes(\"!!!!\")", "\"unterminated", "\"\\", "\"\\u", "\"\\uzzzz\"",
"{a", "{a:", "{a:1", "[1", "[1,", "tru", "fals", "nul", "-", "0.",
"1e", "1.e5", "--5", "01", "{\"a\":1}{\"b\":2}", "ip(\"not-an-ip\")",
"int8(99999999999999999999999999)", "+", "0x10", ".5", "[[[",
];
for input in inputs {
let _ = jsonx::from_str::<jsonx::Value>(input);
}
}
#[test]
fn deep_nesting_is_bounded() {
let deep = "[".repeat(100_000);
let err = jsonx::from_str::<jsonx::Value>(&deep).unwrap_err();
assert!(matches!(err, jsonx::Error::Syntax { .. }));
}
#[test]
fn number_overflow_is_rejected() {
assert!(jsonx::from_str::<Value>("1e400").is_err());
assert!(jsonx::from_str::<f64>("-1e400").is_err());
}
#[test]
fn extreme_exponent_does_not_overflow() {
assert_eq!(jsonx::from_str::<f64>("0.00e-99999999999999999999").unwrap(), 0.0);
assert_eq!(
jsonx::from_str::<f64>("1.21e-8888888888888888888888").unwrap(),
0.0
);
assert_eq!(jsonx::from_str::<f64>("0.0e-99999999999999999999").unwrap(), 0.0);
assert!(jsonx::from_str::<f64>("1.21e99999999999999999999").is_err());
}
#[test]
fn float_parsing_is_bit_exact() {
let cases = [
"0", "-0", "0.5", "1.5", "123.456", "0.001", "1e-3", "6.022e23",
"1.7976931348623157e308", "2.2250738585072014e-308", "5e-324",
"9007199254740993", "0.1", "0.2", "0.3", "1234567.0",
"12345678901234567890.12345678901234567890", "9.999999999999999e22",
"3.141592653589793", "2.718281828459045", "-0.0", "100000000000000000000",
];
for s in cases {
let want: f64 = s.parse().unwrap();
let got: f64 = jsonx::from_str(s).unwrap();
assert_eq!(want.to_bits(), got.to_bits(), "parsing {s:?}");
}
}
#[test]
fn integer_boundaries_parse() {
assert_eq!(jsonx::from_str::<i64>("-9223372036854775808").unwrap(), i64::MIN);
assert_eq!(jsonx::from_str::<i64>("9223372036854775807").unwrap(), i64::MAX);
assert_eq!(jsonx::from_str::<u64>("18446744073709551615").unwrap(), u64::MAX);
assert_eq!(jsonx::from_str::<u64>("9223372036854775808").unwrap(), 1u64 << 63);
assert_eq!(jsonx::from_str::<i128>("-9223372036854775809").unwrap(), -9223372036854775809i128);
assert_eq!(jsonx::from_str::<u64>(r#"uint64("18446744073709551615")"#).unwrap(), u64::MAX);
assert_eq!(jsonx::from_str::<i64>("int64(-9223372036854775808)").unwrap(), i64::MIN);
assert!(jsonx::from_str::<u8>("256").is_err());
assert!(jsonx::from_str::<i32>("2147483648").is_err());
assert!(jsonx::from_str::<u16>("-1").is_err());
}
#[test]
fn float_formatting_round_trips() {
assert_eq!(jsonx::to_string(&5432.0_f64).unwrap(), "5432.0");
assert_eq!(jsonx::to_string(&-1.0_f64).unwrap(), "-1.0");
assert_eq!(jsonx::to_string(&0.0_f64).unwrap(), "0.0");
assert_eq!(jsonx::to_string(&-0.0_f64).unwrap(), "-0.0");
assert_eq!(jsonx::to_string(&1.5_f64).unwrap(), "1.5");
assert_eq!(jsonx::to_string(&6.022e23_f64).unwrap(), "6.022e23");
for v in [0.0, -0.0, 1.5, -2.5e-310, 9007199254740993.0, 42.0, f64::MAX] {
let s = jsonx::to_string(&v).unwrap();
let back: f64 = jsonx::from_str(&s).unwrap();
assert_eq!(v.to_bits(), back.to_bits(), "round-trip {v} via {s}");
}
}
#[test]
fn utf8_strings_are_validated() {
assert_eq!(jsonx::from_str::<String>("\"café 日本語 🦀\"").unwrap(), "café 日本語 🦀");
assert!(jsonx::from_slice::<String>(b"\"a\xffb\"").is_err());
assert!(jsonx::from_slice::<String>(b"\"\xe2\x28\xa1\"").is_err());
assert_eq!(jsonx::from_str::<String>("\"café\\tend 🦀\"").unwrap(), "café\tend 🦀");
assert!(jsonx::from_slice::<String>(b"\"a\\t\xffb\"").is_err());
}