use crate::format::JsonPrettyError;
use crate::format::JsonPrettyOptions;
use crate::format::internal::Action;
use crate::format::internal::Container;
use crate::format::internal::ContainerKind;
use crate::lexical::JsonLexicalConsumer;
use crate::lexical::JsonLexicalEvent;
pub(in crate::format) struct Planner<'a> {
pub(in crate::format) input: &'a [u8],
pub(in crate::format) options: &'a JsonPrettyOptions,
pub(in crate::format) maximum: Option<usize>,
pub(in crate::format) actions: Vec<Action>,
pub(in crate::format) stack: Vec<Container>,
pub(in crate::format) length: usize,
}
impl Planner<'_> {
fn add_len(&mut self, amount: usize) -> Result<(), JsonPrettyError> {
self.length = self
.length
.checked_add(amount)
.ok_or_else(|| crate::format::json_token_formatter::capacity_error(None))?;
Ok(())
}
fn push(&mut self, action: Action, length: usize) -> Result<(), JsonPrettyError> {
self.add_len(length)?;
if self.maximum.is_some_and(|maximum| self.length > maximum) {
return Ok(());
}
self.actions
.try_reserve(1)
.map_err(|error| crate::format::json_token_formatter::capacity_error(Some(Box::new(error))))?;
self.actions.push(action);
Ok(())
}
pub(in crate::format) fn byte(&mut self, byte: u8) -> Result<(), JsonPrettyError> {
self.push(Action::Byte(byte), 1)
}
fn span(&mut self, offset: usize, length: usize) -> Result<(), JsonPrettyError> {
let end = offset
.checked_add(length)
.filter(|end| *end <= self.input.len())
.ok_or_else(|| crate::format::json_token_formatter::capacity_error(None))?;
self.push(Action::Span(offset, end), length)
}
fn spaces(&mut self, levels: usize) -> Result<(), JsonPrettyError> {
let shown = self
.options
.max_indent_levels
.map_or(levels, |maximum| levels.min(maximum));
let count = self
.options
.indent_width
.checked_mul(shown)
.ok_or_else(|| crate::format::json_token_formatter::capacity_error(None))?;
if count != 0 {
self.push(Action::Spaces(count), count)?;
}
Ok(())
}
fn prepare_value(&mut self) -> Result<(), JsonPrettyError> {
let Some(frame) = self.stack.last_mut() else {
return Ok(());
};
match frame.kind {
ContainerKind::Array => {
let first = frame.count == 0;
frame.count = frame
.count
.checked_add(1)
.ok_or_else(|| crate::format::json_token_formatter::capacity_error(None))?;
if !first {
self.byte(b',')?;
}
self.byte(b'\n')?;
self.spaces(self.stack.len())
}
ContainerKind::Object => {
if !frame.awaiting_value {
return Err(crate::format::json_token_formatter::capacity_error(None));
}
frame.awaiting_value = false;
self.byte(b':')?;
self.byte(b' ')
}
}
}
fn key(&mut self, offset: usize, length: usize) -> Result<(), JsonPrettyError> {
let Some(frame) = self.stack.last_mut() else {
return Err(crate::format::json_token_formatter::capacity_error(None));
};
if !matches!(frame.kind, ContainerKind::Object) || frame.awaiting_value {
return Err(crate::format::json_token_formatter::capacity_error(None));
}
let first = frame.count == 0;
frame.count = frame
.count
.checked_add(1)
.ok_or_else(|| crate::format::json_token_formatter::capacity_error(None))?;
frame.awaiting_value = true;
if !first {
self.byte(b',')?;
}
self.byte(b'\n')?;
self.spaces(self.stack.len())?;
self.span(offset, length)
}
fn begin(&mut self, kind: ContainerKind, offset: usize) -> Result<(), JsonPrettyError> {
self.span(offset, 1)?;
self.stack
.try_reserve(1)
.map_err(|error| crate::format::json_token_formatter::capacity_error(Some(Box::new(error))))?;
self.stack.push(Container {
kind,
count: 0,
awaiting_value: false,
});
Ok(())
}
fn end(&mut self, offset: usize, expected: ContainerKind) -> Result<(), JsonPrettyError> {
let frame = self
.stack
.pop()
.ok_or_else(|| crate::format::json_token_formatter::capacity_error(None))?;
if !matches!(
(frame.kind, expected),
(ContainerKind::Array, ContainerKind::Array) | (ContainerKind::Object, ContainerKind::Object)
) || frame.awaiting_value
{
return Err(crate::format::json_token_formatter::capacity_error(None));
}
if frame.count > 0 {
self.byte(b'\n')?;
self.spaces(self.stack.len())?;
}
self.span(offset, 1)?;
Ok(())
}
}
impl<'input> JsonLexicalConsumer<'input> for Planner<'_> {
type Error = JsonPrettyError;
fn before_value(&mut self, _depth: usize, _offset: usize) -> Result<(), Self::Error> {
self.prepare_value()
}
fn before_key(&mut self, _offset: usize) -> Result<(), Self::Error> {
Ok(())
}
fn before_number(&mut self, _token: &'input str, _offset: usize) -> Result<(), Self::Error> {
Ok(())
}
fn event(&mut self, event: JsonLexicalEvent<'input>) -> Result<(), Self::Error> {
match event {
JsonLexicalEvent::BeginArray { offset } => self.begin(ContainerKind::Array, offset),
JsonLexicalEvent::BeginObject { offset } => self.begin(ContainerKind::Object, offset),
JsonLexicalEvent::EndArray { offset } => self.end(offset, ContainerKind::Array),
JsonLexicalEvent::EndObject { offset } => self.end(offset, ContainerKind::Object),
JsonLexicalEvent::Key { token, offset } => self.key(offset, token.len()),
JsonLexicalEvent::String { token, offset } | JsonLexicalEvent::Number { token, offset } => {
self.span(offset, token.len())
}
JsonLexicalEvent::Null { offset } => self.span(offset, 4),
JsonLexicalEvent::Boolean { value, offset } => self.span(offset, if value { 4 } else { 5 }),
}
}
}
#[cfg(test)]
mod tests {
use qubit_budget::json::JsonDecodeLimits;
use qubit_budget::json::JsonDecodeSession;
use qubit_budget::json::JsonResource;
use super::Planner;
use crate::format::JsonPrettyErrorKind;
use crate::format::JsonPrettyOptions;
use crate::format::internal::Container;
use crate::format::internal::ContainerKind;
use crate::lexical::JsonLexicalScanner;
use crate::lexical::JsonNumberModel;
#[test]
fn test_planner_rejects_invalid_spans_and_unmatched_end() {
let options = JsonPrettyOptions::builder().build().expect("valid defaults");
let mut planner = Planner {
input: b"[]",
options: &options,
maximum: None,
actions: Vec::new(),
stack: Vec::new(),
length: 0,
};
for (offset, length) in [(usize::MAX, 2), (1, 2)] {
assert_eq!(
planner.span(offset, length).expect_err("invalid span").kind(),
JsonPrettyErrorKind::Capacity
);
assert!(planner.actions.is_empty());
assert_eq!(planner.length, 0);
}
assert_eq!(
planner.end(1, ContainerKind::Array).expect_err("missing frame").kind(),
JsonPrettyErrorKind::Capacity
);
}
#[test]
fn test_planner_container_counts_do_not_wrap() {
let options = JsonPrettyOptions::builder().build().expect("valid defaults");
for kind in [ContainerKind::Array, ContainerKind::Object] {
let mut planner = Planner {
input: b"[]",
options: &options,
maximum: None,
actions: Vec::new(),
length: 0,
stack: vec![Container {
kind,
count: usize::MAX,
awaiting_value: false,
}],
};
let result = match kind {
ContainerKind::Array => planner.prepare_value(),
ContainerKind::Object => planner.key(0, 1),
};
assert_eq!(
result.expect_err("item count must not wrap").kind(),
JsonPrettyErrorKind::Capacity
);
assert_eq!(planner.stack[0].count, usize::MAX);
assert!(planner.actions.is_empty());
}
}
#[test]
fn test_planner_stops_retaining_actions_after_output_limit() {
fn planned_storage(input: &[u8], options: &JsonPrettyOptions) -> (usize, usize) {
let mut planner = Planner {
input,
options,
maximum: Some(8),
actions: Vec::new(),
stack: Vec::new(),
length: 0,
};
let limits = JsonDecodeLimits::<JsonResource, usize>::new();
let mut session = JsonDecodeSession::from_limits(limits);
let mut attempt = session.begin_value();
let mut scanner = JsonLexicalScanner::new(attempt.value_transaction_mut(), false)
.with_number_model(JsonNumberModel::SyntaxOnly);
scanner
.scan_with_consumer(input, &mut planner)
.expect("valid array must scan");
(planner.actions.len(), planner.actions.capacity())
}
let options = JsonPrettyOptions::builder().build().expect("valid defaults");
let mut zero_limit_planner = Planner {
input: b"0",
options: &options,
maximum: Some(0),
actions: Vec::new(),
stack: Vec::new(),
length: 0,
};
zero_limit_planner
.push(crate::format::internal::Action::Byte(b'0'), 1)
.expect("crossing the limit does not stop scanning");
assert_eq!(zero_limit_planner.actions.len(), 0, "zero limit retains no actions");
assert_eq!(
zero_limit_planner.actions.capacity(),
0,
"zero limit allocates no action storage"
);
let small = format!("[{}]", vec!["0"; 32].join(","));
let large = format!("[{}]", vec!["0"; 4_096].join(","));
let small_storage = planned_storage(small.as_bytes(), &options);
let large_storage = planned_storage(large.as_bytes(), &options);
assert_eq!(
small_storage.0, large_storage.0,
"retained actions must stop at the same prefix"
);
assert_eq!(
small_storage.1, large_storage.1,
"action capacity must not grow after the limit"
);
assert!(
large_storage.0 > 0,
"the planner retains the prefix before exceeding the limit"
);
}
}