use std::error::Error;
use std::io;
use std::sync::Arc;
use qubit_budget::ResourceQuantity;
use qubit_budget::json::JsonDecodeAttempt;
use qubit_budget::json::JsonDecodeSession;
use qubit_io::Input;
use serde::de::DeserializeSeed;
use serde_json::from_slice;
use serde_json::value::RawValue;
use super::DecodeMetadata;
use super::JsonNormalizer;
use super::admit_json_document;
use super::deserialize_json_document;
use crate::decode::DiagnosticPolicy;
use crate::decode::JsonDecodeError;
use crate::decode::JsonDecodeStage;
use crate::decode::JsonRootKind;
use crate::decode::NormalizedJsonDocument;
use crate::lexical::JsonLexicalError;
#[derive(Debug)]
pub(in crate::decode) struct JsonDecodeEngine<'budget, R, Q>
where
Q: ResourceQuantity,
{
session: JsonDecodeSession<'budget, R, Q>,
}
impl<'budget, R, Q> JsonDecodeEngine<'budget, R, Q>
where
R: Clone,
Q: ResourceQuantity,
{
#[inline]
#[must_use]
pub(in crate::decode) const fn new(session: JsonDecodeSession<'budget, R, Q>) -> Self {
Self { session }
}
#[inline(always)]
#[must_use]
pub(in crate::decode) const fn session(&self) -> &JsonDecodeSession<'budget, R, Q> {
&self.session
}
#[inline(always)]
#[must_use]
pub(in crate::decode) const fn session_mut(&mut self) -> &mut JsonDecodeSession<'budget, R, Q> {
&mut self.session
}
#[inline(always)]
#[must_use]
pub(in crate::decode) fn into_session(self) -> JsonDecodeSession<'budget, R, Q> {
self.session
}
pub(in crate::decode) fn prepare_str<'input>(
&mut self,
normalizer: &JsonNormalizer,
input: &'input str,
) -> Result<NormalizedJsonDocument<'input>, JsonDecodeError<R, Q>> {
let raw_input_bytes = input.len();
let diagnostic_policy = normalizer.policy().diagnostic_policy();
let mut attempt = self.session.begin_value();
let normalized = normalizer.normalize(input, &mut attempt)?;
let document = NormalizedJsonDocument::new(normalized, raw_input_bytes);
debug_assert_eq!(document.raw_input_bytes(), raw_input_bytes);
debug_assert_eq!(diagnostic_policy, normalizer.policy().diagnostic_policy());
Ok(document)
}
pub(in crate::decode) fn prepare_utf8<'input>(
&mut self,
normalizer: &JsonNormalizer,
input: &'input [u8],
) -> Result<NormalizedJsonDocument<'input>, JsonDecodeError<R, Q>> {
let raw_input_bytes = input.len();
let diagnostic_policy = normalizer.policy().diagnostic_policy();
let mut attempt = self.session.begin_value();
attempt.try_consume_input_bytes(raw_input_bytes).map_err(|source| {
JsonDecodeError::budget(source, JsonDecodeStage::Input, raw_input_bytes, None, diagnostic_policy)
})?;
let input = std::str::from_utf8(input)
.map_err(|source| JsonDecodeError::invalid_utf8(source, raw_input_bytes, diagnostic_policy))?;
let normalized = normalizer.normalize_after_raw_charge(input, &mut attempt)?;
Ok(NormalizedJsonDocument::new(normalized, raw_input_bytes))
}
pub(in crate::decode) fn decode_seed_utf8<'de, S>(
&mut self,
seed: S,
input: &'de [u8],
diagnostic_policy: DiagnosticPolicy,
) -> Result<S::Value, JsonDecodeError<R, Q>>
where
S: DeserializeSeed<'de>,
{
self.decode_seed_utf8_with_top_level(seed, input, diagnostic_policy, None)
}
pub(in crate::decode) fn decode_seed_utf8_with_top_level<'de, S>(
&mut self,
seed: S,
input: &'de [u8],
diagnostic_policy: DiagnosticPolicy,
expected: Option<JsonRootKind>,
) -> Result<S::Value, JsonDecodeError<R, Q>>
where
S: DeserializeSeed<'de>,
{
let metadata = DecodeMetadata {
raw_input_bytes: input.len(),
normalized_input_bytes: None,
diagnostic_policy,
};
self.decode_seed(seed, input, metadata, true, expected)
}
pub(in crate::decode) fn decode_document_seed<'de, S>(
&mut self,
document: &'de NormalizedJsonDocument<'_>,
seed: S,
diagnostic_policy: DiagnosticPolicy,
expected: Option<JsonRootKind>,
) -> Result<S::Value, JsonDecodeError<R, Q>>
where
S: DeserializeSeed<'de>,
{
let metadata = DecodeMetadata {
raw_input_bytes: document.raw_input_bytes(),
normalized_input_bytes: Some(document.normalized_input_bytes()),
diagnostic_policy,
};
self.decode_seed(seed, document.as_str().as_bytes(), metadata, false, expected)
}
pub(in crate::decode) fn validate_utf8(
&mut self,
input: &[u8],
diagnostic_policy: DiagnosticPolicy,
) -> Result<(), JsonDecodeError<R, Q>> {
let metadata = DecodeMetadata {
raw_input_bytes: input.len(),
normalized_input_bytes: None,
diagnostic_policy,
};
self.validate(input, metadata, true, None)
}
pub(in crate::decode) fn validate_document(
&mut self,
document: &NormalizedJsonDocument<'_>,
diagnostic_policy: DiagnosticPolicy,
expected: Option<JsonRootKind>,
) -> Result<(), JsonDecodeError<R, Q>> {
let metadata = DecodeMetadata {
raw_input_bytes: document.raw_input_bytes(),
normalized_input_bytes: Some(document.normalized_input_bytes()),
diagnostic_policy,
};
self.validate(document.as_str().as_bytes(), metadata, false, expected)
}
pub(in crate::decode) fn decode_input<T, I>(
&mut self,
input: &mut I,
diagnostic_policy: DiagnosticPolicy,
) -> Result<T, JsonDecodeError<R, Q>>
where
T: serde::de::DeserializeOwned,
I: Input<Item = u8> + ?Sized,
{
let has_value_limits = self.session.value_budget().limits().has_limits();
let mut attempt = self.session.begin_value();
let mut scratch = [0_u8; 8192];
let mut bytes = Vec::new();
let mut raw_input_bytes = 0_usize;
loop {
let admitted = attempt
.input_budget()
.map(|budget| Self::max_admitted_input_bytes(budget.remaining()));
let read_len = admitted.map_or(scratch.len(), |admitted| {
if admitted == scratch.len() {
scratch.len()
} else {
admitted.saturating_add(1)
}
});
let count = match Input::read(input, &mut scratch[..read_len]) {
Ok(0) => break,
Ok(count) => count,
Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
Err(error) => return Err(JsonDecodeError::read(error, raw_input_bytes, diagnostic_policy)),
};
let charged = admitted.map_or(count, |admitted| count.min(admitted));
let next_total = raw_input_bytes.checked_add(charged).ok_or_else(|| {
JsonDecodeError::read(
io::Error::new(io::ErrorKind::InvalidData, "input byte count overflow"),
raw_input_bytes,
diagnostic_policy,
)
})?;
attempt.try_consume_input_bytes(charged).map_err(|source| {
JsonDecodeError::budget(source, JsonDecodeStage::Input, raw_input_bytes, None, diagnostic_policy)
})?;
bytes.extend_from_slice(&scratch[..charged]);
raw_input_bytes = next_total;
if count > charged {
return match attempt.try_consume_input_bytes(1) {
Err(source) => Err(JsonDecodeError::budget(
source,
JsonDecodeStage::Input,
raw_input_bytes,
None,
diagnostic_policy,
)),
Ok(()) => Err(JsonDecodeError::read(
io::Error::new(io::ErrorKind::InvalidData, "input budget probe invariant violated"),
raw_input_bytes,
diagnostic_policy,
)),
};
}
}
let metadata = DecodeMetadata {
raw_input_bytes,
normalized_input_bytes: None,
diagnostic_policy,
};
let value = Self::decode_seed_attempt(
&mut attempt,
crate::decode::internal::TypedSeed::<T>::new(),
&bytes,
metadata,
false,
has_value_limits,
None,
)?;
attempt.commit().map_err(|source| {
JsonDecodeError::budget(
source,
JsonDecodeStage::Admission,
metadata.raw_input_bytes,
None,
diagnostic_policy,
)
})?;
Ok(value)
}
fn max_admitted_input_bytes(remaining: Q) -> usize {
let mut lower = 0_usize;
let mut upper = 8192_usize;
while lower < upper {
let middle = lower + (upper - lower).div_ceil(2);
if Q::try_from_usize(middle).is_ok_and(|quantity| quantity <= remaining) {
lower = middle;
} else {
upper = middle - 1;
}
}
lower
}
fn decode_seed<'de, S>(
&mut self,
seed: S,
input: &'de [u8],
metadata: DecodeMetadata,
charge_raw_input: bool,
expected: Option<JsonRootKind>,
) -> Result<S::Value, JsonDecodeError<R, Q>>
where
S: DeserializeSeed<'de>,
{
let has_value_limits = self.session.value_budget().limits().has_limits();
let mut attempt = self.session.begin_value();
let value = Self::decode_seed_attempt(
&mut attempt,
seed,
input,
metadata,
charge_raw_input,
has_value_limits,
expected,
)?;
attempt.commit().map_err(|source| {
JsonDecodeError::budget(
source,
JsonDecodeStage::Admission,
metadata.raw_input_bytes,
metadata.normalized_input_bytes,
metadata.diagnostic_policy,
)
})?;
Ok(value)
}
fn decode_seed_attempt<'de, S>(
attempt: &mut JsonDecodeAttempt<'_, R, Q>,
seed: S,
input: &'de [u8],
metadata: DecodeMetadata,
charge_raw_input: bool,
has_value_limits: bool,
expected: Option<JsonRootKind>,
) -> Result<S::Value, JsonDecodeError<R, Q>>
where
S: DeserializeSeed<'de>,
{
Self::prepare_attempt(attempt, input, metadata, charge_raw_input, has_value_limits)?;
Self::check_top_level(input, metadata, expected)?;
let value = deserialize_json_document(seed, input).map_err(|source| {
JsonDecodeError::deserialize(
source,
metadata.raw_input_bytes,
metadata.normalized_input_bytes,
metadata.diagnostic_policy,
)
})?;
Ok(value)
}
fn validate(
&mut self,
input: &[u8],
metadata: DecodeMetadata,
charge_raw_input: bool,
expected: Option<JsonRootKind>,
) -> Result<(), JsonDecodeError<R, Q>> {
let has_value_limits = self.session.value_budget().limits().has_limits();
let mut attempt = self.session.begin_value();
Self::prepare_attempt(&mut attempt, input, metadata, charge_raw_input, has_value_limits)?;
Self::check_top_level(input, metadata, expected)?;
attempt.commit().map_err(|source| {
JsonDecodeError::budget(
source,
JsonDecodeStage::Admission,
metadata.raw_input_bytes,
metadata.normalized_input_bytes,
metadata.diagnostic_policy,
)
})?;
Ok(())
}
fn prepare_attempt(
attempt: &mut JsonDecodeAttempt<'_, R, Q>,
input: &[u8],
metadata: DecodeMetadata,
charge_raw_input: bool,
has_value_limits: bool,
) -> Result<(), JsonDecodeError<R, Q>> {
if charge_raw_input {
attempt
.try_consume_input_bytes(metadata.raw_input_bytes)
.map_err(|source| {
JsonDecodeError::budget(
source,
JsonDecodeStage::Input,
metadata.raw_input_bytes,
metadata.normalized_input_bytes,
metadata.diagnostic_policy,
)
})?;
}
std::str::from_utf8(input).map_err(|source| {
JsonDecodeError::invalid_utf8(source, metadata.raw_input_bytes, metadata.diagnostic_policy)
})?;
admit_json_document(attempt, input, has_value_limits).map_err(|error| match error {
JsonLexicalError::Budget(source) => JsonDecodeError::budget(
source,
JsonDecodeStage::Admission,
metadata.raw_input_bytes,
metadata.normalized_input_bytes,
metadata.diagnostic_policy,
),
JsonLexicalError::Syntax(source) => {
let detailed_source = (metadata.diagnostic_policy == DiagnosticPolicy::Detailed).then(|| {
from_slice::<&RawValue>(input).err().map_or_else(
|| Arc::new(source) as Arc<dyn Error + Send + Sync>,
|error| Arc::new(error) as Arc<dyn Error + Send + Sync>,
)
});
JsonDecodeError::invalid_json(
source,
detailed_source,
metadata.raw_input_bytes,
metadata.normalized_input_bytes,
metadata.diagnostic_policy,
)
}
})
}
fn check_top_level(
input: &[u8],
metadata: DecodeMetadata,
expected: Option<JsonRootKind>,
) -> Result<(), JsonDecodeError<R, Q>> {
let Some(expected) = expected else {
return Ok(());
};
let text = std::str::from_utf8(input).expect("admitted JSON input must be valid UTF-8");
let actual = JsonRootKind::of_normalized_json(text);
if actual == expected {
Ok(())
} else {
Err(JsonDecodeError::unexpected_top_level(
expected,
actual,
metadata.raw_input_bytes,
metadata.normalized_input_bytes,
metadata.diagnostic_policy,
))
}
}
}