use tabnas_alchemy::shared::{Fail, Flow, JsonEvent, Number, Sink};
use crate::number::{check_number, write_value};
use crate::text::TextOut;
pub use tabnas_alchemy::shared::json::JsonOptions;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Frame {
Object { first: bool, expecting_key: bool },
Array { first: bool },
}
pub struct JsonRenderer<O: TextOut> {
out: O,
options: JsonOptions,
indent: usize,
stack: Vec<Frame>,
root_done: bool,
ended: bool,
emitted: bool,
scratch: String,
pad: String,
}
impl<O: TextOut> JsonRenderer<O> {
pub fn new(out: O, options: JsonOptions) -> Self {
JsonRenderer {
indent: options.indent.unwrap_or(0),
out,
options,
stack: Vec::new(),
root_done: false,
ended: false,
emitted: false,
scratch: String::new(),
pad: String::new(),
}
}
pub fn options(&self) -> &JsonOptions {
&self.options
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn is_done(&self) -> bool {
self.ended
}
pub fn into_inner(self) -> O {
self.out
}
fn fail(&self, f: Fail) -> Fail {
if self.emitted && self.out.has_committed() {
f.committed()
} else {
f
}
}
fn protocol(&self, message: &str) -> Fail {
self.fail(Fail::protocol(message))
}
fn put(&mut self, s: &str) -> Result<(), Fail> {
self.emitted = true;
self.out.write_str(s)
}
fn put_string(&mut self, s: &str) -> Result<(), Fail> {
self.put("\"")?;
let mut rest = s;
while let Some((i, width, escaped)) = rest
.char_indices()
.find_map(|(i, c)| escape(c).map(|e| (i, c.len_utf8(), e)))
{
if i > 0 {
self.put(&rest[..i])?;
}
self.put(escaped)?;
rest = &rest[i + width..];
}
if !rest.is_empty() {
self.put(rest)?;
}
self.put("\"")
}
fn put_number(&mut self, n: Number<'_>) -> Result<(), Fail> {
match n.lexeme {
Some(l) => self.put(l),
None => {
self.emitted = true;
self.out.write_str(write_value(n.value, &mut self.scratch))
}
}
}
fn break_line(&mut self, depth: usize) -> Result<(), Fail> {
if self.indent == 0 {
return Ok(());
}
let width = depth.saturating_mul(self.indent);
while self.pad.len() < width {
self.pad.push(' ');
}
let pad = std::mem::take(&mut self.pad);
self.put("\n")?;
let r = self.put(&pad[..width]);
self.pad = pad;
r
}
fn begin_value(&mut self) -> Result<(), Fail> {
if self.ended {
return Err(self.protocol("a value after the end"));
}
let depth = self.stack.len();
match self.stack.last_mut() {
None if self.root_done => Err(self.protocol("a second root value")),
None => Ok(()),
Some(Frame::Object {
expecting_key: true,
..
}) => Err(self.protocol("a value where a key is due")),
Some(Frame::Object { .. }) => Ok(()),
Some(Frame::Array { first }) => {
let comma = !*first;
*first = false;
if comma {
self.put(",")?;
}
self.break_line(depth)
}
}
}
fn end_value(&mut self) {
match self.stack.last_mut() {
None => self.root_done = true,
Some(Frame::Object { expecting_key, .. }) => *expecting_key = true,
Some(Frame::Array { .. }) => {}
}
}
fn key(&mut self, k: &str) -> Result<(), Fail> {
if self.ended {
return Err(self.protocol("a key after the end"));
}
let depth = self.stack.len();
match self.stack.last_mut() {
Some(Frame::Object {
first,
expecting_key: true,
}) => {
let comma = !*first;
*first = false;
if comma {
self.put(",")?;
}
self.break_line(depth)?;
self.put_string(k)?;
self.put(if self.indent > 0 { ": " } else { ":" })?;
if let Some(Frame::Object { expecting_key, .. }) = self.stack.last_mut() {
*expecting_key = false;
}
Ok(())
}
Some(Frame::Object { .. }) => Err(self.protocol("a key where a value is due")),
Some(Frame::Array { .. }) => Err(self.protocol("a key inside an array")),
None => Err(self.protocol("a key outside an object")),
}
}
fn start(&mut self, open: &str, frame: Frame) -> Result<(), Fail> {
self.begin_value()?;
self.put(open)?;
self.stack.push(frame);
Ok(())
}
fn close_object(&mut self) -> Result<(), Fail> {
if self.ended {
return Err(self.protocol("an object end after the end"));
}
let first = match self.stack.last() {
Some(Frame::Object {
expecting_key: false,
..
}) => return Err(self.protocol("an object ended after a key with no value")),
Some(Frame::Object { first, .. }) => *first,
Some(Frame::Array { .. }) => return Err(self.protocol("an object end inside an array")),
None => return Err(self.protocol("an object end with no open object")),
};
self.stack.pop();
if !first {
self.break_line(self.stack.len())?;
}
self.put("}")?;
self.end_value();
Ok(())
}
fn close_array(&mut self) -> Result<(), Fail> {
if self.ended {
return Err(self.protocol("an array end after the end"));
}
let first = match self.stack.last() {
Some(Frame::Array { first }) => *first,
Some(Frame::Object { .. }) => {
return Err(self.protocol("an array end inside an object"))
}
None => return Err(self.protocol("an array end with no open array")),
};
self.stack.pop();
if !first {
self.break_line(self.stack.len())?;
}
self.put("]")?;
self.end_value();
Ok(())
}
fn scalar(&mut self, ev: JsonEvent<'_>) -> Result<(), Fail> {
if let JsonEvent::Number(n) = ev {
check_number(n.value, n.lexeme).map_err(|f| self.fail(f))?;
}
self.begin_value()?;
match ev {
JsonEvent::Null => self.put("null")?,
JsonEvent::Bool(true) => self.put("true")?,
JsonEvent::Bool(false) => self.put("false")?,
JsonEvent::Number(n) => self.put_number(n)?,
JsonEvent::String(s) => self.put_string(s)?,
_ => return Err(self.protocol("not a scalar")),
}
self.end_value();
Ok(())
}
fn end(&mut self) -> Result<(), Fail> {
if self.ended {
return Err(self.protocol("a second end"));
}
if !self.stack.is_empty() {
return Err(self.protocol(&format!(
"the end with {} open container(s)",
self.stack.len()
)));
}
if !self.root_done {
return Err(self.protocol("the end before a root value"));
}
if self.options.trailing_newline {
self.put("\n")?;
}
self.out.flush()?;
self.ended = true;
Ok(())
}
}
const CONTROL: [&str; 32] = [
"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b",
"\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012",
"\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a",
"\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
];
fn escape(c: char) -> Option<&'static str> {
match c {
'"' => Some("\\\""),
'\\' => Some("\\\\"),
c if (c as u32) < 0x20 => CONTROL.get(c as usize).copied(),
_ => None,
}
}
impl<O: TextOut> Sink for JsonRenderer<O> {
fn event(&mut self, ev: JsonEvent<'_>) -> Result<Flow, Fail> {
match ev {
JsonEvent::ObjectStart => self.start(
"{",
Frame::Object {
first: true,
expecting_key: true,
},
)?,
JsonEvent::ArrayStart => self.start("[", Frame::Array { first: true })?,
JsonEvent::ObjectEnd => self.close_object()?,
JsonEvent::ArrayEnd => self.close_array()?,
JsonEvent::Key(k) => self.key(k)?,
JsonEvent::Null | JsonEvent::Bool(_) | JsonEvent::Number(_) | JsonEvent::String(_) => {
self.scalar(ev)?
}
JsonEvent::End => self.end()?,
}
Ok(Flow::Continue)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::text::{StringOut, WriteOut};
use tabnas_alchemy::shared::Code;
use JsonEvent::*;
fn num(lexeme: &str) -> JsonEvent<'_> {
Number(tabnas_alchemy::shared::Number::with_lexeme(
lexeme.parse().unwrap_or(0.0),
lexeme,
))
}
fn value(v: f64) -> JsonEvent<'static> {
Number(tabnas_alchemy::shared::Number::new(v))
}
fn render(options: JsonOptions, events: &[JsonEvent<'_>]) -> Result<std::string::String, Fail> {
let mut r = JsonRenderer::new(StringOut::new(), options);
for ev in events {
r.event(*ev)?;
}
Ok(r.into_inner().into_string())
}
fn compact(events: &[JsonEvent<'_>]) -> Result<std::string::String, Fail> {
render(JsonOptions::default(), events)
}
fn indented(n: usize, events: &[JsonEvent<'_>]) -> std::string::String {
render(
JsonOptions {
indent: Some(n),
trailing_newline: false,
},
events,
)
.unwrap()
}
const DOC: &[JsonEvent<'static>] = &[
ObjectStart,
Key("a"),
ArrayStart,
Number(tabnas_alchemy::shared::Number {
value: 1.0,
lexeme: Some("1"),
}),
Number(tabnas_alchemy::shared::Number {
value: 2.5,
lexeme: None,
}),
String("x"),
Bool(true),
Null,
ArrayEnd,
Key("b"),
ObjectStart,
ObjectEnd,
Key("c"),
ArrayStart,
ArrayEnd,
Key("d"),
ObjectStart,
Key("e"),
Bool(false),
ObjectEnd,
ObjectEnd,
End,
];
#[test]
fn compact_output_has_no_whitespace() {
assert_eq!(
compact(DOC).unwrap(),
r#"{"a":[1,2.5,"x",true,null],"b":{},"c":[],"d":{"e":false}}"#
);
}
#[test]
fn an_indent_writes_fixed_nesting_and_keeps_empty_containers_on_one_line() {
assert_eq!(
indented(2, DOC),
"{\n \"a\": [\n 1,\n 2.5,\n \"x\",\n true,\n null\n ],\n \"b\": {},\n \"c\": [],\n \"d\": {\n \"e\": false\n }\n}"
);
assert_eq!(
indented(4, &[ArrayStart, ArrayStart, Null, ArrayEnd, ArrayEnd, End]),
"[\n [\n null\n ]\n]"
);
}
#[test]
fn an_indent_of_zero_is_compact() {
assert_eq!(indented(0, DOC), compact(DOC).unwrap());
}
#[test]
fn the_trailing_newline_is_written_at_end_when_asked() {
let options = JsonOptions {
indent: None,
trailing_newline: true,
};
assert_eq!(render(options, &[Null, End]).unwrap(), "null\n");
assert_eq!(compact(&[Null, End]).unwrap(), "null");
}
#[test]
fn a_root_scalar_is_a_document() {
assert_eq!(compact(&[String("x"), End]).unwrap(), "\"x\"");
assert_eq!(compact(&[num("-0.5e3"), End]).unwrap(), "-0.5e3");
assert_eq!(compact(&[Bool(false), End]).unwrap(), "false");
}
#[test]
fn strings_are_escaped_as_rfc_8259_requires_and_no_more() {
let text =
"q\" b\\ n\n r\r t\t bs\u{8} ff\u{c} nul\0 c1\u{1} us\u{1f} del\u{7f} é 日本 🚀 /";
assert_eq!(
compact(&[String(text), End]).unwrap(),
"\"q\\\" b\\\\ n\\n r\\r t\\t bs\\b ff\\f nul\\u0000 c1\\u0001 us\\u001f del\u{7f} é 日本 🚀 /\""
);
assert_eq!(
compact(&[ObjectStart, Key("k\"\n"), Null, ObjectEnd, End]).unwrap(),
"{\"k\\\"\\n\":null}"
);
}
#[derive(Default)]
struct Fragments(Vec<std::string::String>);
impl TextOut for Fragments {
fn write_str(&mut self, s: &str) -> Result<(), Fail> {
self.0.push(s.to_owned());
Ok(())
}
fn flush(&mut self) -> Result<(), Fail> {
Ok(())
}
}
#[test]
fn strings_escape_exactly_as_transduce_does() {
let mut every_control = std::string::String::new();
for c in 0u32..0x20 {
every_control.push(char::from_u32(c).unwrap_or(' '));
every_control.push('x');
}
for text in [
"",
"plain",
"\"",
"\\",
"\"\\\"\\",
"a\"b\\c\nd",
every_control.as_str(),
"é 日本 🚀 \u{7f} \u{80} \u{2028} \u{ffff}",
"ends with control \u{1}",
"\u{1} starts with control",
] {
let mut want = std::string::String::new();
tabnas_alchemy::shared::write_json_string(text, &mut want);
assert_eq!(compact(&[String(text), End]).unwrap(), want, "{text:?}");
}
}
#[test]
fn strings_are_streamed_in_runs_and_never_copied_whole() {
let mut r = JsonRenderer::new(Fragments::default(), JsonOptions::default());
r.event(String("ab\"cd\n\u{1}ef")).unwrap();
assert_eq!(
r.out.0,
["\"", "ab", "\\\"", "cd", "\\n", "\\u0001", "ef", "\""]
);
let big = "\u{1}".repeat(64 * 1024);
let mut r = JsonRenderer::new(StringOut::new(), JsonOptions::default());
r.event(String(&big)).unwrap();
assert_eq!(r.out.as_str().len(), big.len() * 6 + 2);
assert_eq!(r.scratch.capacity(), 0, "strings do not touch the scratch");
}
#[test]
fn numbers_keep_their_lexeme_or_take_the_shortest_form() {
let events = [
ArrayStart,
num("1.00"),
num("123456789012345678901234567890"),
num("-0"),
num("1E+2"),
value(0.0),
value(1e21),
value(0.1),
value(-2.0),
ArrayEnd,
End,
];
assert_eq!(
compact(&events).unwrap(),
"[1.00,123456789012345678901234567890,-0,1E+2,0,1e21,0.1,-2]"
);
assert_eq!(
compact(&[
ArrayStart,
value(1e300),
value(1e-300),
value(1.5e17),
value(1e20),
ArrayEnd,
End
])
.unwrap(),
"[1e300,1e-300,150000000000000000,100000000000000000000]"
);
}
#[test]
fn a_lexeme_that_is_not_a_json_number_is_invalid_number() {
for bad in ["1.", "01", "NaN", "+1", "0x1"] {
let err = compact(&[ArrayStart, num(bad), ArrayEnd, End]).unwrap_err();
assert_eq!(err.code, Code::InvalidNumber, "{bad:?}");
assert!(err.committed_output);
}
let err = compact(&[num("1."), End]).unwrap_err();
assert!(!err.committed_output);
}
#[test]
fn nan_and_infinity_are_unrepresentable() {
for v in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let err = compact(&[value(v), End]).unwrap_err();
assert_eq!(err.code, Code::TargetValueUnrepresentable);
}
}
#[test]
fn an_overflowed_lexeme_is_unrepresentable_too() {
let overflowed = Number(tabnas_alchemy::shared::Number::with_lexeme(
f64::INFINITY,
"1e999",
));
let mut r = JsonRenderer::new(StringOut::new(), JsonOptions::default());
r.event(ArrayStart).unwrap();
r.event(num("1")).unwrap();
let err = r.event(overflowed).unwrap_err();
assert_eq!(err.code, Code::TargetValueUnrepresentable);
assert!(err.committed_output);
assert_eq!(r.out.as_str(), "[1");
let err = compact(&[overflowed, End]).unwrap_err();
assert_eq!(err.code, Code::TargetValueUnrepresentable);
assert!(!err.committed_output);
}
#[test]
fn a_rejected_number_leaves_no_separator_behind() {
let mut r = JsonRenderer::new(StringOut::new(), JsonOptions::default());
for ev in [ArrayStart, num("1")] {
r.event(ev).unwrap();
}
assert_eq!(r.event(num("01")).unwrap_err().code, Code::InvalidNumber);
assert_eq!(r.out.as_str(), "[1");
assert_eq!(
r.event(value(f64::NAN)).unwrap_err().code,
Code::TargetValueUnrepresentable
);
assert_eq!(r.out.as_str(), "[1");
for ev in [value(2.0), ArrayEnd, End] {
r.event(ev).unwrap();
}
assert_eq!(r.into_inner().as_str(), "[1,2]");
}
fn protocol_error(events: &[JsonEvent<'_>]) -> Fail {
let err = compact(events).unwrap_err();
assert_eq!(err.code, Code::ProtocolOrderError, "{events:?}");
err
}
#[test]
fn a_second_root_is_a_protocol_error() {
protocol_error(&[Null, Null]);
protocol_error(&[ObjectStart, ObjectEnd, ArrayStart]);
protocol_error(&[String("a"), String("b"), End]);
}
#[test]
fn an_end_without_a_complete_root_is_a_protocol_error() {
assert!(!protocol_error(&[End]).committed_output);
protocol_error(&[ArrayStart, End]);
protocol_error(&[ObjectStart, Key("a"), End]);
protocol_error(&[ObjectStart, Key("a"), Null, End]);
}
#[test]
fn a_key_outside_an_object_or_where_a_value_is_due_is_a_protocol_error() {
protocol_error(&[Key("a")]);
protocol_error(&[ArrayStart, Key("a")]);
protocol_error(&[ObjectStart, Key("a"), Key("b")]);
protocol_error(&[Null, Key("a")]);
}
#[test]
fn a_value_where_a_key_is_due_is_a_protocol_error() {
protocol_error(&[ObjectStart, Null]);
protocol_error(&[ObjectStart, ArrayStart]);
protocol_error(&[ObjectStart, Key("a"), Null, String("b")]);
}
#[test]
fn an_unbalanced_or_mismatched_close_is_a_protocol_error() {
protocol_error(&[ObjectEnd]);
protocol_error(&[ArrayEnd]);
protocol_error(&[ArrayStart, ObjectEnd]);
protocol_error(&[ObjectStart, ArrayEnd]);
protocol_error(&[ObjectStart, Key("a"), ObjectEnd]);
protocol_error(&[ArrayStart, ArrayEnd, ArrayEnd]);
}
#[test]
fn anything_after_the_end_is_a_protocol_error() {
for after in [End, Null, Key("a"), ObjectStart, ObjectEnd, ArrayEnd] {
let err = protocol_error(&[Null, End, after]);
assert!(err.committed_output);
}
}
#[test]
fn a_failure_leaves_the_output_a_prefix_of_the_document() {
let mut r = JsonRenderer::new(StringOut::new(), JsonOptions::default());
for ev in [ObjectStart, Key("a"), ArrayStart, Null] {
r.event(ev).unwrap();
}
assert_eq!(r.depth(), 2);
assert_eq!(
r.event(Key("b")).unwrap_err().code,
Code::ProtocolOrderError
);
assert_eq!(r.into_inner().as_str(), "{\"a\":[null");
}
struct NoFlush;
impl std::io::Write for NoFlush {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Err(std::io::Error::other("pipe closed"))
}
}
#[test]
fn a_failed_flush_at_end_leaves_the_renderer_not_done() {
let mut r = JsonRenderer::new(WriteOut::new(NoFlush), JsonOptions::default());
r.event(Null).unwrap();
let err = r.event(End).unwrap_err();
assert_eq!(err.code, Code::OutputFailed);
assert!(!r.is_done());
}
#[test]
fn committed_output_means_bytes_that_reached_the_writer() {
let mut r = JsonRenderer::new(WriteOut::new(Vec::new()), JsonOptions::default());
r.event(ArrayStart).unwrap();
let err = r.event(Key("k")).unwrap_err();
assert_eq!(err.code, Code::ProtocolOrderError);
assert!(!err.committed_output, "the bracket is only buffered");
assert_eq!(r.out.committed(), 0);
let mut r = JsonRenderer::new(
WriteOut::new(Vec::new()).with_budget(0),
JsonOptions::default(),
);
r.event(ArrayStart).unwrap();
let err = r.event(Key("k")).unwrap_err();
assert!(err.committed_output, "the bracket reached the writer");
}
#[test]
fn end_flushes_the_output_and_nothing_else_does() {
let mut r = JsonRenderer::new(WriteOut::new(Vec::new()), JsonOptions::default());
for ev in [ArrayStart, Bool(true), ArrayEnd] {
assert_eq!(r.event(ev).unwrap(), Flow::Continue);
}
assert_eq!(r.out.committed(), 0);
assert!(!r.is_done());
r.event(End).unwrap();
assert!(r.is_done());
assert_eq!(r.out.committed(), 6);
assert_eq!(r.into_inner().into_inner(), b"[true]");
}
}