use winnow::error::{ContextError, ErrMode, ModalResult, Needed, StrContext, StrContextValue};
use winnow::stream::{Offset, Partial, Stream};
use super::Result;
pub(super) fn partial_prefix_len(buffer: &str, token: &str) -> usize {
let Some(first_byte) = token.as_bytes().first().copied() else {
return 0;
};
let max_len = buffer.len().min(token.len().saturating_sub(1));
let tail_start = buffer.len() - max_len;
let buffer_bytes = buffer.as_bytes();
let token_bytes = token.as_bytes();
for index in tail_start..buffer.len() {
if buffer_bytes[index] != first_byte {
continue;
}
let len = buffer.len() - index;
if buffer.is_char_boundary(index)
&& token.is_char_boundary(len)
&& token_bytes[..len] == buffer_bytes[index..]
{
return len;
}
}
0
}
pub(super) fn safe_text_len(input: &mut Partial<&str>, marker: &str) -> ModalResult<usize> {
let text = **input;
if text.is_empty() {
return incomplete();
}
if let Some(start_idx) = text.find(marker) {
input.next_slice(start_idx);
return Ok(start_idx);
}
let keep_len = partial_prefix_len(text, marker);
let emit_len = text.len().saturating_sub(keep_len);
if emit_len == 0 {
return incomplete();
}
input.next_slice(emit_len);
Ok(emit_len)
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(super) struct JsonObjectScanState {
object_depth: usize,
array_depth: usize,
in_string: bool,
escape: bool,
phase: JsonObjectScanPhase,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
enum JsonObjectScanPhase {
#[default]
Initial,
Scanning,
Complete,
}
impl JsonObjectScanState {
pub(super) const fn complete(&self) -> bool {
matches!(self.phase, JsonObjectScanPhase::Complete)
}
}
pub(super) fn take_json_object(
input: &mut Partial<&str>,
state: &mut JsonObjectScanState,
) -> ModalResult<usize> {
let text = **input;
if text.is_empty() {
return incomplete();
}
if state.complete() {
return Err(json_scan_error(
"JSON object argument",
StrContextValue::Description("active JSON object scan"),
));
}
let bytes = text.as_bytes();
let just_started = matches!(state.phase, JsonObjectScanPhase::Initial);
if just_started {
if bytes[0] != b'{' {
return Err(json_scan_error(
"JSON object argument",
StrContextValue::CharLiteral('{'),
));
}
state.phase = JsonObjectScanPhase::Scanning;
state.object_depth = 1;
}
let mut index = usize::from(just_started);
while index < bytes.len() {
let byte = bytes[index];
index += 1;
if state.in_string {
if state.escape {
state.escape = false;
} else if byte == b'\\' {
state.escape = true;
} else if byte == b'"' {
state.in_string = false;
}
continue;
}
match byte {
b'"' => state.in_string = true,
b'{' => state.object_depth += 1,
b'}' => {
state.object_depth = state.object_depth.checked_sub(1).ok_or_else(|| {
json_scan_error(
"JSON object argument",
StrContextValue::Description("balanced object braces"),
)
})?;
if state.object_depth == 0 && state.array_depth == 0 {
state.phase = JsonObjectScanPhase::Complete;
input.next_slice(index);
return Ok(index);
}
if state.object_depth == 0 {
return Err(json_scan_error(
"JSON object argument",
StrContextValue::Description(
"nested arrays to close before the top-level object",
),
));
}
}
b'[' => state.array_depth += 1,
b']' => {
state.array_depth = state.array_depth.checked_sub(1).ok_or_else(|| {
json_scan_error(
"JSON object argument",
StrContextValue::Description("balanced array brackets"),
)
})?;
}
_ => {}
}
}
input.next_slice(text.len());
Ok(text.len())
}
pub(super) fn json_str(input: &mut Partial<&str>) -> ModalResult<String> {
let text = **input;
if text.is_empty() {
return incomplete();
}
let bytes = text.as_bytes();
if bytes[0] != b'"' {
return Err(json_scan_error(
"JSON string",
StrContextValue::CharLiteral('"'),
));
}
let mut escape = false;
let mut index = 1;
while index < bytes.len() {
let byte = bytes[index];
index += 1;
if escape {
escape = false;
continue;
}
match byte {
b'\\' => escape = true,
b'"' => {
let raw = &text[..index];
let value = serde_json::from_str::<String>(raw).map_err(|_| {
json_scan_error(
"JSON string",
StrContextValue::Description("valid JSON string"),
)
})?;
input.next_slice(index);
return Ok(value);
}
_ => {}
}
}
incomplete()
}
fn json_scan_error(label: &'static str, expected: StrContextValue) -> ErrMode<ContextError> {
let mut error = ContextError::new();
error.push(StrContext::Label(label));
error.push(StrContext::Expected(expected));
ErrMode::Cut(error)
}
pub(super) fn parse_buffered_event<E>(
buffer: &str,
parse: impl FnOnce(&mut Partial<&str>) -> ModalResult<E>,
) -> Result<Option<(E, usize)>> {
let mut input = Partial::new(buffer);
let checkpoint = input.checkpoint();
let event = match parse(&mut input) {
Ok(event) => event,
Err(ErrMode::Incomplete(_)) => return Ok(None),
Err(ErrMode::Backtrack(e) | ErrMode::Cut(e)) => {
return Err(parsing_failed!("{}", e));
}
};
let consumed_len = input.offset_from(&checkpoint);
if consumed_len == 0 {
return Ok(None);
}
Ok(Some((event, consumed_len)))
}
pub(super) fn incomplete<T>() -> ModalResult<T> {
Err(ErrMode::Incomplete(Needed::Unknown))
}