use std::cell::Cell;
use std::collections::BTreeMap;
use std::fmt;
use qubit_budget::BudgetError;
use qubit_budget::MeasuredBudgetError;
use qubit_budget::Observation;
use qubit_budget::json::JsonResource;
use serde::Serialize;
use serde::Serializer;
use serde::ser::SerializeMap;
use serde::ser::SerializeSeq;
use serde::ser::SerializeStruct;
use serde::ser::SerializeStructVariant;
use serde::ser::SerializeTupleStruct;
use serde::ser::SerializeTupleVariant;
use serde_json::to_vec;
use serde_json::value::RawValue;
use crate::encode::json_encode_test_support::encode;
use crate::fixtures::JsonTestLimits;
const JSON_NUMBER_TOKEN: &str = concat!("$", "serde_json", ":", ":private::Number");
const JSON_RAW_VALUE_TOKEN: &str = concat!("$", "serde_json", ":", ":private::RawValue");
struct SkipFieldStruct;
impl Serialize for SkipFieldStruct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("SkipFieldStruct", 1)?;
state.skip_field("omitted")?;
state.serialize_field("kept", &true)?;
state.end()
}
}
#[test]
fn test_json_encode_compound_forwards_skip_field() {
let mut session = JsonTestLimits::new().encode_session();
let encoded = encode(&SkipFieldStruct, &mut session).expect("skipped fields should be forwarded");
assert_eq!(encoded, br#"{"kept":true}"#);
}
struct SkipFieldVariant;
impl Serialize for SkipFieldVariant {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct_variant("V", 0, "V", 1)?;
state.skip_field("omitted")?;
state.end()
}
}
#[test]
fn test_json_encode_compound_forwards_struct_variant_skip_field() {
let mut session = JsonTestLimits::new().encode_session();
let output = encode(&SkipFieldVariant, &mut session).expect("skipped struct-variant fields should encode");
assert_eq!(output, br#"{"V":{}}"#);
}
const LARGE_NUMBER_TEXT: &str = "18446744073709551615";
struct CountedDisplay<'a> {
formatted: &'a Cell<usize>,
chunk: &'static str,
chunks: usize,
}
impl fmt::Display for CountedDisplay<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for _ in 0..self.chunks {
self.formatted.set(self.formatted.get() + 1);
formatter.write_str(self.chunk)?;
}
Ok(())
}
}
struct CollectedText<'a>(CountedDisplay<'a>);
impl Serialize for CollectedText<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(&self.0)
}
}
struct CollectedKeyMap<'a>(CollectedText<'a>);
impl Serialize for CollectedKeyMap<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry(&self.0, &())?;
map.end()
}
}
struct CollectedPrivateText<'a> {
token: &'static str,
payload: CountedDisplay<'a>,
}
struct ObservedUnit<'a>(&'a Cell<usize>);
impl Serialize for ObservedUnit<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.set(self.0.get() + 1);
serializer.serialize_unit()
}
}
struct UnderreportedSequence<'a>(&'a Cell<usize>);
impl Serialize for UnderreportedSequence<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut sequence = serializer.serialize_seq(Some(1))?;
for _ in 0..3 {
sequence.serialize_element(&ObservedUnit(self.0))?;
}
sequence.end()
}
}
struct OverreportedSequence;
impl Serialize for OverreportedSequence {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut sequence = serializer.serialize_seq(Some(3))?;
sequence.serialize_element(&())?;
sequence.end()
}
}
struct UnderreportedMap<'a>(&'a Cell<usize>);
impl Serialize for UnderreportedMap<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
for key in ["a", "b", "c"] {
map.serialize_entry(key, &ObservedUnit(self.0))?;
}
map.end()
}
}
struct UnderreportedStruct<'a>(&'a Cell<usize>);
impl Serialize for UnderreportedStruct<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("UnderreportedStruct", 1)?;
state.serialize_field("a", &ObservedUnit(self.0))?;
state.serialize_field("b", &ObservedUnit(self.0))?;
state.serialize_field("c", &ObservedUnit(self.0))?;
state.end()
}
}
struct UnderreportedTupleStruct<'a>(&'a Cell<usize>);
impl Serialize for UnderreportedTupleStruct<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_tuple_struct("Underreported", 1)?;
for _ in 0..3 {
state.serialize_field(&ObservedUnit(self.0))?;
}
state.end()
}
}
struct UnderreportedVariant<'a> {
observed: &'a Cell<usize>,
tuple: bool,
}
impl Serialize for UnderreportedVariant<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.tuple {
let mut state = serializer.serialize_tuple_variant("E", 0, "V", 1)?;
for _ in 0..3 {
state.serialize_field(&ObservedUnit(self.observed))?;
}
state.end()
} else {
let mut state = serializer.serialize_struct_variant("E", 0, "V", 1)?;
state.serialize_field("a", &ObservedUnit(self.observed))?;
state.serialize_field("b", &ObservedUnit(self.observed))?;
state.serialize_field("c", &ObservedUnit(self.observed))?;
state.end()
}
}
}
impl Serialize for CollectedPrivateText<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct(self.token, 1)?;
state.serialize_field(
self.token,
&CollectedText(CountedDisplay {
formatted: self.payload.formatted,
chunk: self.payload.chunk,
chunks: self.payload.chunks,
}),
)?;
state.end()
}
}
fn assert_resource<T>(value: &T, limits: JsonTestLimits, expected: JsonResource)
where
T: Serialize + ?Sized,
{
let mut budget = limits.encode_session();
let error = encode(value, &mut budget).expect_err("the configured JSON limit must reject the value");
let error = error.into_budget_error().expect("expected a budget error");
assert_eq!(
error
.budget_error()
.expect("the error must contain a budget failure")
.resource(),
&expected,
);
}
fn assert_same_json<T>(value: &T)
where
T: Serialize + ?Sized,
{
let expected = to_vec(value).expect("reference JSON");
let mut budget = JsonTestLimits::new().encode_session();
let actual = encode(value, &mut budget).expect("budget-aware JSON should serialize");
assert_eq!(actual, expected);
}
fn assert_node_count<T>(value: &T, nodes: usize)
where
T: Serialize + ?Sized,
{
let mut exact = JsonTestLimits::new().max_nodes(nodes).encode_session();
encode(value, &mut exact).expect("the exact node budget should accept the value");
let mut insufficient = JsonTestLimits::new()
.max_nodes(nodes.saturating_sub(1))
.encode_session();
let error = encode(value, &mut insufficient).expect_err("one fewer node must reject the value");
let error = error.into_budget_error().expect("expected a budget error");
assert_eq!(
error
.budget_error()
.expect("the error must contain a budget failure")
.resource(),
&JsonResource::Nodes,
);
}
struct UnknownSequence(usize);
impl Serialize for UnknownSequence {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut sequence = serializer.serialize_seq(None)?;
for value in 0..self.0 {
sequence.serialize_element(&value)?;
}
sequence.end()
}
}
struct UnknownMap(usize);
impl Serialize for UnknownMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
for value in 0..self.0 {
map.serialize_entry(&value, &value)?;
}
map.end()
}
}
struct CountedUnknownSequence<'a> {
serialized: &'a Cell<usize>,
len: usize,
}
struct CountedUnknownMap<'a> {
serialized: &'a Cell<usize>,
len: usize,
}
struct CountedPrivateRawValue<'a> {
raw: &'a str,
serialized: &'a Cell<usize>,
}
impl Serialize for CountedPrivateRawValue<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct(JSON_RAW_VALUE_TOKEN, 1)?;
state.serialize_field(
JSON_RAW_VALUE_TOKEN,
&CountedPrivateRawValuePayload {
raw: self.raw,
serialized: self.serialized,
},
)?;
state.end()
}
}
struct CountedPrivateRawValuePayload<'a> {
raw: &'a str,
serialized: &'a Cell<usize>,
}
impl Serialize for CountedPrivateRawValuePayload<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.serialized.set(self.serialized.get() + 1);
serializer.serialize_str(self.raw)
}
}
struct RawThenCountedTail<'a> {
raw: &'a RawValue,
serialized_tail: &'a Cell<usize>,
}
impl Serialize for RawThenCountedTail<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut sequence = serializer.serialize_seq(Some(2))?;
sequence.serialize_element(self.raw)?;
self.serialized_tail.set(self.serialized_tail.get() + 1);
sequence.serialize_element(&())?;
sequence.end()
}
}
impl Serialize for CountedUnknownSequence<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut sequence = serializer.serialize_seq(None)?;
for value in 0..self.len {
self.serialized.set(self.serialized.get() + 1);
sequence.serialize_element(&value)?;
}
sequence.end()
}
}
impl Serialize for CountedUnknownMap<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
for value in 0..self.len {
self.serialized.set(self.serialized.get() + 1);
map.serialize_entry(&value, &value)?;
}
map.end()
}
}
#[derive(Serialize)]
struct Nested {
value: Vec<Vec<bool>>,
}
#[derive(Serialize)]
struct SharedPayload {
text: &'static str,
number: u8,
}
#[derive(Serialize)]
struct Newtype(u8);
#[derive(Serialize)]
enum EnumShape {
Unit,
Newtype(u8),
Tuple(u8, u8),
Struct { value: u8 },
}
#[test]
fn test_json_encode_serializer_charges_scalar_nodes() {
assert_resource(&true, JsonTestLimits::new().max_nodes(0), JsonResource::Nodes);
}
#[test]
fn test_json_encode_serializer_checks_sequence_items() {
let limits = JsonTestLimits::new().max_sequence_items(2);
let mut exact = limits.clone().encode_session();
encode(&UnknownSequence(2), &mut exact).expect("an unknown sequence at the exact item limit must serialize");
assert_resource(&[1, 2, 3], limits, JsonResource::SequenceItems);
assert_resource(
&UnknownSequence(3),
JsonTestLimits::new().max_sequence_items(2),
JsonResource::SequenceItems,
);
}
#[test]
fn test_json_encode_serializer_checks_unknown_sequence_after_traversal() {
let serialized = Cell::new(0);
let value = CountedUnknownSequence {
serialized: &serialized,
len: 1_000,
};
assert_resource(
&value,
JsonTestLimits::new().max_sequence_items(2),
JsonResource::SequenceItems,
);
assert_eq!(serialized.get(), 3);
}
#[test]
fn test_json_encode_serializer_checks_map_entries() {
let limits = JsonTestLimits::new().max_map_entries(1);
let values = BTreeMap::from([("a", 1), ("b", 2)]);
assert_resource(&values, limits, JsonResource::MapEntries);
let mut exact = JsonTestLimits::new().max_map_entries(2).encode_session();
encode(&UnknownMap(2), &mut exact).expect("an unknown map at the exact entry limit must serialize");
assert_resource(
&UnknownMap(2),
JsonTestLimits::new().max_map_entries(1),
JsonResource::MapEntries,
);
}
#[test]
fn test_json_encode_serializer_checks_unknown_map_after_traversal() {
let serialized = Cell::new(0);
let value = CountedUnknownMap {
serialized: &serialized,
len: 1_000,
};
assert_resource(
&value,
JsonTestLimits::new().max_map_entries(2),
JsonResource::MapEntries,
);
assert_eq!(serialized.get(), 3);
}
#[test]
fn test_json_encode_serializer_checks_nested_depth() {
let value = Nested {
value: vec![vec![true]],
};
assert_resource(&value, JsonTestLimits::new().max_depth(3), JsonResource::Depth);
}
#[test]
fn test_json_encode_serializer_checks_utf8_key_and_string_bytes() {
let values = BTreeMap::from([("ä½ å¥½", true)]);
assert_resource(&values, JsonTestLimits::new().max_key_bytes(5), JsonResource::KeyBytes);
assert_resource(
&"ä½ å¥½",
JsonTestLimits::new().max_string_bytes(5),
JsonResource::StringBytes,
);
}
#[test]
fn test_json_encode_serializer_consumes_shared_payload() {
assert_resource(
&SharedPayload { text: "bc", number: 12 },
JsonTestLimits::new().max_payload_bytes(13),
JsonResource::PayloadBytes,
);
}
#[test]
fn test_json_encode_serializer_checks_number_bytes() {
assert_resource(
&-123_i32,
JsonTestLimits::new().max_number_bytes(3),
JsonResource::NumberBytes,
);
assert_resource(
&12.25_f64,
JsonTestLimits::new().max_number_bytes(4),
JsonResource::NumberBytes,
);
assert_resource(
&1.0_f64,
JsonTestLimits::new().max_number_bytes(2),
JsonResource::NumberBytes,
);
}
#[test]
fn test_json_encode_serializer_charges_u64_number_once() {
let number = u64::MAX;
assert_resource(
&number,
JsonTestLimits::new().max_number_bytes(LARGE_NUMBER_TEXT.len() - 1),
JsonResource::NumberBytes,
);
assert_node_count(&number, 1);
}
#[test]
fn test_json_preflight_treats_u64_as_scalar() {
let number = u64::MAX;
let mut budget = JsonTestLimits::new()
.max_nodes(1)
.max_map_entries(0)
.max_key_bytes(0)
.max_number_bytes(LARGE_NUMBER_TEXT.len())
.encode_session();
let output = encode(&number, &mut budget).expect("an integer is a scalar JSON node");
assert_eq!(output, LARGE_NUMBER_TEXT.as_bytes());
}
#[test]
fn test_json_preflight_treats_raw_u64_as_scalar() {
let raw = RawValue::from_string(String::from(LARGE_NUMBER_TEXT)).expect("fixture must contain valid raw JSON");
let mut budget = JsonTestLimits::new()
.max_nodes(1)
.max_map_entries(0)
.max_key_bytes(0)
.max_string_bytes(0)
.max_number_bytes(LARGE_NUMBER_TEXT.len())
.encode_session();
let output = encode(&raw, &mut budget).expect("raw integer text is a scalar JSON node");
assert_eq!(output, LARGE_NUMBER_TEXT.as_bytes());
assert_resource(
&raw,
JsonTestLimits::new().max_number_bytes(LARGE_NUMBER_TEXT.len() - 1),
JsonResource::NumberBytes,
);
}
#[test]
fn test_raw_value_charges_single_number_token_object_as_object() {
let raw = RawValue::from_string(format!(r#"{{"{JSON_NUMBER_TOKEN}":"x"}}"#))
.expect("fixture must contain valid raw JSON");
let mut budget = JsonTestLimits::new().max_key_bytes(0).encode_session();
let error = encode(&raw, &mut budget).expect_err("the raw object's key must consume the key budget");
assert!(matches!(
error.budget_error(),
Some(MeasuredBudgetError::Budget(BudgetError::LimitExceeded {
resource: JsonResource::KeyBytes,
observed: Observation::Exact(actual),
maximum: 0,
})) if *actual == JSON_NUMBER_TOKEN.len()
));
}
#[test]
fn test_collect_str_rejects_after_complete_formatting() {
let formatted = Cell::new(0);
let value = CollectedText(CountedDisplay {
formatted: &formatted,
chunk: "ab",
chunks: 1_000,
});
assert_resource(
&value,
JsonTestLimits::new().max_string_bytes(3),
JsonResource::StringBytes,
);
assert_eq!(formatted.get(), 1_000);
}
#[test]
fn test_collect_str_map_key_rejects_after_complete_formatting() {
let formatted = Cell::new(0);
let map = CollectedKeyMap(CollectedText(CountedDisplay {
formatted: &formatted,
chunk: "ab",
chunks: 1_000,
}));
assert_resource(&map, JsonTestLimits::new().max_key_bytes(3), JsonResource::KeyBytes);
assert_eq!(formatted.get(), 1_000);
}
#[test]
fn test_private_raw_value_collect_str_rejects_during_formatting() {
let formatted = Cell::new(0);
let value = CollectedPrivateText {
token: JSON_RAW_VALUE_TOKEN,
payload: CountedDisplay {
formatted: &formatted,
chunk: "null ",
chunks: 1_000,
},
};
assert_resource(
&value,
JsonTestLimits::new().max_output_bytes(8),
JsonResource::OutputBytes,
);
assert!(formatted.get() < 1_000);
}
#[test]
fn test_output_only_limit_stops_collect_str_formatting() {
let formatted = Cell::new(0);
let value = CollectedText(CountedDisplay {
formatted: &formatted,
chunk: "ab",
chunks: 1_000,
});
assert_resource(
&value,
JsonTestLimits::new().max_output_bytes(3),
JsonResource::OutputBytes,
);
assert!(formatted.get() < 1_000);
}
#[test]
fn test_output_only_limit_stops_map_key_collect_str_formatting() {
let formatted = Cell::new(0);
let map = CollectedKeyMap(CollectedText(CountedDisplay {
formatted: &formatted,
chunk: "ab",
chunks: 1_000,
}));
assert_resource(
&map,
JsonTestLimits::new().max_output_bytes(3),
JsonResource::OutputBytes,
);
assert!(formatted.get() < 1_000);
}
#[test]
fn test_collect_str_output_lower_bound_preserves_valid_boundaries() {
let formatted = Cell::new(0);
let value = CollectedText(CountedDisplay {
formatted: &formatted,
chunk: "a",
chunks: 1,
});
let mut budget = JsonTestLimits::new().max_output_bytes(3).encode_session();
assert_eq!(
encode(&value, &mut budget).expect("quoted collected string must fit its exact output limit"),
br#""a""#,
);
let formatted = Cell::new(0);
let map = CollectedKeyMap(CollectedText(CountedDisplay {
formatted: &formatted,
chunk: "a",
chunks: 1,
}));
let mut budget = JsonTestLimits::new().max_output_bytes(10).encode_session();
assert_eq!(
encode(&map, &mut budget).expect("collected map key must fit its exact output limit"),
br#"{"a":null}"#,
);
}
#[test]
fn test_underreported_sequence_is_rejected_after_third_child() {
let observed = Cell::new(0);
assert_resource(
&UnderreportedSequence(&observed),
JsonTestLimits::new().max_sequence_items(2),
JsonResource::SequenceItems,
);
assert_eq!(observed.get(), 2);
}
#[test]
fn test_overreported_sequence_charges_actual_items_only() {
let mut session = JsonTestLimits::new().max_sequence_items(1).encode_session();
let output = encode(&OverreportedSequence, &mut session).expect("only the emitted sequence item must be charged");
assert_eq!(output, b"[null]");
}
#[test]
fn test_underreported_map_is_rejected_after_third_value() {
let observed = Cell::new(0);
assert_resource(
&UnderreportedMap(&observed),
JsonTestLimits::new().max_map_entries(2),
JsonResource::MapEntries,
);
assert_eq!(observed.get(), 2);
}
#[test]
fn test_underreported_struct_is_rejected_after_third_value() {
let observed = Cell::new(0);
assert_resource(
&UnderreportedStruct(&observed),
JsonTestLimits::new().max_map_entries(2),
JsonResource::MapEntries,
);
assert_eq!(observed.get(), 2);
}
#[test]
fn test_underreported_tuple_struct_is_rejected_after_third_value() {
let observed = Cell::new(0);
assert_resource(
&UnderreportedTupleStruct(&observed),
JsonTestLimits::new().max_sequence_items(2),
JsonResource::SequenceItems,
);
assert_eq!(observed.get(), 2);
}
#[test]
fn test_underreported_tuple_variant_is_rejected_after_third_value() {
let observed = Cell::new(0);
assert_resource(
&UnderreportedVariant {
observed: &observed,
tuple: true,
},
JsonTestLimits::new().max_sequence_items(2),
JsonResource::SequenceItems,
);
assert_eq!(observed.get(), 2);
}
#[test]
fn test_underreported_struct_variant_is_rejected_after_third_value() {
let observed = Cell::new(0);
assert_resource(
&UnderreportedVariant {
observed: &observed,
tuple: false,
},
JsonTestLimits::new().max_map_entries(2),
JsonResource::MapEntries,
);
assert_eq!(observed.get(), 2);
}
#[test]
fn test_json_encode_serializer_measures_simulated_raw_value_structure_once() {
let serialized = Cell::new(0);
let value = CountedPrivateRawValue {
raw: "123",
serialized: &serialized,
};
let mut budget = JsonTestLimits::new()
.max_nodes(1)
.max_key_bytes(0)
.max_string_bytes(0)
.max_number_bytes(3)
.encode_session();
let output = encode(&value, &mut budget).expect("private raw metadata must not consume JSON key or string limits");
assert_eq!(output, b"123");
assert_eq!(serialized.get(), 1);
}
#[test]
fn test_json_encode_serializer_measures_actual_raw_value_structure() {
let raw = RawValue::from_string(String::from(r#"{"ok":[1,true]}"#)).expect("fixture must contain valid raw JSON");
let mut budget = JsonTestLimits::new()
.max_nodes(4)
.max_map_entries(1)
.max_sequence_items(2)
.max_key_bytes(2)
.max_string_bytes(0)
.encode_session();
let output = encode(&raw, &mut budget).expect("the raw JSON structure should fit its exact limits");
assert_eq!(output, br#"{"ok":[1,true]}"#);
}
#[test]
fn test_json_encode_serializer_checks_raw_nodes_after_tail_traversal() {
let raw = RawValue::from_string(String::from("[null,null]")).expect("fixture must contain valid raw JSON");
let serialized_tail = Cell::new(0);
let value = RawThenCountedTail {
raw: &raw,
serialized_tail: &serialized_tail,
};
assert_resource(&value, JsonTestLimits::new().max_nodes(3), JsonResource::Nodes);
assert_eq!(serialized_tail.get(), 0);
}
#[test]
fn test_json_encode_serializer_checks_raw_depth_before_tail() {
let raw = RawValue::from_string(String::from("[[null]]")).expect("fixture must contain valid raw JSON");
let serialized_tail = Cell::new(0);
let value = RawThenCountedTail {
raw: &raw,
serialized_tail: &serialized_tail,
};
assert_resource(&value, JsonTestLimits::new().max_depth(3), JsonResource::Depth);
assert_eq!(serialized_tail.get(), 0);
}
#[test]
fn test_json_encode_serializer_checks_raw_output_lower_bound() {
let raw = RawValue::from_string(String::from("[null]")).expect("fixture must contain valid raw JSON");
assert_resource(
&raw,
JsonTestLimits::new().max_output_bytes(5).max_nodes(0),
JsonResource::OutputBytes,
);
}
#[test]
fn test_json_encode_serializer_prioritizes_raw_output_over_depth() {
let raw = RawValue::from_string(String::from("[null]")).expect("fixture must contain valid raw JSON");
assert_resource(
&raw,
JsonTestLimits::new().max_output_bytes(5).max_depth(0),
JsonResource::OutputBytes,
);
}
#[test]
fn test_json_encode_serializer_checks_raw_output_remaining_in_reused_session() {
let raw = RawValue::from_string(String::from("[null]")).expect("fixture must contain valid raw JSON");
let mut session = JsonTestLimits::new().max_output_bytes(8).max_nodes(1).encode_session();
assert_eq!(
encode(&true, &mut session).expect("the first document must fit"),
b"true",
);
let output = session.output_budget().expect("the test configures output accounting");
assert_eq!(output.used(), 4);
assert_eq!(output.remaining(), 4);
let error = encode(&raw, &mut session).expect_err("the raw fragment must exceed live remaining output");
assert!(matches!(
error.budget_error(),
Some(MeasuredBudgetError::Budget(BudgetError::Insufficient {
resource: JsonResource::OutputBytes,
limit: 8,
remaining: 4,
requested: 6,
}))
));
let output = session.output_budget().expect("the test configures output accounting");
assert_eq!(output.used(), 4);
assert_eq!(output.remaining(), 4);
}
#[test]
fn test_json_encode_serializer_preserves_wrapper_and_enum_shapes() {
assert_same_json(&Some(Newtype(7)));
assert_same_json(&Option::<Newtype>::None);
assert_same_json(&EnumShape::Unit);
assert_same_json(&EnumShape::Newtype(1));
assert_same_json(&EnumShape::Tuple(1, 2));
assert_same_json(&EnumShape::Struct { value: 3 });
assert_node_count(&Some(Newtype(7)), 1);
assert_node_count(&Option::<Newtype>::None, 1);
assert_node_count(&EnumShape::Unit, 1);
assert_node_count(&EnumShape::Newtype(1), 2);
assert_node_count(&EnumShape::Tuple(1, 2), 4);
assert_node_count(&EnumShape::Struct { value: 3 }, 3);
}