use qubit_budget::ResourceLimit;
use qubit_budget::json::JsonDecodeLimits;
use qubit_budget::json::JsonResource;
use qubit_json::decode::NormalizingJsonDecodePolicy;
use qubit_json::decode::NormalizingJsonDecoder;
use serde::Deserialize;
use serde::de::DeserializeSeed;
use serde::de::Deserializer;
use serde_json::Value;
#[derive(Clone, Debug, PartialEq, Eq)]
enum CustomResource {
InputBytes,
}
struct BorrowedStrSeed;
impl<'de> DeserializeSeed<'de> for BorrowedStrSeed {
type Value = &'de str;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
<&str>::deserialize(deserializer)
}
}
#[test]
fn test_normalized_json_document_supports_borrowing_and_seed() {
let mut decoder = NormalizingJsonDecoder::with_limits(
NormalizingJsonDecodePolicy::lenient(),
JsonDecodeLimits::<JsonResource, usize>::default(),
);
let document = decoder
.prepare_str(" \"borrowed\" ")
.expect("normalization must succeed");
let typed: &str = decoder
.decode_precharged_document(&document)
.expect("typed document decoding must borrow");
let seeded = decoder
.decode_precharged_document_seed(&document, BorrowedStrSeed)
.expect("seeded document decoding must borrow");
assert_eq!(typed, "borrowed");
assert_eq!(seeded, "borrowed");
assert_eq!(document.as_str(), "\"borrowed\"");
assert_eq!(document.raw_input_bytes(), 14);
assert_eq!(document.normalized_input_bytes(), 10);
}
#[test]
fn test_normalized_json_document_owns_repaired_text() {
let mut decoder = NormalizingJsonDecoder::with_limits(
NormalizingJsonDecodePolicy::lenient(),
JsonDecodeLimits::<JsonResource, usize>::default(),
);
let document = decoder
.prepare_str("\"line\nfeed\"")
.expect("control-character escaping must succeed");
let value: String = decoder
.decode_precharged_document(&document)
.expect("repaired document must support owned decoding");
assert_eq!(value, "line\nfeed");
assert_eq!(document.as_str(), "\"line\\nfeed\"");
}
#[test]
fn test_normalized_json_document_separates_input_and_value_accounting() {
let limits = JsonDecodeLimits::builder()
.max_input_bytes(3_usize)
.max_normalized_input_bytes(3_usize)
.max_nodes(2_usize)
.build();
let mut decoder = NormalizingJsonDecoder::with_limits(NormalizingJsonDecodePolicy::lenient(), limits);
let document = decoder.prepare_str("\"x\"").expect("prepare must fit");
let _: &str = decoder
.decode_precharged_document(&document)
.expect("first decode must fit");
let _: &str = decoder
.decode_precharged_document(&document)
.expect("second decode must fit");
assert_eq!(decoder.session().input_budget().expect("input budget").used(), 3);
assert_eq!(
decoder
.session()
.normalized_input_budget()
.expect("normalized input budget")
.used(),
3,
);
assert_eq!(decoder.session().value_budget().used_nodes(), Some(2));
}
#[test]
fn test_normalizing_decoder_supports_custom_resource_and_quantity_types() {
let limits = JsonDecodeLimits::<CustomResource, u64>::builder()
.input_bytes_limit(ResourceLimit::new(CustomResource::InputBytes, 2_u64))
.build();
let mut decoder = NormalizingJsonDecoder::with_limits(NormalizingJsonDecodePolicy::lenient(), limits);
let error = decoder
.prepare_str("\"x\"")
.expect_err("the custom input budget must reject three bytes");
assert_eq!(
error.budget_error().expect("budget error").resource(),
&CustomResource::InputBytes
);
}
#[test]
fn test_normalized_json_document_supports_typed_root_checks_and_validation() {
let mut decoder = NormalizingJsonDecoder::with_limits(
NormalizingJsonDecodePolicy::lenient(),
JsonDecodeLimits::<JsonResource, usize>::default(),
);
let object = decoder.prepare_str(" {\"value\":1} ").expect("object prepare");
let array = decoder.prepare_str(" [1,2] ").expect("array prepare");
let object_value: Value = decoder
.decode_precharged_object_document(&object)
.expect("object document decode");
let array_value: Vec<u8> = decoder
.decode_precharged_array_document(&array)
.expect("array document decode");
decoder
.validate_precharged_document(&object)
.expect("prepared document validation");
assert_eq!(object_value["value"], 1);
assert_eq!(array_value, [1, 2]);
}
#[test]
fn test_normalized_json_document_failure_rolls_back_only_value_usage() {
let input = r#"{"flag":1}"#;
let limits = JsonDecodeLimits::builder()
.max_input_bytes(input.len())
.max_normalized_input_bytes(input.len())
.max_nodes(2_usize)
.build();
let mut decoder = NormalizingJsonDecoder::with_limits(NormalizingJsonDecodePolicy::lenient(), limits);
let document = decoder.prepare_str(input).expect("prepare must succeed");
let _ = decoder
.decode_precharged_document::<std::collections::HashMap<String, bool>>(&document)
.expect_err("number must not deserialize as bool");
assert_eq!(
decoder.session().input_budget().expect("input budget").used(),
input.len()
);
assert_eq!(
decoder
.session()
.normalized_input_budget()
.expect("normalized input budget")
.used(),
input.len(),
);
assert_eq!(decoder.session().value_budget().used_nodes(), Some(0));
}