use super::*;
pub(super) fn parse_json_string_at(text: &str, start: usize) -> Option<(usize, String)> {
let mut values = serde_json::Deserializer::from_str(&text[start..]).into_iter::<String>();
let value = values.next()?.ok()?;
let consumed = values.byte_offset();
(consumed > 0).then_some((start + consumed, value))
}
pub(super) fn skip_json_whitespace(text: &str, mut index: usize) -> usize {
while matches!(
text.as_bytes().get(index),
Some(b' ' | b'\t' | b'\r' | b'\n')
) {
index += 1;
}
index
}
pub(super) fn skip_diff_prefixed_value_start(text: &str, key_start: usize, index: usize) -> usize {
let line_start = text[..key_start].rfind('\n').map_or(0, |offset| offset + 1);
let prefix = text
.as_bytes()
.get(line_start)
.copied()
.filter(|prefix| matches!(prefix, b'+' | b'-'));
let mut value_start = skip_json_whitespace(text, index);
while prefix == text.as_bytes().get(value_start).copied()
&& (value_start == 0 || text.as_bytes().get(value_start - 1) == Some(&b'\n'))
{
value_start = skip_json_whitespace(text, value_start + 1);
}
value_start
}
pub(super) fn json_string_boundary_map(token: &str, decoded: &str) -> Option<Vec<(usize, usize)>> {
let raw = token.strip_prefix('"')?.strip_suffix('"')?;
let mut boundaries = vec![(0, 1)];
let mut raw_offset = 0;
let mut decoded_offset = 0;
while raw_offset < raw.len() {
let consumed = if raw.as_bytes()[raw_offset] == b'\\' {
json_escape_len(&raw[raw_offset..])?
} else {
raw[raw_offset..].chars().next()?.len_utf8()
};
let raw_end = raw_offset + consumed;
let piece =
serde_json::from_str::<String>(&format!("\"{}\"", &raw[raw_offset..raw_end])).ok()?;
decoded_offset += piece.len();
raw_offset = raw_end;
boundaries.push((decoded_offset, raw_offset + 1));
}
(decoded_offset == decoded.len()).then_some(boundaries)
}
fn json_escape_len(text: &str) -> Option<usize> {
match text.as_bytes().get(1).copied()? {
b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't' => Some(2),
b'u' => {
let first = u16::from_str_radix(text.get(2..6)?, 16).ok()?;
if (0xD800..=0xDBFF).contains(&first) {
text.get(6..12)
.filter(|trail| trail.starts_with("\\u"))
.map(|_| 12)
} else {
Some(6)
}
}
_ => None,
}
}
pub(super) fn encode_json_string_fragment(text: &str) -> String {
serde_json::to_string(text)
.ok()
.and_then(|encoded| {
encoded
.strip_prefix('"')
.and_then(|inner| inner.strip_suffix('"'))
.map(str::to_string)
})
.unwrap_or_else(|| "<redacted>".to_string())
}
pub(super) fn json_value_end(text: &str, start: usize) -> Option<usize> {
if start >= text.len() {
return None;
}
let mut values = serde_json::Deserializer::from_str(&text[start..]).into_iter::<Value>();
if matches!(values.next(), Some(Ok(_))) && values.byte_offset() > 0 {
return Some(start + values.byte_offset());
}
if matches!(text.as_bytes().get(start), Some(b'{' | b'[')) {
return balanced_json_container_end(text, start).or(Some(text.len()));
}
let relative_end = text[start..]
.char_indices()
.find(|(_, character)| {
character.is_whitespace() || matches!(character, ',' | ';' | '}' | ']')
})
.map_or(text.len() - start, |(offset, _)| offset);
Some(start + relative_end.max(1))
}
fn balanced_json_container_end(text: &str, start: usize) -> Option<usize> {
let mut stack = Vec::new();
let mut in_string = false;
let mut escaped = false;
for (offset, character) in text[start..].char_indices() {
if in_string {
if escaped {
escaped = false;
} else if character == '\\' {
escaped = true;
} else if character == '"' {
in_string = false;
}
continue;
}
match character {
'"' => in_string = true,
'{' => stack.push('}'),
'[' => stack.push(']'),
'}' | ']' => {
if stack.pop() != Some(character) {
return None;
}
if stack.is_empty() {
return Some(start + offset + character.len_utf8());
}
}
_ => {}
}
}
None
}