| // 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(); |
| } |