use structio::json::{Raw, Write as _};
use structio::{
AllowComments, Documents, ErrorCode, Feed, Options, Pretty, PrettyInlineArrays, SkipNull,
Value, from_str, from_str_with, prettify, prettify_with, read_into, read_into_with, to_string,
to_string_with,
};
#[derive(Default, Debug, PartialEq)]
struct Envelope<'a> {
id: u32,
payload: Raw<'a>,
}
structio::json_object!(['a] Envelope<'a> { id, payload });
#[derive(Default, Debug, PartialEq)]
struct Framed<'a> {
before: u32,
body: Raw<'a>,
after: bool,
}
structio::json_object!(['a] Framed<'a> { before, body, after });
#[derive(Clone, Copy)]
struct PrettyComments;
impl Options for PrettyComments {
const PRETTY: bool = true;
const ALLOW_COMMENTS: bool = true;
}
#[derive(Clone, Copy)]
struct WideIndent;
impl Options for WideIndent {
const PRETTY: bool = true;
const INDENT: usize = 4;
}
fn carrying(id: u32, span: &str) -> Envelope<'_> {
Envelope {
id,
payload: Raw::new(span).unwrap(),
}
}
#[test]
fn a_float_keeps_the_spelling_it_arrived_with() {
let text = r#"{"id":1,"payload":{"ratio":1.50,"exp":1E2,"neg":-0.0}}"#;
let envelope: Envelope = from_str(text).unwrap();
assert_eq!(
envelope.payload.as_str(),
r#"{"ratio":1.50,"exp":1E2,"neg":-0.0}"#
);
assert_eq!(to_string(&envelope), text);
}
#[test]
fn an_integer_wider_than_any_type_here_keeps_every_digit() {
let text = r#"{"id":1,"payload":12345678901234567890123}"#;
let envelope: Envelope = from_str(text).unwrap();
assert_eq!(envelope.payload.as_str(), "12345678901234567890123");
assert_eq!(to_string(&envelope), text);
}
#[test]
fn an_objects_keys_stay_in_the_order_they_arrived_in() {
let text = r#"{"id":1,"payload":{"z":1,"m":2,"a":3,"B":4}}"#;
let envelope: Envelope = from_str(text).unwrap();
assert_eq!(envelope.payload.as_str(), r#"{"z":1,"m":2,"a":3,"B":4}"#);
assert_eq!(to_string(&envelope), text);
}
#[test]
fn a_strings_escapes_are_forwarded_rather_than_decoded() {
let text = r#"{"id":1,"payload":["\u0041","\/","a\tb","\u00e9"]}"#;
let envelope: Envelope = from_str(text).unwrap();
assert_eq!(
envelope.payload.as_str(),
r#"["\u0041","\/","a\tb","\u00e9"]"#
);
assert!(!envelope.payload.as_str().contains('A'));
assert_eq!(to_string(&envelope), text);
}
#[test]
fn interior_whitespace_survives_the_compact_path() {
let text = "{\"id\":1,\"payload\":{ \"a\" : [1, 2],\n \"b\" : {} }}";
let envelope: Envelope = from_str(text).unwrap();
assert_eq!(
envelope.payload.as_str(),
"{ \"a\" : [1, 2],\n \"b\" : {} }"
);
assert_eq!(to_string(&envelope), text);
}
#[test]
fn raw_forwards_the_document_that_value_reshapes() {
let text = r#"{"z":1.50,"a":12345678901234567890123,"s":"\u0041","w":{ "k" : [] }}"#;
let raw = Raw::new(text).unwrap();
assert_eq!(raw.as_str(), text);
assert_eq!(to_string(&raw), text);
let value = Value::from_json(text).unwrap();
assert_eq!(
value.to_string(),
r#"{"z":1.5,"a":1.2345678901234568E22,"s":"A","w":{"k":[]}}"#
);
assert_ne!(value.to_string(), to_string(&raw));
}
#[test]
fn the_default_policy_borrows_the_span_out_of_the_document() {
let text = r#"{"id":1,"payload":{"a":[1,2,3]}}"#;
let envelope: Envelope = from_str(text).unwrap();
assert!(
text.as_bytes()
.as_ptr_range()
.contains(&envelope.payload.as_str().as_ptr())
);
}
#[test]
fn a_pretty_document_with_a_raw_member_matches_prettify() {
let value = Framed {
before: 1,
body: Raw::new(r#"{"b":[1,2],"a":{},"c":"x"}"#).unwrap(),
after: true,
};
let compact = to_string(&value);
assert_eq!(
to_string_with::<Pretty, _>(&value),
prettify(&compact).unwrap()
);
assert_eq!(
to_string_with::<Pretty, _>(&value),
"{\n \"before\": 1,\n \"body\": {\n \"b\": [\n 1,\n 2\n ],\n \
\"a\": {},\n \"c\": \"x\"\n },\n \"after\": true\n}"
);
assert_eq!(
structio::minify(&to_string_with::<Pretty, _>(&value)).unwrap(),
compact
);
}
#[test]
fn a_forwarded_value_follows_the_policys_layout() {
let value = Framed {
before: 1,
body: Raw::new(r#"{"b":[1,2],"a":{}}"#).unwrap(),
after: true,
};
let compact = to_string(&value);
assert_eq!(
to_string_with::<PrettyInlineArrays, _>(&value),
prettify_with::<PrettyInlineArrays>(&compact).unwrap()
);
assert!(to_string_with::<PrettyInlineArrays, _>(&value).contains("\"b\": [1, 2]"));
assert_eq!(
to_string_with::<WideIndent, _>(&value),
prettify_with::<WideIndent>(&compact).unwrap()
);
assert!(to_string_with::<WideIndent, _>(&value).contains("\n 1,\n"));
}
#[test]
fn a_nested_raw_indents_to_its_own_depth() {
let values = vec![
carrying(1, r#"{"a":[1,{"b":2}]}"#),
carrying(2, r#"[[1],[]]"#),
];
let compact = to_string(&values);
assert_eq!(
to_string_with::<Pretty, _>(&values),
prettify(&compact).unwrap()
);
assert!(to_string_with::<Pretty, _>(&values).contains("\n \"b\": 2\n"));
}
#[test]
fn every_kind_of_value_lays_out_where_it_stands() {
for span in [
"{}",
"[]",
"0",
"1.50",
"\"s\"",
"null",
"true",
r#"{"a":{}}"#,
r#"[[],{}]"#,
] {
let value = carrying(1, span);
assert_eq!(
to_string_with::<Pretty, _>(&value),
prettify(&to_string(&value)).unwrap(),
"at {span}"
);
}
}
#[test]
fn laying_a_span_out_leaves_its_tokens_alone() {
let value = carrying(
1,
"{\"z\":12345678901234567890123,\"a\":[1.50,\"\\u0041\"]}",
);
let pretty = to_string_with::<Pretty, _>(&value);
assert!(
pretty.contains("\"z\": 12345678901234567890123"),
"{pretty}"
);
assert!(pretty.contains("1.50"), "{pretty}");
assert!(pretty.contains("\"\\u0041\""), "{pretty}");
assert!(pretty.find("\"z\"") < pretty.find("\"a\""));
}
#[test]
fn an_unchecked_span_that_is_not_one_value_is_written_verbatim() {
let value = Envelope {
id: 1,
payload: Raw::new_unchecked("1 2"),
};
assert_eq!(to_string(&value), r#"{"id":1,"payload":1 2}"#);
assert_eq!(
to_string_with::<Pretty, _>(&value),
"{\n \"id\": 1,\n \"payload\": 1 2\n}"
);
}
#[test]
fn a_defaulted_raw_is_the_literal_null() {
assert_eq!(Raw::default().as_str(), "null");
assert_eq!(to_string(&Raw::default()), "null");
assert!(!Raw::default().as_str().is_empty());
let value = Envelope {
id: 1,
..Default::default()
};
let text = to_string(&value);
assert_eq!(text, r#"{"id":1,"payload":null}"#);
let back: Envelope = from_str(&text).unwrap();
assert_eq!(back.payload.as_str(), "null");
let framed = Framed {
before: 1,
after: true,
..Default::default()
};
let framed_text = to_string(&framed);
assert_eq!(framed_text, r#"{"before":1,"body":null,"after":true}"#);
assert_eq!(
from_str::<Framed>(&framed_text).unwrap().body.as_str(),
"null"
);
}
#[test]
fn a_forwarded_null_is_a_forwarded_absence() {
assert!(Raw::new("null").unwrap().is_null());
assert!(Raw::default().is_null());
assert_eq!(
to_string_with::<SkipNull, _>(&carrying(1, "null")),
r#"{"id":1}"#
);
assert_eq!(
to_string_with::<SkipNull, _>(&Envelope {
id: 1,
..Default::default()
}),
r#"{"id":1}"#
);
}
#[test]
fn a_value_that_is_merely_empty_is_not_absent() {
for span in ["0", "false", r#""""#, "[]", "{}", "0.0"] {
let value = carrying(1, span);
assert!(!value.payload.is_null(), "{span} answered absent");
assert_eq!(
to_string_with::<SkipNull, _>(&value),
format!(r#"{{"id":1,"payload":{span}}}"#),
"at {span}"
);
}
}
#[test]
fn only_a_span_that_is_exactly_null_is_absent() {
let unchecked = Raw::new_unchecked(" null ");
assert!(!unchecked.is_null());
assert_eq!(
to_string_with::<SkipNull, _>(&Envelope {
id: 1,
payload: unchecked
}),
r#"{"id":1,"payload": null }"#
);
assert!(Raw::new(" null ").unwrap().is_null());
assert_eq!(
to_string_with::<SkipNull, _>(&carrying(1, " null ")),
r#"{"id":1}"#
);
}
#[test]
fn comments_are_stripped_out_of_a_forwarded_span() {
let text = "{\"id\":1,\"payload\":{ // which one\n\
\"a\": /* the count */ [1, 2 /* and a tail */],\n\
\"b\": { \"c\": 3 } // trailing\n\
}}";
let envelope: Envelope = from_str_with::<AllowComments, _>(text).unwrap();
let span = envelope.payload.as_str();
assert!(!span.contains("//") && !span.contains("/*"), "{span}");
assert_eq!(span, r#"{"a":[1,2],"b":{"c":3}}"#);
let out = to_string(&envelope);
assert_eq!(out, r#"{"id":1,"payload":{"a":[1,2],"b":{"c":3}}}"#);
assert_eq!(
from_str::<Envelope>(&out).unwrap().payload.as_str(),
r#"{"a":[1,2],"b":{"c":3}}"#
);
}
#[test]
fn a_comment_between_a_key_and_its_value_goes_too() {
let text = "{\"id\":1,\"payload\":{\"a\" /* key */ : /* value */ 1}}";
let envelope: Envelope = from_str_with::<AllowComments, _>(text).unwrap();
assert_eq!(envelope.payload.as_str(), r#"{"a":1}"#);
}
#[test]
fn a_comment_does_not_reach_inside_a_string() {
let text = "{\"id\":1,\"payload\":[\"http://x/*y*/\", // gone\n \"a\\\"//b\"]}";
let envelope: Envelope = from_str_with::<AllowComments, _>(text).unwrap();
assert_eq!(
envelope.payload.as_str(),
"[\"http://x/*y*/\",\"a\\\"//b\"]"
);
}
#[test]
fn a_span_read_under_comments_is_minified_only_if_it_holds_a_comment() {
let text = "{\"id\":1,\"payload\":{ \"a\" : [1, 2] }}";
let strict: Envelope = from_str(text).unwrap();
assert_eq!(strict.payload.as_str(), "{ \"a\" : [1, 2] }");
let lenient: Envelope = from_str_with::<AllowComments, _>(text).unwrap();
assert_eq!(lenient.payload.as_str(), "{ \"a\" : [1, 2] }");
assert_eq!(lenient.payload.as_str(), strict.payload.as_str());
assert!(
text.as_bytes()
.as_ptr_range()
.contains(&lenient.payload.as_str().as_ptr())
);
let commented = "{\"id\":1,\"payload\":{ \"a\" : [1, /* two */ 2] }}";
let stripped: Envelope = from_str_with::<AllowComments, _>(commented).unwrap();
assert_eq!(stripped.payload.as_str(), r#"{"a":[1,2]}"#);
assert_eq!(
structio::minify(strict.payload.as_str()).unwrap(),
stripped.payload.as_str()
);
}
#[test]
fn a_slash_inside_a_string_still_costs_the_span_its_spacing() {
let text = "{\"id\":1,\"payload\":{ \"url\" : \"http://x\" }}";
let value: Envelope = from_str_with::<AllowComments, _>(text).unwrap();
assert_eq!(value.payload.as_str(), r#"{"url":"http://x"}"#);
}
#[test]
fn a_stripped_span_is_laid_out_like_any_other() {
let text = "{\"before\":1,\"body\":{ /* here */ \"a\": [1] },\"after\":true}";
let value: Framed = from_str_with::<PrettyComments, _>(text).unwrap();
assert_eq!(
to_string_with::<PrettyComments, _>(&value),
prettify(&to_string(&value)).unwrap()
);
assert_eq!(
to_string_with::<PrettyComments, _>(&value),
"{\n \"before\": 1,\n \"body\": {\n \"a\": [\n 1\n ]\n },\n \
\"after\": true\n}"
);
}
#[test]
fn new_takes_one_value_of_every_kind() {
for s in [
"null",
"true",
"false",
"0",
"-1.5e3",
r#""s""#,
r#""\u0041""#,
"[]",
"{}",
r#"[1,"two",null]"#,
r#"{"a":[1,{"b":null}]}"#,
] {
assert_eq!(Raw::new(s).unwrap().as_str(), s, "at {s}");
}
}
#[test]
fn new_refuses_what_is_not_one_complete_value() {
assert_eq!(
Raw::new(r#"{"a":}"#).unwrap_err().code,
ErrorCode::UnexpectedCharacter
);
assert_eq!(
Raw::new("1 2").unwrap_err().code,
ErrorCode::TrailingContent
);
assert_eq!(
Raw::new(r#"{"a":1} junk"#).unwrap_err().code,
ErrorCode::TrailingContent
);
for s in ["", " ", "\n\t ", "[1,", r#"{"a""#, "tru"] {
assert!(Raw::new(s).is_err(), "took {s:?}");
}
}
#[test]
fn new_trims_around_the_value_and_nothing_inside_it() {
assert_eq!(Raw::new(" \n\t1 \n").unwrap().as_str(), "1");
assert_eq!(
Raw::new(" { \"a\" : [1, 2] } ").unwrap().as_str(),
"{ \"a\" : [1, 2] }"
);
assert_eq!(Raw::new(" 1 ").unwrap(), Raw::new("1").unwrap());
}
#[test]
fn new_steps_over_a_number_by_its_alphabet() {
assert_eq!(Raw::new("01").unwrap().as_str(), "01");
assert_eq!(Raw::new("1.2.3").unwrap().as_str(), "1.2.3");
assert_eq!(
Raw::new("12345678901234567890123").unwrap().as_str(),
"12345678901234567890123"
);
}
#[test]
fn new_refuses_an_escape_the_string_reader_refuses() {
for (s, code) in [
(r#""\q""#, ErrorCode::InvalidEscape),
(r#""\uZZZZ""#, ErrorCode::InvalidEscape),
(r#""\ud800""#, ErrorCode::InvalidSurrogate),
(r#""\udc00""#, ErrorCode::InvalidSurrogate),
] {
let refused = Raw::new(s).unwrap_err();
assert_eq!(refused.code, code, "at {s}");
assert_eq!(Some(refused), from_str::<Value>(s).err(), "at {s}");
}
for s in [
r#"["ok","\q"]"#,
r#"{"\q":1}"#,
r#"{"a":{"b":"x\ud800y"}}"#,
r#"["\n\q"]"#,
r#""\ud800\u0041""#,
r#""\ud800\udbff""#,
r#""\u00G0""#,
r#""\u12""#,
r#""\u12"#,
r#""\"#,
" [1, \"\\x\"] ",
] {
let refused = Raw::new(s).unwrap_err();
assert_eq!(Some(refused), from_str::<Value>(s).err(), "at {s}");
assert_eq!(
Raw::from_string(s.to_owned()).unwrap_err(),
refused,
"at {s}"
);
}
}
#[test]
fn a_raw_field_refuses_an_escape_the_string_reader_refuses() {
#[derive(Default, Debug)]
struct Forwarded<'a> {
r: Raw<'a>,
n: u8,
}
structio::json_object!(['a] Forwarded<'a> { r, n });
#[derive(Default, Debug)]
struct Decoded {
r: String,
n: u8,
}
structio::json_object!(Decoded { r, n });
for (doc, code) in [
(r#"{"r":"\q","n":1}"#, ErrorCode::InvalidEscape),
(r#"{"r":"\ud800","n":1}"#, ErrorCode::InvalidSurrogate),
] {
let refused = from_str::<Forwarded>(doc).unwrap_err();
assert_eq!(refused.code, code, "at {doc}");
assert_eq!(Some(refused), from_str::<Decoded>(doc).err(), "at {doc}");
assert_eq!(
from_str_with::<AllowComments, Forwarded>(doc)
.unwrap_err()
.code,
code,
"at {doc}"
);
}
}
#[test]
fn a_valid_escape_is_accepted_and_kept_as_it_was_spelled() {
for s in [
r#""a\nb""#,
r#""caf\u00e9""#,
r#""\ud83d\ude00""#,
r#""a\/b""#,
r#""\"\\\b\f\r\t""#,
r#"{"\u00e9\n":["\ud83d\ude00","\/"]}"#,
] {
assert_eq!(Raw::new(s).unwrap().as_str(), s, "at {s}");
let doc = format!(r#"{{"id":1,"payload":{s}}}"#);
let envelope: Envelope = from_str(&doc).unwrap();
assert_eq!(envelope.payload.as_str(), s, "at {s}");
assert_eq!(to_string(&envelope), doc);
}
}
#[test]
fn new_unchecked_stores_the_span_as_it_was_given() {
assert_eq!(Raw::new_unchecked(" 1 ").as_str(), " 1 ");
assert_eq!(Raw::new_unchecked("").as_str(), "");
assert_eq!(
to_string(&Envelope {
id: 1,
payload: Raw::new_unchecked(r#"{"a":1}"#)
}),
r#"{"id":1,"payload":{"a":1}}"#
);
}
#[test]
fn from_string_checks_and_trims_exactly_as_new_does() {
for s in [
"null",
"-1.5e3",
r#""\u0041""#,
"[]",
r#"{"a":[1,{"b":null}]}"#,
" 1 ",
"\n[1, 2]\t",
" \"h\u{e9}llo\u{2192}\" ",
r#"["\ud83d\ude00"]"#,
"\t[\"\u{1f600}\", 1]\n",
"{\"\u{e9}\":\"\u{2192}\"}",
] {
assert_eq!(
Raw::from_string(s.to_owned()).unwrap(),
Raw::new(s).unwrap(),
"at {s}"
);
}
for s in [r#"{"a":}"#, "1 2", "", " ", "\u{e9}"] {
assert_eq!(
Raw::from_string(s.to_owned()).unwrap_err(),
Raw::new(s).unwrap_err(),
"at {s}"
);
}
}
#[test]
fn from_string_takes_the_buffer_rather_than_copying_it() {
let text = String::from(" {\"a\":[1,2]} ");
let before = text.as_ptr();
let raw = Raw::from_string(text).unwrap();
assert_eq!(raw.as_str(), r#"{"a":[1,2]}"#);
assert_eq!(raw.as_str().as_ptr(), before);
}
#[test]
fn from_string_unchecked_stores_the_buffer_as_it_was_given() {
assert_eq!(
Raw::from_string_unchecked(String::from(" 1 2 ")).as_str(),
" 1 2 "
);
let empty = Envelope {
id: 1,
payload: Raw::from_string_unchecked(String::new()),
};
assert_eq!(to_string(&empty), r#"{"id":1,"payload":}"#);
}
#[test]
fn an_owned_raw_outlives_the_document_it_came_from() {
let owned: Raw<'static> = {
let text = String::from(r#"{"id":7,"payload":{"b":1.50,"a":[1,2]}}"#);
let envelope: Envelope = from_str(&text).unwrap();
envelope.payload.into_owned()
};
assert_eq!(owned.as_str(), r#"{"b":1.50,"a":[1,2]}"#);
assert_eq!(to_string(&owned), r#"{"b":1.50,"a":[1,2]}"#);
let stripped: Raw<'static> = {
let text = String::from("{\"id\":7,\"payload\":[1 /* two */, 2]}");
let envelope: Envelope = from_str_with::<AllowComments, _>(&text).unwrap();
envelope.payload.into_owned()
};
assert_eq!(stripped.as_str(), "[1,2]");
}
#[test]
fn a_raw_built_from_a_string_borrows_nothing() {
let owned: Raw<'static> = {
let text = format!(r#"{{"id":{},"tag":"{}"}}"#, 7, "a");
Raw::from_string(text).unwrap()
};
assert_eq!(owned.as_str(), r#"{"id":7,"tag":"a"}"#);
assert_eq!(to_string(&owned), r#"{"id":7,"tag":"a"}"#);
}
#[test]
fn reading_into_an_existing_raw_replaces_what_it_held() {
let mut envelope = Envelope::default();
read_into(&mut envelope, r#"{"id":1,"payload":[1,2,3]}"#).unwrap();
assert_eq!(envelope.payload.as_str(), "[1,2,3]");
read_into(&mut envelope, r#"{"id":2,"payload":{"a":1.50}}"#).unwrap();
assert_eq!(envelope.id, 2);
assert_eq!(envelope.payload.as_str(), r#"{"a":1.50}"#);
let fresh: Envelope = from_str(r#"{"id":3,"payload":null}"#).unwrap();
assert_eq!(fresh.payload.as_str(), "null");
}
#[test]
fn an_owning_read_refills_the_destinations_allocation() {
let mut envelope = Envelope::default();
read_into_with::<AllowComments, _>(
&mut envelope,
r#"{"id":1,"payload":[1,2,3,4,5,6,7,8,9,10,11,12 /* twelve */]}"#,
)
.unwrap();
assert_eq!(envelope.payload.as_str(), "[1,2,3,4,5,6,7,8,9,10,11,12]");
let first = envelope.payload.as_str().as_ptr();
read_into_with::<AllowComments, _>(&mut envelope, r#"{"id":2,"payload":[9 /* nine */]}"#)
.unwrap();
assert_eq!(envelope.payload.as_str(), "[9]");
assert_eq!(
envelope.payload.as_str().as_ptr(),
first,
"the span moved, so the destination's allocation was not reused"
);
}
#[test]
fn a_raw_member_survives_a_streaming_read() {
let text = "{\"id\":1,\"payload\":{\"b\":1.50,\"a\":[1,2]}}\n{\"id\":2,\"payload\":[{}]}\n";
let mut docs = Documents::lines(text.as_bytes());
let first: Envelope = docs.next_value().unwrap().unwrap();
assert_eq!(first.id, 1);
assert_eq!(first.payload.as_str(), r#"{"b":1.50,"a":[1,2]}"#);
let second: Envelope = docs.next_value().unwrap().unwrap();
assert_eq!(second.id, 2);
assert_eq!(second.payload.as_str(), "[{}]");
assert!(docs.next_value::<Envelope>().is_none());
}
#[test]
fn a_streamed_span_survives_the_window_moving_under_it() {
let n = if cfg!(miri) { 20 } else { 200 };
let text: String = (0..n)
.map(|i| format!("{{\"id\":{i},\"payload\":{{\"n\":1.50,\"i\":[{i}]}}}}\n"))
.collect();
let mut docs = Documents::lines(text.as_bytes()).read_size(3);
let mut seen = 0;
while let Some(result) = docs.next_value::<Envelope>() {
let envelope = result.unwrap();
assert_eq!(
envelope.payload.as_str(),
format!(r#"{{"n":1.50,"i":[{seen}]}}"#)
);
seen += 1;
}
assert_eq!(seen, n);
}
#[test]
fn a_pushed_raw_borrows_until_the_next_call() {
let mut feed = Feed::values();
feed.push(b"{\"id\":1,\"payload\":{\"b\":1.50,\"a\":[1,2]}}");
feed.end();
let value: Envelope = feed.next_value().unwrap().unwrap();
assert_eq!(value.payload.as_str(), r#"{"b":1.50,"a":[1,2]}"#);
}
#[test]
fn a_span_written_to_a_sink_matches_the_in_memory_writer() {
let value = carrying(1, r#"{"b":1.50,"a":[1,2],"s":"x y"}"#);
let want = to_string(&value);
for cap in 1..=want.len() + 4 {
let mut got = Vec::new();
structio::to_writer_buffered(&value, &mut got, cap).unwrap();
assert_eq!(
String::from_utf8(got).unwrap(),
want,
"buffer size {cap} changed the output"
);
}
}
#[test]
fn a_raw_can_be_a_whole_document() {
let text = r#"{"b":1.50,"a":[1,2]}"#;
let raw: Raw = from_str(text).unwrap();
assert_eq!(raw.as_str(), text);
assert_eq!(to_string(&raw), text);
let padded: Raw = from_str(" [1, 2] ").unwrap();
assert_eq!(padded.as_str(), "[1, 2]");
let scalar: Raw = from_str("1.50").unwrap();
assert_eq!(scalar.as_str(), "1.50");
}
#[test]
fn a_vec_of_raw_keeps_each_element_as_it_was_written() {
let text = r#"[1.50,{"z":1,"a":2},"\u0041",[],{}]"#;
let items: Vec<Raw> = from_str(text).unwrap();
assert_eq!(
items.iter().map(Raw::as_str).collect::<Vec<_>>(),
["1.50", r#"{"z":1,"a":2}"#, r#""\u0041""#, "[]", "{}"]
);
assert_eq!(to_string(&items), text);
assert_eq!(
to_string_with::<Pretty, _>(&items),
prettify(text).unwrap(),
"elements laid out at their own depth"
);
}
#[test]
fn a_deeply_nested_value_is_stepped_over_whole() {
let depth = 100;
let span = format!("{}{}{}", "[".repeat(depth), "1.50", "]".repeat(depth));
let text = format!(r#"{{"id":1,"payload":{span}}}"#);
let envelope: Envelope = from_str(&text).unwrap();
assert_eq!(envelope.payload.as_str(), span);
assert_eq!(to_string(&envelope), text);
assert_eq!(
to_string_with::<Pretty, _>(&envelope),
prettify(&text).unwrap()
);
}
#[test]
fn a_raw_holding_null_is_a_value_like_any_other() {
let text = r#"{"id":1,"payload":null}"#;
let envelope: Envelope = from_str(text).unwrap();
assert_eq!(envelope.payload.as_str(), "null");
assert!(envelope.payload.is_null());
assert_eq!(to_string(&envelope), text);
assert_eq!(
to_string_with::<Pretty, _>(&envelope),
"{\n \"id\": 1,\n \"payload\": null\n}"
);
let nested = carrying(1, r#"{"a":null}"#);
assert!(!nested.payload.is_null());
}
#[test]
fn display_is_the_span_and_nothing_else() {
for s in [
"null",
"1.50",
r#""a\nb""#,
r#""\u0041""#,
r#"{"b":1,"a":2}"#,
"[1, 2]",
] {
let raw = Raw::new(s).unwrap();
assert_eq!(raw.to_string(), s, "at {s}");
assert_eq!(format!("{raw:#}"), s, "at {s}");
}
}
#[test]
fn display_is_what_a_compact_write_emits() {
let raw = Raw::new(r#"{"b":1.50,"a":[1,2]}"#).unwrap();
assert_eq!(raw.to_string(), to_string(&raw));
let laid_out = to_string_with::<Pretty, _>(&raw);
assert_ne!(raw.to_string(), laid_out);
assert_eq!(format!("{raw:#}"), raw.to_string());
let unchecked = Raw::new_unchecked("1 2");
assert_eq!(unchecked.to_string(), "1 2");
assert_eq!(unchecked.to_string(), to_string(&unchecked));
assert_eq!(Raw::default().to_string(), "null");
}