use std::borrow::Cow;
use qubit_budget::ResourceQuantity;
use qubit_budget::json::JsonDecodeAttempt;
use super::super::JsonDecodeError;
use super::super::JsonDecodeStage;
use super::super::NormalizingJsonDecodePolicy;
use super::control_character_escaper::ControlCharacterEscaper;
use super::markdown_fence::MarkdownFence;
#[derive(Debug, Clone)]
pub(crate) struct JsonNormalizer {
policy: NormalizingJsonDecodePolicy,
}
impl Default for JsonNormalizer {
#[inline(always)]
fn default() -> Self {
Self::new(NormalizingJsonDecodePolicy::default())
}
}
impl JsonNormalizer {
#[inline(always)]
#[must_use]
pub(crate) const fn new(policy: NormalizingJsonDecodePolicy) -> Self {
Self { policy }
}
#[inline(always)]
#[must_use]
pub(crate) const fn policy(&self) -> &NormalizingJsonDecodePolicy {
&self.policy
}
pub(crate) fn normalize<'input, R, Q>(
&self,
input: &'input str,
attempt: &mut JsonDecodeAttempt<'_, R, Q>,
) -> Result<Cow<'input, str>, JsonDecodeError<R, Q>>
where
R: Clone,
Q: ResourceQuantity,
{
self.normalize_with_attempt(input, attempt, true)
}
pub(crate) fn normalize_after_raw_charge<'input, R, Q>(
&self,
input: &'input str,
attempt: &mut JsonDecodeAttempt<'_, R, Q>,
) -> Result<Cow<'input, str>, JsonDecodeError<R, Q>>
where
R: Clone,
Q: ResourceQuantity,
{
self.normalize_with_attempt(input, attempt, false)
}
fn normalize_with_attempt<'input, R, Q>(
&self,
input: &'input str,
attempt: &mut JsonDecodeAttempt<'_, R, Q>,
charge_raw_input: bool,
) -> Result<Cow<'input, str>, JsonDecodeError<R, Q>>
where
R: Clone,
Q: ResourceQuantity,
{
let raw_input_bytes = input.len();
if charge_raw_input {
self.consume_raw_input(attempt, raw_input_bytes)?;
}
let input = self.require_non_empty(input, raw_input_bytes)?;
let input = self.trim_if_enabled(input);
let input = self.strip_utf8_bom(input);
let input = self.trim_if_enabled(input);
let input = MarkdownFence::strip_outer(input, self.policy.markdown_fence_policy());
let input = self.trim_if_enabled(input);
let (normalized_len, needs_escape) = self.scan_normalized_size(input);
self.consume_normalized_input(attempt, raw_input_bytes, normalized_len)?;
let input = ControlCharacterEscaper::escape_with_scan(input, normalized_len, needs_escape);
if input.is_empty() {
Err(JsonDecodeError::empty_input(
JsonDecodeStage::Normalize,
raw_input_bytes,
Some(input.len()),
self.policy.diagnostic_policy(),
))
} else {
Ok(input)
}
}
fn require_non_empty<'input, R, Q>(
&self,
input: &'input str,
raw_input_bytes: usize,
) -> Result<&'input str, JsonDecodeError<R, Q>>
where
Q: ResourceQuantity,
{
let empty = if self.policy.trim_whitespace() {
input.trim().is_empty()
} else {
input.is_empty()
};
if empty {
Err(JsonDecodeError::empty_input(
JsonDecodeStage::Normalize,
raw_input_bytes,
None,
self.policy.diagnostic_policy(),
))
} else {
Ok(input)
}
}
#[inline]
#[must_use]
fn scan_normalized_size(&self, input: &str) -> (usize, bool) {
ControlCharacterEscaper::scan(input, self.policy.escape_control_chars_in_strings())
}
fn consume_raw_input<R, Q>(
&self,
attempt: &mut JsonDecodeAttempt<'_, R, Q>,
amount: usize,
) -> Result<(), JsonDecodeError<R, Q>>
where
R: Clone,
Q: ResourceQuantity,
{
attempt.try_consume_input_bytes(amount).map_err(|source| {
JsonDecodeError::budget(
source,
JsonDecodeStage::Input,
amount,
None,
self.policy.diagnostic_policy(),
)
})
}
fn consume_normalized_input<R, Q>(
&self,
attempt: &mut JsonDecodeAttempt<'_, R, Q>,
raw_input_bytes: usize,
normalized_input_bytes: usize,
) -> Result<(), JsonDecodeError<R, Q>>
where
R: Clone,
Q: ResourceQuantity,
{
attempt
.try_consume_normalized_input_bytes(normalized_input_bytes)
.map_err(|source| {
JsonDecodeError::budget(
source,
JsonDecodeStage::Normalize,
raw_input_bytes,
Some(normalized_input_bytes),
self.policy.diagnostic_policy(),
)
})
}
#[inline]
#[must_use]
fn trim_if_enabled<'input>(&self, input: &'input str) -> &'input str {
if self.policy.trim_whitespace() {
input.trim()
} else {
input
}
}
#[inline]
#[must_use]
fn strip_utf8_bom<'input>(&self, input: &'input str) -> &'input str {
if self.policy.strip_utf8_bom() {
input.strip_prefix('\u{feff}').unwrap_or(input)
} else {
input
}
}
}