use std::borrow::Cow;
use crate::{
JsonDecodeError,
JsonDecodeOptions,
};
use super::{
control_character_escaper::ControlCharacterEscaper,
markdown_fence::MarkdownFence,
};
#[derive(Debug, Clone)]
pub(crate) struct LenientJsonNormalizer {
options: JsonDecodeOptions,
}
impl Default for LenientJsonNormalizer {
#[inline(always)]
fn default() -> Self {
Self::new(JsonDecodeOptions::default())
}
}
impl LenientJsonNormalizer {
#[inline(always)]
#[must_use]
pub(crate) const fn new(options: JsonDecodeOptions) -> Self {
Self { options }
}
#[inline(always)]
#[must_use = "the normalizer options should be inspected or retained"]
pub(crate) const fn options(&self) -> &JsonDecodeOptions {
&self.options
}
pub(crate) fn normalize<'a>(
&self,
input: &'a str,
) -> Result<Cow<'a, str>, JsonDecodeError> {
let raw_input_bytes = input.len();
self.require_within_size_limit(input)?;
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.options.markdown_fence_policy(),
);
let input = self.trim_if_enabled(input);
let control_character_scan =
self.require_within_normalized_size_limit(input, raw_input_bytes)?;
let input = match control_character_scan {
Some((normalized_len, needs_escape)) => {
ControlCharacterEscaper::escape_with_scan(
input,
normalized_len,
needs_escape,
)
}
None => ControlCharacterEscaper::escape(
input,
self.options.escape_control_chars_in_strings(),
),
};
if input.is_empty() {
Err(JsonDecodeError::empty_input(
raw_input_bytes,
Some(input.len()),
self.options.error_privacy_policy(),
))
} else {
Ok(input)
}
}
fn require_non_empty<'a>(
&self,
input: &'a str,
raw_input_bytes: usize,
) -> Result<&'a str, JsonDecodeError> {
if self.options.trim_whitespace() {
if input.trim().is_empty() {
return Err(JsonDecodeError::empty_input(
raw_input_bytes,
None,
self.options.error_privacy_policy(),
));
}
} else if input.is_empty() {
return Err(JsonDecodeError::empty_input(
raw_input_bytes,
None,
self.options.error_privacy_policy(),
));
}
Ok(input)
}
fn require_within_size_limit(
&self,
input: &str,
) -> Result<(), JsonDecodeError> {
if let Some(limit) = self.options.max_input_bytes() {
let size = input.len();
if size > limit {
return Err(JsonDecodeError::input_too_large(
size,
limit,
self.options.error_privacy_policy(),
));
}
}
Ok(())
}
fn require_within_normalized_size_limit(
&self,
input: &str,
raw_input_bytes: usize,
) -> Result<Option<(usize, bool)>, JsonDecodeError> {
if let Some(limit) = self.options.max_normalized_bytes() {
let (normalized_input_bytes, needs_escape) =
ControlCharacterEscaper::scan(
input,
self.options.escape_control_chars_in_strings(),
);
if normalized_input_bytes > limit {
return Err(JsonDecodeError::normalized_input_too_large(
raw_input_bytes,
normalized_input_bytes,
limit,
self.options.error_privacy_policy(),
));
}
return Ok(Some((normalized_input_bytes, needs_escape)));
}
Ok(None)
}
#[inline]
#[must_use]
fn trim_if_enabled<'a>(&self, input: &'a str) -> &'a str {
if self.options.trim_whitespace() {
input.trim()
} else {
input
}
}
#[inline]
#[must_use]
fn strip_utf8_bom<'a>(&self, input: &'a str) -> &'a str {
if self.options.strip_utf8_bom() {
input.strip_prefix('\u{feff}').unwrap_or(input)
} else {
input
}
}
}