blob: 15da1b74aacd83d4e66c8bf93dd2129ede6e78c5 [file] [log] [blame]
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#![deny(missing_docs)]
use {
crate::{test_error, Json5Format},
json5format::*,
maplit::hashmap,
maplit::hashset,
};
struct FormatTest<'a> {
options: Option<FormatOptions>,
input: &'a str,
error: Option<&'a str>,
expected: &'a str,
}
impl<'a> Default for FormatTest<'a> {
fn default() -> Self {
FormatTest { options: None, input: "", error: None, expected: "" }
}
}
fn try_test_format(test: FormatTest<'_>) -> Result<(), Error> {
let result = match ParsedDocument::from_str(test.input, None) {
Ok(parsed_document) => {
let format = match test.options {
Some(options) => Json5Format::with_options(options)?,
None => Json5Format::new()?,
};
format.to_utf8(&parsed_document)
}
Err(actual_error) => Err(actual_error),
};
match result {
Ok(bytes) => {
let actual_formatted_document = std::str::from_utf8(&bytes).unwrap();
match test.error {
Some(expected_error) => {
println!("Unexpected formatted result:");
println!("===========================");
println!("{}", actual_formatted_document);
println!("===========================");
println!("Expected error: {}", expected_error);
Err(test_error!(format!(
"Unexpected 'Ok()' result.\n expected: '{}'",
expected_error
)))
}
None => {
if actual_formatted_document == test.expected {
Ok(())
} else {
println!("expected:");
println!("========");
println!("{}", test.expected);
println!("========");
println!("actual:");
println!("======");
println!("{}", actual_formatted_document);
println!("======");
Err(test_error!(format!(
"Actual formatted document did not match expected."
)))
}
}
}
}
Err(actual_error) => match test.error {
Some(expected_error) => {
let actual_error = format!("{}", actual_error);
if expected_error == actual_error {
Ok(())
} else {
println!("expected: {}", expected_error);
println!(" actual: {}", actual_error);
Err(test_error!("Actual error did not match expected error."))
}
}
None => Err(actual_error),
},
}
}
fn test_format(test: FormatTest<'_>) -> Result<(), Error> {
try_test_format(test).map_err(|e| {
println!("{}", e);
e
})
}
#[test]
fn test_format_simple_objects() {
test_format(FormatTest {
input: r##"{ "program": {} }"##,
expected: r##"{
program: {},
}
"##,
..Default::default()
})
.unwrap()
}
#[test]
fn test_last_scope_is_array() {
test_format(FormatTest {
input: r##"{
program: {},
expose: [
{
}
/* and this */
]
} // line comment on primary object
// line comment at the end of the document
// second line comment
/* block comment at the end of the document
* block comment continues.
* end of block comment at end of doc */
"##,
expected: r##"{
program: {},
expose: [
{},
/* and this */
],
} // line comment on primary object
// line comment at the end of the document
// second line comment
/* block comment at the end of the document
* block comment continues.
* end of block comment at end of doc */
"##,
..Default::default()
})
.unwrap()
}
#[test]
fn test_comment_block() {
test_format(FormatTest {
input: r##"// Copyright or other header
// goes here
{
program: {},
expose: [
/*
what happens
with this
*/
/*
what happens
with this
*/
/*
what happens
with this
*/
/*
what happens
with this
*/
/* what happens
with this
*/
/* what happens
with this
and this */
{
}
/* and this */
]
}
// and end of
// the doc comment"##,
expected: r##"// Copyright or other header
// goes here
{
program: {},
expose: [
/*
what happens
with this
*/
/*
what happens
with this
*/
/*
what happens
with this
*/
/*
what happens
with this
*/
/* what happens
with this
*/
/* what happens
with this
and this */
{},
/* and this */
],
}
// and end of
// the doc comment
"##,
..Default::default()
})
.unwrap()
}
#[test]
fn test_end_of_line_comments() {
test_format(FormatTest {
input: r##"
{ // not an end-of-line comment
// because it's not an end of a value
program: {}, // end of line comment
expose: [
"value1",// eol comment
// is here
"value2", // eol comment 2
//
//
// is also here
"value3", // this end of line comment is followed by a comment that is not vertically aligned
// so we assume this line comment is not part of the previous end-of-line comment
/*item4*/"value4", /*item5*/"value5", /*item6*/"value6" // eol comment without comma
// here also
],
some_object: {
prop1: // eol comment is not here
"value1",// eol comment
// is here
prop2: "value2", // eol comment 2
//
//
// is also here
prop3: "value3", // this end of line comment is followed by a comment that is not vertically aligned
// so we assume this line comment is not part of the previous end-of-line comment
prop4: "value4", prop5: "value5", prop6: "value6" // eol comment without comma
// here also
},
children:
[ // line comment after open brace for "children"
],
use: // line comment for "use"
// and "use" line comment's second line
[
],
offer: [
], // end of line comment for "offer"
collections: [
], // not just one line but this
// is a multi-line end of line comment for "collections"
//
// - and should have indentation preserved
// - with multiple bullet points
other: [
], /// This doc comment style should still work like any other line
/// or end-of-line comment
///
/// - and should also have indentation preserved
/// - also with multiple bullet points
}
// not an end-of-line comment because there is a newline; and end of
// the doc comment was another break,
// and the document ends without the required newline"##,
expected: r##"{
// not an end-of-line comment
// because it's not an end of a value
program: {}, // end of line comment
expose: [
"value1", // eol comment
// is here
"value2", // eol comment 2
//
//
// is also here
"value3", // this end of line comment is followed by a comment that is not vertically aligned
// so we assume this line comment is not part of the previous end-of-line comment
/*item4*/
"value4",
/*item5*/
"value5",
/*item6*/
"value6", // eol comment without comma
// here also
],
some_object: {
// eol comment is not here
prop1: "value1", // eol comment
// is here
prop2: "value2", // eol comment 2
//
//
// is also here
prop3: "value3", // this end of line comment is followed by a comment that is not vertically aligned
// so we assume this line comment is not part of the previous end-of-line comment
prop4: "value4",
prop5: "value5",
prop6: "value6", // eol comment without comma
// here also
},
children: [
// line comment after open brace for "children"
],
// line comment for "use"
// and "use" line comment's second line
use: [],
offer: [], // end of line comment for "offer"
collections: [], // not just one line but this
// is a multi-line end of line comment for "collections"
//
// - and should have indentation preserved
// - with multiple bullet points
other: [], /// This doc comment style should still work like any other line
/// or end-of-line comment
///
/// - and should also have indentation preserved
/// - also with multiple bullet points
}
// not an end-of-line comment because there is a newline; and end of
// the doc comment was another break,
// and the document ends without the required newline
"##,
..Default::default()
})
.unwrap()
}
#[test]
fn test_breaks_between_line_comments() {
test_format(FormatTest {
input: r##"// Copyright or other header
// goes here
// Another comment block
// separate from the copyright block.
{
/// doc comment
/// is here
program: {},
/// another doc comment
/* and block comment */
/// and doc comment
/// and multiple blank lines were above this line comment,
/// but replaced by one.
/// more than
/// two contiguous
/// line comments
/// are
/// here
///
/// including empty line comments
expose: [ // inside array so not end of line comment
// comment block
// is here
//comment block
// is here 2
//comment block
// is here 3
// and one more
/* and a block comment
*/
],
children:
[ // line comment after open brace for "children"
],
use: // line comment for "use"
[
],
collections: [
], // not just one line but this
// is a multi-line end of line comment for "collections"
//
// - and should have indentation preserved
offer: [
], // end of line comment for "offer"
}
// and end of
// the doc comment
// was another break"##,
expected: r##"// Copyright or other header
// goes here
// Another comment block
// separate from the copyright block.
{
/// doc comment
/// is here
program: {},
/// another doc comment
/* and block comment */
/// and doc comment
/// and multiple blank lines were above this line comment,
/// but replaced by one.
/// more than
/// two contiguous
/// line comments
/// are
/// here
///
/// including empty line comments
expose: [
// inside array so not end of line comment
// comment block
// is here
//comment block
// is here 2
//comment block
// is here 3
// and one more
/* and a block comment
*/
],
children: [
// line comment after open brace for "children"
],
// line comment for "use"
use: [],
collections: [], // not just one line but this
// is a multi-line end of line comment for "collections"
//
// - and should have indentation preserved
offer: [], // end of line comment for "offer"
}
// and end of
// the doc comment
// was another break
"##,
..Default::default()
})
.unwrap()
}
#[test]
fn test_format_sort_and_align_block_comment() {
test_format(FormatTest {
options: Some(FormatOptions { sort_array_items: true, ..Default::default() }),
input: r##"{
"program": {
"binary": "bin/session_manager"
},
"use": [
{ "runner": "elf" },
{
// The Realm service allows session_manager to start components.
"protocol": "/svc/fuchsia.sys2.Realm",
"from": "framework",
},
{
/* indented block
comment:
* is here
* ok
*/
"protocol": [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
"from": "realm",
},
],
}
"##,
expected: r##"{
program: {
binary: "bin/session_manager",
},
use: [
{
runner: "elf",
},
{
// The Realm service allows session_manager to start components.
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework",
},
{
/* indented block
comment:
* is here
* ok
*/
protocol: [
"/svc/fuchsia.cobalt.LoggerFactory",
"/svc/fuchsia.logger.LogSink",
],
from: "realm",
},
],
}
"##,
..Default::default()
})
.unwrap()
}
#[test]
fn test_property_name_formatting() {
test_format(FormatTest {
input: r##"{
unquotedName: 1,
$_is_ok_$: 2,
$10million: 3,
_10_9_8___: 4,
"remove_quotes_$_123": 5,
"keep quotes": 6,
"multi \
line \
is \
valid": 7,
"3.14159": "pi",
"with 'quotes'": 9,
'with "quotes"': 10,
}
"##,
expected: r##"{
unquotedName: 1,
$_is_ok_$: 2,
$10million: 3,
_10_9_8___: 4,
remove_quotes_$_123: 5,
"keep quotes": 6,
"multi \
line \
is \
valid": 7,
"3.14159": "pi",
"with 'quotes'": 9,
'with "quotes"': 10,
}
"##,
..Default::default()
})
.unwrap()
}
#[test]
fn test_parse_error_missing_property_value() {
test_format(FormatTest {
input: r##"{
property: {
sub_property_1: "value",
sub_property_2: ,
}
}
"##,
error: Some(
"Parse error: 4:25: Property 'sub_property_2' must have a value before the next \
comma-separated property:
sub_property_2: ,
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_missing_property_value_when_closing_object() {
test_format(FormatTest {
input: r##"{
property: {
sub_property_1: "value",
sub_property_2:
}
}
"##,
error: Some(
"Parse error: 5:5: Property 'sub_property_2' must have a value before closing an \
object:
}
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_incomplete_property() {
test_format(FormatTest {
input: r##"{
property: {
sub_property_1: "value1"
sub_property_2: "value2",
}
}
"##,
error: Some(
r#"Parse error: 4:9: Properties must be separated by a comma:
sub_property_2: "value2",
^~~~~~~~~~~~~~~"#,
),
..Default::default()
})
.unwrap();
test_format(FormatTest {
input: r##"{
property: {
sub_property_1:
sub_property_2: "value2",
}
}
"##,
error: Some(
r#"Parse error: 4:9: Properties must be separated by a comma:
sub_property_2: "value2",
^~~~~~~~~~~~~~~"#,
),
..Default::default()
})
.unwrap();
test_format(FormatTest {
input: r##"{
property: {
sub_property_1: ,
sub_property_2: "value2",
}
}
"##,
error: Some(
"Parse error: 3:25: Property 'sub_property_1' must have a value before the next \
comma-separated property:
sub_property_1: ,
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_property_name_when_array_value_is_expected() {
test_format(FormatTest {
input: r##"{
property: [
"item1",
sub_property_1: "value",
}
}
"##,
error: Some(r#"Parse error: 4:9: Invalid Object token found while parsing an Array of 1 item (mismatched braces?):
sub_property_1: "value",
^~~~~~~~~~~~~~~"#),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_bad_non_string_primitive() {
test_format(FormatTest {
input: r##"{
non_string_literals: [
null,
true,
false,
12345,
12345.67890,
12345.,
.67890,
1234e5678,
1234E5678,
1234e+5678,
1234E+5678,
1234e-5678,
1234E-5678,
0xabc123ef,
0Xabc123EF,
NaN,
Infinity,
-12345,
-12345.67890,
-12345.,
-.67890,
-1234e5678,
-1234E5678,
-1234e+5678,
-1234E+5678,
-1234e-5678,
-1234E-5678,
-0xabc123ef,
-0Xabc123EF,
-NaN,
-Infinity,
+12345,
+12345.67890,
+12345.,
+.67890,
+1234e5678,
+1234E5678,
+1234e+5678,
+1234E+5678,
+1234e-5678,
+1234E-5678,
+0xabc123ef,
+0Xabc123EF,
+NaN,
+Infinity,
123def,
0x123def,
]
}
"##,
error: Some(
"Parse error: 52:9: Unexpected token:
123def,
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_expected_object() {
test_format(FormatTest {
input: r##"{
property: [}
}
"##,
error: Some(r#"Parse error: 2:16: Invalid Object token found while parsing an Array of 0 items (mismatched braces?):
property: [}
^"#),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_expected_array() {
test_format(FormatTest {
input: r##"{
property: {]
}
"##,
error: Some(r#"Parse error: 2:16: Invalid Array token found while parsing an Object of 0 properties (mismatched braces?):
property: {]
^"#),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_mismatched_braces() {
test_format(FormatTest {
input: r##"{
property_1: "value1",
property_2: "value2","##,
error: Some(
r#"Parse error: 3:25: Mismatched braces in the document:
property_2: "value2",
^"#,
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_property_name_separator_missing() {
test_format(FormatTest {
input: r##"{
property_1 "value1",
}
"##,
error: Some(
r#"Parse error: 2:5: Unexpected token:
property_1 "value1",
^"#,
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_quoted_property_name_separator_missing() {
test_format(FormatTest {
input: r##"{
"property_1" "value1",
}
"##,
error: Some(
r#"Parse error: 2:17: Property name separator (:) missing:
"property_1" "value1",
^"#,
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_extra_comma_between_properties() {
test_format(FormatTest {
input: r##"{
property_1: "value1",
,
property_2: "value2",
}
"##,
error: Some(
"Parse error: 3:5: Unexpected comma without a preceding property:
,
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_comma_before_first_property() {
test_format(FormatTest {
input: r##"{
,
property_1: "value1",
property_2: "value2",
}
"##,
error: Some(
"Parse error: 2:5: Unexpected comma without a preceding property:
,
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_extra_comma_between_array_items() {
test_format(FormatTest {
input: r##"[
"value1",
,
"value2",
]"##,
error: Some(
"Parse error: 3:5: Unexpected comma without a preceding array item value:
,
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_comma_before_first_array_item() {
test_format(FormatTest {
input: r##"[
,
"value1",
"value2",
]"##,
error: Some(
"Parse error: 2:5: Unexpected comma without a preceding array item value:
,
^",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_quoted_property_name_and_comma_looks_like_a_value() {
test_format(FormatTest {
input: r##"{
property_1: "value1",
"value2",
}
"##,
error: Some(
r#"Parse error: 3:13: Property name separator (:) missing:
"value2",
^"#,
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_value_without_property_name() {
test_format(FormatTest {
input: r##"{
property_1: "value1",
false,
}
"##,
error: Some(
"Parse error: 3:5: Object values require property names:
false,
^~~~~",
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_unclosed_string() {
test_format(FormatTest {
input: r##"{
property: "bad quotes',
}
"##,
error: Some(
r#"Parse error: 2:16: Unclosed string:
property: "bad quotes',
^"#,
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_parse_error_not_json() {
test_format(FormatTest {
input: r##"
# Fuchsia
Pink + Purple == Fuchsia (a new operating system)
## How can I build and run Fuchsia?
See [Getting Started](https://fuchsia.dev/fuchsia-src/getting_started.md).
## Where can I learn more about Fuchsia?
See [fuchsia.dev](https://fuchsia.dev).
"##,
error: Some(
r#"Parse error: 2:1: Unexpected token:
# Fuchsia
^"#,
),
..Default::default()
})
.unwrap();
}
#[test]
fn test_options() {
let options = FormatOptions { ..Default::default() };
assert_eq!(options.indent_by, 4);
assert_eq!(options.trailing_commas, true);
assert_eq!(options.collapse_containers_of_one, false);
assert_eq!(options.sort_array_items, false);
let options = FormatOptions {
indent_by: 2,
trailing_commas: false,
collapse_containers_of_one: true,
sort_array_items: true,
options_by_path: hashmap! {
"/*" => hashset! {
PathOption::PropertyNameOrder(vec![
"program",
"use",
"expose",
"offer",
"children",
"collections",
"storage",
"facets",
"runners",
"resolvers",
"environments",
]),
},
"/*/use" => hashset! {
PathOption::TrailingCommas(false),
PathOption::CollapseContainersOfOne(false),
PathOption::SortArrayItems(true),
PathOption::PropertyNameOrder(vec![
"name",
"url",
"startup",
"environment",
"durability",
"service",
"protocol",
"directory",
"storage",
"runner",
"resolver",
"to",
"from",
"as",
"rights",
"subdir",
"path",
"dependency",
]),
},
"/*/use/service" => hashset! {
PathOption::SortArrayItems(true),
},
},
..Default::default()
};
assert_eq!(options.indent_by, 2);
assert_eq!(options.trailing_commas, false);
assert_eq!(options.collapse_containers_of_one, true);
assert_eq!(options.sort_array_items, true);
let path_options = options
.options_by_path
.get("/*/use")
.expect("Expected to find path options for the given path");
match path_options
.get(&PathOption::TrailingCommas(true))
.expect("Expected to find a PathOption::TrailingCommas setting")
{
PathOption::TrailingCommas(trailing_commas) => assert_eq!(*trailing_commas, false),
_ => panic!("PathOption enum as key should return a value of the same type"),
};
match path_options
.get(&PathOption::CollapseContainersOfOne(true))
.expect("Expected to find a PathOption::CollapseContainersOfOne setting")
{
PathOption::CollapseContainersOfOne(collapsed_container_of_one) => {
assert_eq!(*collapsed_container_of_one, false)
}
_ => panic!("PathOption enum as key should return a value of the same type"),
};
match path_options
.get(&PathOption::SortArrayItems(true))
.expect("Expected to find a PathOption::SortArrayItems setting")
{
PathOption::SortArrayItems(sort_array_items) => assert_eq!(*sort_array_items, true),
_ => panic!("PathOption enum as key should return a value of the same type"),
};
match path_options
.get(&PathOption::PropertyNameOrder(vec![]))
.expect("Expected to find a PathOption::PropertyNameOrder setting")
{
PathOption::PropertyNameOrder(property_names) => assert_eq!(property_names[1], "url"),
_ => panic!("PathOption enum as key should return a value of the same type"),
};
}
#[test]
fn test_duplicated_key_in_subpath_options_is_ignored() {
let options = FormatOptions {
options_by_path: hashmap! {
"/*/use" => hashset! {
PathOption::TrailingCommas(false),
PathOption::CollapseContainersOfOne(false),
PathOption::SortArrayItems(true),
PathOption::PropertyNameOrder(vec![
"name",
"url",
"startup",
"environment",
"durability",
"service",
"protocol",
"directory",
"storage",
"runner",
"resolver",
"to",
"from",
"as",
"rights",
"subdir",
"path",
"dependency",
]),
PathOption::SortArrayItems(false),
},
},
..Default::default()
};
match options.options_by_path.get("/*/use") {
Some(path_options) => {
match path_options.get(&PathOption::TrailingCommas(true)) {
Some(path_option) => match path_option {
PathOption::TrailingCommas(trailing_commas) => {
assert_eq!(*trailing_commas, false);
}
_ => panic!("PathOption enum as key should return a value of the same type"),
},
None => panic!("Expected to find a PathOption::TrailingCommas setting"),
}
match path_options.get(&PathOption::CollapseContainersOfOne(true)) {
Some(path_option) => match path_option {
PathOption::CollapseContainersOfOne(collapsed_container_of_one) => {
assert_eq!(*collapsed_container_of_one, false);
}
_ => panic!("PathOption enum as key should return a value of the same type"),
},
None => panic!("Expected to find a PathOption::CollapseContainersOfOne setting"),
}
match path_options.get(&PathOption::SortArrayItems(true)) {
Some(path_option) => match path_option {
PathOption::SortArrayItems(sort_array_items) => {
assert_eq!(*sort_array_items, true);
}
_ => panic!("PathOption enum as key should return a value of the same type"),
},
None => panic!("Expected to find a PathOption::SortArrayItems setting"),
}
match path_options.get(&PathOption::PropertyNameOrder(vec![])) {
Some(path_option) => match path_option {
PathOption::PropertyNameOrder(property_names) => {
assert_eq!(property_names[1], "url");
}
_ => panic!("PathOption enum as key should return a value of the same type"),
},
None => panic!("Expected to find a PathOption::PropertyNamePriorities setting"),
}
}
None => panic!("Expected to find path options for the given path"),
}
}
#[test]
fn test_format_options() {
test_format(FormatTest {
options: Some(FormatOptions {
collapse_containers_of_one: true,
sort_array_items: true, // but use options_by_path to turn this off for program args
options_by_path: hashmap! {
"/*" => hashset! {
PathOption::PropertyNameOrder(vec![
"program",
"children",
"collections",
"use",
"offer",
"expose",
"resolvers",
"runners",
"storage",
"environments",
"facets",
])
},
"/*/program" => hashset! {
PathOption::CollapseContainersOfOne(false),
PathOption::PropertyNameOrder(vec![
"binary",
"args",
])
},
"/*/program/args" => hashset! {
PathOption::SortArrayItems(false),
},
"/*/*/*" => hashset! {
PathOption::PropertyNameOrder(vec![
"name",
"url",
"startup",
"environment",
"durability",
"service",
"protocol",
"directory",
"resolver",
"runner",
"storage",
"from",
"as",
"to",
"rights",
"path",
"subdir",
"event",
"dependency",
"extends",
"resolvers",
])
},
},
..Default::default()
}),
input: r##"{
offer: [
{
runner: "elf",
},
{
from: "framework",
to: "#elements",
protocol: "/svc/fuchsia.sys2.Realm",
},
{
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
from: "realm",
},
],
collections: [
"elements",
],
use: [
{
runner: "elf",
},
{
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework",
},
{
to: "#elements",
from: "realm",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
},
],
children: [
],
program: {
binary: "bin/session_manager",
},
}
"##,
expected: r##"{
program: {
binary: "bin/session_manager",
},
children: [],
collections: [ "elements" ],
use: [
{ runner: "elf" },
{
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework",
},
{
protocol: [
"/svc/fuchsia.cobalt.LoggerFactory",
"/svc/fuchsia.logger.LogSink",
],
from: "realm",
to: "#elements",
},
],
offer: [
{ runner: "elf" },
{
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework",
to: "#elements",
},
{
protocol: [
"/svc/fuchsia.cobalt.LoggerFactory",
"/svc/fuchsia.logger.LogSink",
],
from: "realm",
to: "#elements",
},
],
}
"##,
..Default::default()
})
.unwrap();
}
#[test]
fn test_no_trailing_commas() {
test_format(FormatTest {
options: Some(FormatOptions { trailing_commas: false, ..Default::default() }),
input: r##"{
offer: [
{
runner: "elf",
},
{
from: "framework",
to: "#elements",
protocol: "/svc/fuchsia.sys2.Realm",
},
{
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
from: "realm",
},
],
collections: [
"elements",
],
use: [
{
runner: "elf",
},
{
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework",
},
{
from: "realm",
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
},
],
children: [
],
program: {
binary: "bin/session_manager",
},
}
"##,
expected: r##"{
offer: [
{
runner: "elf"
},
{
from: "framework",
to: "#elements",
protocol: "/svc/fuchsia.sys2.Realm"
},
{
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory"
],
from: "realm"
}
],
collections: [
"elements"
],
use: [
{
runner: "elf"
},
{
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework"
},
{
from: "realm",
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory"
]
}
],
children: [],
program: {
binary: "bin/session_manager"
}
}
"##,
..Default::default()
})
.unwrap();
}
#[test]
fn test_collapse_containers_of_one() {
test_format(FormatTest {
options: Some(FormatOptions { collapse_containers_of_one: true, ..Default::default() }),
input: r##"{
offer: [
{
runner: "elf",
},
{
from: "framework",
to: "#elements",
protocol: "/svc/fuchsia.sys2.Realm",
},
{
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
from: "realm",
},
],
collections: [
"elements",
],
use: [
{
runner: "elf",
},
{
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework",
},
{
from: "realm",
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
},
],
children: [
],
program: {
binary: "bin/session_manager",
},
}
"##,
expected: r##"{
offer: [
{ runner: "elf" },
{
from: "framework",
to: "#elements",
protocol: "/svc/fuchsia.sys2.Realm",
},
{
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
from: "realm",
},
],
collections: [ "elements" ],
use: [
{ runner: "elf" },
{
protocol: "/svc/fuchsia.sys2.Realm",
from: "framework",
},
{
from: "realm",
to: "#elements",
protocol: [
"/svc/fuchsia.logger.LogSink",
"/svc/fuchsia.cobalt.LoggerFactory",
],
},
],
children: [],
program: { binary: "bin/session_manager" },
}
"##,
..Default::default()
})
.unwrap();
}
#[test]
fn test_validate_example_in_documentation() {
test_format(FormatTest {
options: Some(FormatOptions {
options_by_path: hashmap! {
"/*" => hashset! {
PathOption::PropertyNameOrder(vec![
"name",
"address",
"contact_options",
]),
},
"/*/name" => hashset! {
PathOption::PropertyNameOrder(vec![
"first",
"middle",
"last",
"suffix",
]),
},
"/*/*/*" => hashset! {
PathOption::PropertyNameOrder(vec![
"work",
"home",
"other",
]),
},
"/*/*/*/work" => hashset! {
PathOption::PropertyNameOrder(vec![
"phone",
"email",
]),
},
},
..Default::default()
}),
input: r##"{
name: {
last: "Smith",
first: "John",
middle: "Jacob",
},
address: {
city: "Anytown",
country: "USA",
state: "New York",
street: "101 Main Street",
},
contact_options: [
{
other: {
email: "volunteering@serviceprojectsrus.org",
},
home: {
email: "jj@notreallygmail.com",
phone: "212-555-4321",
},
},
{
home: {
email: "john.smith@notreallygmail.com",
phone: "212-555-2222",
},
work: {
email: "john.j.smith@worksforme.gov",
phone: "212-555-1234",
},
},
],
}
"##,
expected: r##"{
name: {
first: "John",
middle: "Jacob",
last: "Smith",
},
address: {
city: "Anytown",
country: "USA",
state: "New York",
street: "101 Main Street",
},
contact_options: [
{
home: {
email: "jj@notreallygmail.com",
phone: "212-555-4321",
},
other: {
email: "volunteering@serviceprojectsrus.org",
},
},
{
work: {
phone: "212-555-1234",
email: "john.j.smith@worksforme.gov",
},
home: {
email: "john.smith@notreallygmail.com",
phone: "212-555-2222",
},
},
],
}
"##,
..Default::default()
})
.unwrap();
}