use std::error::Error;
use qubit_budget::json::JsonDecodeLimits;
use qubit_budget::json::JsonDecodeSession;
use qubit_budget::json::JsonResource;
use qubit_io::ByteOutput;
use super::JsonPrettyError;
use super::JsonPrettyErrorKind;
use super::JsonPrettyOptions;
use super::internal::Action;
use super::internal::Plan;
use super::internal::Planner;
use crate::lexical::JsonLexicalScanError;
use crate::lexical::JsonLexicalScanner;
use crate::lexical::JsonNumberModel;
pub fn format_json_tokens(
input: &[u8],
options: &JsonPrettyOptions,
max_output_bytes: Option<usize>,
) -> Result<Vec<u8>, JsonPrettyError> {
let plan = make_plan(input, options, max_output_bytes)?;
let mut output = Vec::new();
output
.try_reserve(plan.length)
.map_err(|error| capacity_error(Some(Box::new(error))))?;
render_vec(input, &plan.actions, &mut output);
Ok(output)
}
pub fn write_json_tokens<O: ByteOutput + ?Sized>(
output: &mut O,
input: &[u8],
options: &JsonPrettyOptions,
max_output_bytes: Option<usize>,
) -> Result<(), JsonPrettyError> {
let plan = make_plan(input, options, max_output_bytes)?;
for action in &plan.actions {
let bytes: &[u8] = match action {
Action::Span(start, end) => &input[*start..*end],
Action::Byte(byte) => std::slice::from_ref(byte),
Action::Spaces(count) => {
const SPACES: [u8; 256] = [b' '; 256];
let mut remaining = *count;
while remaining > 0 {
let chunk = remaining.min(SPACES.len());
output.write_fully(&SPACES[..chunk]).map_err(io_error)?;
remaining -= chunk;
}
continue;
}
};
output.write_fully(bytes).map_err(io_error)?;
}
Ok(())
}
fn make_plan(input: &[u8], options: &JsonPrettyOptions, maximum: Option<usize>) -> Result<Plan, JsonPrettyError> {
let mut planner = Planner {
input,
options,
maximum,
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 scan = {
let mut scanner = JsonLexicalScanner::new(attempt.value_transaction_mut(), false)
.with_number_model(JsonNumberModel::SyntaxOnly);
scanner.scan_with_consumer(input, &mut planner)
};
if let Err(error) = scan {
return Err(map_scan_error(error));
}
if !planner.stack.is_empty() {
return Err(capacity_error(None));
}
if options.trailing_newline {
planner.byte(b'\n')?;
}
if maximum.is_some_and(|maximum| planner.length > maximum) {
return Err(JsonPrettyError::new(JsonPrettyErrorKind::OutputLimit, None, None));
}
Ok(Plan {
actions: planner.actions,
length: planner.length,
})
}
fn render_vec(input: &[u8], actions: &[Action], output: &mut Vec<u8>) {
const SPACES: [u8; 256] = [b' '; 256];
for action in actions {
match action {
Action::Span(start, end) => output.extend_from_slice(&input[*start..*end]),
Action::Byte(byte) => output.push(*byte),
Action::Spaces(count) => {
let mut remaining = *count;
while remaining > 0 {
let chunk = remaining.min(SPACES.len());
output.extend_from_slice(&SPACES[..chunk]);
remaining -= chunk;
}
}
}
}
}
fn map_scan_error(error: JsonLexicalScanError<JsonPrettyError, JsonResource, usize>) -> JsonPrettyError {
match error {
JsonLexicalScanError::Lexical(crate::lexical::JsonLexicalError::Syntax(failure)) => JsonPrettyError::new(
JsonPrettyErrorKind::Syntax,
Some(failure.offset),
Some(Box::new(failure)),
),
JsonLexicalScanError::Lexical(_) => JsonPrettyError::new(JsonPrettyErrorKind::Syntax, None, None),
JsonLexicalScanError::Consumer(error) => error,
JsonLexicalScanError::EncodedStringLimit { offset, .. } => {
JsonPrettyError::new(JsonPrettyErrorKind::Syntax, Some(offset), None)
}
}
}
pub(super) fn capacity_error(source: Option<Box<dyn Error + Send + Sync>>) -> JsonPrettyError {
JsonPrettyError::new(JsonPrettyErrorKind::Capacity, None, source)
}
fn io_error(error: std::io::Error) -> JsonPrettyError {
JsonPrettyError::new(JsonPrettyErrorKind::Io, None, Some(Box::new(error)))
}