structio/json/prettify.rs
1//! Laying out JSON text that is already JSON.
2//!
3//! What a caller can rely on is documented on [`prettify`], which the other
4//! entry points here refer back to. Why the walk goes through the value path's
5//! writer rather than carrying layout rules of its own, and why a number is
6//! stepped over by its alphabet rather than held to the grammar, is in
7//! [docs/design.md](https://github.com/matrix-research-inc/structio/blob/main/docs/design.md#prettifying-is-the-writer-not-a-second-layout).
8//!
9//! Every entry point is [`prettify_value_into`] underneath. It is the one that
10//! lays out into a writer already part-way through a document; the others hand
11//! it a fresh writer, at depth zero, and return what it wrote.
12
13use crate::error::{Error, ErrorCode, PResult, Result};
14use crate::json::parser::Parser;
15use crate::json::writer::Writer;
16use crate::options::{Options, Pretty};
17
18/// Lay out a JSON document across indented lines.
19///
20/// Two spaces per level, one member or element per line: [`Pretty`], the same
21/// policy [`to_string_with`](crate::to_string_with) takes.
22///
23/// ```
24/// let out = structio::prettify(r#"{"a":[1,2],"b":{}}"#).unwrap();
25/// assert_eq!(out, "{\n \"a\": [\n 1,\n 2\n ],\n \"b\": {}\n}");
26/// ```
27///
28/// This is for a document that arrived as text, such as a log line, a response
29/// body or a file on disk, rather than out of a [`Write`](crate::json::Write)
30/// impl. Its whitespace goes through the same writer such an impl writes
31/// through, so the output is byte-identical to what writing the same data under
32/// the same policy would have produced.
33///
34/// Values are copied, not re-encoded. A number keeps the spelling the input
35/// gave it and a string keeps its escapes, so `1.50` stays `1.50` and
36/// `"\u0041"` stays `"\u0041"`. The output is the input's data laid out again,
37/// not a round trip through this crate's number and string formatters.
38///
39/// ```
40/// let out = structio::prettify(r#"[1.50,"\u0041"]"#).unwrap();
41/// assert_eq!(out, "[\n 1.50,\n \"\\u0041\"\n]");
42/// ```
43///
44/// Structure is checked as the walk goes, because it has to be known to be laid
45/// out at all: which container a value is in decides whether it gets a line or
46/// a space, and how deep it is decides the indent. The input must be one
47/// complete JSON document whose structure holds up. Anything else is an
48/// [`Error`] naming the byte that stopped the walk, rather than output that is
49/// quietly wrong:
50///
51/// ```
52/// use structio::ErrorCode;
53///
54/// let e = structio::prettify(r#"{"a":}"#).unwrap_err();
55/// assert_eq!(e.code, ErrorCode::UnexpectedCharacter);
56/// assert_eq!(e.index, 5);
57/// ```
58///
59/// Tokens are not checked past what stepping over them requires. A number is
60/// taken by its alphabet, so `01` and `1.2.3` lay out unchanged rather than
61/// being refused:
62///
63/// ```
64/// assert_eq!(structio::prettify("[01,1.2.3]").unwrap(), "[\n 01,\n 1.2.3\n]");
65/// ```
66///
67/// This is not a validator, and [`from_str`](crate::from_str) is the thing to
68/// reach for when the question is whether a document is good.
69#[inline]
70pub fn prettify(input: &str) -> Result<String> {
71 prettify_with::<Pretty>(input)
72}
73
74/// [`prettify`] under an explicit [write policy](crate::Options).
75///
76/// The policy decides the layout exactly as it does when a value is written:
77/// [`INDENT`](Options::INDENT) sets the width and
78/// [`NEW_LINES_IN_ARRAYS`](Options::NEW_LINES_IN_ARRAYS) decides whether an
79/// array gets a line per element.
80///
81/// ```
82/// use structio::{PrettyInlineArrays, json::prettify_with};
83///
84/// let out = prettify_with::<PrettyInlineArrays>(r#"{"v":[1,2,3]}"#).unwrap();
85/// assert_eq!(out, "{\n \"v\": [1, 2, 3]\n}");
86/// ```
87///
88/// [`PRETTY`](Options::PRETTY) is honoured too, rather than assumed, so a
89/// compact policy compacts. That makes [`Standard`](crate::Standard) a
90/// minifier, which is the same walk with nothing to emit between tokens:
91///
92/// ```
93/// use structio::{Standard, json::prettify_with};
94///
95/// let out = prettify_with::<Standard>("{\n \"a\": [1, 2]\n}").unwrap();
96/// assert_eq!(out, r#"{"a":[1,2]}"#);
97/// ```
98///
99/// [`minify`](crate::minify) reaches the same bytes on any document that is
100/// really JSON, and much faster, because compacting needs none of the structure
101/// this walks. Reach for that unless you want the walk's checking too.
102///
103/// The reading settings do not apply, there being no schema here to have an
104/// opinion about: an unknown key is every key. The exception is
105/// [`ALLOW_COMMENTS`](Options::ALLOW_COMMENTS), which decides whether a
106/// document may carry comments at all. It drops them, as everything in this
107/// crate does, a comment being no part of what a writer can emit.
108#[inline]
109pub fn prettify_with<O: Options>(input: &str) -> Result<String> {
110 let mut out = String::new();
111 prettify_into_with::<O>(input, &mut out)?;
112 Ok(out)
113}
114
115/// [`prettify`] into an existing `String`, replacing its contents and keeping
116/// its allocation.
117///
118/// Prefer this in a loop, the way [`write_into`](crate::write_into) is
119/// preferred over [`to_string`](crate::to_string).
120///
121/// On failure `out` holds however much was laid out before the error, which is
122/// the same bargain [`read_into`](crate::read_into) makes: recovering the
123/// original would mean copying it first, on every call, to serve the failing
124/// case.
125#[inline]
126pub fn prettify_into(input: &str, out: &mut String) -> Result<()> {
127 prettify_into_with::<Pretty>(input, out)
128}
129
130/// [`prettify_into`] under an explicit [write policy](crate::Options).
131pub fn prettify_into_with<O: Options>(input: &str, out: &mut String) -> Result<()> {
132 let mut buf = core::mem::take(out).into_bytes();
133 // Emptied before the reserve for `minify_into_with`'s reason: `reserve`
134 // counts from the length, so clearing has to happen first rather than
135 // being left to `Writer::from_vec`.
136 buf.clear();
137 // Indenting roughly doubles a document of small values, which is the shape
138 // that gets prettified; compacting never grows one. Either way this is the
139 // only allocation the common case makes.
140 buf.reserve(if O::PRETTY {
141 input.len().saturating_mul(2)
142 } else {
143 input.len()
144 });
145
146 let mut w = Writer::<O>::from_vec(buf);
147 // The same call a `Write` impl makes, over a writer that happens to be
148 // fresh and so at depth zero. One implementation, and it is the public one
149 // that every test of this function exercises.
150 let result = prettify_value_into::<O>(input, &mut w);
151 // Hand the buffer back whether or not the walk got to the end, so the
152 // caller keeps the allocation either way.
153 *out = w.into_string();
154 result
155}
156
157/// Lay one JSON value out into a writer that is already part-way through a
158/// document.
159///
160/// Every other entry point here begins a fresh document at depth zero and
161/// hands back the text of it, which is everything a caller holding a document
162/// wants and nothing a [`Write`](crate::json::Write) impl can use. This one
163/// takes the writer instead, and lays `input` out at that writer's current
164/// nesting depth under the policy it carries, so the value arrives indented
165/// against its neighbours rather than as a blob starting again at column zero.
166/// It is what a type holding JSON *text* where the document expects a *value*
167/// needs; [`Raw`](crate::json::Raw) is that type here, and this is the call its
168/// [`Write`](crate::json::Write) impl makes.
169///
170/// ```
171/// use structio::{Options, Pretty, json};
172///
173/// /// A value this program forwards rather than reads, kept as its text.
174/// struct Body(&'static str);
175///
176/// impl json::Write for Body {
177/// fn write<O: Options>(&self, w: &mut json::Writer<'_, O>) {
178/// // The span was settled when the `Body` was built, so it lays out.
179/// json::prettify_value_into(self.0, w).unwrap();
180/// }
181/// }
182///
183/// // Laid out where the element actually sits, not at column zero.
184/// assert_eq!(
185/// structio::to_string_with::<Pretty, _>(&vec![Body(r#"{"a":[1]}"#)]),
186/// "[\n {\n \"a\": [\n 1\n ]\n }\n]"
187/// );
188/// ```
189///
190/// `input` has to be exactly one JSON value, whitespace on either side and
191/// nothing else, which is [`prettify`]'s rule too, a document being one value.
192/// Anything after it is [`TrailingContent`](crate::ErrorCode::TrailingContent)
193/// rather than something copied through, a tail landing in the middle of the
194/// surrounding document being the one place it could land.
195///
196/// The tokens are what survive. A number keeps the spelling `input` gave it and
197/// a string keeps its escapes, exactly as under [`prettify`], because the walk
198/// copies tokens rather than decoding and re-encoding them. The whitespace
199/// between them does not: the input's own layout is dropped and the policy's is
200/// written in its place, which is the whole of what the call is for. Under
201/// [`ALLOW_COMMENTS`](Options::ALLOW_COMMENTS) a comment is whitespace and goes
202/// the same way, there being no writer here that can emit one.
203///
204/// A compact policy is honoured rather than overridden, as it is by
205/// [`prettify_with`], so this minifies `input` into the writer under
206/// [`Standard`](crate::Standard). That is why `Raw` reaches here only under
207/// [`PRETTY`](Options::PRETTY): compacted output and the span it arrived as
208/// differ only in whitespace, and rewriting that whitespace would give up the
209/// bytes it exists to preserve for nothing.
210///
211/// **A call that fails leaves behind what it had already written.** A writer
212/// cannot be rewound, for the reason [`Writer`] gives, so a span that turns out
213/// to be malformed halfway through has its first half in the document and no
214/// way to take it back. An impl that must not do that has to settle the
215/// question before it emits a byte, by walking `input` first with
216/// [`Parser::skip_value`](crate::json::Parser::skip_value) and
217/// [`Parser::finish`](crate::json::Parser::finish), or by having accepted it
218/// earlier through [`Raw::new`](crate::json::Raw::new). `Raw`'s own
219/// [`Write`](crate::json::Write) impl takes the first of those routes, and the
220/// probe pass it pays for is what a second layout walk would cost anyway.
221///
222/// The error's offset is a position in `input`. An impl composing this into one
223/// of its own has nowhere to put that: an offset into a span of a larger
224/// document names the wrong byte of that document, so the code travels and the
225/// offset is dropped. See
226/// [docs/errors.md](https://github.com/matrix-research-inc/structio/blob/main/docs/errors.md#two-error-currencies-and-why)
227/// for why the seam is there.
228///
229/// This is the one member of the `prettify` family not also re-exported at the
230/// crate root. The root carries the entry points you reach for with a document
231/// in hand; reaching for this one means having a [`Writer`], which happens
232/// inside a `Write` impl and nowhere else, and `Writer` is not at the root
233/// either.
234pub fn prettify_value_into<O: Options>(input: &str, w: &mut Writer<'_, O>) -> Result<()> {
235 let mut p = Parser::<O>::with_options(input);
236 walk(&mut p, w).map_err(|code| Error::new(code, p.position()))
237}
238
239/// One whole document: the value, then nothing but whitespace.
240fn walk<O: Options>(p: &mut Parser<'_, O>, w: &mut Writer<'_, O>) -> PResult<()> {
241 p.skip_ws();
242 value(p, w)?;
243 p.finish()
244}
245
246/// Lay out one value, whatever it is.
247///
248/// The cursor must be on the value's first byte, whitespace already behind it.
249/// That is the parser's own convention and every route here keeps it: the walk
250/// above skips the leading run once, an opening bracket skips what follows it,
251/// and `colon` and `comma_or_close` skip what follows them. Skipping again per
252/// value would be a third of the whitespace work in a document that has none.
253///
254/// Private, and [`prettify_value_into`] is what a caller outside this module
255/// reaches for: the same walk with the leading whitespace skipped for it, the
256/// tail checked, and the error given a position, rather than a cursor contract
257/// stated in prose and enforced by a debug assertion.
258fn value<O: Options>(p: &mut Parser<'_, O>, w: &mut Writer<'_, O>) -> PResult<()> {
259 debug_assert!(
260 !matches!(p.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')),
261 "the cursor is not on a token: whitespace was left unskipped"
262 );
263 match p.peek() {
264 Some(b'{') => object(p, w),
265 Some(b'[') => array(p, w),
266 _ => copy_token(p, w),
267 }
268}
269
270/// `{ "key": value, ... }`, one member per line.
271fn object<O: Options>(p: &mut Parser<'_, O>, w: &mut Writer<'_, O>) -> PResult<()> {
272 p.expect(b'{', ErrorCode::ExpectedBrace)?;
273 p.nested(|p| {
274 w.open(b'{');
275 p.skip_ws();
276 if !p.try_byte(b'}') {
277 loop {
278 // The cursor is on a token: the `{` above skipped its
279 // whitespace, and so does `comma_or_close` at the foot of the
280 // loop.
281 match p.peek() {
282 Some(b'"') => {}
283 None => return Err(ErrorCode::UnexpectedEnd),
284 Some(_) => return Err(ErrorCode::ExpectedQuote),
285 }
286 w.line();
287 copy_token(p, w)?;
288 p.colon()?;
289 w.colon();
290 value(p, w)?;
291 // The unconditional trailing comma every container in this
292 // crate writes; `close` below either overwrites it or takes it
293 // back.
294 w.push(b',');
295 if !p.comma_or_close(b'}')? {
296 break;
297 }
298 }
299 }
300 w.close(b'}');
301 Ok(())
302 })
303}
304
305/// `[value, ...]`, one element per line or all on one, as the policy says.
306fn array<O: Options>(p: &mut Parser<'_, O>, w: &mut Writer<'_, O>) -> PResult<()> {
307 p.expect(b'[', ErrorCode::ExpectedBracket)?;
308 p.nested(|p| {
309 w.open(b'[');
310 p.skip_ws();
311 if !p.try_byte(b']') {
312 loop {
313 w.item();
314 value(p, w)?;
315 w.push(b',');
316 if !p.comma_or_close(b']')? {
317 break;
318 }
319 }
320 }
321 w.close(b']');
322 Ok(())
323 })
324}
325
326/// Copy one scalar token through, exactly as the input spelled it.
327///
328/// A string or a number is the input's own bytes, and its length is not known
329/// until the parser has walked it, so it is copied. The three literals are
330/// known text and go through the writer's fixed-size appenders instead, which
331/// store a compile-time-constant run rather than calling out to a copy of four
332/// bytes whose length the compiler cannot see.
333///
334/// Either way the parser is what finds the token's end, and it checks only what
335/// finding the end requires: a string's escapes and its closing quote, a
336/// literal's spelling, and a number's alphabet. Object keys come through here
337/// too, a key being a string like any other.
338///
339/// The cursor must already be on the token, as it must be for [`value`].
340#[inline]
341fn copy_token<O: Options>(p: &mut Parser<'_, O>, w: &mut Writer<'_, O>) -> PResult<()> {
342 match p.peek() {
343 Some(b't') => {
344 p.expect_lit(b"true", ErrorCode::ExpectedTrue)?;
345 w.write_bool(true);
346 }
347 Some(b'f') => {
348 p.expect_lit(b"false", ErrorCode::ExpectedFalse)?;
349 w.write_bool(false);
350 }
351 Some(b'n') => {
352 p.expect_lit(b"null", ErrorCode::ExpectedNull)?;
353 w.write_null();
354 }
355 _ => {
356 // `rest` is tied to the input's lifetime rather than to the borrow,
357 // so the token stays reachable across the walk that measures it.
358 let from = p.rest();
359 let start = p.position();
360 p.skip_scalar()?;
361 // Whole tokens out of a `&str`, so the buffer stays valid UTF-8.
362 w.raw_bytes(&from[..p.position() - start]);
363 }
364 }
365 Ok(())
366}