diff --git a/crates/nu-command/src/formats/to/xml.rs b/crates/nu-command/src/formats/to/xml.rs index 57026f445..62bc79f59 100644 --- a/crates/nu-command/src/formats/to/xml.rs +++ b/crates/nu-command/src/formats/to/xml.rs @@ -34,10 +34,10 @@ impl Command for ToXml { fn extra_usage(&self) -> &str { r#"Every XML entry is represented via a record with tag, attribute and content fields. To represent different types of entries different values must be written to this fields: -1. Tag entry: `{tag: attrs: {: "" ...} content: []}` -2. Comment entry: `{tag: '!' attrs: null content: ""}` -3. Processing instruction (PI): `{tag: '?' attrs: null content: ""}` -4. Text: `{tag: null attrs: null content: ""}`. Or as plain `` instead of record. +1. Tag entry: `{tag: attributes: {: "" ...} content: []}` +2. Comment entry: `{tag: '!' attributes: null content: ""}` +3. Processing instruction (PI): `{tag: '?' attributes: null content: ""}` +4. Text: `{tag: null attributes: null content: ""}`. Or as plain `` instead of record. Additionally any field which is: empty record, empty list or null, can be omitted."# } @@ -46,7 +46,7 @@ Additionally any field which is: empty record, empty list or null, can be omitte vec![ Example { description: "Outputs an XML string representing the contents of this table", - example: r#"{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attrs: null content : Event}]}]} | to xml"#, + example: r#"{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attributes: null content : Event}]}]} | to xml"#, result: Some(Value::test_string( "Event", )), @@ -110,6 +110,17 @@ fn to_xml_entry( } if let Value::Record { val: record, .. } = &entry { + if let Some(bad_column) = find_invalid_column(record) { + return Err(ShellError::CantConvert { + to_type: "XML".into(), + from_type: "record".into(), + span: entry_span, + help: Some(format!( + "Invalid column \"{}\" in xml entry. Only \"{}\", \"{}\" and \"{}\" are permitted", + bad_column, COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME + )), + }); + } // If key is not found it is assumed to be nothing. This way // user can write a tag like {tag: a content: [...]} instead // of longer {tag: a attributes: {} content: [...]} @@ -144,7 +155,12 @@ fn to_xml_entry( (Value::String { val: tag_name, .. }, attrs, children) => to_tag_like( entry_span, tag_name, tag_span, attrs, children, top_level, writer, ), - _ => Ok(()), + _ => Err(ShellError::CantConvert { + to_type: "XML".into(), + from_type: "record".into(), + span: entry_span, + help: Some("Tag missing or is not a string".into()), + }), } } else { Err(ShellError::CantConvert { @@ -156,6 +172,14 @@ fn to_xml_entry( } } +fn find_invalid_column(record: &Record) -> Option<&String> { + const VALID_COLS: [&str; 3] = [COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME]; + record + .cols + .iter() + .find(|col| !VALID_COLS.contains(&col.as_str())) +} + /// Convert record to tag-like entry: tag, PI, comment. fn to_tag_like( entry_span: Span, diff --git a/crates/nu-command/tests/format_conversions/xml.rs b/crates/nu-command/tests/format_conversions/xml.rs index b92186e16..5946ff494 100644 --- a/crates/nu-command/tests/format_conversions/xml.rs +++ b/crates/nu-command/tests/format_conversions/xml.rs @@ -22,3 +22,39 @@ fn table_to_xml_text_and_from_xml_text_back_into_table() { assert_eq!(actual.out, "true"); } + +#[test] +fn to_xml_error_unknown_column() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + {tag: a bad_column: b} | to xml + "# + )); + + assert!(actual.err.contains("Invalid column \"bad_column\"")); +} + +#[test] +fn to_xml_error_no_tag() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + {attributes: {a: b c: d}} | to xml + "# + )); + + assert!(actual.err.contains("Tag missing")); +} + +#[test] +fn to_xml_error_tag_not_string() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + {tag: 1 attributes: {a: b c: d}} | to xml + "# + )); + + assert!(actual.err.contains("not a string")); +}