From 7883a772be61d9b7e8d221dd74564c9b14e2bfde Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 15 Sep 2022 22:15:45 +0500 Subject: [PATCH 01/21] Correctly test `deserialize_ignored_any` for simple type deserializers We expect that `deserialize_any` was called for a type. The wrapper type is only required to add a PartialEq trait for use in assert_eq!, so we add a #[serde(transparent)] --- src/de/simple_type.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 09f0249f..add142c2 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -899,6 +899,7 @@ mod tests { } #[derive(Debug, Deserialize)] + #[serde(transparent)] struct Any(IgnoredAny); impl PartialEq for Any { fn eq(&self, _other: &Any) -> bool { @@ -952,9 +953,6 @@ mod tests { }; } - deserialized_to!(any_owned: String = "<escaped string" => " "non-escaped string"); - deserialized_to!(false_: bool = "false" => false); deserialized_to!(true_: bool = "true" => true); From 856bfe3c34fa1a6c2f7a59aa4452b036fb6902c0 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 15 Sep 2022 22:25:04 +0500 Subject: [PATCH 02/21] Replace HEX escape codes in tests with decimal codes to be compatible with serializer Roundtrip with serializer will be added soon --- src/de/simple_type.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index add142c2..1450470e 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -977,12 +977,12 @@ mod tests { deserialized_to!(char_unescaped: char = "h" => 'h'); deserialized_to!(char_escaped: char = "<" => '<'); - deserialized_to!(string: String = "<escaped string" => " " "non-escaped string"); - err!(escaped_str: &str = "escaped string" + err!(escaped_str: &str = "escaped string" => Custom("invalid type: string \"escaped string\", expected a borrowed string")); - err!(byte_buf: ByteBuf = "<escaped string" + err!(byte_buf: ByteBuf = "<escaped string" => Unsupported("byte arrays are not supported as `xs:list` items")); err!(borrowed_bytes: Bytes = "non-escaped string" => Unsupported("byte arrays are not supported as `xs:list` items")); @@ -993,7 +993,7 @@ mod tests { deserialized_to!(unit: () = "anything" => ()); deserialized_to!(unit_struct: Unit = "anything" => Unit); - deserialized_to!(newtype_owned: Newtype = "<escaped string" => Newtype(" Newtype(" BorrowedNewtype("non-escaped string")); err!(seq: Vec<()> = "non-escaped string" @@ -1172,12 +1172,12 @@ mod tests { simple!(utf8, char_unescaped: char = "h" => 'h'); simple!(utf8, char_escaped: char = "<" => '<'); - simple!(utf8, string: String = "<escaped string" => " " Unsupported("binary data content is not supported by XML format")); simple!(utf8, borrowed_str: &str = "non-escaped string" => "non-escaped string"); - err!(utf8, borrowed_bytes: Bytes = "<escaped string" + err!(utf8, borrowed_bytes: Bytes = "<escaped string" => Unsupported("binary data content is not supported by XML format")); simple!(utf8, option_none: Option<&str> = "" => None); @@ -1186,7 +1186,7 @@ mod tests { simple!(utf8, unit: () = "any data" => ()); simple!(utf8, unit_struct: Unit = "any data" => Unit); - simple!(utf8, newtype_owned: Newtype = "<escaped string" => Newtype(" Newtype(" BorrowedNewtype("non-escaped string")); err!(utf8, map: HashMap<(), ()> = "any data" @@ -1256,8 +1256,8 @@ mod tests { utf16!(char_unescaped: char = "h" => 'h'); utf16!(char_escaped: char = "<" => '<'); - utf16!(string: String = "<escaped string" => " " "binary data content is not supported by XML format"); utf16!(option_none: Option<()> = "" => None); @@ -1266,7 +1266,7 @@ mod tests { utf16!(unit: () = "any data" => ()); utf16!(unit_struct: Unit = "any data" => Unit); - utf16!(newtype_owned: Newtype = "<escaped string" => Newtype(" Newtype(" Date: Wed, 14 Sep 2022 23:06:20 +0500 Subject: [PATCH 03/21] Remove excess tests, that already covered by other tests more systematically That cases already covered by tests in tests/serde-de.rs in `seq` and `struct_` modules --- tests/serde-de.rs | 158 +++++++++++ tests/test.rs | 686 +--------------------------------------------- 2 files changed, 159 insertions(+), 685 deletions(-) diff --git a/tests/serde-de.rs b/tests/serde-de.rs index 1b6deebc..6432d6b1 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -557,6 +557,52 @@ mod seq { .unwrap(); assert_eq!(data, vec![(), (), ()]); } + + /// This test ensures that composition of deserializer building blocks plays well + #[test] + fn list_of_struct() { + #[derive(Debug, PartialEq, Default, Deserialize)] + #[serde(default)] + struct Struct { + attribute: Option, + element: Option, + } + + let data: Vec = from_str( + r#" + + + + value + + + value + + "#, + ) + .unwrap(); + assert_eq!( + data, + vec![ + Struct { + attribute: None, + element: None, + }, + Struct { + attribute: Some("value".to_string()), + element: None, + }, + Struct { + attribute: None, + element: Some("value".to_string()), + }, + Struct { + attribute: Some("value".to_string()), + element: Some("value".to_string()), + }, + ] + ); + } } /// Tests where each sequence item have an identical name in an XML. @@ -1147,6 +1193,62 @@ mod seq { .unwrap_err(); } + /// This test ensures that composition of deserializer building blocks + /// plays well + #[test] + fn list_of_struct() { + #[derive(Debug, PartialEq, Default, Deserialize)] + #[serde(default)] + struct Struct { + attribute: Option, + element: Option, + } + + #[derive(Debug, PartialEq, Deserialize)] + struct List { + item: [Struct; 4], + } + + let data: List = from_str( + r#" + + + + + value + + + value + + + "#, + ) + .unwrap(); + assert_eq!( + data, + List { + item: [ + Struct { + attribute: None, + element: None, + }, + Struct { + attribute: Some("value".to_string()), + element: None, + }, + Struct { + attribute: None, + element: Some("value".to_string()), + }, + Struct { + attribute: Some("value".to_string()), + element: Some("value".to_string()), + }, + ], + } + ); + } + /// Checks that sequences represented by elements can contain sequences, /// represented by [`xs:list`s](https://www.w3schools.com/xml/el_list.asp) mod xs_list { @@ -1910,6 +2012,62 @@ mod seq { .unwrap_err(); } + /// This test ensures that composition of deserializer building blocks + /// plays well + #[test] + fn list_of_struct() { + #[derive(Debug, PartialEq, Default, Deserialize)] + #[serde(default)] + struct Struct { + attribute: Option, + element: Option, + } + + #[derive(Debug, PartialEq, Deserialize)] + struct List { + item: Vec, + } + + let data: List = from_str( + r#" + + + + + value + + + value + + + "#, + ) + .unwrap(); + assert_eq!( + data, + List { + item: vec![ + Struct { + attribute: None, + element: None, + }, + Struct { + attribute: Some("value".to_string()), + element: None, + }, + Struct { + attribute: None, + element: Some("value".to_string()), + }, + Struct { + attribute: Some("value".to_string()), + element: Some("value".to_string()), + }, + ], + } + ); + } + /// Checks that sequences represented by elements can contain sequences, /// represented by `xs:list`s mod xs_list { diff --git a/tests/test.rs b/tests/test.rs index 32b2a6f8..8eaee46e 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -6,7 +6,7 @@ use quick_xml::Error; use std::borrow::Cow; #[cfg(feature = "serialize")] -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use pretty_assertions::assert_eq; @@ -155,690 +155,6 @@ fn test_clone_reader() { assert!(matches!(cloned.read_event().unwrap(), End(_))); } -#[cfg(feature = "serialize")] -#[test] -fn line_score() { - #[derive(Debug, PartialEq, Deserialize)] - struct LineScoreData { - game_pk: u32, - game_type: char, - venue: String, - venue_w_chan_loc: String, - venue_id: u32, - time: String, - time_zone: String, - ampm: String, - home_team_id: u32, - home_team_city: String, - home_team_name: String, - home_league_id: u32, - away_team_id: u32, - away_team_city: String, - away_team_name: String, - away_league_id: u32, - #[serde(rename = "linescore", skip_serializing)] - innings: Vec, - } - #[derive(Debug, PartialEq, Deserialize)] - struct LineScore { - #[serde(rename = "away_inning_runs")] - away_runs: u32, - #[serde(rename = "home_inning_runs")] - //needs to be an Option, since home team doesn't always bat. - home_runs: Option, - // Keeping the inning as a string, since we'll need it to construct URLs later - inning: String, - } - - let res: LineScoreData = - quick_xml::de::from_str(include_str!("documents/linescore.xml")).unwrap(); - - let expected = LineScoreData { - game_pk: 239575, - game_type: 'R', - venue: "Generic".to_owned(), - venue_w_chan_loc: "USNY0996".to_owned(), - venue_id: 401, - time: "Gm 2".to_owned(), - time_zone: "ET".to_owned(), - ampm: "AM".to_owned(), - home_team_id: 611, - home_team_city: "DSL Dodgers".to_owned(), - home_team_name: "DSL Dodgers".to_owned(), - home_league_id: 130, - away_team_id: 604, - away_team_city: "DSL Blue Jays1".to_owned(), - away_team_name: "DSL Blue Jays1".to_owned(), - away_league_id: 130, - innings: vec![ - LineScore { - away_runs: 1, - home_runs: Some(0), - inning: "1".to_owned(), - }, - LineScore { - away_runs: 0, - home_runs: Some(0), - inning: "2".to_owned(), - }, - LineScore { - away_runs: 1, - home_runs: Some(1), - inning: "3".to_owned(), - }, - LineScore { - away_runs: 2, - home_runs: Some(0), - inning: "4".to_owned(), - }, - LineScore { - away_runs: 0, - home_runs: Some(0), - inning: "5".to_owned(), - }, - LineScore { - away_runs: 0, - home_runs: Some(0), - inning: "6".to_owned(), - }, - LineScore { - away_runs: 0, - home_runs: Some(0), - inning: "7".to_owned(), - }, - ], - }; - assert_eq!(res, expected); -} - -#[cfg(feature = "serialize")] -#[test] -fn players() { - #[derive(PartialEq, Deserialize, Serialize, Debug)] - struct Game { - #[serde(rename = "team")] - teams: Vec, - //umpires: Umpires - } - - #[derive(PartialEq, Deserialize, Serialize, Debug)] - struct Team { - #[serde(rename = "type")] - home_away: HomeAway, - id: String, - name: String, - #[serde(rename = "player")] - players: Vec, - #[serde(rename = "coach")] - coaches: Vec, - } - - #[derive(PartialEq, Deserialize, Serialize, Debug)] - enum HomeAway { - #[serde(rename = "home")] - Home, - #[serde(rename = "away")] - Away, - } - - #[derive(PartialEq, Deserialize, Serialize, Debug, Clone)] - struct Player { - id: u32, - #[serde(rename = "first")] - name_first: String, - #[serde(rename = "last")] - name_last: String, - game_position: Option, - bat_order: Option, - position: String, - } - - #[derive(PartialEq, Deserialize, Serialize, Debug)] - struct Coach { - position: String, - #[serde(rename = "first")] - name_first: String, - #[serde(rename = "last")] - name_last: String, - id: u32, - } - - let res: Game = quick_xml::de::from_str(include_str!("documents/players.xml")).unwrap(); - - let expected = Game { - teams: vec![ - Team { - home_away: HomeAway::Away, - id: "CIN".to_owned(), - name: "Cincinnati Reds".to_owned(), - players: vec![ - Player { - id: 115135, - name_first: "Ken".to_owned(), - name_last: "Griffey".to_owned(), - game_position: Some("RF".to_owned()), - bat_order: Some(3), - position: "RF".to_owned(), - }, - Player { - id: 115608, - name_first: "Scott".to_owned(), - name_last: "Hatteberg".to_owned(), - game_position: None, - bat_order: None, - position: "1B".to_owned(), - }, - Player { - id: 118967, - name_first: "Kent".to_owned(), - name_last: "Mercker".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 136460, - name_first: "Alex".to_owned(), - name_last: "Gonzalez".to_owned(), - game_position: None, - bat_order: None, - position: "SS".to_owned(), - }, - Player { - id: 150020, - name_first: "Jerry".to_owned(), - name_last: "Hairston".to_owned(), - game_position: None, - bat_order: None, - position: "SS".to_owned(), - }, - Player { - id: 150188, - name_first: "Francisco".to_owned(), - name_last: "Cordero".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 150221, - name_first: "Mike".to_owned(), - name_last: "Lincoln".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 150319, - name_first: "Josh".to_owned(), - name_last: "Fogg".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 150472, - name_first: "Ryan".to_owned(), - name_last: "Freel".to_owned(), - game_position: Some("LF".to_owned()), - bat_order: Some(2), - position: "CF".to_owned(), - }, - Player { - id: 276520, - name_first: "Bronson".to_owned(), - name_last: "Arroyo".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 279571, - name_first: "Matt".to_owned(), - name_last: "Belisle".to_owned(), - game_position: Some("P".to_owned()), - bat_order: Some(9), - position: "P".to_owned(), - }, - Player { - id: 279913, - name_first: "Corey".to_owned(), - name_last: "Patterson".to_owned(), - game_position: Some("CF".to_owned()), - bat_order: Some(1), - position: "CF".to_owned(), - }, - Player { - id: 346793, - name_first: "Jeremy".to_owned(), - name_last: "Affeldt".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 408252, - name_first: "Brandon".to_owned(), - name_last: "Phillips".to_owned(), - game_position: Some("2B".to_owned()), - bat_order: Some(4), - position: "2B".to_owned(), - }, - Player { - id: 421685, - name_first: "Aaron".to_owned(), - name_last: "Harang".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 424325, - name_first: "David".to_owned(), - name_last: "Ross".to_owned(), - game_position: Some("C".to_owned()), - bat_order: Some(8), - position: "C".to_owned(), - }, - Player { - id: 429665, - name_first: "Edwin".to_owned(), - name_last: "Encarnacion".to_owned(), - game_position: Some("3B".to_owned()), - bat_order: Some(6), - position: "3B".to_owned(), - }, - Player { - id: 433898, - name_first: "Jeff".to_owned(), - name_last: "Keppinger".to_owned(), - game_position: Some("SS".to_owned()), - bat_order: Some(7), - position: "SS".to_owned(), - }, - Player { - id: 435538, - name_first: "Bill".to_owned(), - name_last: "Bray".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 440361, - name_first: "Norris".to_owned(), - name_last: "Hopper".to_owned(), - game_position: None, - bat_order: None, - position: "O".to_owned(), - }, - Player { - id: 450172, - name_first: "Edinson".to_owned(), - name_last: "Volquez".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 454537, - name_first: "Jared".to_owned(), - name_last: "Burton".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 455751, - name_first: "Bobby".to_owned(), - name_last: "Livingston".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 456501, - name_first: "Johnny".to_owned(), - name_last: "Cueto".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 458015, - name_first: "Joey".to_owned(), - name_last: "Votto".to_owned(), - game_position: Some("1B".to_owned()), - bat_order: Some(5), - position: "1B".to_owned(), - }, - ], - coaches: vec![ - Coach { - position: "manager".to_owned(), - name_first: "Dusty".to_owned(), - name_last: "Baker".to_owned(), - id: 110481, - }, - Coach { - position: "batting_coach".to_owned(), - name_first: "Brook".to_owned(), - name_last: "Jacoby".to_owned(), - id: 116461, - }, - Coach { - position: "pitching_coach".to_owned(), - name_first: "Dick".to_owned(), - name_last: "Pole".to_owned(), - id: 120649, - }, - Coach { - position: "first_base_coach".to_owned(), - name_first: "Billy".to_owned(), - name_last: "Hatcher".to_owned(), - id: 115602, - }, - Coach { - position: "third_base_coach".to_owned(), - name_first: "Mark".to_owned(), - name_last: "Berry".to_owned(), - id: 427028, - }, - Coach { - position: "bench_coach".to_owned(), - name_first: "Chris".to_owned(), - name_last: "Speier".to_owned(), - id: 122573, - }, - Coach { - position: "bullpen_coach".to_owned(), - name_first: "Juan".to_owned(), - name_last: "Lopez".to_owned(), - id: 427306, - }, - Coach { - position: "bullpen_catcher".to_owned(), - name_first: "Mike".to_owned(), - name_last: "Stefanski".to_owned(), - id: 150464, - }, - ], - }, - Team { - home_away: HomeAway::Home, - id: "NYM".to_owned(), - name: "New York Mets".to_owned(), - players: vec![ - Player { - id: 110189, - name_first: "Moises".to_owned(), - name_last: "Alou".to_owned(), - game_position: Some("LF".to_owned()), - bat_order: Some(6), - position: "LF".to_owned(), - }, - Player { - id: 112116, - name_first: "Luis".to_owned(), - name_last: "Castillo".to_owned(), - game_position: Some("2B".to_owned()), - bat_order: Some(2), - position: "2B".to_owned(), - }, - Player { - id: 113232, - name_first: "Carlos".to_owned(), - name_last: "Delgado".to_owned(), - game_position: Some("1B".to_owned()), - bat_order: Some(7), - position: "1B".to_owned(), - }, - Player { - id: 113702, - name_first: "Damion".to_owned(), - name_last: "Easley".to_owned(), - game_position: None, - bat_order: None, - position: "2B".to_owned(), - }, - Player { - id: 118377, - name_first: "Pedro".to_owned(), - name_last: "Martinez".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 123790, - name_first: "Billy".to_owned(), - name_last: "Wagner".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 133340, - name_first: "Orlando".to_owned(), - name_last: "Hernandez".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 135783, - name_first: "Ramon".to_owned(), - name_last: "Castro".to_owned(), - game_position: None, - bat_order: None, - position: "C".to_owned(), - }, - Player { - id: 136724, - name_first: "Marlon".to_owned(), - name_last: "Anderson".to_owned(), - game_position: None, - bat_order: None, - position: "LF".to_owned(), - }, - Player { - id: 136860, - name_first: "Carlos".to_owned(), - name_last: "Beltran".to_owned(), - game_position: Some("CF".to_owned()), - bat_order: Some(4), - position: "CF".to_owned(), - }, - Player { - id: 150411, - name_first: "Brian".to_owned(), - name_last: "Schneider".to_owned(), - game_position: Some("C".to_owned()), - bat_order: Some(8), - position: "C".to_owned(), - }, - Player { - id: 276371, - name_first: "Johan".to_owned(), - name_last: "Santana".to_owned(), - game_position: Some("P".to_owned()), - bat_order: Some(9), - position: "P".to_owned(), - }, - Player { - id: 277184, - name_first: "Matt".to_owned(), - name_last: "Wise".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 346795, - name_first: "Endy".to_owned(), - name_last: "Chavez".to_owned(), - game_position: None, - bat_order: None, - position: "RF".to_owned(), - }, - Player { - id: 407901, - name_first: "Jorge".to_owned(), - name_last: "Sosa".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 408230, - name_first: "Pedro".to_owned(), - name_last: "Feliciano".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 408310, - name_first: "Aaron".to_owned(), - name_last: "Heilman".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 408314, - name_first: "Jose".to_owned(), - name_last: "Reyes".to_owned(), - game_position: Some("SS".to_owned()), - bat_order: Some(1), - position: "SS".to_owned(), - }, - Player { - id: 425508, - name_first: "Ryan".to_owned(), - name_last: "Church".to_owned(), - game_position: Some("RF".to_owned()), - bat_order: Some(5), - position: "RF".to_owned(), - }, - Player { - id: 429720, - name_first: "John".to_owned(), - name_last: "Maine".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 431151, - name_first: "David".to_owned(), - name_last: "Wright".to_owned(), - game_position: Some("3B".to_owned()), - bat_order: Some(3), - position: "3B".to_owned(), - }, - Player { - id: 434586, - name_first: "Ambiorix".to_owned(), - name_last: "Burgos".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 434636, - name_first: "Angel".to_owned(), - name_last: "Pagan".to_owned(), - game_position: None, - bat_order: None, - position: "LF".to_owned(), - }, - Player { - id: 450306, - name_first: "Jason".to_owned(), - name_last: "Vargas".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - Player { - id: 460059, - name_first: "Mike".to_owned(), - name_last: "Pelfrey".to_owned(), - game_position: None, - bat_order: None, - position: "P".to_owned(), - }, - ], - coaches: vec![ - Coach { - position: "manager".to_owned(), - name_first: "Willie".to_owned(), - name_last: "Randolph".to_owned(), - id: 120927, - }, - Coach { - position: "batting_coach".to_owned(), - name_first: "Howard".to_owned(), - name_last: "Johnson".to_owned(), - id: 116593, - }, - Coach { - position: "pitching_coach".to_owned(), - name_first: "Rick".to_owned(), - name_last: "Peterson".to_owned(), - id: 427395, - }, - Coach { - position: "first_base_coach".to_owned(), - name_first: "Tom".to_owned(), - name_last: "Nieto".to_owned(), - id: 119796, - }, - Coach { - position: "third_base_coach".to_owned(), - name_first: "Sandy".to_owned(), - name_last: "Alomar".to_owned(), - id: 110185, - }, - Coach { - position: "bench_coach".to_owned(), - name_first: "Jerry".to_owned(), - name_last: "Manuel".to_owned(), - id: 118262, - }, - Coach { - position: "bullpen_coach".to_owned(), - name_first: "Guy".to_owned(), - name_last: "Conti".to_owned(), - id: 434699, - }, - Coach { - position: "bullpen_catcher".to_owned(), - name_first: "Dave".to_owned(), - name_last: "Racaniello".to_owned(), - id: 534948, - }, - Coach { - position: "coach".to_owned(), - name_first: "Sandy".to_owned(), - name_last: "Alomar".to_owned(), - id: 110184, - }, - Coach { - position: "coach".to_owned(), - name_first: "Juan".to_owned(), - name_last: "Lopez".to_owned(), - id: 495390, - }, - ], - }, - ], - }; - - assert_eq!(res, expected); -} - #[test] fn test_issue299() -> Result<(), Error> { let xml = r#" From e1f64afce7b34c297f3b78441cb3e8a3b5a91f8d Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 14 Sep 2022 22:35:41 +0500 Subject: [PATCH 04/21] Rename XmlNameSerializer to QNameSerializer --- src/se/key.rs | 10 +++++----- src/se/var.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/se/key.rs b/src/se/key.rs index 0adf84a1..dbe80516 100644 --- a/src/se/key.rs +++ b/src/se/key.rs @@ -11,19 +11,19 @@ use std::fmt::Write; /// that would be filtered on higher level. /// /// [not allowed]: https://www.w3.org/TR/REC-xml/#sec-common-syn -pub struct XmlNameSerializer { +pub struct QNameSerializer { /// Writer to which this serializer writes content pub writer: W, } -impl XmlNameSerializer { +impl QNameSerializer { #[inline] fn write_str(&mut self, value: &str) -> Result<(), DeError> { Ok(self.writer.write_str(value)?) } } -impl Serializer for XmlNameSerializer { +impl Serializer for QNameSerializer { type Ok = W; type Error = DeError; @@ -188,7 +188,7 @@ mod tests { ($name:ident: $data:expr => $expected:literal) => { #[test] fn $name() { - let ser = XmlNameSerializer { + let ser = QNameSerializer { writer: String::new(), }; @@ -205,7 +205,7 @@ mod tests { #[test] fn $name() { let mut buffer = String::new(); - let ser = XmlNameSerializer { + let ser = QNameSerializer { writer: &mut buffer, }; diff --git a/src/se/var.rs b/src/se/var.rs index eb154406..c3953684 100644 --- a/src/se/var.rs +++ b/src/se/var.rs @@ -2,7 +2,7 @@ use crate::{ de::{INNER_VALUE, UNFLATTEN_PREFIX}, errors::{serialize::DeError, Error}, events::{BytesEnd, BytesStart, Event}, - se::key::XmlNameSerializer, + se::key::QNameSerializer, se::Serializer, writer::Writer, }; @@ -65,7 +65,7 @@ where key: &K, value: &V, ) -> Result<(), DeError> { - let key = key.serialize(XmlNameSerializer { + let key = key.serialize(QNameSerializer { writer: String::new(), })?; From bf48446626ddf24f73b55a5e52a66c3c220411a9 Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 14 Sep 2022 22:13:08 +0500 Subject: [PATCH 05/21] Do not allow serialize unit structs as XML names. We never use struct name as a data, except in the root serializer --- src/se/key.rs | 24 ++++++++++++++++-------- src/se/mod.rs | 4 ---- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/se/key.rs b/src/se/key.rs index dbe80516..dbe95d0e 100644 --- a/src/se/key.rs +++ b/src/se/key.rs @@ -42,14 +42,26 @@ impl Serializer for QNameSerializer { Ok(self.writer) } - /// We cannot store anything, so the absence of a unit and presence of it - /// does not differ, so serialization of unit returns `Err(Unsupported)` + /// Because unit type can be represented only by empty string which is not + /// a valid XML name, serialization of unit returns `Err(Unsupported)` fn serialize_unit(self) -> Result { Err(DeError::Unsupported( "unit type `()` cannot be serialized as an XML tag name".into(), )) } + /// Because unit struct can be represented only by empty string which is not + /// a valid XML name, serialization of unit struct returns `Err(Unsupported)` + fn serialize_unit_struct(self, name: &'static str) -> Result { + Err(DeError::Unsupported( + format!( + "unit struct `{}` cannot be serialized as an XML tag name", + name + ) + .into(), + )) + } + /// We cannot store both a variant discriminant and a variant value, /// so serialization of enum newtype variant returns `Err(Unsupported)` fn serialize_newtype_variant( @@ -154,10 +166,6 @@ mod tests { #[derive(Debug, Serialize, PartialEq)] struct Unit; - #[derive(Debug, Serialize, PartialEq)] - #[serde(rename = "<\"&'>")] - struct UnitEscaped; - #[derive(Debug, Serialize, PartialEq)] struct Newtype(bool); @@ -269,8 +277,8 @@ mod tests { err!(unit: () => Unsupported("unit type `()` cannot be serialized as an XML tag name")); - serialize_as!(unit_struct: Unit => "Unit"); - serialize_as!(unit_struct_escaped: UnitEscaped => "<\"&'>"); + err!(unit_struct: Unit + => Unsupported("unit struct `Unit` cannot be serialized as an XML tag name")); serialize_as!(enum_unit: Enum::Unit => "Unit"); serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<\"&'>"); diff --git a/src/se/mod.rs b/src/se/mod.rs index 9f734690..0640b8e6 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -52,10 +52,6 @@ macro_rules! write_primitive { value.serialize(self) } - fn serialize_unit_struct(self, name: &'static str) -> Result { - self.serialize_str(name) - } - fn serialize_unit_variant( self, _name: &'static str, From d1b0a9c3a8fd2d28217ef5375d06e33cb9d1b0b4 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 8 Sep 2022 23:26:18 +0500 Subject: [PATCH 06/21] Introduce internal `XmlName` struct to validate serialized names against XML rules for names https://www.w3.org/TR/xml11/#NT-Name --- src/errors.rs | 9 +++++++ src/se/mod.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/errors.rs b/src/errors.rs index 57f8f3b1..1f60c0b5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -188,6 +188,15 @@ pub mod serialize { /// An attempt to deserialize to a type, that is not supported by the XML /// store at current position, for example, attempt to deserialize `struct` /// from attribute or attempt to deserialize binary data. + /// + /// Serialized type cannot be represented in an XML due to violation of the + /// XML rules in the final XML document. For example, attempt to serialize + /// a `HashMap<{integer}, ...>` would cause this error because [XML name] + /// cannot start from a digit or a hyphen (minus sign). The same result + /// would occur if map key is a complex type that cannot be serialized as + /// a primitive type (i.e. string, char, bool, unit struct or unit variant). + /// + /// [XML name]: https://www.w3.org/TR/REC-xml/#sec-common-syn Unsupported(Cow<'static, str>), /// Too many events were skipped while deserializing a sequence, event limit /// exceeded. The limit was provided as an argument diff --git a/src/se/mod.rs b/src/se/mod.rs index 0640b8e6..85266613 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -100,6 +100,74 @@ pub fn to_string(value: &S) -> Result { Ok(String::from_utf8(writer)?) } +/// Almost all characters can form a name. Citation from : +/// +/// > The overall philosophy of names has changed since XML 1.0. Whereas XML 1.0 +/// > provided a rigid definition of names, wherein everything that was not permitted +/// > was forbidden, XML 1.1 names are designed so that everything that is not +/// > forbidden (for a specific reason) is permitted. Since Unicode will continue +/// > to grow past version 4.0, further changes to XML can be avoided by allowing +/// > almost any character, including those not yet assigned, in names. +/// +/// +const fn is_xml11_name_start_char(ch: char) -> bool { + match ch { + ':' + | 'A'..='Z' + | '_' + | 'a'..='z' + | '\u{00C0}'..='\u{00D6}' + | '\u{00D8}'..='\u{00F6}' + | '\u{00F8}'..='\u{02FF}' + | '\u{0370}'..='\u{037D}' + | '\u{037F}'..='\u{1FFF}' + | '\u{200C}'..='\u{200D}' + | '\u{2070}'..='\u{218F}' + | '\u{2C00}'..='\u{2FEF}' + | '\u{3001}'..='\u{D7FF}' + | '\u{F900}'..='\u{FDCF}' + | '\u{FDF0}'..='\u{FFFD}' + | '\u{10000}'..='\u{EFFFF}' => true, + _ => false, + } +} +/// +const fn is_xml11_name_char(ch: char) -> bool { + match ch { + '-' | '.' | '0'..='9' | '\u{00B7}' | '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}' => { + true + } + _ => is_xml11_name_start_char(ch), + } +} + +/// Helper struct to self-defense from errors +#[derive(Clone, Copy, Debug, PartialEq)] +pub(self) struct XmlName<'n>(&'n str); + +impl<'n> XmlName<'n> { + /// Checks correctness of the XML name according to [XML 1.1 specification] + /// + /// [XML 1.1 specification]: https://www.w3.org/TR/REC-xml/#NT-Name + pub fn try_from(name: &'n str) -> Result, DeError> { + //TODO: Customization point: allow user to decide if he want to reject or encode the name + match name.chars().next() { + Some(ch) if !is_xml11_name_start_char(ch) => Err(DeError::Unsupported( + format!("character `{ch}` is not allowed at the start of an XML name `{name}`") + .into(), + )), + _ => match name.matches(|ch| !is_xml11_name_char(ch)).next() { + Some(s) => Err(DeError::Unsupported( + format!("character `{s}` is not allowed in an XML name `{name}`").into(), + )), + None => Ok(XmlName(name)), + }, + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + /// A Serializer pub struct Serializer<'r, W: Write> { writer: Writer, From e24716b1455823e7c61856a15f59cc9cc689ae41 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 29 Aug 2022 00:15:20 +0500 Subject: [PATCH 07/21] Implement escaping with 3 levels targeted for a text and attribute value --- src/escapei.rs | 14 +- src/se/mod.rs | 51 ++++++ src/se/simple_type.rs | 396 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 src/se/simple_type.rs diff --git a/src/escapei.rs b/src/escapei.rs index 846f2278..d1d966f3 100644 --- a/src/escapei.rs +++ b/src/escapei.rs @@ -94,7 +94,7 @@ pub fn partial_escape(raw: &str) -> Cow { /// Escapes an `&str` and replaces a subset of xml special characters (`<`, `>`, /// `&`, `'`, `"`) with their corresponding xml escaped value. -fn _escape bool>(raw: &str, escape_chars: F) -> Cow { +pub(crate) fn _escape bool>(raw: &str, escape_chars: F) -> Cow { let bytes = raw.as_bytes(); let mut escaped = None; let mut iter = bytes.iter(); @@ -112,7 +112,17 @@ fn _escape bool>(raw: &str, escape_chars: F) -> Cow { b'\'' => escaped.extend_from_slice(b"'"), b'&' => escaped.extend_from_slice(b"&"), b'"' => escaped.extend_from_slice(b"""), - _ => unreachable!("Only '<', '>','\', '&' and '\"' are escaped"), + + // This set of escapes handles characters that should be escaped + // in elements of xs:lists, because those characters works as + // delimiters of list elements + b'\t' => escaped.extend_from_slice(b" "), + b'\r' => escaped.extend_from_slice(b" "), + b'\n' => escaped.extend_from_slice(b" "), + b' ' => escaped.extend_from_slice(b" "), + _ => unreachable!( + "Only '<', '>','\', '&', '\"', '\\t', '\\r', '\\n', and ' ' are escaped" + ), } pos = new_pos + 1; } diff --git a/src/se/mod.rs b/src/se/mod.rs index 85266613..ef9f733e 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -74,6 +74,7 @@ macro_rules! write_primitive { //////////////////////////////////////////////////////////////////////////////////////////////////// mod key; +pub(crate) mod simple_type; mod var; use self::var::{Map, Seq, Struct, Tuple}; @@ -100,6 +101,56 @@ pub fn to_string(value: &S) -> Result { Ok(String::from_utf8(writer)?) } +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Defines which characters would be escaped in [`Text`] events and attribute +/// values. +/// +/// [`Text`]: Event::Text +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum QuoteLevel { + /// Performs escaping, escape all characters that could have special meaning + /// in the XML. This mode is compatible with SGML specification. + /// + /// Characters that will be replaced: + /// + /// Original | Replacement + /// ---------|------------ + /// `<` | `<` + /// `>` | `>` + /// `&` | `&` + /// `"` | `"` + /// `'` | `'` + Full, + /// Performs escaping that is compatible with SGML specification. + /// + /// This level adds escaping of `>` to the `Minimal` level, which is [required] + /// for compatibility with SGML. + /// + /// Characters that will be replaced: + /// + /// Original | Replacement + /// ---------|------------ + /// `<` | `<` + /// `>` | `>` + /// `&` | `&` + /// + /// [required]: https://www.w3.org/TR/xml11/#syntax + Partial, + /// Performs the minimal possible escaping, escape only strictly necessary + /// characters. + /// + /// Characters that will be replaced: + /// + /// Original | Replacement + /// ---------|------------ + /// `<` | `<` + /// `&` | `&` + Minimal, +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + /// Almost all characters can form a name. Citation from : /// /// > The overall philosophy of names has changed since XML 1.0. Whereas XML 1.0 diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs new file mode 100644 index 00000000..11e83042 --- /dev/null +++ b/src/se/simple_type.rs @@ -0,0 +1,396 @@ +//! Contains Serde `Serializer` for XML [simple types] [as defined] in the XML Schema. +//! +//! [simple types]: https://www.w3schools.com/xml/el_simpletype.asp +//! [as defined]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition + +use crate::escapei::_escape; +use crate::se::QuoteLevel; +use std::borrow::Cow; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum QuoteTarget { + /// Escape data for a text content. No additional escape symbols + Text, + /// Escape data for a double-quoted attribute. `"` always escaped + DoubleQAttr, + /// Escape data for a single-quoted attribute. `'` always escaped + SingleQAttr, +} + +/// Escapes atomic value that could be part of a `xs:list`. All whitespace characters +/// additionally escaped +fn escape_item(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow { + use QuoteLevel::*; + use QuoteTarget::*; + + match (target, level) { + (_, Full) => _escape(value, |ch| match ch { + // Spaces used as delimiters of list items, cannot be used in the item + b' ' | b'\r' | b'\n' | b'\t' => true, + // Required characters to escape + b'&' | b'<' | b'>' | b'\'' | b'\"' => true, + _ => false, + }), + //---------------------------------------------------------------------- + (Text, Partial) => _escape(value, |ch| match ch { + // Spaces used as delimiters of list items, cannot be used in the item + b' ' | b'\r' | b'\n' | b'\t' => true, + // Required characters to escape + b'&' | b'<' | b'>' => true, + _ => false, + }), + (Text, Minimal) => _escape(value, |ch| match ch { + // Spaces used as delimiters of list items, cannot be used in the item + b' ' | b'\r' | b'\n' | b'\t' => true, + // Required characters to escape + b'&' | b'<' => true, + _ => false, + }), + //---------------------------------------------------------------------- + (DoubleQAttr, Partial) => _escape(value, |ch| match ch { + // Spaces used as delimiters of list items, cannot be used in the item + b' ' | b'\r' | b'\n' | b'\t' => true, + // Required characters to escape + b'&' | b'<' | b'>' => true, + // Double quoted attribute should escape quote + b'"' => true, + _ => false, + }), + (DoubleQAttr, Minimal) => _escape(value, |ch| match ch { + // Spaces used as delimiters of list items, cannot be used in the item + b' ' | b'\r' | b'\n' | b'\t' => true, + // Required characters to escape + b'&' | b'<' => true, + // Double quoted attribute should escape quote + b'"' => true, + _ => false, + }), + //---------------------------------------------------------------------- + (SingleQAttr, Partial) => _escape(value, |ch| match ch { + // Spaces used as delimiters of list items + b' ' | b'\r' | b'\n' | b'\t' => true, + // Required characters to escape + b'&' | b'<' | b'>' => true, + // Single quoted attribute should escape quote + b'\'' => true, + _ => false, + }), + (SingleQAttr, Minimal) => _escape(value, |ch| match ch { + // Spaces used as delimiters of list items + b' ' | b'\r' | b'\n' | b'\t' => true, + // Required characters to escape + b'&' | b'<' => true, + // Single quoted attribute should escape quote + b'\'' => true, + _ => false, + }), + } +} + +/// Escapes XSD simple type value +fn escape_list(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow { + use QuoteLevel::*; + use QuoteTarget::*; + + match (target, level) { + (_, Full) => _escape(value, |ch| match ch { + // Required characters to escape + b'&' | b'<' | b'>' | b'\'' | b'\"' => true, + _ => false, + }), + //---------------------------------------------------------------------- + (Text, Partial) => _escape(value, |ch| match ch { + // Required characters to escape + b'&' | b'<' | b'>' => true, + _ => false, + }), + (Text, Minimal) => _escape(value, |ch| match ch { + // Required characters to escape + b'&' | b'<' => true, + _ => false, + }), + //---------------------------------------------------------------------- + (DoubleQAttr, Partial) => _escape(value, |ch| match ch { + // Required characters to escape + b'&' | b'<' | b'>' => true, + // Double quoted attribute should escape quote + b'"' => true, + _ => false, + }), + (DoubleQAttr, Minimal) => _escape(value, |ch| match ch { + // Required characters to escape + b'&' | b'<' => true, + // Double quoted attribute should escape quote + b'"' => true, + _ => false, + }), + //---------------------------------------------------------------------- + (SingleQAttr, Partial) => _escape(value, |ch| match ch { + // Required characters to escape + b'&' | b'<' | b'>' => true, + // Single quoted attribute should escape quote + b'\'' => true, + _ => false, + }), + (SingleQAttr, Minimal) => _escape(value, |ch| match ch { + // Required characters to escape + b'&' | b'<' => true, + // Single quoted attribute should escape quote + b'\'' => true, + _ => false, + }), + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + + mod escape_item { + use super::*; + + mod full { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn text() { + assert_eq!( + escape_item("text<\"'&> \t\r\ntext", QuoteTarget::Text, QuoteLevel::Full), + "text<"'&> text" + ); + } + + #[test] + fn double_quote_attr() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::DoubleQAttr, + QuoteLevel::Full + ), + "text<"'&> text" + ); + } + + #[test] + fn single_quote_attr() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::SingleQAttr, + QuoteLevel::Full + ), + "text<"'&> text" + ); + } + } + + mod partial { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn text() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::Text, + QuoteLevel::Partial + ), + "text<\"'&> text" + ); + } + + #[test] + fn double_quote_attr() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::DoubleQAttr, + QuoteLevel::Partial + ), + "text<"'&> text" + ); + } + + #[test] + fn single_quote_attr() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::SingleQAttr, + QuoteLevel::Partial + ), + "text<\"'&> text" + ); + } + } + + mod minimal { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn text() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::Text, + QuoteLevel::Minimal + ), + "text<\"'&> text" + ); + } + + #[test] + fn double_quote_attr() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::DoubleQAttr, + QuoteLevel::Minimal + ), + "text<"'&> text" + ); + } + + #[test] + fn single_quote_attr() { + assert_eq!( + escape_item( + "text<\"'&> \t\r\ntext", + QuoteTarget::SingleQAttr, + QuoteLevel::Minimal + ), + "text<\"'&> text" + ); + } + } + } + + mod escape_list { + use super::*; + + mod full { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn text() { + assert_eq!( + escape_list("text<\"'&> \t\r\ntext", QuoteTarget::Text, QuoteLevel::Full), + "text<"'&> \t\r\ntext" + ); + } + + #[test] + fn double_quote_attr() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::DoubleQAttr, + QuoteLevel::Full + ), + "text<"'&> \t\r\ntext" + ); + } + + #[test] + fn single_quote_attr() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::SingleQAttr, + QuoteLevel::Full + ), + "text<"'&> \t\r\ntext" + ); + } + } + + mod partial { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn text() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::Text, + QuoteLevel::Partial + ), + "text<\"'&> \t\r\ntext" + ); + } + + #[test] + fn double_quote_attr() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::DoubleQAttr, + QuoteLevel::Partial + ), + "text<"'&> \t\r\ntext" + ); + } + + #[test] + fn single_quote_attr() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::SingleQAttr, + QuoteLevel::Partial + ), + "text<\"'&> \t\r\ntext" + ); + } + } + + mod minimal { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn text() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::Text, + QuoteLevel::Minimal + ), + "text<\"'&> \t\r\ntext" + ); + } + + #[test] + fn double_quote_attr() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::DoubleQAttr, + QuoteLevel::Minimal + ), + "text<"'&> \t\r\ntext" + ); + } + + #[test] + fn single_quote_attr() { + assert_eq!( + escape_list( + "text<\"'&> \t\r\ntext", + QuoteTarget::SingleQAttr, + QuoteLevel::Minimal + ), + "text<\"'&> \t\r\ntext" + ); + } + } + } +} From 397422ec682ee2e85444d9cf85e8283c2bad82ce Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 28 Aug 2022 22:07:10 +0500 Subject: [PATCH 08/21] Implement AtomicSerializer for serialize XSD atomic values --- src/de/simple_type.rs | 61 ++++++-- src/se/simple_type.rs | 323 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 371 insertions(+), 13 deletions(-) diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 1450470e..2a9d2eb2 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -830,7 +830,7 @@ mod tests { use super::*; use crate::utils::{ByteBuf, Bytes}; use serde::de::IgnoredAny; - use serde::Deserialize; + use serde::{Deserialize, Serialize}; use std::collections::HashMap; macro_rules! simple { @@ -869,22 +869,22 @@ mod tests { }; } - #[derive(Debug, Deserialize, PartialEq)] + #[derive(Debug, Deserialize, Serialize, PartialEq)] struct Unit; - #[derive(Debug, Deserialize, PartialEq)] + #[derive(Debug, Deserialize, Serialize, PartialEq)] struct Newtype(String); - #[derive(Debug, Deserialize, PartialEq)] + #[derive(Debug, Deserialize, Serialize, PartialEq)] struct BorrowedNewtype<'a>(&'a str); - #[derive(Debug, Deserialize, PartialEq)] + #[derive(Debug, Deserialize, Serialize, PartialEq)] struct Struct { key: String, val: usize, } - #[derive(Debug, Deserialize, PartialEq)] + #[derive(Debug, Deserialize, Serialize, PartialEq)] enum Enum { Unit, Newtype(String), @@ -910,9 +910,28 @@ mod tests { /// Tests for deserialize atomic and union values, as defined in XSD specification mod atomic { use super::*; + use crate::se::simple_type::{AtomicSerializer, QuoteTarget}; + use crate::se::QuoteLevel; use pretty_assertions::assert_eq; /// Checks that given `$input` successfully deserializing into given `$result` + macro_rules! deserialized_to_only { + ($name:ident: $type:ty = $input:literal => $result:expr) => { + #[test] + fn $name() { + let de = AtomicDeserializer { + content: Content::Input($input), + escaped: true, + }; + let data: $type = Deserialize::deserialize(de).unwrap(); + + assert_eq!(data, $result); + } + }; + } + + /// Checks that given `$input` successfully deserializing into given `$result` + /// and the result is serialized back to the `$input` macro_rules! deserialized_to { ($name:ident: $type:ty = $input:literal => $result:expr) => { #[test] @@ -924,6 +943,17 @@ mod tests { let data: $type = Deserialize::deserialize(de).unwrap(); assert_eq!(data, $result); + + // Roundtrip to ensure that serializer corresponds to deserializer + assert_eq!( + data.serialize(AtomicSerializer { + writer: String::new(), + target: QuoteTarget::Text, + level: QuoteLevel::Full, + }) + .unwrap(), + $input + ); } }; } @@ -978,7 +1008,9 @@ mod tests { deserialized_to!(char_escaped: char = "<" => '<'); deserialized_to!(string: String = "<escaped string" => " "non-escaped string"); + // Serializer will escape space. Because borrowing has meaning only for deserializer, + // no need to test roundtrip here, it is already tested with non-borrowing version + deserialized_to_only!(borrowed_str: &str = "non-escaped string" => "non-escaped string"); err!(escaped_str: &str = "escaped string" => Custom("invalid type: string \"escaped string\", expected a borrowed string")); @@ -988,13 +1020,16 @@ mod tests { => Unsupported("byte arrays are not supported as `xs:list` items")); deserialized_to!(option_none: Option<&str> = "" => None); - deserialized_to!(option_some: Option<&str> = "non-escaped string" => Some("non-escaped string")); + deserialized_to!(option_some: Option<&str> = "non-escaped-string" => Some("non-escaped-string")); - deserialized_to!(unit: () = "anything" => ()); - deserialized_to!(unit_struct: Unit = "anything" => Unit); + deserialized_to_only!(unit: () = "anything" => ()); + deserialized_to_only!(unit_struct: Unit = "anything" => Unit); deserialized_to!(newtype_owned: Newtype = "<escaped string" => Newtype(" BorrowedNewtype("non-escaped string")); + // Serializer will escape space. Because borrowing has meaning only for deserializer, + // no need to test roundtrip here, it is already tested with non-borrowing version + deserialized_to_only!(newtype_borrowed: BorrowedNewtype = "non-escaped string" + => BorrowedNewtype("non-escaped string")); err!(seq: Vec<()> = "non-escaped string" => Unsupported("sequences are not supported as `xs:list` items")); @@ -1018,8 +1053,8 @@ mod tests { err!(enum_other: Enum = "any data" => Custom("unknown variant `any data`, expected one of `Unit`, `Newtype`, `Tuple`, `Struct`")); - deserialized_to!(identifier: Id = "Field" => Id::Field); - deserialized_to!(ignored_any: Any = "any data" => Any(IgnoredAny)); + deserialized_to_only!(identifier: Id = "Field" => Id::Field); + deserialized_to_only!(ignored_any: Any = "any data" => Any(IgnoredAny)); /// Checks that deserialization from an owned content is working #[test] diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index 11e83042..e397c032 100644 --- a/src/se/simple_type.rs +++ b/src/se/simple_type.rs @@ -3,9 +3,13 @@ //! [simple types]: https://www.w3schools.com/xml/el_simpletype.asp //! [as defined]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition +use crate::errors::serialize::DeError; use crate::escapei::_escape; use crate::se::QuoteLevel; +use serde::ser::{Impossible, Serialize, Serializer}; +use serde::serde_if_integer128; use std::borrow::Cow; +use std::fmt::Write; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum QuoteTarget { @@ -144,9 +148,211 @@ fn escape_list(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow //////////////////////////////////////////////////////////////////////////////////////////////////// +/// A serializer that handles ordinary [simple type definition][item] with +/// `{variety} = atomic`, or an ordinary [simple type] definition with +/// `{variety} = union` whose basic members are all atomic. +/// +/// This serializer can serialize only primitive types: +/// - numbers +/// - booleans +/// - strings +/// - units +/// - options +/// - unit variants of enums +/// +/// Identifiers represented as strings and serialized accordingly. +/// +/// Serialization of all other types returns [`Unsupported`][DeError::Unsupported] error. +/// +/// [item]: https://www.w3.org/TR/xmlschema11-1/#std-item_type_definition +/// [simple type]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition +pub struct AtomicSerializer { + pub writer: W, + pub target: QuoteTarget, + /// Defines which XML characters need to be escaped + pub level: QuoteLevel, +} + +impl AtomicSerializer { + fn write_str(&mut self, value: &str) -> Result<(), DeError> { + Ok(self + .writer + .write_str(&escape_item(value, self.target, self.level))?) + } +} + +impl Serializer for AtomicSerializer { + type Ok = W; + type Error = DeError; + + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + write_primitive!(); + + fn serialize_str(mut self, value: &str) -> Result { + self.write_str(value)?; + Ok(self.writer) + } + + /// We cannot store anything, so the absence of a unit and presence of it + /// does not differ, so serialization of unit returns `Err(Unsupported)` + fn serialize_unit(self) -> Result { + Err(DeError::Unsupported( + "unit type `()` cannot be serialized as an `xs:list` item".into(), + )) + } + + /// We cannot store anything, so the absence of a unit and presence of it + /// does not differ, so serialization of unit returns `Err(Unsupported)` + fn serialize_unit_struct(self, name: &'static str) -> Result { + Err(DeError::Unsupported( + format!( + "unit struct `{}` cannot be serialized as an `xs:list` item", + name + ) + .into(), + )) + } + + /// We cannot store both a variant discriminant and a variant value, + /// so serialization of enum newtype variant returns `Err(Unsupported)` + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + Err(DeError::Unsupported( + format!( + "enum newtype variant `{}::{}` cannot be serialized as an `xs:list` item", + name, variant + ) + .into(), + )) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(DeError::Unsupported( + "sequence cannot be serialized as an `xs:list` item".into(), + )) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(DeError::Unsupported( + "tuple cannot be serialized as an `xs:list` item".into(), + )) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "tuple struct `{}` cannot be serialized as an `xs:list` item", + name + ) + .into(), + )) + } + + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "enum tuple variant `{}::{}` cannot be serialized as an `xs:list` item", + name, variant + ) + .into(), + )) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + "map cannot be serialized as an `xs:list` item".into(), + )) + } + + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "struct `{}` cannot be serialized as an `xs:list` item", + name + ) + .into(), + )) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "enum struct variant `{}::{}` cannot be serialized as an `xs:list` item", + name, variant + ) + .into(), + )) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + #[cfg(test)] mod tests { use super::*; + use crate::utils::Bytes; + use serde::Serialize; + use std::collections::BTreeMap; + + #[derive(Debug, Serialize, PartialEq)] + struct Unit; + + #[derive(Debug, Serialize, PartialEq)] + struct Newtype(usize); + + #[derive(Debug, Serialize, PartialEq)] + struct Tuple(&'static str, usize); + + #[derive(Debug, Serialize, PartialEq)] + struct Struct { + key: &'static str, + val: usize, + } + + #[derive(Debug, Serialize, PartialEq)] + enum Enum { + Unit, + #[serde(rename = "<\"&'>")] + UnitEscaped, + Newtype(usize), + Tuple(&'static str, usize), + Struct { + key: &'static str, + val: usize, + }, + } mod escape_item { use super::*; @@ -393,4 +599,121 @@ mod tests { } } } + + /// Tests for serialize atomic and union values, as defined in XSD specification + mod atomic { + use super::*; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:literal) => { + #[test] + fn $name() { + let ser = AtomicSerializer { + writer: String::new(), + target: QuoteTarget::Text, + level: QuoteLevel::Full, + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = AtomicSerializer { + writer: &mut buffer, + target: QuoteTarget::Text, + level: QuoteLevel::Full, + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + assert_eq!(buffer, ""); + } + }; + } + + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + + serialize_as!(str_non_escaped: "non-escaped-string" => "non-escaped-string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") + => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::<&str>::None => ""); + serialize_as!(option_some: Some("non-escaped-string") => "non-escaped-string"); + + err!(unit: () + => Unsupported("unit type `()` cannot be serialized as an `xs:list` item")); + err!(unit_struct: Unit + => Unsupported("unit struct `Unit` cannot be serialized as an `xs:list` item")); + + serialize_as!(enum_unit: Enum::Unit => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + serialize_as!(newtype: Newtype(42) => "42"); + err!(enum_newtype: Enum::Newtype(42) + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an `xs:list` item")); + + err!(seq: vec![1, 2, 3] + => Unsupported("sequence cannot be serialized as an `xs:list` item")); + err!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => Unsupported("tuple cannot be serialized as an `xs:list` item")); + err!(tuple_struct: Tuple("first", 42) + => Unsupported("tuple struct `Tuple` cannot be serialized as an `xs:list` item")); + err!(enum_tuple: Enum::Tuple("first", 42) + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an `xs:list` item")); + + err!(map: BTreeMap::from([(1, 2), (3, 4)]) + => Unsupported("map cannot be serialized as an `xs:list` item")); + err!(struct_: Struct { key: "answer", val: 42 } + => Unsupported("struct `Struct` cannot be serialized as an `xs:list` item")); + err!(enum_struct: Enum::Struct { key: "answer", val: 42 } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an `xs:list` item")); + } } From 0d955d750fec08fc40aaf3006c8d892344f5a534 Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 28 Aug 2022 22:48:48 +0500 Subject: [PATCH 09/21] Implement SimpleTypeSerializer that used for serializing content of attributes and text nodes --- src/de/simple_type.rs | 52 +++++-- src/se/simple_type.rs | 327 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 368 insertions(+), 11 deletions(-) diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 2a9d2eb2..5181f869 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -828,11 +828,27 @@ impl<'de> VariantAccess<'de> for SimpleTypeUnitOnly { #[cfg(test)] mod tests { use super::*; + use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; + use crate::se::QuoteLevel; use crate::utils::{ByteBuf, Bytes}; use serde::de::IgnoredAny; use serde::{Deserialize, Serialize}; use std::collections::HashMap; + macro_rules! simple_only { + ($encoding:ident, $name:ident: $type:ty = $xml:expr => $result:expr) => { + #[test] + fn $name() { + let decoder = Decoder::$encoding(); + let xml = $xml; + let de = SimpleTypeDeserializer::new(CowRef::Input(xml.as_ref()), true, decoder); + let data: $type = Deserialize::deserialize(de).unwrap(); + + assert_eq!(data, $result); + } + }; + } + macro_rules! simple { ($encoding:ident, $name:ident: $type:ty = $xml:expr => $result:expr) => { #[test] @@ -843,6 +859,17 @@ mod tests { let data: $type = Deserialize::deserialize(de).unwrap(); assert_eq!(data, $result); + + // Roundtrip to ensure that serializer corresponds to deserializer + assert_eq!( + data.serialize(SimpleTypeSerializer { + writer: String::new(), + target: QuoteTarget::Text, + level: QuoteLevel::Full, + }) + .unwrap(), + xml + ); } }; } @@ -910,8 +937,7 @@ mod tests { /// Tests for deserialize atomic and union values, as defined in XSD specification mod atomic { use super::*; - use crate::se::simple_type::{AtomicSerializer, QuoteTarget}; - use crate::se::QuoteLevel; + use crate::se::simple_type::AtomicSerializer; use pretty_assertions::assert_eq; /// Checks that given `$input` successfully deserializing into given `$result` @@ -1207,7 +1233,7 @@ mod tests { simple!(utf8, char_unescaped: char = "h" => 'h'); simple!(utf8, char_escaped: char = "<" => '<'); - simple!(utf8, string: String = "<escaped string" => " " Unsupported("binary data content is not supported by XML format")); @@ -1218,11 +1244,17 @@ mod tests { simple!(utf8, option_none: Option<&str> = "" => None); simple!(utf8, option_some: Option<&str> = "non-escaped string" => Some("non-escaped string")); - simple!(utf8, unit: () = "any data" => ()); - simple!(utf8, unit_struct: Unit = "any data" => Unit); + simple_only!(utf8, unit: () = "any data" => ()); + simple_only!(utf8, unit_struct: Unit = "any data" => Unit); - simple!(utf8, newtype_owned: Newtype = "<escaped string" => Newtype(" BorrowedNewtype("non-escaped string")); + // Serializer will not escape space because this is unnecessary. + // Because borrowing has meaning only for deserializer, no need to test + // roundtrip here, it is already tested for strings where compatible list + // of escaped characters is used + simple_only!(utf8, newtype_owned: Newtype = "<escaped string" + => Newtype(" BorrowedNewtype("non-escaped string")); err!(utf8, map: HashMap<(), ()> = "any data" => Unsupported("maps are not supported for XSD `simpleType`s")); @@ -1239,8 +1271,8 @@ mod tests { err!(utf8, enum_other: Enum = "any data" => Custom("unknown variant `any data`, expected one of `Unit`, `Newtype`, `Tuple`, `Struct`")); - simple!(utf8, identifier: Id = "Field" => Id::Field); - simple!(utf8, ignored_any: Any = "any data" => Any(IgnoredAny)); + simple_only!(utf8, identifier: Id = "Field" => Id::Field); + simple_only!(utf8, ignored_any: Any = "any data" => Any(IgnoredAny)); } #[cfg(feature = "encoding")] @@ -1258,7 +1290,7 @@ mod tests { macro_rules! utf16 { ($name:ident: $type:ty = $xml:literal => $result:expr) => { - simple!(utf16, $name: $type = to_utf16($xml) => $result); + simple_only!(utf16, $name: $type = to_utf16($xml) => $result); }; } diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index e397c032..14f0160b 100644 --- a/src/se/simple_type.rs +++ b/src/se/simple_type.rs @@ -6,7 +6,9 @@ use crate::errors::serialize::DeError; use crate::escapei::_escape; use crate::se::QuoteLevel; -use serde::ser::{Impossible, Serialize, Serializer}; +use serde::ser::{ + Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer, +}; use serde::serde_if_integer128; use std::borrow::Cow; use std::fmt::Write; @@ -319,6 +321,214 @@ impl Serializer for AtomicSerializer { //////////////////////////////////////////////////////////////////////////////////////////////////// +/// A serializer for a values representing XSD [simple types], which used in: +/// - attribute values (`<... ...="value" ...>`) +/// - text content (`<...>text`) +/// - CDATA content (`<...>`) +/// +/// [simple types]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition +pub struct SimpleTypeSerializer { + /// Writer to which this serializer writes content + pub writer: W, + /// Target for which element is serializing. Affects additional characters to escape. + pub target: QuoteTarget, + /// Defines which XML characters need to be escaped + pub level: QuoteLevel, +} + +impl SimpleTypeSerializer { + fn write_str(&mut self, value: &str) -> Result<(), DeError> { + Ok(self + .writer + .write_str(&escape_list(value, self.target, self.level))?) + } +} + +impl Serializer for SimpleTypeSerializer { + type Ok = W; + type Error = DeError; + + type SerializeSeq = SimpleSeq; + type SerializeTuple = SimpleSeq; + type SerializeTupleStruct = SimpleSeq; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + write_primitive!(); + + fn serialize_str(mut self, value: &str) -> Result { + self.write_str(value)?; + Ok(self.writer) + } + + /// Does not write anything + fn serialize_unit(self) -> Result { + Ok(self.writer) + } + + /// Does not write anything + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(self.writer) + } + + /// We cannot store both a variant discriminant and a variant value, + /// so serialization of enum newtype variant returns `Err(Unsupported)` + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + Err(DeError::Unsupported( + format!("enum newtype variant `{}::{}` cannot be serialized as an attribute or text content value", name, variant).into(), + )) + } + + #[inline] + fn serialize_seq(self, _len: Option) -> Result { + Ok(SimpleSeq { + writer: self.writer, + target: self.target, + level: self.level, + first: true, + }) + } + + #[inline] + fn serialize_tuple(self, _len: usize) -> Result { + self.serialize_seq(None) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + self.serialize_seq(None) + } + + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("enum tuple variant `{}::{}` cannot be serialized as an attribute or text content value", name, variant).into(), + )) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + "map cannot be serialized as an attribute or text content value".into(), + )) + } + + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "struct `{}` cannot be serialized as an attribute or text content value", + name + ) + .into(), + )) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("enum struct variant `{}::{}` cannot be serialized as an attribute or text content value", name, variant).into(), + )) + } +} + +/// Serializer for a sequence of atomic values delimited by space +pub struct SimpleSeq { + writer: W, + target: QuoteTarget, + level: QuoteLevel, + /// If `true`, nothing was written yet + first: bool, +} + +impl SerializeSeq for SimpleSeq { + type Ok = W; + type Error = DeError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + if !self.first { + self.writer.write_char(' ')?; + } + self.first = false; + value.serialize(AtomicSerializer { + writer: &mut self.writer, + target: self.target, + level: self.level, + })?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.writer) + } +} + +impl SerializeTuple for SimpleSeq { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +impl SerializeTupleStruct for SimpleSeq { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + #[cfg(test)] mod tests { use super::*; @@ -716,4 +926,119 @@ mod tests { err!(enum_struct: Enum::Struct { key: "answer", val: 42 } => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an `xs:list` item")); } + + mod simple_type { + use super::*; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:literal) => { + #[test] + fn $name() { + let ser = SimpleTypeSerializer { + writer: String::new(), + target: QuoteTarget::Text, + level: QuoteLevel::Full, + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = SimpleTypeSerializer { + writer: &mut buffer, + target: QuoteTarget::Text, + level: QuoteLevel::Full, + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + assert_eq!(buffer, ""); + } + }; + } + + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") + => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::<&str>::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + + serialize_as!(enum_unit: Enum::Unit => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + serialize_as!(newtype: Newtype(42) => "42"); + err!(enum_newtype: Enum::Newtype(42) + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + serialize_as!(seq: vec![1, 2, 3] => "1 2 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(seq_with_1_empty_str: vec![""] => ""); + serialize_as!(seq_with_2_empty_strs: vec!["", ""] => " "); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> with spaces 3"); + serialize_as!(tuple_struct: Tuple("first", 42) => "first 42"); + err!(enum_tuple: Enum::Tuple("first", 42) + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + err!(map: BTreeMap::from([(1, 2), (3, 4)]) + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: Struct { key: "answer", val: 42 } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: Enum::Struct { key: "answer", val: 42 } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } } From 459b9bff0fb1bfe2d6622a463e9393ed5affec6c Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 3 Apr 2022 02:41:05 +0500 Subject: [PATCH 10/21] Remove `$unflatten=` prefix, introduced in #298 This prefix just does nothing. Deserialzier already works as with them, out-of-box. Support for namespaces will be done in another PR This commit reverts 8cd971e85a53ab0448dd8ee8923e2c3e702b19f3 Removed tests covered by following existing tests: - test_parse_unflatten_field - serde-de::struct_::elements - test_issue305_unflatten_namespace - serde-de::struct_::namespaces - test_issue305_unflatten_nesting - serde-de::nested_struct Fixes #430 --- Changelog.md | 6 ++++ README.md | 14 -------- src/de/map.rs | 34 +++---------------- src/de/mod.rs | 1 - src/se/var.rs | 33 ++++++++----------- tests/serde_roundtrip.rs | 20 ------------ tests/test.rs | 70 ---------------------------------------- 7 files changed, 23 insertions(+), 155 deletions(-) diff --git a/Changelog.md b/Changelog.md index 034bd905..ebe8b725 100644 --- a/Changelog.md +++ b/Changelog.md @@ -16,6 +16,12 @@ ### Misc Changes +- [#490]: Removed `$unflatten=` special prefix for fields for serde (de)serializer, because: + - it is useless for deserializer + - serializer was rewritten and does not require it anymore + +[#490]: https://github.com/tafia/quick-xml/pull/490 + ## 0.26.0 -- 2022-10-23 ### Misc Changes diff --git a/README.md b/README.md index c55e45ff..3d971f29 100644 --- a/README.md +++ b/README.md @@ -129,20 +129,6 @@ struct Foo { } ``` -### Unflattening structs into verbose XML - -If your XML files look like `valuevalue`, you can -(de)serialize them with the special name prefix `$unflatten=`: - -```rust,ignore -struct Root { - #[serde(rename = "$unflatten=first")] - first: String, - #[serde(rename = "$unflatten=second")] - other_field: String, -} -``` - ### Serializing unit variants as primitives The `$primitive` prefix lets you serialize enum variants without associated values (internally referred to as _unit variants_) as primitive strings rather than self-closing tags. Consider the following definitions: diff --git a/src/de/map.rs b/src/de/map.rs index 76524e68..c7b3b165 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -4,7 +4,7 @@ use crate::{ de::escape::EscapedDeserializer, de::seq::{not_in, TagFilter}, de::simple_type::SimpleTypeDeserializer, - de::{str2bool, DeEvent, Deserializer, XmlRead, INNER_VALUE, UNFLATTEN_PREFIX}, + de::{str2bool, DeEvent, Deserializer, XmlRead, INNER_VALUE}, errors::serialize::DeError, events::attributes::IterState, events::BytesStart, @@ -187,8 +187,6 @@ where /// value for INNER_VALUE field /// ``` has_value_field: bool, - /// list of fields yet to unflatten (defined as starting with $unflatten=) - unflatten_fields: Vec<&'static [u8]>, } impl<'de, 'a, R> MapAccess<'de, 'a, R> @@ -208,11 +206,6 @@ where source: ValueSource::Unknown, fields, has_value_field: fields.contains(&INNER_VALUE), - unflatten_fields: fields - .iter() - .filter(|f| f.starts_with(UNFLATTEN_PREFIX)) - .map(|f| f.as_bytes()) - .collect(), }) } } @@ -275,28 +268,9 @@ where } DeEvent::Start(e) => { self.source = ValueSource::Nested; - let key = if let Some(p) = self - .unflatten_fields - .iter() - .position(|f| e.name().as_ref() == &f[UNFLATTEN_PREFIX.len()..]) - { - // Used to deserialize elements, like: - // - // test - // - // - // into - // - // struct Root { - // #[serde(rename = "$unflatten=xxx")] - // xxx: String, - // } - seed.deserialize(self.unflatten_fields.remove(p).into_deserializer()) - } else { - let name = Cow::Borrowed(e.local_name().into_inner()); - seed.deserialize(EscapedDeserializer::new(name, decoder, false)) - }; - key.map(Some) + let name = Cow::Borrowed(e.local_name().into_inner()); + seed.deserialize(EscapedDeserializer::new(name, decoder, false)) + .map(Some) } // Stop iteration after reaching a closing tag DeEvent::End(e) if e.name() == self.start.name() => Ok(None), diff --git a/src/de/mod.rs b/src/de/mod.rs index e2593363..79a37f2a 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -125,7 +125,6 @@ use std::io::BufRead; use std::num::NonZeroUsize; pub(crate) const INNER_VALUE: &str = "$value"; -pub(crate) const UNFLATTEN_PREFIX: &str = "$unflatten="; pub(crate) const PRIMITIVE_PREFIX: &str = "$primitive="; /// Simplified event which contains only these variants that used by deserializer diff --git a/src/se/var.rs b/src/se/var.rs index c3953684..3cd3c099 100644 --- a/src/se/var.rs +++ b/src/se/var.rs @@ -1,5 +1,5 @@ use crate::{ - de::{INNER_VALUE, UNFLATTEN_PREFIX}, + de::INNER_VALUE, errors::{serialize::DeError, Error}, events::{BytesEnd, BytesStart, Event}, se::key::QNameSerializer, @@ -7,7 +7,6 @@ use crate::{ writer::Writer, }; use serde::ser::{self, Serialize}; -use serde::Serializer as _; use std::io::Write; /// An implementation of `SerializeMap` for serializing to XML. @@ -130,24 +129,18 @@ where ) -> Result<(), DeError> { // TODO: Inherit indentation state from self.parent.writer let writer = Writer::new(&mut self.buffer); - if key.starts_with(UNFLATTEN_PREFIX) { - let key = &key[UNFLATTEN_PREFIX.len()..]; - let mut serializer = Serializer::with_root(writer, Some(key)); - serializer.serialize_newtype_struct(key, value)?; - self.children.append(&mut self.buffer); - } else { - let mut serializer = Serializer::with_root(writer, Some(key)); - value.serialize(&mut serializer)?; - - if !self.buffer.is_empty() { - if self.buffer[0] == b'<' || key == INNER_VALUE { - // Drains buffer, moves it to children - self.children.append(&mut self.buffer); - } else { - self.attrs - .push_attribute((key.as_bytes(), self.buffer.as_ref())); - self.buffer.clear(); - } + + let mut serializer = Serializer::with_root(writer, Some(key)); + value.serialize(&mut serializer)?; + + if !self.buffer.is_empty() { + if self.buffer[0] == b'<' || key == INNER_VALUE { + // Drains buffer, moves it to children + self.children.append(&mut self.buffer); + } else { + self.attrs + .push_attribute((key.as_bytes(), self.buffer.as_ref())); + self.buffer.clear(); } } diff --git a/tests/serde_roundtrip.rs b/tests/serde_roundtrip.rs index dc183654..c14eaaf5 100644 --- a/tests/serde_roundtrip.rs +++ b/tests/serde_roundtrip.rs @@ -51,23 +51,3 @@ fn round_trip_list_of_enums() { let deserialized_nodes: Nodes = from_str(serialized_nodes.as_str()).unwrap(); assert_eq!(deserialized_nodes, nodes); } - -#[test] -fn test_parse_unflatten_field() { - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] - struct Unflatten { - #[serde(rename = "$unflatten=NewKey")] - field: String, - } - - let source = "Foo"; - let expected = Unflatten { - field: "Foo".to_string(), - }; - - let parsed: Unflatten = ::quick_xml::de::from_str(source).unwrap(); - assert_eq!(&parsed, &expected); - - let stringified = to_string(&parsed).unwrap(); - assert_eq!(&stringified, source); -} diff --git a/tests/test.rs b/tests/test.rs index 8eaee46e..55da32fa 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -5,9 +5,6 @@ use quick_xml::reader::Reader; use quick_xml::Error; use std::borrow::Cow; -#[cfg(feature = "serialize")] -use serde::Deserialize; - use pretty_assertions::assert_eq; #[test] @@ -188,70 +185,3 @@ fn test_issue299() -> Result<(), Error> { } Ok(()) } - -#[cfg(feature = "serialize")] -#[test] -fn test_issue305_unflatten_namespace() -> Result<(), quick_xml::DeError> { - use quick_xml::de::from_str; - - #[derive(Deserialize, Debug, PartialEq)] - struct NamespaceBug { - #[serde(rename = "$unflatten=d:test2")] - test2: String, - } - - let namespace_bug: NamespaceBug = from_str( - r#" - - - doesntwork - "#, - )?; - - assert_eq!( - namespace_bug, - NamespaceBug { - test2: "doesntwork".into(), - } - ); - - Ok(()) -} - -#[cfg(feature = "serialize")] -#[test] -fn test_issue305_unflatten_nesting() -> Result<(), quick_xml::DeError> { - use quick_xml::de::from_str; - - #[derive(Deserialize, Debug, PartialEq)] - struct InnerNestingBug {} - - #[derive(Deserialize, Debug, PartialEq)] - struct NestingBug { - // comment out one of these fields and it works - #[serde(rename = "$unflatten=outer1")] - outer1: InnerNestingBug, - - #[serde(rename = "$unflatten=outer2")] - outer2: String, - } - - let nesting_bug: NestingBug = from_str::( - r#" - - - - - "#, - )?; - - assert_eq!( - nesting_bug, - NestingBug { - outer1: InnerNestingBug {}, - outer2: "".into(), - } - ); - - Ok(()) -} From 931db2e28052e9bf16a86cf012718ce301611883 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 8 Sep 2022 00:04:52 +0500 Subject: [PATCH 11/21] Add a `$text` special name - `$text` serialized as a simple type - `$value` serialized as an element with name that depends from value instead of key - Text or CDATA elements deserialized as `$value` field, if that field is expected, and as `#text` field, if not expected - any not-listed in known fields elements deserialized as a `$value` field, if that field is expected doc-test failures (2): src\de\mod.rs - de (line 139) src\de\mod.rs - de (line 36) Co-authored-by: Daniel Alley --- Changelog.md | 11 ++ README.md | 7 +- benches/microbenches.rs | 4 +- src/de/map.rs | 45 ++++---- src/de/mod.rs | 239 +++++++++++++++++++++++++++++++++++++++- src/se/var.rs | 4 +- tests/serde-de.rs | 95 ++++++++-------- tests/serde-se.rs | 50 ++++----- 8 files changed, 356 insertions(+), 99 deletions(-) diff --git a/Changelog.md b/Changelog.md index ebe8b725..752d63dc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -19,8 +19,19 @@ - [#490]: Removed `$unflatten=` special prefix for fields for serde (de)serializer, because: - it is useless for deserializer - serializer was rewritten and does not require it anymore +- [#490]: In addition to the `$value` special name for a field a new `$text` + special name was added: + - `$text` is used if you want to map field to text content only. No markup is + expected (but text can represent a list as defined by `xs:list` type) + - `$value` is used if you want to map elements with different names to one field, + that should be represented either by an `enum`, or by sequence of `enum`s + (`Vec`, tuple, etc.), or by string. Use it when you want to map field to any + content of the field, text or markup + + Refer to [documentation] for details. [#490]: https://github.com/tafia/quick-xml/pull/490 +[documentation]: https://docs.rs/quick-xml/0.27.0/quick_xml/de/index.html#difference-between-text-and-value-special-names ## 0.26.0 -- 2022-10-23 diff --git a/README.md b/README.md index 3d971f29..21897d2a 100644 --- a/README.md +++ b/README.md @@ -119,16 +119,19 @@ quick-xml follows its convention for deserialization, including the ### Parsing the "value" of a tag -If you have an input of the form `bar`, and you want to get at the `bar`, you can use the special name `$value`: +If you have an input of the form `bar`, and you want to get at the `bar`, +you can use either the special name `$text`, or the special name `$value`: ```rust,ignore struct Foo { pub abc: String, - #[serde(rename = "$value")] + #[serde(rename = "$text")] pub body: String, } ``` +Read about the difference in the [documentation](https://docs.rs/quick-xml/latest/quick_xml/de/index.html#difference-between-text-and-value-special-names). + ### Serializing unit variants as primitives The `$primitive` prefix lets you serialize enum variants without associated values (internally referred to as _unit variants_) as primitive strings rather than self-closing tags. Consider the following definitions: diff --git a/benches/microbenches.rs b/benches/microbenches.rs index c8556fe4..aa5c8b70 100644 --- a/benches/microbenches.rs +++ b/benches/microbenches.rs @@ -24,7 +24,7 @@ volutpat sed cras ornare arcu dui vivamus arcu. Cursus in hac habitasse platea d purus. Consequat id porta nibh venenatis cras sed felis."; /// Benchmarks the `Reader::read_event` function with all XML well-formless -/// checks disabled (with and without trimming content of #text nodes) +/// checks disabled (with and without trimming content of $text nodes) fn read_event(c: &mut Criterion) { let mut group = c.benchmark_group("read_event"); group.bench_function("trim_text = false", |b| { @@ -70,7 +70,7 @@ fn read_event(c: &mut Criterion) { } /// Benchmarks the `NsReader::read_resolved_event_into` function with all XML well-formless -/// checks disabled (with and without trimming content of #text nodes) +/// checks disabled (with and without trimming content of $text nodes) fn read_resolved_event_into(c: &mut Criterion) { let mut group = c.benchmark_group("NsReader::read_resolved_event_into"); group.bench_function("trim_text = false", |b| { diff --git a/src/de/map.rs b/src/de/map.rs index c7b3b165..a7a957b5 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -4,7 +4,7 @@ use crate::{ de::escape::EscapedDeserializer, de::seq::{not_in, TagFilter}, de::simple_type::SimpleTypeDeserializer, - de::{str2bool, DeEvent, Deserializer, XmlRead, INNER_VALUE}, + de::{str2bool, DeEvent, Deserializer, XmlRead, TEXT_KEY, VALUE_KEY}, errors::serialize::DeError, events::attributes::IterState, events::BytesStart, @@ -49,19 +49,19 @@ enum ValueSource { Text, /// Next value should be deserialized from an element with an any name, except /// elements with a name matching one of the struct fields. Corresponding tag - /// name will always be associated with a field with name [`INNER_VALUE`]. + /// name will always be associated with a field with name [`VALUE_KEY`]. /// /// That state is set when call to [`peek()`] returns a [`Start`] event, which /// [`name()`] is not listed in the [list of known fields] (which for a struct /// is a list of field names, and for a map that is an empty list), _and_ - /// struct has a field with a special name [`INNER_VALUE`]. + /// struct has a field with a special name [`VALUE_KEY`]. /// /// When in this state, next event, returned by [`next()`], will be a [`Start`], /// which represents both a key, and a value. Value would be deserialized from /// the whole element and how is will be done determined by the value deserializer. /// The [`MapAccess`] do not consume any events in that state. /// - /// Because in that state any encountered `` is mapped to the [`INNER_VALUE`] + /// Because in that state any encountered `` is mapped to the [`VALUE_KEY`] /// field, it is possible to use tag name as an enum discriminator, so `enum`s /// can be deserialized from that XMLs: /// @@ -97,9 +97,6 @@ enum ValueSource { /// of a `...` or `...` node, including /// the tag name. /// - /// Currently, processing of that enum variant is fully equivalent to the - /// processing of a [`Text`] variant. Split of variants made for clarity. - /// /// [`Start`]: DeEvent::Start /// [`peek()`]: Deserializer::peek() /// [`next()`]: Deserializer::next() @@ -136,7 +133,7 @@ enum ValueSource { /// variant based on the tag name. If that is needed, then [`Content`] variant /// of this enum should be used. Such usage is enabled by annotating a struct /// field as "content" field, which implemented as given the field a special - /// [`INNER_VALUE`] name. + /// [`VALUE_KEY`] name. /// /// [`Start`]: DeEvent::Start /// [`peek()`]: Deserializer::peek() @@ -180,11 +177,11 @@ where /// List of field names of the struct. It is empty for maps fields: &'static [&'static str], /// If `true`, then the deserialized struct has a field with a special name: - /// [`INNER_VALUE`]. That field should be deserialized from the text content - /// of an XML node: + /// [`VALUE_KEY`]. That field should be deserialized from the whole content + /// of an XML node, including tag name: /// /// ```xml - /// value for INNER_VALUE field + /// value for VALUE_KEY field /// ``` has_value_field: bool, } @@ -205,7 +202,7 @@ where start, source: ValueSource::Unknown, fields, - has_value_field: fields.contains(&INNER_VALUE), + has_value_field: fields.contains(&VALUE_KEY), }) } } @@ -239,12 +236,22 @@ where } else { // try getting from events (value) match self.de.peek()? { + // We shouldn't have both `$value` and `$text` fields in the same + // struct, so if we have `$value` field, the we should deserialize + // text content to `$value` + DeEvent::Text(_) | DeEvent::CData(_) if self.has_value_field => { + self.source = ValueSource::Content; + // Deserialize `key` from special attribute name which means + // that value should be taken from the text content of the + // XML node + seed.deserialize(VALUE_KEY.into_deserializer()).map(Some) + } DeEvent::Text(_) | DeEvent::CData(_) => { self.source = ValueSource::Text; // Deserialize `key` from special attribute name which means // that value should be taken from the text content of the // XML node - seed.deserialize(INNER_VALUE.into_deserializer()).map(Some) + seed.deserialize(TEXT_KEY.into_deserializer()).map(Some) } // Used to deserialize collections of enums, like: // @@ -264,7 +271,7 @@ where // See https://github.com/serde-rs/serde/issues/1905 DeEvent::Start(e) if self.has_value_field && not_in(self.fields, e, decoder)? => { self.source = ValueSource::Content; - seed.deserialize(INNER_VALUE.into_deserializer()).map(Some) + seed.deserialize(VALUE_KEY.into_deserializer()).map(Some) } DeEvent::Start(e) => { self.source = ValueSource::Nested; @@ -299,7 +306,7 @@ where // text value // // The whole map represented by an `` element, the map key - // is implicit and equals to the `INNER_VALUE` constant, and the value + // is implicit and equals to the `TEXT_KEY` constant, and the value // is a `Text` or a `CData` event (the value deserializer will see one // of that events) // This case are checked by "xml_schema_lists::element" tests in tests/serde-de.rs @@ -322,7 +329,7 @@ where // ... // // The whole map represented by an `` element, the map key - // is implicit and equals to the `INNER_VALUE` constant, and the value + // is implicit and equals to the `VALUE_KEY` constant, and the value // is a `Start` event (the value deserializer will see that event) ValueSource::Content => seed.deserialize(MapValueDeserializer { map: self, @@ -425,7 +432,7 @@ where /// ``` /// /// The whole map represented by an `` element, the map key is - /// implicit and equals to the [`INNER_VALUE`] constant, and the value is + /// implicit and equals to the [`VALUE_KEY`] constant, and the value is /// a [`Text`], a [`CData`], or a [`Start`] event (the value deserializer /// will see one of those events). In the first two cases the value of this /// field do not matter (because we already see the textual event and there @@ -435,7 +442,7 @@ where /// /// ```ignore /// struct AnyName { - /// #[serde(rename = "$value")] + /// #[serde(rename = "$text")] /// any_name: String, /// } /// ``` @@ -444,7 +451,7 @@ where /// Changing this can be valuable for , /// but those fields should be explicitly marked that they want to get any /// possible markup as a `String` and that mark is different from marking them - /// as accepting "text content" which the currently `$value` means. + /// as accepting "text content" which the currently `$text` means. /// /// [`Text`]: DeEvent::Text /// [`CData`]: DeEvent::CData diff --git a/src/de/mod.rs b/src/de/mod.rs index 79a37f2a..e54eb625 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -1,4 +1,232 @@ //! Serde `Deserializer` module +//! +//! # Difference between `$text` and `$value` special names +//! +//! quick-xml supports two special names for fields -- `$text` and `$value`. +//! Although they may seem the same, there is a distinction. Two different +//! names is required mostly for serialization, because quick-xml should know +//! how you want to serialize certain constructs, which could be represented +//! through XML in multiple different ways. +//! +//! The only difference in how complex types and sequences are serialized. +//! If you doubt which one you should select, begin with [`$value`](#value). +//! +//! ## `$text` +//! `$text` is used when you want to write your XML as a text or a CDATA content. +//! More formally, field with that name represents simple type definition with +//! `{variety} = atomic` or `{variety} = union` whose basic members are all atomic, +//! as described in the [specification]. +//! +//! As a result, not all types of such fields can be serialized. Only serialization +//! of following types are supported: +//! - all primitive types (strings, numbers, booleans) +//! - unit variants of enumerations (serializes to a name of a variant) +//! - newtypes (delegates serialization to inner type) +//! - [`Option`] of above (`None` serializes to nothing) +//! - sequences (including tuples and tuple variants of enumerations) of above, +//! excluding `None` and empty string elements (because it will not be possible +//! to deserialize them back). The elements are separated by space(s) +//! - unit type `()` and unit structs (serializes to nothing) +//! +//! Complex types, such as structs and maps, are not supported in this field. +//! If you want them, you should use `$value`. +//! +//! Sequences serialized to a space-delimited string, that is why only certain +//! types are allowed in this mode: +//! +//! ``` +//! # use serde::{Deserialize, Serialize}; +//! # use quick_xml::de::from_str; +//! # use quick_xml::se::to_string; +//! #[derive(Deserialize, Serialize, PartialEq, Debug)] +//! struct AnyName { +//! #[serde(rename = "$text")] +//! field: Vec, +//! } +//! +//! let obj = AnyName { field: vec![1, 2, 3] }; +//! let xml = to_string(&obj).unwrap(); +//! assert_eq!(xml, "1 2 3"); +//! +//! let object: AnyName = from_str(&xml).unwrap(); +//! assert_eq!(object, obj); +//! ``` +//! +//! ## `$value` +//! > Note: a name `#content` would better explain the purpose of that field, +//! > but `$value` is used for compatibility with other XML serde crates, which +//! > uses that name. This allow you to switch XML crate more smoothly if required. +//! +//! Representation of primitive types in `$value` does not differ from their +//! representation in `$text` field. The difference is how sequences are serialized. +//! `$value` serializes each sequence item as a separate XML element. The name +//! of that element is taken from serialized type, and because only `enum`s provide +//! such name (their variant name), only they should be used for such fields. +//! +//! `$value` fields does not support `struct` types with fields, the serialization +//! of such types would end with an `Err(Unsupported)`. Unit structs and unit +//! type `()` serializing to nothing and can be deserialized from any content. +//! +//! Serialization and deserialization of `$value` field performed as usual, except +//! that name for an XML element will be given by the serialized type, instead of +//! field. The latter allow to serialize enumerated types, where variant is encoded +//! as a tag name, and, so, represent an XSD `xs:choice` schema by the Rust `enum`. +//! +//! In the example below, field will be serialized as ``, because elements +//! get their names from the field name. It cannot be deserialized, because `Enum` +//! expects elements ``, `` or ``, but `AnyName` looked only for ``: +//! +//! ```no_run +//! # use serde::{Deserialize, Serialize}; +//! #[derive(Deserialize, Serialize)] +//! enum Enum { A, B, C } +//! +//! #[derive(Deserialize, Serialize)] +//! struct AnyName { +//! // +//! field: Enum, +//! } +//! ``` +//! +//! If you rename field to `$value`, then `field` would be serialized as ``, +//! `` or ``, depending on the its content. It is also possible to +//! deserialize it from the same elements: +//! +//! ```no_run +//! # use serde::{Deserialize, Serialize}; +//! # #[derive(Deserialize, Serialize)] +//! # enum Enum { A, B, C } +//! # +//! #[derive(Deserialize, Serialize)] +//! struct AnyName { +//! // , or +//! #[serde(rename = "$value")] +//! field: Enum, +//! } +//! ``` +//! +//! ### Primitives and sequences of primitives +//! +//! Sequences serialized to a list of elements. Note, that types that does not +//! produce their own tag (i. e. primitives) are written as is, without delimiters: +//! +//! ``` +//! # use serde::{Deserialize, Serialize}; +//! # use quick_xml::de::from_str; +//! # use quick_xml::se::to_string; +//! #[derive(Deserialize, Serialize, PartialEq, Debug)] +//! struct AnyName { +//! #[serde(rename = "$value")] +//! field: Vec, +//! } +//! +//! let obj = AnyName { field: vec![1, 2, 3] }; +//! let xml = to_string(&obj).unwrap(); +//! // Note, that types that does not produce their own tag are written as is! +//! assert_eq!(xml, "123"); +//! +//! let object: AnyName = from_str("123").unwrap(); +//! assert_eq!(object, AnyName { field: vec![123] }); +//! +//! // `1 2 3` is mapped to a single `usize` element +//! // It is impossible to deserialize list of primitives to such field +//! from_str::("1 2 3").unwrap_err(); +//! ``` +//! +//! A particular case of that example is a string `$value` field, which probably +//! would be a most used example of that attribute: +//! +//! ``` +//! # use serde::{Deserialize, Serialize}; +//! # use quick_xml::de::from_str; +//! # use quick_xml::se::to_string; +//! #[derive(Deserialize, Serialize, PartialEq, Debug)] +//! struct AnyName { +//! #[serde(rename = "$value")] +//! field: String, +//! } +//! +//! let obj = AnyName { field: "content".to_string() }; +//! let xml = to_string(&obj).unwrap(); +//! assert_eq!(xml, "content"); +//! ``` +//! +//! ### Structs and sequences of structs +//! +//! Note, that structures does not have serializable name as well (name of the +//! type are never used), so it is impossible to serialize non-unit struct or +//! sequence of non-unit structs in `$value` field. (sequences of) unit structs +//! are serialized as empty string, although, because units itself serializing +//! to nothing: +//! +//! ``` +//! # use serde::{Deserialize, Serialize}; +//! # use quick_xml::de::from_str; +//! # use quick_xml::se::to_string; +//! #[derive(Deserialize, Serialize, PartialEq, Debug)] +//! struct Unit; +//! +//! #[derive(Deserialize, Serialize, PartialEq, Debug)] +//! struct AnyName { +//! // #[serde(default)] is required to deserialization of empty lists +//! // This is a general note, not related to $value +//! #[serde(rename = "$value", default)] +//! field: Vec, +//! } +//! +//! let obj = AnyName { field: vec![Unit, Unit, Unit] }; +//! let xml = to_string(&obj).unwrap(); +//! assert_eq!(xml, ""); +//! +//! let object: AnyName = from_str("").unwrap(); +//! assert_eq!(object, AnyName { field: vec![] }); +//! +//! let object: AnyName = from_str("").unwrap(); +//! assert_eq!(object, AnyName { field: vec![] }); +//! +//! let object: AnyName = from_str("").unwrap(); +//! assert_eq!(object, AnyName { field: vec![Unit, Unit, Unit] }); +//! ``` +//! +//! ### Enums and sequences of enums +//! +//! Enumerations uses the variant name as an element name: +//! +//! ``` +//! # use serde::{Deserialize, Serialize}; +//! # use quick_xml::de::from_str; +//! # use quick_xml::se::to_string; +//! #[derive(Deserialize, Serialize, PartialEq, Debug)] +//! struct AnyName { +//! #[serde(rename = "$value")] +//! field: Vec, +//! } +//! +//! #[derive(Deserialize, Serialize, PartialEq, Debug)] +//! enum Enum { A, B, C } +//! +//! let obj = AnyName { field: vec![Enum::A, Enum::B, Enum::C] }; +//! let xml = to_string(&obj).unwrap(); +//! assert_eq!( +//! xml, +//! "\ +//! \ +//! \ +//! \ +//! " +//! ); +//! +//! let object: AnyName = from_str(&xml).unwrap(); +//! assert_eq!(object, obj); +//! ``` +//! +//! ---------------------------------------------------------------------------- +//! +//! You can have either `$text` or `$value` field in your structs. Unfortunately, +//! that is not enforced, so you can theoretically have both, but you should +//! avoid that. +//! +//! [specification]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition // Macros should be defined before the modules that using them // Also, macros should be imported before using them @@ -124,7 +352,10 @@ use std::io::BufRead; #[cfg(feature = "overlapped-lists")] use std::num::NonZeroUsize; -pub(crate) const INNER_VALUE: &str = "$value"; +/// Data represented by a text node or a CDATA node. XML markup is not expected +pub(crate) const TEXT_KEY: &str = "$text"; +/// Data represented by any XML markup inside +pub(crate) const VALUE_KEY: &str = "$value"; pub(crate) const PRIMITIVE_PREFIX: &str = "$primitive="; /// Simplified event which contains only these variants that used by deserializer @@ -274,7 +505,7 @@ where /// Set the maximum number of events that could be skipped during deserialization /// of sequences. /// - /// If `` contains more than specified nested elements, `#text` or + /// If `` contains more than specified nested elements, `$text` or /// CDATA nodes, then [`DeError::TooManyEvents`] will be returned during /// deserialization of sequence field (any type that uses [`deserialize_seq`] /// for the deserialization, for example, `Vec`). @@ -316,7 +547,7 @@ where /// /// - `` /// - `` - /// - `#text(with text)` + /// - `$text(with text)` /// - `` /// - `` (virtual start event) /// - `` (vitrual end event) @@ -981,7 +1212,7 @@ mod tests { let checkpoint = de.skip_checkpoint(); assert_eq!(checkpoint, 0); - // Skip `#text` node and consume after it + // Skip `$text` node and consume after it de.skip().unwrap(); assert_eq!( de.read, diff --git a/src/se/var.rs b/src/se/var.rs index 3cd3c099..15991b57 100644 --- a/src/se/var.rs +++ b/src/se/var.rs @@ -1,5 +1,5 @@ use crate::{ - de::INNER_VALUE, + de::{TEXT_KEY, VALUE_KEY}, errors::{serialize::DeError, Error}, events::{BytesEnd, BytesStart, Event}, se::key::QNameSerializer, @@ -134,7 +134,7 @@ where value.serialize(&mut serializer)?; if !self.buffer.is_empty() { - if self.buffer[0] == b'<' || key == INNER_VALUE { + if self.buffer[0] == b'<' || key == VALUE_KEY || key == TEXT_KEY { // Drains buffer, moves it to children self.children.append(&mut self.buffer); } else { diff --git a/tests/serde-de.rs b/tests/serde-de.rs index 6432d6b1..afa85399 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -34,7 +34,7 @@ where fn string_borrow() { #[derive(Debug, Deserialize, PartialEq)] struct BorrowedText<'a> { - #[serde(rename = "$value")] + #[serde(rename = "$text")] text: &'a str, } @@ -43,52 +43,57 @@ fn string_borrow() { assert_eq!(borrowed_item.text, "Hello world"); } -/// Test for https://github.com/tafia/quick-xml/issues/231 -#[test] -fn implicit_value() { - use serde_value::Value; +/// Tests for deserializing into specially named field `$text` which represent +/// textual content of an XML element +mod text { + use super::*; + use pretty_assertions::assert_eq; - let item: Value = from_str(r#"content"#).unwrap(); + /// Test for https://github.com/tafia/quick-xml/issues/231 + #[test] + fn implicit() { + use serde_value::Value; - assert_eq!( - item, - Value::Map( - vec![( - Value::String("$value".into()), - Value::String("content".into()) - )] - .into_iter() - .collect() - ) - ); -} + let item: Value = from_str(r#"content"#).unwrap(); -#[test] -fn explicit_value() { - #[derive(Debug, Deserialize, PartialEq)] - struct Item { - #[serde(rename = "$value")] - content: String, + assert_eq!( + item, + Value::Map( + vec![( + Value::String("$text".into()), + Value::String("content".into()) + )] + .into_iter() + .collect() + ) + ); } - let item: Item = from_str(r#"content"#).unwrap(); - - assert_eq!( - item, - Item { - content: "content".into() + #[test] + fn explicit() { + #[derive(Debug, Deserialize, PartialEq)] + struct Item { + #[serde(rename = "$text")] + content: String, } - ); -} -#[test] -fn without_value() { - #[derive(Debug, Deserialize, PartialEq)] - struct Item; + let item: Item = from_str(r#"content"#).unwrap(); + + assert_eq!( + item, + Item { + content: "content".into() + } + ); + } - let item: Item = from_str(r#"content"#).unwrap(); + #[test] + fn without() { + #[derive(Debug, Deserialize, PartialEq)] + struct Item; - assert_eq!(item, Item); + let _: Item = from_str(r#"content"#).unwrap(); + } } /// Tests calling `deserialize_ignored_any` @@ -219,10 +224,10 @@ mod trivial { /// fields of this struct. /// /// Because we want to get access to unnamed content of the tag (usually, this internal - /// XML node called `#text`) we use a rename to a special name `$value` + /// XML node called `$text`) we use a rename to a special name `$text` #[derive(Debug, Deserialize, PartialEq)] struct Trivial { - #[serde(rename = "$value")] + #[serde(rename = "$text")] value: T, } @@ -236,9 +241,9 @@ mod trivial { match from_str::>(&format!("{}", $value)) { // Expected unexpected start element `` - Err(DeError::UnexpectedStart(tag)) => assert_eq!(tag, b"root"), + Err(DeError::Custom(reason)) => assert_eq!(reason, "missing field `$text`"), x => panic!( - r#"Expected `Err(DeError::UnexpectedStart("root"))`, but got `{:?}`"#, + r#"Expected `Err(DeError::Custom("missing field `$text`"))`, but got `{:?}`"#, x ), } @@ -723,7 +728,7 @@ mod seq { } /// Mixed content assumes, that some elements will have an internal - /// name `$value`, so, unless field named the same, it is expected + /// name `$text` or `$value`, so, unless field named the same, it is expected /// to fail #[test] fn mixed_content() { @@ -1438,7 +1443,7 @@ mod seq { } /// Mixed content assumes, that some elements will have an internal - /// name `$value`, so, unless field named the same, it is expected + /// name `$text` or `$value`, so, unless field named the same, it is expected /// to fail #[test] fn mixed_content() { @@ -5746,7 +5751,7 @@ mod xml_schema_lists { #[derive(Debug, Deserialize, PartialEq)] struct List { // Give it a special name that means text content of the XML node - #[serde(rename = "$value")] + #[serde(rename = "$text")] list: Vec, } diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 99d2b5ba..85f75e46 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -46,7 +46,7 @@ fn serialize_struct_value_number() { #[derive(Serialize)] struct Person { name: String, - #[serde(rename = "$value")] + #[serde(rename = "$text")] age: u32, } @@ -62,7 +62,7 @@ fn serialize_struct_value_string() { #[derive(Serialize)] struct Person { name: String, - #[serde(rename = "$value")] + #[serde(rename = "$text")] age: String, } @@ -144,8 +144,8 @@ struct Nested { struct Empty {} #[derive(Serialize)] -struct Value { - #[serde(rename = "$value")] +struct Text { + #[serde(rename = "$text")] float: f64, string: &'static str, } @@ -171,8 +171,8 @@ enum ExternallyTagged { string: &'static str, }, Empty {}, - Value { - #[serde(rename = "$value")] + Text { + #[serde(rename = "$text")] float: f64, string: &'static str, }, @@ -199,8 +199,8 @@ enum InternallyTagged { string: &'static str, }, Empty {}, - Value { - #[serde(rename = "$value")] + Text { + #[serde(rename = "$text")] float: f64, string: &'static str, }, @@ -226,8 +226,8 @@ enum AdjacentlyTagged { string: &'static str, }, Empty {}, - Value { - #[serde(rename = "$value")] + Text { + #[serde(rename = "$text")] float: f64, string: &'static str, }, @@ -253,8 +253,8 @@ enum Untagged { string: &'static str, }, Empty {}, - Value { - #[serde(rename = "$value")] + Text { + #[serde(rename = "$text")] float: f64, string: &'static str, }, @@ -310,8 +310,8 @@ mod with_root { serialize_as!(empty_struct: Empty {} => ""); - serialize_as!(value: - Value { + serialize_as!(text: + Text { float: 42.0, string: "answer" } @@ -357,12 +357,12 @@ mod with_root { serialize_as!(empty_struct: ExternallyTagged::Empty {} => ""); - serialize_as!(value: - ExternallyTagged::Value { + serialize_as!(text: + ExternallyTagged::Text { float: 42.0, string: "answer" } - => r#"42"#); + => r#"42"#); } mod internally_tagged { @@ -396,12 +396,12 @@ mod with_root { serialize_as!(empty_struct: InternallyTagged::Empty {} => r#""#); - serialize_as!(value: - InternallyTagged::Value { + serialize_as!(text: + InternallyTagged::Text { float: 42.0, string: "answer" } - => r#"42"#); + => r#"42"#); } mod adjacently_tagged { @@ -438,12 +438,12 @@ mod with_root { serialize_as!(empty_struct: AdjacentlyTagged::Empty {} => r#""#); - serialize_as!(value: - AdjacentlyTagged::Value { + serialize_as!(text: + AdjacentlyTagged::Text { float: 42.0, string: "answer", } - => r#"42"#); + => r#"42"#); } mod untagged { @@ -482,8 +482,8 @@ mod with_root { serialize_as!(empty_struct: Untagged::Empty {} => ""); - serialize_as!(value: - Untagged::Value { + serialize_as!(text: + Untagged::Text { float: 42.0, string: "answer" } From ad854e1a166c25e6b4104164f1d0d753a2487169 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 29 Aug 2022 22:55:31 +0500 Subject: [PATCH 12/21] Implement ElementSerializer which can store object as an XML tree. Fix #252 Still 2 doc-test failures --- src/se/content.rs | 614 +++++++++++++++++++ src/se/element.rs | 1456 +++++++++++++++++++++++++++++++++++++++++++++ src/se/mod.rs | 2 + 3 files changed, 2072 insertions(+) create mode 100644 src/se/content.rs create mode 100644 src/se/element.rs diff --git a/src/se/content.rs b/src/se/content.rs new file mode 100644 index 00000000..6f756fb9 --- /dev/null +++ b/src/se/content.rs @@ -0,0 +1,614 @@ +//! Contains serializer for content of an XML element + +use crate::errors::serialize::DeError; +use crate::se::element::{ElementSerializer, Struct}; +use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; +use crate::se::{QuoteLevel, XmlName}; +use serde::ser::{ + Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer, +}; +use serde::serde_if_integer128; +use std::fmt::Write; + +macro_rules! write_primitive { + ($method:ident ( $ty:ty )) => { + #[inline] + fn $method(self, value: $ty) -> Result { + self.into_simple_type_serializer().$method(value) + } + }; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer used to serialize content of the element. It does not write +/// surrounding tags. +/// +/// This serializer does the following: +/// - primitives (booleans, numbers and strings) serialized as naked strings +/// - `None` does not write anything +/// - sequences serialized without delimiters. `[1, 2, 3]` would be serialized as `123` +/// - units (`()`) and unit structs are not supported +/// - structs and maps are not supported +/// - unit variants serialized as self-closed `<${variant}/>` +/// - tuple variants serialized as sequences where each is wrapped in +/// `<${variant}>...` +/// - struct variants serialized wrapped `<${variant}>...` +/// +/// The difference between this serializer and [`SimpleTypeSerializer`] is in how +/// sequences and maps are serialized. Unlike `SimpleTypeSerializer` it supports +/// any types in sequences and serializes them as list of elements, but that has +/// drawbacks. Sequence of primitives would be serialized without delimiters and +/// it will be impossible to distinguish between them. +pub struct ContentSerializer { + pub writer: W, + /// Defines which XML characters need to be escaped in text content + pub level: QuoteLevel, + //TODO: add settings to disallow consequent serialization of primitives +} + +impl ContentSerializer { + /// Turns this serializer into serializer of a text content + #[inline] + pub fn into_simple_type_serializer(self) -> SimpleTypeSerializer { + //TODO: Customization point: choose between CDATA and Text representation + SimpleTypeSerializer { + writer: self.writer, + target: QuoteTarget::Text, + level: self.level, + } + } + + /// Creates new serializer that shares state with this serializer and + /// writes to the same underlying writer + #[inline] + pub fn new_seq_element_serializer(&mut self) -> ContentSerializer<&mut W> { + ContentSerializer { + writer: &mut self.writer, + level: self.level, + } + } + + /// Writes `name` as self-closed tag + #[inline] + pub(super) fn write_empty(mut self, name: XmlName) -> Result { + self.writer.write_char('<')?; + self.writer.write_str(name.0)?; + self.writer.write_str("/>")?; + Ok(self.writer) + } + + /// Writes simple type content between `name` tags + pub(super) fn write_wrapped(mut self, name: XmlName, serialize: S) -> Result + where + S: FnOnce(SimpleTypeSerializer) -> Result, + { + self.writer.write_char('<')?; + self.writer.write_str(name.0)?; + self.writer.write_char('>')?; + + let mut writer = serialize(self.into_simple_type_serializer())?; + + writer.write_str("')?; + Ok(writer) + } +} + +impl Serializer for ContentSerializer { + type Ok = W; + type Error = DeError; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = ElementSerializer<'static, W>; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Struct<'static, W>; + + write_primitive!(serialize_bool(bool)); + + write_primitive!(serialize_i8(i8)); + write_primitive!(serialize_i16(i16)); + write_primitive!(serialize_i32(i32)); + write_primitive!(serialize_i64(i64)); + + write_primitive!(serialize_u8(u8)); + write_primitive!(serialize_u16(u16)); + write_primitive!(serialize_u32(u32)); + write_primitive!(serialize_u64(u64)); + + serde_if_integer128! { + write_primitive!(serialize_i128(i128)); + write_primitive!(serialize_u128(u128)); + } + + write_primitive!(serialize_f32(f32)); + write_primitive!(serialize_f64(f64)); + + write_primitive!(serialize_char(char)); + write_primitive!(serialize_str(&str)); + write_primitive!(serialize_bytes(&[u8])); + + /// Does not write anything + #[inline] + fn serialize_none(self) -> Result { + Ok(self.writer) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + /// Does not write anything + #[inline] + fn serialize_unit(self) -> Result { + Ok(self.writer) + } + + /// Does not write anything + #[inline] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(self.writer) + } + + /// Checks `variant` for XML name validity and writes `<${variant}/>` + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + let name = XmlName::try_from(variant)?; + self.write_empty(name) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + /// Checks `variant` for XML name validity and writes `value` as new element + /// with name `variant`. + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + value.serialize(ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }) + } + + #[inline] + fn serialize_seq(self, _len: Option) -> Result { + Ok(self) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_tuple(len) + } + + #[inline] + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let ser = ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }; + // `ElementSerializer::serialize_tuple_variant` is the same as + // `ElementSerializer::serialize_tuple_struct`, except that it replaces `.key` + // to `variant` which is not required here + ser.serialize_tuple_struct(name, len) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + format!("serialization of map types is not supported in `$value` field").into(), + )) + } + + #[inline] + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("serialization of struct `{name}` is not supported in `$value` field").into(), + )) + } + + #[inline] + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let ser = ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }; + // `ElementSerializer::serialize_struct_variant` is the same as + // `ElementSerializer::serialize_struct`, except that it replaces `.key` + // to `variant` which is not required here + ser.serialize_struct(name, len) + } +} + +impl SerializeSeq for ContentSerializer { + type Ok = W; + type Error = DeError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + value.serialize(self.new_seq_element_serializer())?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.writer) + } +} + +impl SerializeTuple for ContentSerializer { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +impl SerializeTupleStruct for ContentSerializer { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Make tests public to reuse types in `elements::tests` module +#[cfg(test)] +pub(super) mod tests { + use super::*; + use crate::utils::Bytes; + use serde::Serialize; + use std::collections::BTreeMap; + + #[derive(Debug, Serialize, PartialEq)] + pub struct Unit; + + #[derive(Debug, Serialize, PartialEq)] + #[serde(rename = "<\"&'>")] + pub struct UnitEscaped; + + #[derive(Debug, Serialize, PartialEq)] + pub struct Newtype(pub usize); + + #[derive(Debug, Serialize, PartialEq)] + pub struct Tuple(pub &'static str, pub usize); + + #[derive(Debug, Serialize, PartialEq)] + pub struct Struct { + pub key: &'static str, + pub val: (usize, usize), + } + + #[derive(Debug, Serialize, PartialEq)] + pub struct Text { + pub before: &'static str, + #[serde(rename = "$text")] + pub content: T, + pub after: &'static str, + } + + #[derive(Debug, Serialize, PartialEq)] + pub struct Value { + pub before: &'static str, + #[serde(rename = "$value")] + pub content: T, + pub after: &'static str, + } + + /// Attributes identified by starting with `@` character + #[derive(Debug, Serialize, PartialEq)] + pub struct Attributes { + #[serde(rename = "@key")] + pub key: &'static str, + #[serde(rename = "@val")] + pub val: (usize, usize), + } + #[derive(Debug, Serialize, PartialEq)] + pub struct AttributesBefore { + #[serde(rename = "@key")] + pub key: &'static str, + pub val: usize, + } + #[derive(Debug, Serialize, PartialEq)] + pub struct AttributesAfter { + pub key: &'static str, + #[serde(rename = "@val")] + pub val: usize, + } + + #[derive(Debug, Serialize, PartialEq)] + pub enum Enum { + Unit, + /// Variant name becomes a tag name, but the name of variant is invalid + /// XML name. Serialization of this element should be forbidden + #[serde(rename = "<\"&'>")] + UnitEscaped, + Newtype(usize), + Tuple(&'static str, usize), + Struct { + key: &'static str, + /// Should be serialized as elements + val: (usize, usize), + }, + Attributes { + #[serde(rename = "@key")] + key: &'static str, + #[serde(rename = "@val")] + val: (usize, usize), + }, + AttributesBefore { + #[serde(rename = "@key")] + key: &'static str, + val: usize, + }, + AttributesAfter { + key: &'static str, + #[serde(rename = "@val")] + val: usize, + }, + } + + #[derive(Debug, Serialize, PartialEq)] + pub enum SpecialEnum { + Text { + before: &'static str, + #[serde(rename = "$text")] + content: T, + after: &'static str, + }, + Value { + before: &'static str, + #[serde(rename = "$value")] + content: T, + after: &'static str, + }, + } + + mod without_indent { + use super::Struct; + use super::*; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:literal) => { + #[test] + fn $name() { + let ser = ContentSerializer { + writer: String::new(), + level: QuoteLevel::Full, + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We could write something before fail + // assert_eq!(buffer, ""); + } + }; + } + + // Primitives is serialized in the same way as for SimpleTypeSerializer + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + serialize_as!(char_space: ' ' => " "); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + serialize_as!(option_some_empty_str: Some("") => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + // Unlike SimpleTypeSerializer, enumeration values serialized as tags + serialize_as!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: Enum::UnitEscaped + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + // Newtypes recursively applies ContentSerializer + serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + serialize_as!(seq: vec![1, 2, 3] => "123"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\ + 42"); + serialize_as!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // Structured types cannot be serialized without surrounding tag, which + // only `enum` can provide + err!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: Struct { key: "answer", val: (42, 42) } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + + /// Special field name `$text` should be serialized as a text content + mod text { + use super::*; + use pretty_assertions::assert_eq; + + err!(map: BTreeMap::from([("$text", 2), ("_3", 4)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + Text { + before: "answer", + content: (42, 42), + after: "answer", + } + => Unsupported("serialization of struct `Text` is not supported in `$value` field")); + serialize_as!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: (42, 42), + after: "answer", + } + => "\ + answer\ + 42 42\ + answer\ + "); + } + + mod attributes { + use super::*; + use pretty_assertions::assert_eq; + + err!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + + err!(struct_: Attributes { key: "answer", val: (42, 42) } + => Unsupported("serialization of struct `Attributes` is not supported in `$value` field")); + err!(struct_before: AttributesBefore { key: "answer", val: 42 } + => Unsupported("serialization of struct `AttributesBefore` is not supported in `$value` field")); + err!(struct_after: AttributesAfter { key: "answer", val: 42 } + => Unsupported("serialization of struct `AttributesAfter` is not supported in `$value` field")); + + serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); + } + } +} diff --git a/src/se/element.rs b/src/se/element.rs new file mode 100644 index 00000000..a94485c4 --- /dev/null +++ b/src/se/element.rs @@ -0,0 +1,1456 @@ +//! Contains serializer for an XML element + +use crate::de::{TEXT_KEY, VALUE_KEY}; +use crate::errors::serialize::DeError; +use crate::se::content::ContentSerializer; +use crate::se::key::QNameSerializer; +use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; +use crate::se::XmlName; +use serde::ser::{ + Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, + SerializeTupleStruct, SerializeTupleVariant, Serializer, +}; +use serde::serde_if_integer128; +use std::fmt::Write; + +macro_rules! write_primitive { + ($method:ident ( $ty:ty )) => { + fn $method(self, value: $ty) -> Result { + self.ser.write_wrapped(self.key, |ser| ser.$method(value)) + } + }; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer used to serialize element with specified name. +pub struct ElementSerializer<'k, W: Write> { + pub ser: ContentSerializer, + /// Tag name used to wrap serialized types except enum variants which uses the variant name + pub(super) key: XmlName<'k>, +} + +impl<'k, W: Write> Serializer for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Self; + type SerializeMap = Map<'k, W>; + type SerializeStruct = Struct<'k, W>; + type SerializeStructVariant = Struct<'k, W>; + + write_primitive!(serialize_bool(bool)); + + write_primitive!(serialize_i8(i8)); + write_primitive!(serialize_i16(i16)); + write_primitive!(serialize_i32(i32)); + write_primitive!(serialize_i64(i64)); + + write_primitive!(serialize_u8(u8)); + write_primitive!(serialize_u16(u16)); + write_primitive!(serialize_u32(u32)); + write_primitive!(serialize_u64(u64)); + + serde_if_integer128! { + write_primitive!(serialize_i128(i128)); + write_primitive!(serialize_u128(u128)); + } + + write_primitive!(serialize_f32(f32)); + write_primitive!(serialize_f64(f64)); + + write_primitive!(serialize_char(char)); + write_primitive!(serialize_bytes(&[u8])); + + fn serialize_str(self, value: &str) -> Result { + if value.is_empty() { + self.ser.write_empty(self.key) + } else { + self.ser + .write_wrapped(self.key, |ser| ser.serialize_str(value)) + } + } + + /// By serde contract we should serialize key of [`None`] values. If someone + /// wants to skip the field entirely, he should use + /// `#[serde(skip_serializing_if = "Option::is_none")]`. + /// + /// In XML when we serialize field, we write field name as: + /// - element name, or + /// - attribute name + /// + /// and field value as + /// - content of the element, or + /// - attribute value + /// + /// So serialization of `None` works the same as [serialization of `()`](#method.serialize_unit) + fn serialize_none(self) -> Result { + self.serialize_unit() + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + self.ser.write_empty(self.key) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.ser.write_empty(self.key) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + let name = XmlName::try_from(variant)?; + self.ser.write_empty(name) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + mut self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + self.key = XmlName::try_from(variant)?; + value.serialize(self) + } + + #[inline] + fn serialize_seq(self, _len: Option) -> Result { + Ok(self) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_tuple(len) + } + + #[inline] + fn serialize_tuple_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.key = XmlName::try_from(variant)?; + self.serialize_tuple_struct(name, len) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(Map { + ser: self.serialize_struct("", 0)?, + key: None, + }) + } + + #[inline] + fn serialize_struct( + mut self, + _name: &'static str, + _len: usize, + ) -> Result { + self.ser.writer.write_char('<')?; + self.ser.writer.write_str(self.key.0)?; + Ok(Struct { + ser: self, + children: String::new(), + }) + } + + #[inline] + fn serialize_struct_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.key = XmlName::try_from(variant)?; + self.serialize_struct(name, len) + } +} + +impl<'k, W: Write> SerializeSeq for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + value.serialize(ElementSerializer { + ser: self.ser.new_seq_element_serializer(), + key: self.key, + })?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.ser.writer) + } +} + +impl<'k, W: Write> SerializeTuple for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +impl<'k, W: Write> SerializeTupleStruct for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +impl<'k, W: Write> SerializeTupleVariant for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer for struct variants, which serializes the struct contents inside +/// of wrapping tags (`<${tag}>...`). +/// +/// Serialization of each field depends on it representation: +/// - attributes written directly to the higher serializer +/// - elements buffered into internal buffer and at the end written into higher +/// serializer +pub struct Struct<'k, W: Write> { + ser: ElementSerializer<'k, W>, + /// Buffer to store serialized elements + // TODO: Customization point: allow direct writing of elements, but all + // attributes should be listed first. Fail, if attribute encountered after + // element. Use feature to configure + children: String, +} + +impl<'k, W: Write> Struct<'k, W> { + #[inline] + fn write_field(&mut self, key: &str, value: &T) -> Result<(), DeError> + where + T: ?Sized + Serialize, + { + //TODO: Customization point: allow user to determine if field is attribute or not + if let Some(key) = key.strip_prefix('@') { + let key = XmlName::try_from(key)?; + self.write_attribute(key, value) + } else { + self.write_element(key, value) + } + } + + /// Writes `value` as an attribute + #[inline] + fn write_attribute(&mut self, key: XmlName, value: &T) -> Result<(), DeError> + where + T: ?Sized + Serialize, + { + //TODO: Customization point: each attribute on new line + self.ser.ser.writer.write_char(' ')?; + self.ser.ser.writer.write_str(key.0)?; + self.ser.ser.writer.write_char('=')?; + + //TODO: Customization point: preferred quote style + self.ser.ser.writer.write_char('"')?; + value.serialize(SimpleTypeSerializer { + writer: &mut self.ser.ser.writer, + target: QuoteTarget::DoubleQAttr, + level: self.ser.ser.level, + })?; + self.ser.ser.writer.write_char('"')?; + + Ok(()) + } + + /// Writes `value` either as a text content, or as an element. + /// + /// If `key` has a magic value [`TEXT_KEY`], then `value` serialized as a + /// [simple type]. + /// + /// If `key` has a magic value [`CONTENT_KEY`], then `value` serialized as a + /// [content] without wrapping in tags, otherwise it is wrapped in + /// `<${key}>...`. + /// + /// [simple type]: SimpleTypeSerializer + /// [content]: ContentSerializer + fn write_element(&mut self, key: &str, value: &T) -> Result<(), DeError> + where + T: ?Sized + Serialize, + { + let ser = ContentSerializer { + writer: &mut self.children, + level: self.ser.ser.level, + }; + + if key == TEXT_KEY { + value.serialize(ser.into_simple_type_serializer())?; + } else if key == VALUE_KEY { + value.serialize(ser)?; + } else { + value.serialize(ElementSerializer { + key: XmlName::try_from(key)?, + ser, + })?; + } + Ok(()) + } +} + +impl<'k, W: Write> SerializeStruct for Struct<'k, W> { + type Ok = W; + type Error = DeError; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.write_field(key, value) + } + + fn end(mut self) -> Result { + if self.children.is_empty() { + self.ser.ser.writer.write_str("/>")?; + } else { + self.ser.ser.writer.write_char('>')?; + self.ser.ser.writer.write_str(&self.children)?; + self.ser.ser.writer.write_str("')?; + } + Ok(self.ser.ser.writer) + } +} + +impl<'k, W: Write> SerializeStructVariant for Struct<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_field(self, key, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct Map<'k, W: Write> { + ser: Struct<'k, W>, + /// Key, serialized by `QNameSerializer` if consumer uses `serialize_key` + + /// `serialize_value` calls instead of `serialize_entry` + key: Option, +} + +impl<'k, W: Write> Map<'k, W> { + fn make_key(&mut self, key: &T) -> Result + where + T: ?Sized + Serialize, + { + key.serialize(QNameSerializer { + writer: String::new(), + }) + } +} + +impl<'k, W: Write> SerializeMap for Map<'k, W> { + type Ok = W; + type Error = DeError; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + if let Some(_) = self.key.take() { + return Err(DeError::Custom( + "calling `serialize_key` twice without `serialize_value`".to_string(), + )); + } + self.key = Some(self.make_key(key)?); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + if let Some(key) = self.key.take() { + return self.ser.write_field(&key, value); + } + Err(DeError::Custom( + "calling `serialize_value` without call of `serialize_key`".to_string(), + )) + } + + fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), Self::Error> + where + K: ?Sized + Serialize, + V: ?Sized + Serialize, + { + let key = self.make_key(key)?; + self.ser.write_field(&key, value) + } + + fn end(mut self) -> Result { + if let Some(key) = self.key.take() { + return Err(DeError::Custom(format!( + "calling `end` without call of `serialize_value` for key `{key}`" + ))); + } + SerializeStruct::end(self.ser) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use crate::se::content::tests::*; + use crate::se::QuoteLevel; + use crate::utils::Bytes; + use serde::Serialize; + use std::collections::BTreeMap; + + #[derive(Debug, Serialize, PartialEq)] + struct OptionalElements { + a: Option<&'static str>, + + #[serde(skip_serializing_if = "Option::is_none")] + b: Option<&'static str>, + } + #[derive(Debug, Serialize, PartialEq)] + struct OptionalAttributes { + #[serde(rename = "@a")] + a: Option<&'static str>, + + #[serde(rename = "@b")] + #[serde(skip_serializing_if = "Option::is_none")] + b: Option<&'static str>, + } + + mod without_indent { + use super::*; + use crate::se::content::tests::Struct; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:expr) => { + #[test] + fn $name() { + let ser = ElementSerializer { + ser: ContentSerializer { + writer: String::new(), + level: QuoteLevel::Full, + }, + key: XmlName("root"), + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + }, + key: XmlName("root"), + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We can write something before fail + // assert_eq!(buffer, ""); + } + }; + } + + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::<&str>::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + serialize_as!(option_some_empty_str: Some("") => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + serialize_as!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: Enum::UnitEscaped + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + + serialize_as!(seq: vec![1, 2, 3] + => "1\ + 2\ + 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\ + 42"); + serialize_as!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + serialize_as!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) + => "\ + <_1>2\ + <_3>4\ + "); + serialize_as!(struct_: Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + + /// Special field name `$text` should be serialized as text content. + /// Sequences serialized as an `xs:list` content + mod text { + use super::*; + + /// `$text` key in a map + mod map { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + BTreeMap::from([("$text", $data)]) + => ""); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + BTreeMap::from([("$text", $data)]) + => concat!("", $expected,"")); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("")); + + text!(unit: ()); + text!(unit_struct: Unit); + text!(unit_struct_escaped: UnitEscaped); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new()); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + + /// `$text` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None => ""); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("") => ""); + + text!(unit: () => ""); + text!(unit_struct: Unit => ""); + text!(unit_struct_escaped: UnitEscaped => ""); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new() => ""); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + + /// `$text` field inside a struct variant of an enum + mod enum_struct { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None => ""); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("") => ""); + + text!(unit: () => ""); + text!(unit_struct: Unit => ""); + text!(unit_struct_escaped: UnitEscaped => ""); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + SpecialEnum::Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new() => ""); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + SpecialEnum::Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + SpecialEnum::Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + SpecialEnum::Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + } + + /// Special field name `$value` should be serialized using name, provided + /// by the type of value instead of a key. Sequences serialized as a list + /// of tags with that name (each element can have their own name) + mod value { + use super::*; + + /// `$value` key in a map + mod map { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + BTreeMap::from([("$value", $data)]) + => ""); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + BTreeMap::from([("$value", $data)]) + => concat!("", $expected,"")); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + BTreeMap::from([("$value", Bytes(b"<\"escaped & bytes'>"))]) + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("")); + + value!(unit: ()); + value!(unit_struct: Unit); + value!(unit_struct_escaped: UnitEscaped); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + BTreeMap::from([("$value", Enum::UnitEscaped)]) + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + BTreeMap::from([("$value", BTreeMap::from([("_1", 2), ("_3", 4)]))]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + BTreeMap::from([("$value", Struct { key: "answer", val: (42, 42) })]) + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + } + + /// `$value` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None => ""); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("") => ""); + + value!(unit: () => ""); + value!(unit_struct: Unit => ""); + value!(unit_struct_escaped: UnitEscaped => ""); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new() => ""); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + } + + /// `$value` field inside a struct variant of an enum + mod enum_struct { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None => ""); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("") => ""); + + value!(unit: () => ""); + value!(unit_struct: Unit => ""); + value!(unit_struct_escaped: UnitEscaped => ""); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + SpecialEnum::Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new() => ""); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + SpecialEnum::Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + SpecialEnum::Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + } + } + + mod attributes { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) + => r#""#); + serialize_as!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) + => r#"2"#); + + serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); + + serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); + + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(none: + OptionalAttributes { a: None, b: None } + => r#""#); + serialize_as!(some_empty_str: + OptionalAttributes { + a: Some(""), + b: Some(""), + } + => r#""#); + serialize_as!(some_non_empty: + OptionalAttributes { + a: Some("1"), + b: Some("2"), + } + => r#""#); + } + } + + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(none: + OptionalElements { a: None, b: None } + => "\ + \ + "); + serialize_as!(some_empty_str: + OptionalElements { + a: Some(""), + b: Some(""), + } + => "\ + \ + \ + "); + serialize_as!(some_non_empty: + OptionalElements { + a: Some("1"), + b: Some("2"), + } + => "\ + 1\ + 2\ + "); + } + } +} diff --git a/src/se/mod.rs b/src/se/mod.rs index ef9f733e..130f23f3 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -73,6 +73,8 @@ macro_rules! write_primitive { //////////////////////////////////////////////////////////////////////////////////////////////////// +mod content; +mod element; mod key; pub(crate) mod simple_type; mod var; From bd0b792dc65f4e747d76c5c8745e95dde7e58679 Mon Sep 17 00:00:00 2001 From: Mingun Date: Fri, 9 Sep 2022 20:58:37 +0500 Subject: [PATCH 13/21] Implement indentation for new serializer. Fix #361 Still 2 doc-test failures --- src/de/simple_type.rs | 3 +- src/se/content.rs | 261 ++++++++++- src/se/element.rs | 1041 ++++++++++++++++++++++++++++++++++++++++- src/se/mod.rs | 52 +- src/se/simple_type.rs | 39 +- src/writer.rs | 25 +- 6 files changed, 1380 insertions(+), 41 deletions(-) diff --git a/src/de/simple_type.rs b/src/de/simple_type.rs index 5181f869..d7e4ca3a 100644 --- a/src/de/simple_type.rs +++ b/src/de/simple_type.rs @@ -829,7 +829,7 @@ impl<'de> VariantAccess<'de> for SimpleTypeUnitOnly { mod tests { use super::*; use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; - use crate::se::QuoteLevel; + use crate::se::{Indent, QuoteLevel}; use crate::utils::{ByteBuf, Bytes}; use serde::de::IgnoredAny; use serde::{Deserialize, Serialize}; @@ -866,6 +866,7 @@ mod tests { writer: String::new(), target: QuoteTarget::Text, level: QuoteLevel::Full, + indent: Indent::None, }) .unwrap(), xml diff --git a/src/se/content.rs b/src/se/content.rs index 6f756fb9..4a1a97c9 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -3,7 +3,7 @@ use crate::errors::serialize::DeError; use crate::se::element::{ElementSerializer, Struct}; use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; -use crate::se::{QuoteLevel, XmlName}; +use crate::se::{Indent, QuoteLevel, XmlName}; use serde::ser::{ Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer, }; @@ -39,23 +39,39 @@ macro_rules! write_primitive { /// sequences and maps are serialized. Unlike `SimpleTypeSerializer` it supports /// any types in sequences and serializes them as list of elements, but that has /// drawbacks. Sequence of primitives would be serialized without delimiters and -/// it will be impossible to distinguish between them. -pub struct ContentSerializer { +/// it will be impossible to distinguish between them. Even worse, when serializing +/// with indent, sequence of strings become one big string with additional content +/// and it would be impossible to distinguish between content of the original +/// strings and inserted indent characters. +pub struct ContentSerializer<'i, W: Write> { pub writer: W, /// Defines which XML characters need to be escaped in text content pub level: QuoteLevel, + /// Current indentation level. Note, that `Indent::None` means that there is + /// no indentation at all, but `write_indent == false` means only, that indent + /// writing is disabled in this instantiation of `ContentSerializer`, but + /// child serializers should have access to the actual state of indentation. + pub(super) indent: Indent<'i>, + /// If `true`, then current indent will be written before writing the content, + /// but only if content is not empty. + pub write_indent: bool, //TODO: add settings to disallow consequent serialization of primitives } -impl ContentSerializer { +impl<'i, W: Write> ContentSerializer<'i, W> { /// Turns this serializer into serializer of a text content #[inline] - pub fn into_simple_type_serializer(self) -> SimpleTypeSerializer { + pub fn into_simple_type_serializer(self) -> SimpleTypeSerializer<'i, W> { //TODO: Customization point: choose between CDATA and Text representation SimpleTypeSerializer { writer: self.writer, target: QuoteTarget::Text, level: self.level, + indent: if self.write_indent { + self.indent + } else { + Indent::None + }, } } @@ -66,12 +82,15 @@ impl ContentSerializer { ContentSerializer { writer: &mut self.writer, level: self.level, + indent: self.indent.borrow(), + write_indent: self.write_indent, } } /// Writes `name` as self-closed tag #[inline] pub(super) fn write_empty(mut self, name: XmlName) -> Result { + self.write_indent()?; self.writer.write_char('<')?; self.writer.write_str(name.0)?; self.writer.write_str("/>")?; @@ -81,8 +100,9 @@ impl ContentSerializer { /// Writes simple type content between `name` tags pub(super) fn write_wrapped(mut self, name: XmlName, serialize: S) -> Result where - S: FnOnce(SimpleTypeSerializer) -> Result, + S: FnOnce(SimpleTypeSerializer<'i, W>) -> Result, { + self.write_indent()?; self.writer.write_char('<')?; self.writer.write_str(name.0)?; self.writer.write_char('>')?; @@ -94,19 +114,27 @@ impl ContentSerializer { writer.write_char('>')?; Ok(writer) } + + pub(super) fn write_indent(&mut self) -> Result<(), DeError> { + if self.write_indent { + self.indent.write_indent(&mut self.writer)?; + self.write_indent = false; + } + Ok(()) + } } -impl Serializer for ContentSerializer { +impl<'i, W: Write> Serializer for ContentSerializer<'i, W> { type Ok = W; type Error = DeError; type SerializeSeq = Self; type SerializeTuple = Self; type SerializeTupleStruct = Self; - type SerializeTupleVariant = ElementSerializer<'static, W>; + type SerializeTupleVariant = ElementSerializer<'i, W>; type SerializeMap = Impossible; type SerializeStruct = Impossible; - type SerializeStructVariant = Struct<'static, W>; + type SerializeStructVariant = Struct<'i, W>; write_primitive!(serialize_bool(bool)); @@ -129,9 +157,17 @@ impl Serializer for ContentSerializer { write_primitive!(serialize_f64(f64)); write_primitive!(serialize_char(char)); - write_primitive!(serialize_str(&str)); write_primitive!(serialize_bytes(&[u8])); + #[inline] + fn serialize_str(self, value: &str) -> Result { + if value.is_empty() { + Ok(self.writer) + } else { + self.into_simple_type_serializer().serialize_str(value) + } + } + /// Does not write anything #[inline] fn serialize_none(self) -> Result { @@ -261,7 +297,7 @@ impl Serializer for ContentSerializer { } } -impl SerializeSeq for ContentSerializer { +impl<'i, W: Write> SerializeSeq for ContentSerializer<'i, W> { type Ok = W; type Error = DeError; @@ -270,6 +306,8 @@ impl SerializeSeq for ContentSerializer { T: ?Sized + Serialize, { value.serialize(self.new_seq_element_serializer())?; + // Write indent for next element + self.write_indent = true; Ok(()) } @@ -279,7 +317,7 @@ impl SerializeSeq for ContentSerializer { } } -impl SerializeTuple for ContentSerializer { +impl<'i, W: Write> SerializeTuple for ContentSerializer<'i, W> { type Ok = W; type Error = DeError; @@ -297,7 +335,7 @@ impl SerializeTuple for ContentSerializer { } } -impl SerializeTupleStruct for ContentSerializer { +impl<'i, W: Write> SerializeTupleStruct for ContentSerializer<'i, W> { type Ok = W; type Error = DeError; @@ -442,6 +480,8 @@ pub(super) mod tests { let ser = ContentSerializer { writer: String::new(), level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, }; let buffer = $data.serialize(ser).unwrap(); @@ -460,6 +500,8 @@ pub(super) mod tests { let ser = ContentSerializer { writer: &mut buffer, level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, }; match $data.serialize(ser).unwrap_err() { @@ -611,4 +653,197 @@ pub(super) mod tests { => r#"answer"#); } } + + mod with_indent { + use super::Struct; + use super::*; + use crate::writer::Indentation; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:literal) => { + #[test] + fn $name() { + let ser = ContentSerializer { + writer: String::new(), + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We can write something before fail + // assert_eq!(buffer, ""); + } + }; + } + + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + serialize_as!(char_space: ' ' => " "); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::::None => ""); + serialize_as!(option_some: Some(Enum::Unit) => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + // Unlike SimpleTypeSerializer, enumeration values serialized as tags + serialize_as!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: Enum::UnitEscaped + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + // Newtypes recursively applies ContentSerializer + serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters other that indent! + serialize_as!(seq: vec![1, 2, 3] + => "1\n\ + 2\n\ + 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\n\ + with\t\r\n spaces\n\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\n\ + 42"); + serialize_as!(enum_tuple: Enum::Tuple("first", 42) + => "first\n\ + 42"); + + // Structured types cannot be serialized without surrounding tag, which + // only `enum` can provide + err!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: Struct { key: "answer", val: (42, 42) } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n\ + "); + + /// Special field name `$text` should be serialized as text content + mod text { + use super::*; + use pretty_assertions::assert_eq; + + err!(map: BTreeMap::from([("$text", 2), ("_3", 4)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + Text { + before: "answer", + content: (42, 42), + after: "answer", + } + => Unsupported("serialization of struct `Text` is not supported in `$value` field")); + serialize_as!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: (42, 42), + after: "answer", + } + => "\n \ + answer\n \ + 42 42\n \ + answer\n\ + "); + } + + mod attributes { + use super::*; + use pretty_assertions::assert_eq; + + err!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + + err!(struct_: Attributes { key: "answer", val: (42, 42) } + => Unsupported("serialization of struct `Attributes` is not supported in `$value` field")); + err!(struct_before: AttributesBefore { key: "answer", val: 42 } + => Unsupported("serialization of struct `AttributesBefore` is not supported in `$value` field")); + err!(struct_after: AttributesAfter { key: "answer", val: 42 } + => Unsupported("serialization of struct `AttributesAfter` is not supported in `$value` field")); + + serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } + => "\n \ + 42\n\ + "); + serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } + => "\n \ + answer\n\ + "); + } + } } diff --git a/src/se/element.rs b/src/se/element.rs index a94485c4..57033290 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -5,7 +5,7 @@ use crate::errors::serialize::DeError; use crate::se::content::ContentSerializer; use crate::se::key::QNameSerializer; use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; -use crate::se::XmlName; +use crate::se::{Indent, XmlName}; use serde::ser::{ Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, @@ -25,7 +25,7 @@ macro_rules! write_primitive { /// A serializer used to serialize element with specified name. pub struct ElementSerializer<'k, W: Write> { - pub ser: ContentSerializer, + pub ser: ContentSerializer<'k, W>, /// Tag name used to wrap serialized types except enum variants which uses the variant name pub(super) key: XmlName<'k>, } @@ -176,6 +176,9 @@ impl<'k, W: Write> Serializer for ElementSerializer<'k, W> { _name: &'static str, _len: usize, ) -> Result { + self.ser.write_indent()?; + self.ser.indent.increase(); + self.ser.writer.write_char('<')?; self.ser.writer.write_str(self.key.0)?; Ok(Struct { @@ -209,6 +212,8 @@ impl<'k, W: Write> SerializeSeq for ElementSerializer<'k, W> { ser: self.ser.new_seq_element_serializer(), key: self.key, })?; + // Write indent for next element + self.ser.write_indent = true; Ok(()) } @@ -322,6 +327,7 @@ impl<'k, W: Write> Struct<'k, W> { writer: &mut self.ser.ser.writer, target: QuoteTarget::DoubleQAttr, level: self.ser.ser.level, + indent: Indent::None, })?; self.ser.ser.writer.write_char('"')?; @@ -346,6 +352,8 @@ impl<'k, W: Write> Struct<'k, W> { let ser = ContentSerializer { writer: &mut self.children, level: self.ser.ser.level, + indent: self.ser.ser.indent.borrow(), + write_indent: true, }; if key == TEXT_KEY { @@ -374,11 +382,16 @@ impl<'k, W: Write> SerializeStruct for Struct<'k, W> { } fn end(mut self) -> Result { + self.ser.ser.indent.decrease(); + if self.children.is_empty() { self.ser.ser.writer.write_str("/>")?; } else { self.ser.ser.writer.write_char('>')?; self.ser.ser.writer.write_str(&self.children)?; + + self.ser.ser.indent.write_indent(&mut self.ser.ser.writer)?; + self.ser.ser.writer.write_str("')?; @@ -479,7 +492,7 @@ impl<'k, W: Write> SerializeMap for Map<'k, W> { mod tests { use super::*; use crate::se::content::tests::*; - use crate::se::QuoteLevel; + use crate::se::{Indent, QuoteLevel}; use crate::utils::Bytes; use serde::Serialize; use std::collections::BTreeMap; @@ -515,6 +528,8 @@ mod tests { ser: ContentSerializer { writer: String::new(), level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, }, key: XmlName("root"), }; @@ -536,6 +551,8 @@ mod tests { ser: ContentSerializer { writer: &mut buffer, level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, }, key: XmlName("root"), }; @@ -1453,4 +1470,1022 @@ mod tests { "); } } + + mod with_indent { + use super::*; + use crate::se::content::tests::Struct; + use crate::writer::Indentation; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:expr) => { + #[test] + fn $name() { + let ser = ElementSerializer { + ser: ContentSerializer { + writer: String::new(), + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + }, + key: XmlName("root"), + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + }, + key: XmlName("root"), + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We can write something before fail + // assert_eq!(buffer, ""); + } + }; + } + + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + serialize_as!(char_space: ' ' => " "); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::<&str>::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + serialize_as!(option_some_empty: Some("") => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + serialize_as!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: Enum::UnitEscaped + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + + serialize_as!(seq: vec![1, 2, 3] + => "1\n\ + 2\n\ + 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\n\ + with\t\r\n spaces\n\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\n\ + 42"); + serialize_as!(enum_tuple: Enum::Tuple("first", 42) + => "first\n\ + 42"); + + serialize_as!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) + => "\n \ + <_1>2\n \ + <_3>4\n\ + "); + serialize_as!(struct_: Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n\ + "); + serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n\ + "); + + /// Special field name `$text` should be serialized as text content. + /// Sequences serialized as an `xs:list` content + mod text { + use super::*; + + /// `$text` key in a map + mod map { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + BTreeMap::from([("$text", $data)]) + => ""); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + BTreeMap::from([("$text", $data)]) + => concat!("\n ", $expected,"\n")); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("")); + + text!(unit: ()); + text!(unit_struct: Unit); + text!(unit_struct_escaped: UnitEscaped); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new()); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + + /// `$text` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + Text { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("")); + + text!(unit: ()); + text!(unit_struct: Unit); + text!(unit_struct_escaped: UnitEscaped); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new()); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + + /// `$text` field inside a struct variant of an enum + mod enum_struct { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("")); + + text!(unit: ()); + text!(unit_struct: Unit); + text!(unit_struct_escaped: UnitEscaped); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + SpecialEnum::Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new()); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + SpecialEnum::Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `$text` field + err!(map: + SpecialEnum::Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + SpecialEnum::Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + } + + /// Special field name `$value` should be serialized using name, provided + /// by the type of value instead of a key. Sequences serialized as a list + /// of tags with that name (each element can have their own name) + mod value { + use super::*; + + /// `$value` key in a map + mod map { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + BTreeMap::from([("$value", $data)]) + => ""); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + BTreeMap::from([("$value", $data)]) + => concat!("\n ", $expected,"\n")); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + BTreeMap::from([("$value", Bytes(b"<\"escaped & bytes'>"))]) + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("")); + + value!(unit: ()); + value!(unit_struct: Unit); + value!(unit_struct_escaped: UnitEscaped); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + BTreeMap::from([("$value", Enum::UnitEscaped)]) + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\n \ + with\t\r\n spaces\n \ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\n \ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + BTreeMap::from([("$value", BTreeMap::from([("_1", 2), ("_3", 4)]))]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + BTreeMap::from([("$value", Struct { key: "answer", val: (42, 42) })]) + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n \ + "); + } + + /// `$value` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + Value { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("")); + + value!(unit: ()); + value!(unit_struct: Unit); + value!(unit_struct_escaped: UnitEscaped); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\n \ + with\t\r\n spaces\n \ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\n \ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n \ + "); + } + + /// `$value` field inside a struct variant of an enum + mod enum_struct { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("")); + + value!(unit: ()); + value!(unit_struct: Unit); + value!(unit_struct_escaped: UnitEscaped); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + SpecialEnum::Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\n \ + with\t\r\n spaces\n \ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\n \ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + SpecialEnum::Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + SpecialEnum::Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n \ + "); + } + } + + mod attributes { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) + => r#""#); + serialize_as!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) + => "\n \ + 2\n\ + "); + + serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } + => "\n \ + 42\n\ + "); + serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } + => "\n \ + answer\n\ + "); + + serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } + => "\n \ + 42\n\ + "); + serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } + => "\n \ + answer\n\ + "); + + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(none: + OptionalAttributes { a: None, b: None } + => r#""#); + serialize_as!(some_empty_str: + OptionalAttributes { + a: Some(""), + b: Some("") + } + => r#""#); + serialize_as!(some_non_empty: + OptionalAttributes { + a: Some("a"), + b: Some("b") + } + => r#""#); + } + } + + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(none: + OptionalElements { a: None, b: None } + => "\n \ + \n\ + "); + serialize_as!(some_empty_str: + OptionalElements { + a: Some(""), + b: Some("") + } + => "\n \ + \n \ + \n\ + "); + serialize_as!(some_non_empty: + OptionalElements { + a: Some("a"), + b: Some("b") + } + => "\n \ + a\n \ + b\n\ + "); + } + } } diff --git a/src/se/mod.rs b/src/se/mod.rs index 130f23f3..e981e071 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -84,11 +84,12 @@ use crate::{ de::PRIMITIVE_PREFIX, errors::serialize::DeError, events::{BytesEnd, BytesStart, BytesText, Event}, - writer::Writer, + writer::{Indentation, Writer}, }; use serde::ser::{self, Serialize}; use serde::serde_if_integer128; use std::io::Write; +use std::str::from_utf8; /// Serialize struct into a `Write`r pub fn to_writer(writer: W, value: &S) -> Result<(), DeError> { @@ -221,6 +222,55 @@ impl<'n> XmlName<'n> { //////////////////////////////////////////////////////////////////////////////////////////////////// +pub(crate) enum Indent<'i> { + None, + Owned(Indentation), + Borrow(&'i mut Indentation), +} + +impl<'i> Indent<'i> { + pub fn borrow(&mut self) -> Indent { + match self { + Self::None => Indent::None, + Self::Owned(ref mut i) => Indent::Borrow(i), + Self::Borrow(i) => Indent::Borrow(i), + } + } + + pub fn increase(&mut self) { + match self { + Self::None => {} + Self::Owned(i) => i.grow(), + Self::Borrow(i) => i.grow(), + } + } + + pub fn decrease(&mut self) { + match self { + Self::None => {} + Self::Owned(i) => i.shrink(), + Self::Borrow(i) => i.shrink(), + } + } + + pub fn write_indent(&mut self, mut writer: W) -> Result<(), DeError> { + match self { + Self::None => {} + Self::Owned(i) => { + writer.write_char('\n')?; + writer.write_str(from_utf8(i.current())?)?; + } + Self::Borrow(i) => { + writer.write_char('\n')?; + writer.write_str(from_utf8(i.current())?)?; + } + } + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + /// A Serializer pub struct Serializer<'r, W: Write> { writer: Writer, diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index 14f0160b..17f6dc0b 100644 --- a/src/se/simple_type.rs +++ b/src/se/simple_type.rs @@ -5,7 +5,7 @@ use crate::errors::serialize::DeError; use crate::escapei::_escape; -use crate::se::QuoteLevel; +use crate::se::{Indent, QuoteLevel}; use serde::ser::{ Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer, }; @@ -327,30 +327,33 @@ impl Serializer for AtomicSerializer { /// - CDATA content (`<...>`) /// /// [simple types]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition -pub struct SimpleTypeSerializer { +pub struct SimpleTypeSerializer<'i, W: Write> { /// Writer to which this serializer writes content pub writer: W, /// Target for which element is serializing. Affects additional characters to escape. pub target: QuoteTarget, /// Defines which XML characters need to be escaped pub level: QuoteLevel, + /// Indent that should be written before the content if content is not an empty string + pub(crate) indent: Indent<'i>, } -impl SimpleTypeSerializer { +impl<'i, W: Write> SimpleTypeSerializer<'i, W> { fn write_str(&mut self, value: &str) -> Result<(), DeError> { + self.indent.write_indent(&mut self.writer)?; Ok(self .writer .write_str(&escape_list(value, self.target, self.level))?) } } -impl Serializer for SimpleTypeSerializer { +impl<'i, W: Write> Serializer for SimpleTypeSerializer<'i, W> { type Ok = W; type Error = DeError; - type SerializeSeq = SimpleSeq; - type SerializeTuple = SimpleSeq; - type SerializeTupleStruct = SimpleSeq; + type SerializeSeq = SimpleSeq<'i, W>; + type SerializeTuple = SimpleSeq<'i, W>; + type SerializeTupleStruct = SimpleSeq<'i, W>; type SerializeTupleVariant = Impossible; type SerializeMap = Impossible; type SerializeStruct = Impossible; @@ -359,6 +362,9 @@ impl Serializer for SimpleTypeSerializer { write_primitive!(); fn serialize_str(mut self, value: &str) -> Result { + if value.is_empty() { + self.indent = Indent::None; + } self.write_str(value)?; Ok(self.writer) } @@ -394,6 +400,7 @@ impl Serializer for SimpleTypeSerializer { target: self.target, level: self.level, first: true, + indent: self.indent, }) } @@ -457,15 +464,17 @@ impl Serializer for SimpleTypeSerializer { } /// Serializer for a sequence of atomic values delimited by space -pub struct SimpleSeq { +pub struct SimpleSeq<'i, W: Write> { writer: W, target: QuoteTarget, level: QuoteLevel, /// If `true`, nothing was written yet first: bool, + /// Indent that should be written before the content if content is not an empty string + indent: Indent<'i>, } -impl SerializeSeq for SimpleSeq { +impl<'i, W: Write> SerializeSeq for SimpleSeq<'i, W> { type Ok = W; type Error = DeError; @@ -473,7 +482,11 @@ impl SerializeSeq for SimpleSeq { where T: ?Sized + Serialize, { - if !self.first { + // Write indent for the first element and delimiter for others + //FIXME: sequence with only empty strings will be serialized as indent only + delimiters + if self.first { + self.indent.write_indent(&mut self.writer)?; + } else { self.writer.write_char(' ')?; } self.first = false; @@ -491,7 +504,7 @@ impl SerializeSeq for SimpleSeq { } } -impl SerializeTuple for SimpleSeq { +impl<'i, W: Write> SerializeTuple for SimpleSeq<'i, W> { type Ok = W; type Error = DeError; @@ -509,7 +522,7 @@ impl SerializeTuple for SimpleSeq { } } -impl SerializeTupleStruct for SimpleSeq { +impl<'i, W: Write> SerializeTupleStruct for SimpleSeq<'i, W> { type Ok = W; type Error = DeError; @@ -940,6 +953,7 @@ mod tests { writer: String::new(), target: QuoteTarget::Text, level: QuoteLevel::Full, + indent: Indent::None, }; let buffer = $data.serialize(ser).unwrap(); @@ -959,6 +973,7 @@ mod tests { writer: &mut buffer, target: QuoteTarget::Text, level: QuoteLevel::Full, + indent: Indent::None, }; match $data.serialize(ser).unwrap_err() { diff --git a/src/writer.rs b/src/writer.rs index bf12d83c..2c633c74 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -171,9 +171,7 @@ impl Writer { if let Some(ref i) = self.indent { if i.should_line_break { self.writer.write_all(b"\n").map_err(Error::Io)?; - self.writer - .write_all(&i.indents[..i.indents_len]) - .map_err(Error::Io)?; + self.writer.write_all(i.current()).map_err(Error::Io)?; } } self.write(before)?; @@ -196,9 +194,7 @@ impl Writer { pub fn write_indent(&mut self) -> Result<()> { if let Some(ref i) = self.indent { self.writer.write_all(b"\n").map_err(Error::Io)?; - self.writer - .write_all(&i.indents[..i.indents_len]) - .map_err(Error::Io)?; + self.writer.write_all(i.current()).map_err(Error::Io)?; } Ok(()) } @@ -337,7 +333,7 @@ impl<'a, W: Write> ElementWriter<'a, W> { } #[derive(Clone)] -struct Indentation { +pub(crate) struct Indentation { should_line_break: bool, indent_char: u8, indent_size: usize, @@ -346,8 +342,8 @@ struct Indentation { } impl Indentation { - fn new(indent_char: u8, indent_size: usize) -> Indentation { - Indentation { + pub fn new(indent_char: u8, indent_size: usize) -> Self { + Self { should_line_break: false, indent_char, indent_size, @@ -356,16 +352,23 @@ impl Indentation { } } - fn grow(&mut self) { + /// Increase indentation by one level + pub fn grow(&mut self) { self.indents_len += self.indent_size; if self.indents_len > self.indents.len() { self.indents.resize(self.indents_len, self.indent_char); } } - fn shrink(&mut self) { + /// Decrease indentation by one level. Do nothing, if level already zero + pub fn shrink(&mut self) { self.indents_len = self.indents_len.saturating_sub(self.indent_size); } + + /// Returns indent string for current level + pub fn current(&self) -> &[u8] { + &self.indents[..self.indents_len] + } } #[cfg(test)] From d7daaf2d45fc66f2dcc3d4b1a3bad25c4cde15cd Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 31 Aug 2022 23:11:58 +0500 Subject: [PATCH 14/21] Change expectations about results of serializing Serialization to attributes would be done only when field explicitly marked as an attribute failures (21): with_root::enum_::adjacently_tagged::flatten_struct with_root::enum_::adjacently_tagged::nested_struct with_root::enum_::adjacently_tagged::newtype with_root::enum_::adjacently_tagged::struct_ with_root::enum_::adjacently_tagged::text with_root::enum_::adjacently_tagged::tuple_struct with_root::enum_::adjacently_tagged::unit with_root::enum_::externally_tagged::nested_struct with_root::enum_::externally_tagged::struct_ with_root::enum_::externally_tagged::text with_root::enum_::internally_tagged::nested_struct with_root::enum_::internally_tagged::newtype with_root::enum_::internally_tagged::struct_ with_root::enum_::internally_tagged::text with_root::enum_::internally_tagged::unit with_root::enum_::untagged::nested_struct with_root::enum_::untagged::struct_ with_root::enum_::untagged::text with_root::nested_struct with_root::struct_ with_root::text --- tests/serde-se.rs | 162 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 133 insertions(+), 29 deletions(-) diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 85f75e46..d2212003 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -285,28 +285,41 @@ mod with_root { => "true"); serialize_as!(tuple: (42.0, "answer") - => "42answer"); + => "42\ + answer"); serialize_as!(tuple_struct: Tuple(42.0, "answer") - => "42answer"); + => "42\ + answer"); serialize_as!(struct_: Struct { float: 42.0, string: "answer" } - => r#""#); + => "\ + 42\ + answer\ + "); serialize_as!(nested_struct: NestedStruct { nested: Nested { float: 42.0 }, string: "answer", } - => r#""#); + => "\ + \ + 42\ + \ + answer\ + "); serialize_as!(flatten_struct: FlattenStruct { nested: Nested { float: 42.0 }, string: "answer", } - => r#"42answer"#); + => "\ + 42\ + answer\ + "); serialize_as!(empty_struct: Empty {} => ""); @@ -315,7 +328,10 @@ mod with_root { float: 42.0, string: "answer" } - => r#"42"#); + => "\ + 42\ + answer\ + "); mod enum_ { use super::*; @@ -335,25 +351,37 @@ mod with_root { => "true"); serialize_as!(tuple_struct: ExternallyTagged::Tuple(42.0, "answer") - => "42answer"); + => "42\ + answer"); serialize_as!(struct_: ExternallyTagged::Struct { float: 42.0, string: "answer", } - => r#""#); + => "\ + 42\ + answer\ + "); serialize_as!(nested_struct: ExternallyTagged::Holder { nested: Nested { float: 42.0 }, string: "answer", } - => r#""#); + => "\ + \ + 42\ + \ + answer\ + "); serialize_as!(flatten_struct: ExternallyTagged::Flatten { nested: Nested { float: 42.0 }, string: "answer", } - => r#"42answer"#); + => "\ + 42\ + answer\ + "); serialize_as!(empty_struct: ExternallyTagged::Empty {} => ""); @@ -362,7 +390,10 @@ mod with_root { float: 42.0, string: "answer" } - => r#"42"#); + => "\ + 42\ + answer\ + "); } mod internally_tagged { @@ -371,28 +402,47 @@ mod with_root { serialize_as!(unit: InternallyTagged::Unit - => r#""#); + => "\ + Unit\ + "); serialize_as!(newtype: InternallyTagged::Newtype(Nested { float: 4.2 }) - => r#""#); + => "\ + Newtype\ + 4.2\ + "); serialize_as!(struct_: InternallyTagged::Struct { float: 42.0, string: "answer", } - => r#""#); + => "\ + Struct\ + 42\ + answer\ + "); serialize_as!(nested_struct: InternallyTagged::Holder { nested: Nested { float: 42.0 }, string: "answer", } - => r#""#); + => "\ + Holder\ + \ + 42\ + \ + answer\ + "); serialize_as!(flatten_struct: InternallyTagged::Flatten { nested: Nested { float: 42.0 }, string: "answer", } - => r#"Flatten42answer"#); + => "\ + Flatten\ + 42\ + answer\ + "); serialize_as!(empty_struct: InternallyTagged::Empty {} => r#""#); @@ -401,7 +451,11 @@ mod with_root { float: 42.0, string: "answer" } - => r#"42"#); + => "\ + Text\ + 42\ + answer\ + "); } mod adjacently_tagged { @@ -410,31 +464,60 @@ mod with_root { serialize_as!(unit: AdjacentlyTagged::Unit - => r#""#); + => "\ + Unit\ + "); serialize_as!(newtype: AdjacentlyTagged::Newtype(true) - => r#""#); + => "\ + Newtype\ + true\ + "); serialize_as!(tuple_struct: AdjacentlyTagged::Tuple(42.0, "answer") - => r#"42answer"#); + => "\ + Tuple\ + 42\ + answer\ + "); serialize_as!(struct_: AdjacentlyTagged::Struct { float: 42.0, string: "answer", } - => r#""#); + => "\ + Struct\ + \ + 42\ + answer\ + \ + "); serialize_as!(nested_struct: AdjacentlyTagged::Holder { nested: Nested { float: 42.0 }, string: "answer", } - => r#""#); + => "\ + Holder\ + \ + \ + 42\ + \ + answer\ + \ + "); serialize_as!(flatten_struct: AdjacentlyTagged::Flatten { nested: Nested { float: 42.0 }, string: "answer", } - => r#"42answer"#); + => "\ + Flatten\ + \ + 42\ + answer\ + \ + "); serialize_as!(empty_struct: AdjacentlyTagged::Empty {} => r#""#); @@ -443,7 +526,13 @@ mod with_root { float: 42.0, string: "answer", } - => r#"42"#); + => "\ + Text\ + \ + 42\ + answer\ + \ + "); } mod untagged { @@ -460,25 +549,37 @@ mod with_root { => "true"); serialize_as!(tuple_struct: Untagged::Tuple(42.0, "answer") - => "42answer"); + => "42\ + answer"); serialize_as!(struct_: Untagged::Struct { float: 42.0, string: "answer", } - => r#""#); + => "\ + 42\ + answer\ + "); serialize_as!(nested_struct: Untagged::Holder { nested: Nested { float: 42.0 }, string: "answer", } - => r#""#); + => "\ + \ + 42\ + \ + answer\ + "); serialize_as!(flatten_struct: Untagged::Flatten { nested: Nested { float: 42.0 }, string: "answer", } - => r#"42answer"#); + => "\ + 42\ + answer\ + "); serialize_as!(empty_struct: Untagged::Empty {} => ""); @@ -487,7 +588,10 @@ mod with_root { float: 42.0, string: "answer" } - => r#"42"#); + => "\ + 42\ + answer\ + "); } } } From d050c2b6342ce33041c66f3d8611e71e9caf34e1 Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 31 Aug 2022 23:24:02 +0500 Subject: [PATCH 15/21] Add tests for serialization of primitives This commit changes expectations from serializing primitives - they should be wrapped in root tag failures (53): with_root::char_amp with_root::char_apos with_root::char_gt with_root::char_lt with_root::char_non_escaped with_root::char_quot with_root::char_space with_root::enum_::adjacently_tagged::flatten_struct with_root::enum_::adjacently_tagged::nested_struct with_root::enum_::adjacently_tagged::newtype with_root::enum_::adjacently_tagged::struct_ with_root::enum_::adjacently_tagged::text with_root::enum_::adjacently_tagged::tuple_struct with_root::enum_::adjacently_tagged::unit with_root::enum_::externally_tagged::nested_struct with_root::enum_::externally_tagged::primitive_unit with_root::enum_::externally_tagged::struct_ with_root::enum_::externally_tagged::text with_root::enum_::internally_tagged::nested_struct with_root::enum_::internally_tagged::newtype with_root::enum_::internally_tagged::struct_ with_root::enum_::internally_tagged::text with_root::enum_::internally_tagged::unit with_root::enum_::untagged::nested_struct with_root::enum_::untagged::newtype with_root::enum_::untagged::struct_ with_root::enum_::untagged::text with_root::enum_::untagged::unit with_root::f32_ with_root::f64_ with_root::false_ with_root::i128_ with_root::i16_ with_root::i32_ with_root::i64_ with_root::i8_ with_root::isize_ with_root::map with_root::nested_struct with_root::option_some with_root::seq with_root::str_escaped with_root::str_non_escaped with_root::struct_ with_root::text with_root::true_ with_root::u128_ with_root::u16_ with_root::u32_ with_root::u64_ with_root::u8_ with_root::unit with_root::usize_ --- tests/serde-se.rs | 100 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/tests/serde-se.rs b/tests/serde-se.rs index d2212003..fece2b68 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -1,9 +1,12 @@ use quick_xml::se::{to_string, Serializer}; +use quick_xml::utils::Bytes; use quick_xml::writer::Writer; +use quick_xml::DeError; use pretty_assertions::assert_eq; -use serde::{Serialize, Serializer as SerSerializer}; +use serde::{serde_if_integer128, Serialize, Serializer as SerSerializer}; +use std::collections::BTreeMap; #[test] fn serialize_bool() { @@ -277,20 +280,101 @@ mod with_root { }; } + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = Vec::new(); + let mut ser = Serializer::with_root(Writer::new(&mut buffer), Some("root")); + + match $data.serialize(&mut ser) { + Err(DeError::$kind(e)) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We can write something before fail + // assert_eq!(String::from_utf8(buffer).unwrap(), ""); + } + }; + } + + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + serialize_as!(char_space: ' ' => " "); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::<&str>::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + serialize_as!(unit: + () + => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(newtype: Newtype(true) => "true"); + + serialize_as!(seq: + vec![1, 2, 3] + => "1\ + 2\ + 3"); serialize_as!(tuple: - (42.0, "answer") - => "42\ - answer"); + ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); serialize_as!(tuple_struct: Tuple(42.0, "answer") => "42\ answer"); + + serialize_as!(map: + BTreeMap::from([("$text", 1), ("_2", 3)]) + => "\ + 1\ + <_2>3\ + "); serialize_as!(struct_: Struct { float: 42.0, @@ -345,7 +429,7 @@ mod with_root { => ""); serialize_as!(primitive_unit: ExternallyTagged::PrimitiveUnit - => "PrimitiveUnit"); + => ""); serialize_as!(newtype: ExternallyTagged::Newtype(true) => "true"); @@ -541,12 +625,10 @@ mod with_root { serialize_as!(unit: Untagged::Unit - // Unit variant consists just from the tag, and because tags - // are not written in untagged mode, nothing is written - => ""); + => ""); serialize_as!(newtype: Untagged::Newtype(true) - => "true"); + => "true"); serialize_as!(tuple_struct: Untagged::Tuple(42.0, "answer") => "42\ From 033be6e1da72155cb64133c3a15ac0539471301a Mon Sep 17 00:00:00 2001 From: Mingun Date: Wed, 31 Aug 2022 19:38:14 +0500 Subject: [PATCH 16/21] Copy all serializer tests to test serializer in mode without root tag specified Now serializer tested in both modes: with and without root tag specified failures (114): with_root::char_amp with_root::char_apos with_root::char_gt with_root::char_lt with_root::char_non_escaped with_root::char_quot with_root::char_space with_root::enum_::adjacently_tagged::empty_struct with_root::enum_::adjacently_tagged::flatten_struct with_root::enum_::adjacently_tagged::nested_struct with_root::enum_::adjacently_tagged::newtype with_root::enum_::adjacently_tagged::struct_ with_root::enum_::adjacently_tagged::text with_root::enum_::adjacently_tagged::tuple_struct with_root::enum_::adjacently_tagged::unit with_root::enum_::externally_tagged::nested_struct with_root::enum_::externally_tagged::primitive_unit with_root::enum_::externally_tagged::struct_ with_root::enum_::externally_tagged::text with_root::enum_::internally_tagged::empty_struct with_root::enum_::internally_tagged::nested_struct with_root::enum_::internally_tagged::newtype with_root::enum_::internally_tagged::struct_ with_root::enum_::internally_tagged::text with_root::enum_::internally_tagged::unit with_root::enum_::untagged::nested_struct with_root::enum_::untagged::newtype with_root::enum_::untagged::struct_ with_root::enum_::untagged::text with_root::enum_::untagged::unit with_root::f32_ with_root::f64_ with_root::false_ with_root::i128_ with_root::i16_ with_root::i32_ with_root::i64_ with_root::i8_ with_root::isize_ with_root::map with_root::nested_struct with_root::option_some with_root::seq with_root::str_escaped with_root::str_non_escaped with_root::struct_ with_root::text with_root::true_ with_root::u128_ with_root::u16_ with_root::u32_ with_root::u64_ with_root::u8_ with_root::unit with_root::usize_ without_root::char_amp without_root::char_apos without_root::char_gt without_root::char_lt without_root::char_non_escaped without_root::char_quot without_root::char_space without_root::enum_::adjacently_tagged::empty_struct without_root::enum_::adjacently_tagged::flatten_struct without_root::enum_::adjacently_tagged::nested_struct without_root::enum_::adjacently_tagged::newtype without_root::enum_::adjacently_tagged::struct_ without_root::enum_::adjacently_tagged::text without_root::enum_::adjacently_tagged::tuple_struct without_root::enum_::adjacently_tagged::unit without_root::enum_::externally_tagged::nested_struct without_root::enum_::externally_tagged::primitive_unit without_root::enum_::externally_tagged::struct_ without_root::enum_::externally_tagged::text without_root::enum_::internally_tagged::empty_struct without_root::enum_::internally_tagged::flatten_struct without_root::enum_::internally_tagged::nested_struct without_root::enum_::internally_tagged::newtype without_root::enum_::internally_tagged::struct_ without_root::enum_::internally_tagged::text without_root::enum_::internally_tagged::unit without_root::enum_::untagged::flatten_struct without_root::enum_::untagged::nested_struct without_root::enum_::untagged::newtype without_root::enum_::untagged::struct_ without_root::enum_::untagged::text without_root::enum_::untagged::tuple_struct without_root::enum_::untagged::unit without_root::f32_ without_root::f64_ without_root::false_ without_root::flatten_struct without_root::i128_ without_root::i16_ without_root::i32_ without_root::i64_ without_root::i8_ without_root::isize_ without_root::map without_root::nested_struct without_root::seq without_root::str_escaped without_root::str_non_escaped without_root::struct_ without_root::text without_root::true_ without_root::tuple without_root::u128_ without_root::u16_ without_root::u32_ without_root::u64_ without_root::u8_ without_root::unit without_root::usize_ --- src/se/mod.rs | 2 +- tests/serde-se.rs | 407 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 406 insertions(+), 3 deletions(-) diff --git a/src/se/mod.rs b/src/se/mod.rs index e981e071..d6978125 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -524,7 +524,7 @@ impl<'r, 'w, W: Write> ser::Serializer for &'w mut Serializer<'r, W> { Some(tag) => tag, None => { return Err(DeError::Custom( - "root tag name must be specified when serialize unnamed tuple".into(), + "cannot serialize unnamed tuple without defined root tag".into(), )) } }; diff --git a/tests/serde-se.rs b/tests/serde-se.rs index fece2b68..28f397ce 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -263,6 +263,404 @@ enum Untagged { }, } +mod without_root { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:literal) => { + #[test] + fn $name() { + let mut buffer = Vec::new(); + let mut ser = Serializer::new(&mut buffer); + + $data.serialize(&mut ser).unwrap(); + assert_eq!(String::from_utf8(buffer).unwrap(), $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = Vec::new(); + let mut ser = Serializer::new(&mut buffer); + + match $data.serialize(&mut ser) { + Err(DeError::$kind(e)) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + assert_eq!(String::from_utf8(buffer).unwrap(), ""); + } + }; + } + + err!(false_: false => Unsupported("cannot serialize `bool` without defined root tag")); + err!(true_: true => Unsupported("cannot serialize `bool` without defined root tag")); + + err!(i8_: -42i8 => Unsupported("cannot serialize `i8` without defined root tag")); + err!(i16_: -4200i16 => Unsupported("cannot serialize `i16` without defined root tag")); + err!(i32_: -42000000i32 => Unsupported("cannot serialize `i32` without defined root tag")); + err!(i64_: -42000000000000i64 => Unsupported("cannot serialize `i64` without defined root tag")); + err!(isize_: -42000000000000isize => Unsupported("cannot serialize `i64` without defined root tag")); + + err!(u8_: 42u8 => Unsupported("cannot serialize `u8` without defined root tag")); + err!(u16_: 4200u16 => Unsupported("cannot serialize `u16` without defined root tag")); + err!(u32_: 42000000u32 => Unsupported("cannot serialize `u32` without defined root tag")); + err!(u64_: 42000000000000u64 => Unsupported("cannot serialize `u64` without defined root tag")); + err!(usize_: 42000000000000usize => Unsupported("cannot serialize `u64` without defined root tag")); + + serde_if_integer128! { + err!(i128_: -420000000000000000000000000000i128 => Unsupported("cannot serialize `i128` without defined root tag")); + err!(u128_: 420000000000000000000000000000u128 => Unsupported("cannot serialize `u128` without defined root tag")); + } + + err!(f32_: 4.2f32 => Unsupported("cannot serialize `f32` without defined root tag")); + err!(f64_: 4.2f64 => Unsupported("cannot serialize `f64` without defined root tag")); + + err!(char_non_escaped: 'h' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_lt: '<' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_gt: '>' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_amp: '&' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_apos: '\'' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_quot: '"' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_space: ' ' => Unsupported("cannot serialize `char` without defined root tag")); + + err!(str_non_escaped: "non-escaped string" => Unsupported("cannot serialize `&str` without defined root tag")); + err!(str_escaped: "<\"escaped & string'>" => Unsupported("cannot serialize `&str` without defined root tag")); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::::None => ""); + serialize_as!(option_some: Some(Unit) => ""); + + err!(unit: () => Unsupported("cannot serialize `()` without defined root tag")); + serialize_as!(unit_struct: Unit => ""); + + serialize_as!(newtype: Newtype(true) => "true"); + + err!(seq: vec![1, 2, 3] => Unsupported("cannot serialize sequence without defined root tag")); + err!(tuple: + ("<\"&'>", "with\t\r\n spaces", 3usize) + => Unsupported("cannot serialize unnamed tuple without defined root tag")); + serialize_as!(tuple_struct: + Tuple(42.0, "answer") + => "42\ + answer"); + + err!(map: + BTreeMap::from([("$text", 1), ("_2", 3)]) + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(struct_: + Struct { + float: 42.0, + string: "answer" + } + => "\ + 42\ + answer\ + "); + serialize_as!(nested_struct: + NestedStruct { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\ + \ + 42\ + \ + answer\ + "); + // serde serializes flatten structs as maps, and we do not support + // serialization of maps without root tag + err!(flatten_struct: + FlattenStruct { + nested: Nested { float: 42.0 }, + string: "answer", + } + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(empty_struct: + Empty {} + => ""); + serialize_as!(text: + Text { + float: 42.0, + string: "answer", + } + => "\ + 42\ + answer\ + "); + + mod enum_ { + use super::*; + + mod externally_tagged { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(unit: + ExternallyTagged::Unit + => ""); + serialize_as!(primitive_unit: + ExternallyTagged::PrimitiveUnit + => ""); + serialize_as!(newtype: + ExternallyTagged::Newtype(true) + => "true"); + serialize_as!(tuple_struct: + ExternallyTagged::Tuple(42.0, "answer") + => "42\ + answer"); + serialize_as!(struct_: + ExternallyTagged::Struct { + float: 42.0, + string: "answer" + } + => "\ + 42\ + answer\ + "); + serialize_as!(nested_struct: + ExternallyTagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\ + \ + 42\ + \ + answer\ + "); + serialize_as!(flatten_struct: + ExternallyTagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\ + 42\ + answer\ + "); + serialize_as!(empty_struct: + ExternallyTagged::Empty {} + => ""); + serialize_as!(text: + ExternallyTagged::Text { + float: 42.0, + string: "answer" + } + => "\ + 42\ + answer\ + "); + } + + mod internally_tagged { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(unit: + InternallyTagged::Unit + => "\ + Unit\ + "); + // serde serializes internally tagged newtype structs by delegating + // serialization to the inner type and augmenting it with a tag + serialize_as!(newtype: + InternallyTagged::Newtype(Nested { float: 4.2 }) + => "\ + Newtype\ + 4.2\ + "); + serialize_as!(struct_: + InternallyTagged::Struct { + float: 42.0, + string: "answer" + } + => "\ + Struct\ + 42\ + answer\ + "); + serialize_as!(nested_struct: + InternallyTagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\ + Holder\ + \ + 42\ + \ + answer\ + "); + // serde serializes flatten structs as maps, and we do not support + // serialization of maps without root tag + err!(flatten_struct: + InternallyTagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(empty_struct: + InternallyTagged::Empty {} + => "\ + Empty\ + "); + serialize_as!(text: + InternallyTagged::Text { + float: 42.0, + string: "answer" + } + => "\ + Text\ + 42\ + answer\ + "); + } + + mod adjacently_tagged { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(unit: + AdjacentlyTagged::Unit + => "\ + Unit\ + "); + serialize_as!(newtype: + AdjacentlyTagged::Newtype(true) + => "\ + Newtype\ + true\ + "); + serialize_as!(tuple_struct: + AdjacentlyTagged::Tuple(42.0, "answer") + => "\ + Tuple\ + 42\ + answer\ + "); + serialize_as!(struct_: + AdjacentlyTagged::Struct { + float: 42.0, + string: "answer", + } + => "\ + Struct\ + \ + 42\ + answer\ + \ + "); + serialize_as!(nested_struct: + AdjacentlyTagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\ + Holder\ + \ + \ + 42\ + \ + answer\ + \ + "); + serialize_as!(flatten_struct: + AdjacentlyTagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\ + Flatten\ + \ + 42\ + answer\ + \ + "); + serialize_as!(empty_struct: + AdjacentlyTagged::Empty {} + => "\ + Empty\ + \ + "); + serialize_as!(text: + AdjacentlyTagged::Text { + float: 42.0, + string: "answer", + } + => "\ + Text\ + \ + 42\ + answer\ + \ + "); + } + + mod untagged { + use super::*; + use pretty_assertions::assert_eq; + + // Until https://github.com/serde-rs/serde/pull/2288 will be merged, + // some results can be confusing + err!(unit: Untagged::Unit + => Unsupported("cannot serialize `()` without defined root tag")); + err!(newtype: Untagged::Newtype(true) + => Unsupported("cannot serialize `bool` without defined root tag")); + err!(tuple_struct: Untagged::Tuple(42.0, "answer") + => Unsupported("cannot serialize unnamed tuple without defined root tag")); + serialize_as!(struct_: + Untagged::Struct { + float: 42.0, + string: "answer", + } + => "\ + 42\ + answer\ + "); + serialize_as!(nested_struct: + Untagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\ + \ + 42\ + \ + answer\ + "); + err!(flatten_struct: + Untagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(empty_struct: + Untagged::Empty {} + => ""); + serialize_as!(text: + Untagged::Text { + float: 42.0, + string: "answer" + } + => "\ + 42\ + answer\ + "); + } + } +} + mod with_root { use super::*; use pretty_assertions::assert_eq; @@ -529,7 +927,9 @@ mod with_root { "); serialize_as!(empty_struct: InternallyTagged::Empty {} - => r#""#); + => "\ + Empty\ + "); serialize_as!(text: InternallyTagged::Text { float: 42.0, @@ -604,7 +1004,10 @@ mod with_root { "); serialize_as!(empty_struct: AdjacentlyTagged::Empty {} - => r#""#); + => "\ + Empty\ + \ + "); serialize_as!(text: AdjacentlyTagged::Text { float: 42.0, From 0868beedc66e285847a7c8addbc73df84e23dcc1 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 1 Sep 2022 19:22:42 +0500 Subject: [PATCH 17/21] Removed tests, that became duplicates of new tests: - serialize_bool - duplicate of `without_root::false_` and `without_root::true_` - serialize_struct - duplicate of `without_root::struct_` - serialize_struct_value_number - duplicate of `without_root::value` - serialize_struct_value_string - excess test, duplicate of previous - serialize_enum - duplicate of `without_root::enum_::externally_tagged::newtype` - serialize_a_list - non working test that replaced by `without_root::seq` - test_empty - duplicate of `without_root::empty_struct` - test_nested - duplicate of `without_root::nested_struct` Still 114 failures --- Cargo.toml | 5 -- tests/serde-se.rs | 108 +------------------------------------------ tests/serde_attrs.rs | 75 ------------------------------ 3 files changed, 2 insertions(+), 186 deletions(-) delete mode 100644 tests/serde_attrs.rs diff --git a/Cargo.toml b/Cargo.toml index a996d7ae..e61fea6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,11 +179,6 @@ name = "encodings" required-features = ["encoding"] path = "tests/encodings.rs" -[[test]] -name = "serde_attrs" -required-features = ["serialize"] -path = "tests/serde_attrs.rs" - [[test]] name = "serde_roundtrip" required-features = ["serialize"] diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 28f397ce..5fb1245c 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -1,115 +1,11 @@ -use quick_xml::se::{to_string, Serializer}; +use quick_xml::se::Serializer; use quick_xml::utils::Bytes; use quick_xml::writer::Writer; use quick_xml::DeError; -use pretty_assertions::assert_eq; - -use serde::{serde_if_integer128, Serialize, Serializer as SerSerializer}; +use serde::{serde_if_integer128, Serialize}; use std::collections::BTreeMap; -#[test] -fn serialize_bool() { - let inputs = [(true, "true"), (false, "false")]; - - for (src, should_be) in &inputs { - let mut buffer = Vec::new(); - let mut ser = Serializer::new(&mut buffer); - ser.serialize_bool(*src).unwrap(); - - assert_eq!(String::from_utf8(buffer).unwrap(), *should_be); - } -} - -#[test] -fn serialize_struct() { - #[derive(Serialize)] - struct Person { - name: String, - age: u32, - } - - let bob = Person { - name: "Bob".to_string(), - age: 42, - }; - - let mut buffer = Vec::new(); - let mut ser = Serializer::new(&mut buffer); - bob.serialize(&mut ser).unwrap(); - - assert_eq!( - String::from_utf8(buffer).unwrap(), - "" - ); -} - -#[test] -fn serialize_struct_value_number() { - #[derive(Serialize)] - struct Person { - name: String, - #[serde(rename = "$text")] - age: u32, - } - - let bob = Person { - name: "Bob".to_string(), - age: 42, - }; - assert_eq!(to_string(&bob).unwrap(), "42"); -} - -#[test] -fn serialize_struct_value_string() { - #[derive(Serialize)] - struct Person { - name: String, - #[serde(rename = "$text")] - age: String, - } - - let bob = Person { - name: "Bob".to_string(), - age: "42".to_string(), - }; - assert_eq!(to_string(&bob).unwrap(), "42"); -} - -#[test] -fn serialize_enum() { - #[derive(Serialize)] - #[allow(dead_code)] - enum Node { - Boolean(bool), - Number(f64), - String(String), - } - - let mut buffer = Vec::new(); - let mut ser = Serializer::new(&mut buffer); - let node = Node::Boolean(true); - node.serialize(&mut ser).unwrap(); - - assert_eq!( - String::from_utf8(buffer).unwrap(), - "true" - ); -} - -#[test] -#[ignore] -fn serialize_a_list() { - let inputs = vec![1, 2, 3, 4]; - - let mut buffer = Vec::new(); - let mut ser = Serializer::new(&mut buffer); - inputs.serialize(&mut ser).unwrap(); - - println!("{}", String::from_utf8(buffer).unwrap()); - panic!(); -} - #[derive(Serialize)] struct Unit; diff --git a/tests/serde_attrs.rs b/tests/serde_attrs.rs deleted file mode 100644 index 416cb12c..00000000 --- a/tests/serde_attrs.rs +++ /dev/null @@ -1,75 +0,0 @@ -use quick_xml::se::to_string; -use regex::Regex; -use serde::Serialize; -use std::borrow::Cow; - -use pretty_assertions::assert_eq; - -#[derive(Serialize)] -#[serde(rename = "classroom")] -struct Classroom { - pub students: Students, - pub number: String, - pub adviser: Person, -} - -#[derive(Serialize)] -struct Students { - #[serde(rename = "person")] - pub persons: Vec, -} - -#[derive(Serialize)] -struct Person { - pub name: String, - pub age: u32, -} - -#[derive(Serialize)] -#[serde(rename = "empty")] -struct Empty {} - -#[test] -fn test_nested() { - let s1 = Person { - name: "sherlock".to_string(), - age: 20, - }; - let s2 = Person { - name: "harry".to_string(), - age: 19, - }; - let t = Person { - name: "albus".to_string(), - age: 88, - }; - let doc = Classroom { - students: Students { - persons: vec![s1, s2], - }, - number: "3-1".to_string(), - adviser: t, - }; - let xml = quick_xml::se::to_string(&doc).unwrap(); - - let str = r#" - - - - - - "#; - assert_eq!(xml, inline(str)); -} - -fn inline(str: &str) -> Cow { - let regex = Regex::new(r">\s+<").unwrap(); - regex.replace_all(str, "><") -} - -#[test] -fn test_empty() { - let e = Empty {}; - let xml = to_string(&e).unwrap(); - assert_eq!(xml, ""); -} From 4f376b1e97f17aaccbb83fde12c02dca7b8105b4 Mon Sep 17 00:00:00 2001 From: Mingun Date: Tue, 30 Aug 2022 22:57:55 +0500 Subject: [PATCH 18/21] Switch to new serializer Fixes #252 Fixes #280 Fixes #287 Fixes #343 Fixes #346 Fixes #361 Partially addresses #368 Fixes #429 Fixes #430 Fixes all tests Co-authored-by: Daniel Alley --- Changelog.md | 14 ++ README.md | 17 --- src/de/mod.rs | 1 - src/se/mod.rs | 339 ++++++++++++++++++++-------------------------- src/se/var.rs | 335 --------------------------------------------- tests/serde-se.rs | 39 ++---- 6 files changed, 173 insertions(+), 572 deletions(-) delete mode 100644 src/se/var.rs diff --git a/Changelog.md b/Changelog.md index 752d63dc..e16465a5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,11 +14,24 @@ ### Bug Fixes +- [#490]: Ensure that serialization of map keys always produces valid XML names. + In particular, that means that maps with numeric and numeric-like keys (for + example, `"42"`) no longer can be serialized because [XML name] cannot start + from a digit + ### Misc Changes - [#490]: Removed `$unflatten=` special prefix for fields for serde (de)serializer, because: - it is useless for deserializer - serializer was rewritten and does not require it anymore + + This prefix allowed you to serialize struct field as an XML element and now + replaced by a more thoughtful system explicitly indicating that a field should + be serialized as an attribute by prepending `@` character to its name +- [#490]: Removed `$primitive=` prefix. That prefix allowed you to serialize struct + field as an attribute instead of an element and now replaced by a more thoughtful + system explicitly indicating that a field should be serialized as an attribute + by prepending `@` character to its name - [#490]: In addition to the `$value` special name for a field a new `$text` special name was added: - `$text` is used if you want to map field to text content only. No markup is @@ -31,6 +44,7 @@ Refer to [documentation] for details. [#490]: https://github.com/tafia/quick-xml/pull/490 +[XML name]: https://www.w3.org/TR/xml11/#NT-Name [documentation]: https://docs.rs/quick-xml/0.27.0/quick_xml/de/index.html#difference-between-text-and-value-special-names ## 0.26.0 -- 2022-10-23 diff --git a/README.md b/README.md index 21897d2a..2846e147 100644 --- a/README.md +++ b/README.md @@ -132,23 +132,6 @@ struct Foo { Read about the difference in the [documentation](https://docs.rs/quick-xml/latest/quick_xml/de/index.html#difference-between-text-and-value-special-names). -### Serializing unit variants as primitives - -The `$primitive` prefix lets you serialize enum variants without associated values (internally referred to as _unit variants_) as primitive strings rather than self-closing tags. Consider the following definitions: - -```rust,ignore -enum Foo { - #[serde(rename = "$primitive=Bar")] - Bar -} - -struct Root { - foo: Foo -} -``` - -Serializing `Root { foo: Foo::Bar }` will then yield `` instead of ``. - ### Performance Note that despite not focusing on performance (there are several unnecessary copies), it remains about 10x faster than serde-xml-rs. diff --git a/src/de/mod.rs b/src/de/mod.rs index e54eb625..6e3bf7a1 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -356,7 +356,6 @@ use std::num::NonZeroUsize; pub(crate) const TEXT_KEY: &str = "$text"; /// Data represented by any XML markup inside pub(crate) const VALUE_KEY: &str = "$value"; -pub(crate) const PRIMITIVE_PREFIX: &str = "$primitive="; /// Simplified event which contains only these variants that used by deserializer #[derive(Debug, PartialEq, Eq)] diff --git a/src/se/mod.rs b/src/se/mod.rs index d6978125..3df5b5f4 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -77,31 +77,24 @@ mod content; mod element; mod key; pub(crate) mod simple_type; -mod var; - -use self::var::{Map, Seq, Struct, Tuple}; -use crate::{ - de::PRIMITIVE_PREFIX, - errors::serialize::DeError, - events::{BytesEnd, BytesStart, BytesText, Event}, - writer::{Indentation, Writer}, -}; + +use self::content::ContentSerializer; +use self::element::ElementSerializer; +use crate::errors::serialize::DeError; +use crate::writer::Indentation; use serde::ser::{self, Serialize}; use serde::serde_if_integer128; -use std::io::Write; +use std::fmt::Write; use std::str::from_utf8; /// Serialize struct into a `Write`r -pub fn to_writer(writer: W, value: &S) -> Result<(), DeError> { - let mut serializer = Serializer::new(writer); - value.serialize(&mut serializer) +pub fn to_writer(writer: W, value: &S) -> Result { + value.serialize(Serializer::new(writer)) } /// Serialize struct into a `String` pub fn to_string(value: &S) -> Result { - let mut writer = Vec::new(); - to_writer(&mut writer, value)?; - Ok(String::from_utf8(writer)?) + to_writer(String::new(), value) } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -109,7 +102,7 @@ pub fn to_string(value: &S) -> Result { /// Defines which characters would be escaped in [`Text`] events and attribute /// values. /// -/// [`Text`]: Event::Text +/// [`Text`]: crate::events::Event::Text #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum QuoteLevel { /// Performs escaping, escape all characters that could have special meaning @@ -154,6 +147,18 @@ pub enum QuoteLevel { //////////////////////////////////////////////////////////////////////////////////////////////////// +/// Implements serialization method by forwarding it to the serializer created by +/// the helper method [`Serializer::ser`]. +macro_rules! forward { + ($name:ident($ty:ty)) => { + fn $name(self, value: $ty) -> Result { + self.ser(&concat!("`", stringify!($ty), "`"))?.$name(value) + } + }; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + /// Almost all characters can form a name. Citation from : /// /// > The overall philosophy of names has changed since XML 1.0. Whereas XML 1.0 @@ -273,9 +278,9 @@ impl<'i> Indent<'i> { /// A Serializer pub struct Serializer<'r, W: Write> { - writer: Writer, + ser: ContentSerializer<'r, W>, /// Name of the root tag. If not specified, deduced from the structure name - root_tag: Option<&'r str>, + root_tag: Option>, } impl<'r, W: Write> Serializer<'r, W> { @@ -285,10 +290,19 @@ impl<'r, W: Write> Serializer<'r, W> { /// and newtype structs) will end up to an error. Use `with_root` to create /// serializer with explicitly defined root element name pub fn new(writer: W) -> Self { - Self::with_root(Writer::new(writer), None) + Self { + ser: ContentSerializer { + writer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + }, + root_tag: None, + } } - /// Creates a new `Serializer` that uses specified root tag name + /// Creates a new `Serializer` that uses specified root tag name. `name` should + /// be valid [XML name], otherwise error is returned. /// /// # Examples /// @@ -298,14 +312,13 @@ impl<'r, W: Write> Serializer<'r, W> { /// # use pretty_assertions::assert_eq; /// # use serde::Serialize; /// # use quick_xml::se::Serializer; - /// use quick_xml::writer::Writer; /// - /// let mut buffer = Vec::new(); - /// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 2); - /// let mut ser = Serializer::with_root(writer, Some("root")); + /// let ser = Serializer::with_root(String::new(), Some("root")).unwrap(); /// - /// "node".serialize(&mut ser).unwrap(); - /// assert_eq!(String::from_utf8(buffer).unwrap(), "node"); + /// assert_eq!( + /// "node".serialize(ser).unwrap(), + /// "node" + /// ); /// ``` /// /// When serializing a struct, newtype struct, unit struct or tuple `root_tag` @@ -314,8 +327,7 @@ impl<'r, W: Write> Serializer<'r, W> { /// ``` /// # use pretty_assertions::assert_eq; /// # use serde::Serialize; - /// use quick_xml::se::Serializer; - /// use quick_xml::writer::Writer; + /// # use quick_xml::se::Serializer; /// /// #[derive(Debug, PartialEq, Serialize)] /// struct Struct { @@ -323,144 +335,107 @@ impl<'r, W: Write> Serializer<'r, W> { /// answer: u32, /// } /// - /// let mut buffer = Vec::new(); - /// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 2); - /// let mut ser = Serializer::with_root(writer, Some("root")); + /// let ser = Serializer::with_root(String::new(), Some("root")).unwrap(); /// - /// Struct { + /// let data = Struct { /// question: "The Ultimate Question of Life, the Universe, and Everything".into(), /// answer: 42, - /// }.serialize(&mut ser).unwrap(); + /// }; + /// /// assert_eq!( - /// String::from_utf8(buffer.clone()).unwrap(), - /// r#""# + /// data.serialize(ser).unwrap(), + /// "\ + /// The Ultimate Question of Life, the Universe, and Everything\ + /// 42\ + /// " /// ); /// ``` - pub fn with_root(writer: Writer, root_tag: Option<&'r str>) -> Self { - Self { writer, root_tag } + /// + /// [XML name]: https://www.w3.org/TR/REC-xml/#NT-Name + pub fn with_root(writer: W, root_tag: Option<&'r str>) -> Result { + Ok(Self { + ser: ContentSerializer { + writer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + }, + root_tag: root_tag.map(|tag| XmlName::try_from(tag)).transpose()?, + }) } - fn write_primitive( - &mut self, - value: P, - escaped: bool, - ) -> Result<(), DeError> { - let value = value.to_string(); - let event = if escaped { - BytesText::from_escaped(&value) - } else { - BytesText::new(&value) - }; - self.writer.write_event(Event::Text(event))?; - Ok(()) + /// Configure indent for a serializer + pub fn indent(&mut self, indent_char: char, indent_size: usize) -> &mut Self { + self.ser.indent = Indent::Owned(Indentation::new(indent_char as u8, indent_size)); + self } - /// Writes self-closed tag `` into inner writer - fn write_self_closed(&mut self, tag_name: &str) -> Result<(), DeError> { - self.writer - .write_event(Event::Empty(BytesStart::new(tag_name)))?; - Ok(()) + /// Creates actual serializer or returns an error if root tag is not defined. + /// In that case `err` contains the name of type that cannot be serialized. + fn ser(self, err: &str) -> Result, DeError> { + if let Some(key) = self.root_tag { + Ok(ElementSerializer { ser: self.ser, key }) + } else { + Err(DeError::Unsupported( + format!("cannot serialize {} without defined root tag", err).into(), + )) + } } - /// Writes a serialized `value` surrounded by `...` - fn write_paired( - &mut self, - tag_name: &str, - value: &T, - ) -> Result<(), DeError> { - self.writer - .write_event(Event::Start(BytesStart::new(tag_name)))?; - value.serialize(&mut *self)?; - self.writer - .write_event(Event::End(BytesEnd::new(tag_name)))?; - Ok(()) + /// Creates actual serializer using root tag or a specified `key` if root tag + /// is not defined. Returns an error if root tag is not defined and a `key` + /// does not conform [XML rules](XmlName::try_from) for names. + fn ser_name(self, key: &'static str) -> Result, DeError> { + Ok(ElementSerializer { + ser: self.ser, + key: match self.root_tag { + Some(key) => key, + None => XmlName::try_from(key)?, + }, + }) } } -impl<'r, 'w, W: Write> ser::Serializer for &'w mut Serializer<'r, W> { - type Ok = (); +impl<'r, W: Write> ser::Serializer for Serializer<'r, W> { + type Ok = W; type Error = DeError; - type SerializeSeq = Seq<'r, 'w, W>; - type SerializeTuple = Tuple<'r, 'w, W>; - type SerializeTupleStruct = Tuple<'r, 'w, W>; - type SerializeTupleVariant = Tuple<'r, 'w, W>; - type SerializeMap = Map<'r, 'w, W>; - type SerializeStruct = Struct<'r, 'w, W>; - type SerializeStructVariant = Struct<'r, 'w, W>; - - fn serialize_bool(self, v: bool) -> Result { - self.write_primitive(if v { "true" } else { "false" }, true) - } - - fn serialize_i8(self, v: i8) -> Result { - self.write_primitive(v, true) - } - - fn serialize_i16(self, v: i16) -> Result { - self.write_primitive(v, true) - } - - fn serialize_i32(self, v: i32) -> Result { - self.write_primitive(v, true) - } - - fn serialize_i64(self, v: i64) -> Result { - self.write_primitive(v, true) - } + type SerializeSeq = as ser::Serializer>::SerializeSeq; + type SerializeTuple = as ser::Serializer>::SerializeTuple; + type SerializeTupleStruct = as ser::Serializer>::SerializeTupleStruct; + type SerializeTupleVariant = + as ser::Serializer>::SerializeTupleVariant; + type SerializeMap = as ser::Serializer>::SerializeMap; + type SerializeStruct = as ser::Serializer>::SerializeStruct; + type SerializeStructVariant = + as ser::Serializer>::SerializeStructVariant; - fn serialize_u8(self, v: u8) -> Result { - self.write_primitive(v, true) - } + forward!(serialize_bool(bool)); - fn serialize_u16(self, v: u16) -> Result { - self.write_primitive(v, true) - } + forward!(serialize_i8(i8)); + forward!(serialize_i16(i16)); + forward!(serialize_i32(i32)); + forward!(serialize_i64(i64)); - fn serialize_u32(self, v: u32) -> Result { - self.write_primitive(v, true) - } - - fn serialize_u64(self, v: u64) -> Result { - self.write_primitive(v, true) - } + forward!(serialize_u8(u8)); + forward!(serialize_u16(u16)); + forward!(serialize_u32(u32)); + forward!(serialize_u64(u64)); serde_if_integer128! { - fn serialize_i128(self, v: i128) -> Result { - self.write_primitive(v, true) - } - - fn serialize_u128(self, v: u128) -> Result { - self.write_primitive(v, true) - } - } - - fn serialize_f32(self, v: f32) -> Result { - self.write_primitive(v, true) + forward!(serialize_i128(i128)); + forward!(serialize_u128(u128)); } - fn serialize_f64(self, v: f64) -> Result { - self.write_primitive(v, true) - } - - fn serialize_char(self, v: char) -> Result { - self.write_primitive(v, false) - } - - fn serialize_str(self, value: &str) -> Result { - self.write_primitive(value, false) - } + forward!(serialize_f32(f32)); + forward!(serialize_f64(f64)); - fn serialize_bytes(self, _value: &[u8]) -> Result { - // TODO: I imagine you'd want to use base64 here. - // Not sure how to roundtrip effectively though... - Err(DeError::Unsupported( - "`serialize_bytes` not supported yet".into(), - )) - } + forward!(serialize_char(char)); + forward!(serialize_str(&str)); + forward!(serialize_bytes(&[u8])); fn serialize_none(self) -> Result { - Ok(()) + Ok(self.ser.writer) } fn serialize_some(self, value: &T) -> Result { @@ -468,25 +443,21 @@ impl<'r, 'w, W: Write> ser::Serializer for &'w mut Serializer<'r, W> { } fn serialize_unit(self) -> Result { - self.serialize_none() + self.ser("`()`")?.serialize_unit() } fn serialize_unit_struct(self, name: &'static str) -> Result { - self.write_self_closed(self.root_tag.unwrap_or(name)) + self.ser_name(name)?.serialize_unit_struct(name) } fn serialize_unit_variant( self, - _name: &'static str, - _variant_index: u32, + name: &'static str, + variant_index: u32, variant: &'static str, ) -> Result { - if variant.starts_with(PRIMITIVE_PREFIX) { - let variant = variant.split_at(PRIMITIVE_PREFIX.len()).1; - self.write_primitive(variant, false) - } else { - self.write_self_closed(variant) - } + self.ser_name(name)? + .serialize_unit_variant(name, variant_index, variant) } fn serialize_newtype_struct( @@ -494,85 +465,67 @@ impl<'r, 'w, W: Write> ser::Serializer for &'w mut Serializer<'r, W> { name: &'static str, value: &T, ) -> Result { - self.write_paired(self.root_tag.unwrap_or(name), value) + self.ser_name(name)?.serialize_newtype_struct(name, value) } fn serialize_newtype_variant( self, - _name: &'static str, - _variant_index: u32, + name: &'static str, + variant_index: u32, variant: &'static str, value: &T, ) -> Result { - // Flatten structs in enums are serialized as newtype struct variant + map. - // As serialize_map should write `root_tag` for ordinal maps (because it's - // only way for maps), and for enums this method already written a tag name - // (`variant`), we need to clear root tag before writing content and restore - // it after - let root = self.root_tag.take(); - let result = self.write_paired(variant, value); - self.root_tag = root; - result + self.ser_name(name)? + .serialize_newtype_variant(name, variant_index, variant, value) } - fn serialize_seq(self, _len: Option) -> Result { - Ok(Seq::new(self)) + fn serialize_seq(self, len: Option) -> Result { + self.ser("sequence")?.serialize_seq(len) } - fn serialize_tuple(self, _len: usize) -> Result { - let tag = match self.root_tag { - Some(tag) => tag, - None => { - return Err(DeError::Custom( - "cannot serialize unnamed tuple without defined root tag".into(), - )) - } - }; - Ok(Tuple::new(self, tag)) + fn serialize_tuple(self, len: usize) -> Result { + self.ser("unnamed tuple")?.serialize_tuple(len) } fn serialize_tuple_struct( self, name: &'static str, - _len: usize, + len: usize, ) -> Result { - Ok(Tuple::new(self, self.root_tag.unwrap_or(name))) + self.ser_name(name)?.serialize_tuple_struct(name, len) } fn serialize_tuple_variant( self, - _name: &'static str, - _variant_index: u32, + name: &'static str, + variant_index: u32, variant: &'static str, - _len: usize, + len: usize, ) -> Result { - Ok(Tuple::new(self, variant)) + self.ser_name(name)? + .serialize_tuple_variant(name, variant_index, variant, len) } - fn serialize_map(self, _len: Option) -> Result { - if let Some(tag) = self.root_tag { - // TODO: Write self-closed tag if map is empty - self.writer - .write_event(Event::Start(BytesStart::new(tag)))?; - } - Ok(Map::new(self)) + fn serialize_map(self, len: Option) -> Result { + self.ser("map")?.serialize_map(len) } fn serialize_struct( self, name: &'static str, - _len: usize, + len: usize, ) -> Result { - Ok(Struct::new(self, self.root_tag.unwrap_or(name))) + self.ser_name(name)?.serialize_struct(name, len) } fn serialize_struct_variant( self, - _name: &'static str, - _variant_index: u32, + name: &'static str, + variant_index: u32, variant: &'static str, - _len: usize, + len: usize, ) -> Result { - Ok(Struct::new(self, variant)) + self.ser_name(name)? + .serialize_struct_variant(name, variant_index, variant, len) } } diff --git a/src/se/var.rs b/src/se/var.rs deleted file mode 100644 index 15991b57..00000000 --- a/src/se/var.rs +++ /dev/null @@ -1,335 +0,0 @@ -use crate::{ - de::{TEXT_KEY, VALUE_KEY}, - errors::{serialize::DeError, Error}, - events::{BytesEnd, BytesStart, Event}, - se::key::QNameSerializer, - se::Serializer, - writer::Writer, -}; -use serde::ser::{self, Serialize}; -use std::io::Write; - -/// An implementation of `SerializeMap` for serializing to XML. -pub struct Map<'r, 'w, W> -where - W: 'w + Write, -{ - parent: &'w mut Serializer<'r, W>, -} - -impl<'r, 'w, W> Map<'r, 'w, W> -where - W: 'w + Write, -{ - /// Create a new Map - pub fn new(parent: &'w mut Serializer<'r, W>) -> Self { - Map { parent } - } -} - -impl<'r, 'w, W> ser::SerializeMap for Map<'r, 'w, W> -where - W: 'w + Write, -{ - type Ok = (); - type Error = DeError; - - fn serialize_key(&mut self, key: &T) -> Result<(), DeError> { - /* - Err(DeError::Unsupported( - "impossible to serialize the key on its own, please use serialize_entry()", - )) - */ - write!(self.parent.writer.inner(), "").map_err(Error::Io)?; - Ok(()) - } - - fn serialize_value(&mut self, value: &T) -> Result<(), DeError> { - value.serialize(&mut *self.parent) - } - - fn end(self) -> Result { - if let Some(tag) = self.parent.root_tag { - self.parent - .writer - .write_event(Event::End(BytesEnd::new(tag)))?; - } - Ok(()) - } - - fn serialize_entry( - &mut self, - key: &K, - value: &V, - ) -> Result<(), DeError> { - let key = key.serialize(QNameSerializer { - writer: String::new(), - })?; - - let writer = self.parent.writer.inner(); - writer.write_all(b"<").map_err(Error::Io)?; - writer.write_all(key.as_bytes()).map_err(Error::Io)?; - writer.write_all(b">").map_err(Error::Io)?; - - value.serialize(&mut *self.parent)?; - - let writer = self.parent.writer.inner(); - writer.write_all(b"").map_err(Error::Io)?; - Ok(()) - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/// An implementation of `SerializeStruct` for serializing to XML. -pub struct Struct<'r, 'w, W> -where - W: 'w + Write, -{ - parent: &'w mut Serializer<'r, W>, - /// Buffer for holding fields, serialized as attributes. Doesn't allocate - /// if there are no fields represented as attributes - attrs: BytesStart<'w>, - /// Buffer for holding fields, serialized as elements - children: Vec, - /// Buffer for serializing one field. Cleared after serialize each field - buffer: Vec, -} - -impl<'r, 'w, W> Struct<'r, 'w, W> -where - W: 'w + Write, -{ - /// Create a new `Struct` - pub fn new(parent: &'w mut Serializer<'r, W>, name: &'r str) -> Self { - Struct { - parent, - attrs: BytesStart::new(name), - children: Vec::new(), - buffer: Vec::new(), - } - } -} - -impl<'r, 'w, W> ser::SerializeStruct for Struct<'r, 'w, W> -where - W: 'w + Write, -{ - type Ok = (); - type Error = DeError; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), DeError> { - // TODO: Inherit indentation state from self.parent.writer - let writer = Writer::new(&mut self.buffer); - - let mut serializer = Serializer::with_root(writer, Some(key)); - value.serialize(&mut serializer)?; - - if !self.buffer.is_empty() { - if self.buffer[0] == b'<' || key == VALUE_KEY || key == TEXT_KEY { - // Drains buffer, moves it to children - self.children.append(&mut self.buffer); - } else { - self.attrs - .push_attribute((key.as_bytes(), self.buffer.as_ref())); - self.buffer.clear(); - } - } - - Ok(()) - } - - fn end(self) -> Result { - if self.children.is_empty() { - self.parent.writer.write_event(Event::Empty(self.attrs))?; - } else { - self.parent - .writer - .write_event(Event::Start(self.attrs.borrow()))?; - self.parent.writer.write(&self.children)?; - self.parent - .writer - .write_event(Event::End(self.attrs.to_end()))?; - } - Ok(()) - } -} - -impl<'r, 'w, W> ser::SerializeStructVariant for Struct<'r, 'w, W> -where - W: 'w + Write, -{ - type Ok = (); - type Error = DeError; - - #[inline] - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> { - ::serialize_field(self, key, value) - } - - #[inline] - fn end(self) -> Result { - ::end(self) - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/// An implementation of `SerializeSeq' for serializing to XML. -pub struct Seq<'r, 'w, W> -where - W: 'w + Write, -{ - parent: &'w mut Serializer<'r, W>, -} - -impl<'r, 'w, W> Seq<'r, 'w, W> -where - W: 'w + Write, -{ - /// Create a new `Seq` - pub fn new(parent: &'w mut Serializer<'r, W>) -> Self { - Seq { parent } - } -} - -impl<'r, 'w, W> ser::SerializeSeq for Seq<'r, 'w, W> -where - W: 'w + Write, -{ - type Ok = (); - type Error = DeError; - - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { - value.serialize(&mut *self.parent)?; - Ok(()) - } - - fn end(self) -> Result { - Ok(()) - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/// An implementation of `SerializeTuple`, `SerializeTupleStruct` and -/// `SerializeTupleVariant` for serializing to XML. -pub struct Tuple<'r, 'w, W> -where - W: 'w + Write, -{ - parent: &'w mut Serializer<'r, W>, - /// Possible qualified name of XML tag surrounding each element - name: &'r str, -} - -impl<'r, 'w, W> Tuple<'r, 'w, W> -where - W: 'w + Write, -{ - /// Create a new `Tuple` - pub fn new(parent: &'w mut Serializer<'r, W>, name: &'r str) -> Self { - Tuple { parent, name } - } -} - -impl<'r, 'w, W> ser::SerializeTuple for Tuple<'r, 'w, W> -where - W: 'w + Write, -{ - type Ok = (); - type Error = DeError; - - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { - write!(self.parent.writer.inner(), "<{}>", self.name).map_err(Error::Io)?; - value.serialize(&mut *self.parent)?; - write!(self.parent.writer.inner(), "", self.name).map_err(Error::Io)?; - Ok(()) - } - - #[inline] - fn end(self) -> Result { - Ok(()) - } -} - -impl<'r, 'w, W> ser::SerializeTupleStruct for Tuple<'r, 'w, W> -where - W: 'w + Write, -{ - type Ok = (); - type Error = DeError; - - #[inline] - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { - ::serialize_element(self, value) - } - - #[inline] - fn end(self) -> Result { - ::end(self) - } -} - -impl<'r, 'w, W> ser::SerializeTupleVariant for Tuple<'r, 'w, W> -where - W: 'w + Write, -{ - type Ok = (); - type Error = DeError; - - #[inline] - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { - ::serialize_element(self, value) - } - - #[inline] - fn end(self) -> Result { - ::end(self) - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -#[test] -fn test_serialize_map_entries() { - use serde::ser::SerializeMap; - - let mut buffer = Vec::new(); - - { - let mut ser = Serializer::new(&mut buffer); - let mut map = Map::new(&mut ser); - map.serialize_entry("name", "Bob").unwrap(); - map.serialize_entry("age", "5").unwrap(); - } - - assert_eq!( - String::from_utf8(buffer).unwrap(), - "Bob5" - ); -} diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 5fb1245c..110eac92 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -1,6 +1,5 @@ use quick_xml::se::Serializer; use quick_xml::utils::Bytes; -use quick_xml::writer::Writer; use quick_xml::DeError; use serde::{serde_if_integer128, Serialize}; @@ -52,8 +51,6 @@ struct Text { #[derive(Serialize)] enum ExternallyTagged { Unit, - #[serde(rename = "$primitive=PrimitiveUnit")] - PrimitiveUnit, Newtype(bool), Tuple(f64, &'static str), Struct { @@ -167,11 +164,9 @@ mod without_root { ($name:ident: $data:expr => $expected:literal) => { #[test] fn $name() { - let mut buffer = Vec::new(); - let mut ser = Serializer::new(&mut buffer); + let ser = Serializer::new(String::new()); - $data.serialize(&mut ser).unwrap(); - assert_eq!(String::from_utf8(buffer).unwrap(), $expected); + assert_eq!($data.serialize(ser).unwrap(), $expected); } }; } @@ -182,10 +177,10 @@ mod without_root { ($name:ident: $data:expr => $kind:ident($reason:literal)) => { #[test] fn $name() { - let mut buffer = Vec::new(); - let mut ser = Serializer::new(&mut buffer); + let mut buffer = String::new(); + let ser = Serializer::new(&mut buffer); - match $data.serialize(&mut ser) { + match $data.serialize(ser) { Err(DeError::$kind(e)) => assert_eq!(e, $reason), e => panic!( "Expected `{}({})`, found `{:?}`", @@ -194,7 +189,7 @@ mod without_root { e ), } - assert_eq!(String::from_utf8(buffer).unwrap(), ""); + assert_eq!(buffer, ""); } }; } @@ -233,7 +228,7 @@ mod without_root { err!(str_non_escaped: "non-escaped string" => Unsupported("cannot serialize `&str` without defined root tag")); err!(str_escaped: "<\"escaped & string'>" => Unsupported("cannot serialize `&str` without defined root tag")); - err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("cannot serialize `&[u8]` without defined root tag")); serialize_as!(option_none: Option::::None => ""); serialize_as!(option_some: Some(Unit) => ""); @@ -306,9 +301,6 @@ mod without_root { serialize_as!(unit: ExternallyTagged::Unit => ""); - serialize_as!(primitive_unit: - ExternallyTagged::PrimitiveUnit - => ""); serialize_as!(newtype: ExternallyTagged::Newtype(true) => "true"); @@ -565,11 +557,9 @@ mod with_root { ($name:ident: $data:expr => $expected:literal) => { #[test] fn $name() { - let mut buffer = Vec::new(); - let mut ser = Serializer::with_root(Writer::new(&mut buffer), Some("root")); + let ser = Serializer::with_root(String::new(), Some("root")).unwrap(); - $data.serialize(&mut ser).unwrap(); - assert_eq!(String::from_utf8(buffer).unwrap(), $expected); + assert_eq!($data.serialize(ser).unwrap(), $expected); } }; } @@ -580,10 +570,10 @@ mod with_root { ($name:ident: $data:expr => $kind:ident($reason:literal)) => { #[test] fn $name() { - let mut buffer = Vec::new(); - let mut ser = Serializer::with_root(Writer::new(&mut buffer), Some("root")); + let mut buffer = String::new(); + let ser = Serializer::with_root(&mut buffer, Some("root")).unwrap(); - match $data.serialize(&mut ser) { + match $data.serialize(ser) { Err(DeError::$kind(e)) => assert_eq!(e, $reason), e => panic!( "Expected `{}({})`, found `{:?}`", @@ -593,7 +583,7 @@ mod with_root { ), } // We can write something before fail - // assert_eq!(String::from_utf8(buffer).unwrap(), ""); + // assert_eq!(buffer, ""); } }; } @@ -721,9 +711,6 @@ mod with_root { serialize_as!(unit: ExternallyTagged::Unit => ""); - serialize_as!(primitive_unit: - ExternallyTagged::PrimitiveUnit - => ""); serialize_as!(newtype: ExternallyTagged::Newtype(true) => "true"); From 58924da393c869ee921121b528d810d62afd6830 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 5 Sep 2022 00:24:00 +0500 Subject: [PATCH 19/21] Add tests for indentation --- tests/serde-se.rs | 391 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 110eac92..9426ab74 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -547,6 +547,397 @@ mod without_root { "); } } + + mod with_indent { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:literal) => { + #[test] + fn $name() { + let mut ser = Serializer::new(String::new()); + ser.indent(' ', 2); + + assert_eq!($data.serialize(ser).unwrap(), $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = Serializer::new(&mut buffer); + + match $data.serialize(ser) { + Err(DeError::$kind(e)) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + assert_eq!(buffer, ""); + } + }; + } + + err!(false_: false => Unsupported("cannot serialize `bool` without defined root tag")); + err!(true_: true => Unsupported("cannot serialize `bool` without defined root tag")); + + err!(i8_: -42i8 => Unsupported("cannot serialize `i8` without defined root tag")); + err!(i16_: -4200i16 => Unsupported("cannot serialize `i16` without defined root tag")); + err!(i32_: -42000000i32 => Unsupported("cannot serialize `i32` without defined root tag")); + err!(i64_: -42000000000000i64 => Unsupported("cannot serialize `i64` without defined root tag")); + err!(isize_: -42000000000000isize => Unsupported("cannot serialize `i64` without defined root tag")); + + err!(u8_: 42u8 => Unsupported("cannot serialize `u8` without defined root tag")); + err!(u16_: 4200u16 => Unsupported("cannot serialize `u16` without defined root tag")); + err!(u32_: 42000000u32 => Unsupported("cannot serialize `u32` without defined root tag")); + err!(u64_: 42000000000000u64 => Unsupported("cannot serialize `u64` without defined root tag")); + err!(usize_: 42000000000000usize => Unsupported("cannot serialize `u64` without defined root tag")); + + serde_if_integer128! { + err!(i128_: -420000000000000000000000000000i128 => Unsupported("cannot serialize `i128` without defined root tag")); + err!(u128_: 420000000000000000000000000000u128 => Unsupported("cannot serialize `u128` without defined root tag")); + } + + err!(f32_: 4.2f32 => Unsupported("cannot serialize `f32` without defined root tag")); + err!(f64_: 4.2f64 => Unsupported("cannot serialize `f64` without defined root tag")); + + err!(char_non_escaped: 'h' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_lt: '<' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_gt: '>' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_amp: '&' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_apos: '\'' => Unsupported("cannot serialize `char` without defined root tag")); + err!(char_quot: '"' => Unsupported("cannot serialize `char` without defined root tag")); + + err!(str_non_escaped: "non-escaped string" => Unsupported("cannot serialize `&str` without defined root tag")); + err!(str_escaped: "<\"escaped & string'>" => Unsupported("cannot serialize `&str` without defined root tag")); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("cannot serialize `&[u8]` without defined root tag")); + + serialize_as!(option_none: Option::::None => ""); + serialize_as!(option_some: Some(Unit) => ""); + + err!(unit: () => Unsupported("cannot serialize `()` without defined root tag")); + serialize_as!(unit_struct: Unit => ""); + + serialize_as!(newtype: Newtype(true) => "true"); + + err!(seq: vec![1, 2, 3] => Unsupported("cannot serialize sequence without defined root tag")); + err!(tuple: + ("<\"&'>", "with\t\r\n spaces", 3usize) + => Unsupported("cannot serialize unnamed tuple without defined root tag")); + serialize_as!(tuple_struct: + Tuple(42.0, "answer") + => "42\n\ + answer"); + + err!(map: + BTreeMap::from([("$text", 1), ("_2", 3)]) + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(struct_: + Struct { + float: 42.0, + string: "answer" + } + => "\n \ + 42\n \ + answer\n\ + "); + serialize_as!(nested_struct: + NestedStruct { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\n \ + \n \ + 42\n \ + \n \ + answer\n\ + "); + // serde serializes flatten structs as maps, and we do not support + // serialization of maps without root tag + err!(flatten_struct: + FlattenStruct { + nested: Nested { float: 42.0 }, + string: "answer", + } + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(empty_struct: + Empty {} + => ""); + serialize_as!(text: + Text { + float: 42.0, + string: "answer" + } + => "\n \ + 42\n \ + answer\n\ + "); + + mod enum_ { + use super::*; + + mod externally_tagged { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(unit: + ExternallyTagged::Unit + => ""); + serialize_as!(newtype: + ExternallyTagged::Newtype(true) + => "true"); + serialize_as!(tuple_struct: + ExternallyTagged::Tuple(42.0, "answer") + => "42\n\ + answer"); + serialize_as!(struct_: + ExternallyTagged::Struct { + float: 42.0, + string: "answer" + } + => "\n \ + 42\n \ + answer\n\ + "); + serialize_as!(nested_struct: + ExternallyTagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\n \ + \n \ + 42\n \ + \n \ + answer\n\ + "); + serialize_as!(flatten_struct: + ExternallyTagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\n \ + 42\n \ + answer\n\ + "); + serialize_as!(empty_struct: + ExternallyTagged::Empty {} + => ""); + serialize_as!(text: + ExternallyTagged::Text { + float: 42.0, + string: "answer" + } + => "\n \ + 42\n \ + answer\n\ + "); + } + + mod internally_tagged { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(unit: + InternallyTagged::Unit + => "\n \ + Unit\n\ + "); + // serde serializes internally tagged newtype structs by delegating + // serialization to the inner type and augmenting it with a tag + serialize_as!(newtype: + InternallyTagged::Newtype(Nested { float: 42.0 }) + => "\n \ + Newtype\n \ + 42\n\ + "); + serialize_as!(struct_: + InternallyTagged::Struct { + float: 42.0, + string: "answer" + } + => "\n \ + Struct\n \ + 42\n \ + answer\n\ + "); + serialize_as!(nested_struct: + InternallyTagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\n \ + Holder\n \ + \n \ + 42\n \ + \n \ + answer\n\ + "); + // serde serializes flatten structs as maps, and we do not support + // serialization of maps without root tag + err!(flatten_struct: + InternallyTagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(empty_struct: + InternallyTagged::Empty {} + => "\n \ + Empty\n\ + "); + serialize_as!(text: + InternallyTagged::Text { + float: 42.0, + string: "answer" + } + => "\n \ + Text\n \ + 42\n \ + answer\n\ + "); + } + + mod adjacently_tagged { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(unit: + AdjacentlyTagged::Unit + => "\n \ + Unit\n\ + "); + serialize_as!(newtype: + AdjacentlyTagged::Newtype(true) + => "\n \ + Newtype\n \ + true\n\ + "); + serialize_as!(tuple_struct: + AdjacentlyTagged::Tuple(42.0, "answer") + => "\n \ + Tuple\n \ + 42\n \ + answer\n\ + "); + serialize_as!(struct_: + AdjacentlyTagged::Struct { + float: 42.0, + string: "answer" + } + => "\n \ + Struct\n \ + \n \ + 42\n \ + answer\n \ + \n\ + "); + serialize_as!(nested_struct: + AdjacentlyTagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\n \ + Holder\n \ + \n \ + \n \ + 42\n \ + \n \ + answer\n \ + \n\ + "); + serialize_as!(flatten_struct: + AdjacentlyTagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\n \ + Flatten\n \ + \n \ + 42\n \ + answer\n \ + \n\ + "); + serialize_as!(empty_struct: + AdjacentlyTagged::Empty {} + => "\n \ + Empty\n \ + \n\ + "); + serialize_as!(text: + AdjacentlyTagged::Text { + float: 42.0, + string: "answer" + } + => "\n \ + Text\n \ + \n \ + 42\n \ + answer\n \ + \n\ + "); + } + + mod untagged { + use super::*; + use pretty_assertions::assert_eq; + + err!(unit: Untagged::Unit + => Unsupported("cannot serialize `()` without defined root tag")); + err!(newtype: Untagged::Newtype(true) + => Unsupported("cannot serialize `bool` without defined root tag")); + err!(tuple_struct: Untagged::Tuple(42.0, "answer") + => Unsupported("cannot serialize unnamed tuple without defined root tag")); + serialize_as!(struct_: + Untagged::Struct { + float: 42.0, + string: "answer", + } + => "\n \ + 42\n \ + answer\n\ + "); + serialize_as!(nested_struct: + Untagged::Holder { + nested: Nested { float: 42.0 }, + string: "answer", + } + => "\n \ + \n \ + 42\n \ + \n \ + answer\n\ + "); + err!(flatten_struct: + Untagged::Flatten { + nested: Nested { float: 42.0 }, + string: "answer", + } + => Unsupported("cannot serialize map without defined root tag")); + serialize_as!(empty_struct: + Untagged::Empty {} + => ""); + serialize_as!(text: + Untagged::Text { + float: 42.0, + string: "answer" + } + => "\n \ + 42\n \ + answer\n\ + "); + } + } + } } mod with_root { From d0dc21946524a1829f536d700849d3ed88176bc8 Mon Sep 17 00:00:00 2001 From: Mingun Date: Thu, 21 Jul 2022 20:11:32 +0500 Subject: [PATCH 20/21] Change expectations about deserializing attributes failures (40): enum_::adjacently_tagged::flatten_struct::attributes enum_::adjacently_tagged::nested_struct::attributes enum_::adjacently_tagged::newtype::attributes enum_::adjacently_tagged::struct_::attributes enum_::adjacently_tagged::unit::attributes enum_::externally_tagged::flatten_struct::attributes enum_::externally_tagged::nested_struct::attributes enum_::externally_tagged::struct_::attributes enum_::internally_tagged::flatten_struct::attributes enum_::internally_tagged::nested_struct::attributes enum_::internally_tagged::struct_::attributes enum_::internally_tagged::unit::attributes enum_::untagged::flatten_struct::attributes enum_::untagged::nested_struct::attributes flatten_struct::attributes from_str_should_ignore_encoding nested_struct::attributes seq::fixed_name::fixed_size::list_of_struct seq::fixed_name::variable_size::list_of_struct seq::top_level::list_of_struct struct_::attribute_and_element struct_::attributes struct_::excess_attributes xml_schema_lists::attribute::bool_ xml_schema_lists::attribute::byte_buf xml_schema_lists::attribute::char_ xml_schema_lists::attribute::f32_ xml_schema_lists::attribute::f64_ xml_schema_lists::attribute::i128_ xml_schema_lists::attribute::i16_ xml_schema_lists::attribute::i32_ xml_schema_lists::attribute::i64_ xml_schema_lists::attribute::i8_ xml_schema_lists::attribute::string xml_schema_lists::attribute::u128_ xml_schema_lists::attribute::u16_ xml_schema_lists::attribute::u32_ xml_schema_lists::attribute::u64_ xml_schema_lists::attribute::u8_ xml_schema_lists::attribute::unit --- tests/serde-de.rs | 384 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 289 insertions(+), 95 deletions(-) diff --git a/tests/serde-de.rs b/tests/serde-de.rs index afa85399..25994ec7 100644 --- a/tests/serde-de.rs +++ b/tests/serde-de.rs @@ -281,7 +281,7 @@ mod trivial { in_struct!(true_: bool = "true", true); in_struct!(char_: char = "r", 'r'); - in_struct!(string: String = "escaped string", "escaped string".into()); + in_struct!(string: String = "escaped string", "escaped string".into()); /// XML does not able to store binary data #[test] @@ -344,7 +344,7 @@ mod trivial { in_struct!(char_: char = "", 'r'); // Escape sequences does not processed inside CDATA section - in_struct!(string: String = "", "escaped string".into()); + in_struct!(string: String = "", "escaped string".into()); /// XML does not able to store binary data #[test] @@ -569,6 +569,7 @@ mod seq { #[derive(Debug, PartialEq, Default, Deserialize)] #[serde(default)] struct Struct { + #[serde(rename = "@attribute")] attribute: Option, element: Option, } @@ -1205,6 +1206,7 @@ mod seq { #[derive(Debug, PartialEq, Default, Deserialize)] #[serde(default)] struct Struct { + #[serde(rename = "@attribute")] attribute: Option, element: Option, } @@ -2024,6 +2026,7 @@ mod seq { #[derive(Debug, PartialEq, Default, Deserialize)] #[serde(default)] struct Struct { + #[serde(rename = "@attribute")] attribute: Option, element: Option, } @@ -2082,6 +2085,7 @@ mod seq { #[derive(Debug, Deserialize, PartialEq)] struct List { /// Outer list mapped to elements, inner -- to `xs:list`. + /// /// `#[serde(default)]` is required to correctly deserialize /// empty sequence, because without elements the field /// also is missing and derived `Deserialize` implementation @@ -2131,7 +2135,7 @@ mod seq { fn element() { #[derive(Debug, Deserialize, PartialEq)] struct List { - /// Outer list mapped to elements, inner -- to `xs:list`. + /// List mapped to elements, inner -- to `xs:list`. /// /// `#[serde(default)]` is not required, because correct /// XML will always contains at least 1 element. @@ -3385,7 +3389,7 @@ mod seq { fn element() { #[derive(Debug, Deserialize, PartialEq)] struct List { - /// Outer list mapped to elements, inner -- to `xs:list`. + /// List mapped to elements, String -- to `xs:list`. /// /// `#[serde(default)]` is not required, because correct /// XML will always contains at least 1 element. @@ -4538,6 +4542,12 @@ mod seq { macro_rules! maplike_errors { ($type:ty) => { + maplike_errors!($type, $type); + }; + ( + $attributes:ty, + $mixed:ty + ) => { mod non_closed { use super::*; @@ -4545,7 +4555,7 @@ macro_rules! maplike_errors { /// earlier than error about missing fields #[test] fn missing_field() { - let data = from_str::<$type>(r#""#); + let data = from_str::<$mixed>(r#""#); match data { Err(DeError::UnexpectedEof) => (), @@ -4555,7 +4565,7 @@ macro_rules! maplike_errors { #[test] fn attributes() { - let data = from_str::<$type>(r#""#); + let data = from_str::<$attributes>(r#""#); match data { Err(DeError::UnexpectedEof) => (), @@ -4565,7 +4575,7 @@ macro_rules! maplike_errors { #[test] fn elements_root() { - let data = from_str::<$type>(r#"answer"#); + let data = from_str::<$mixed>(r#"answer"#); match data { Err(DeError::UnexpectedEof) => (), @@ -4575,7 +4585,7 @@ macro_rules! maplike_errors { #[test] fn elements_child() { - let data = from_str::<$type>(r#"answer"#); + let data = from_str::<$mixed>(r#"answer"#); match data { Err(DeError::UnexpectedEof) => (), @@ -4592,7 +4602,7 @@ macro_rules! maplike_errors { /// earlier than error about missing fields #[test] fn missing_field() { - let data = from_str::<$type>(r#""#); + let data = from_str::<$mixed>(r#""#); match data { Err(DeError::InvalidXml(EndEventMismatch { .. })) => (), @@ -4602,7 +4612,7 @@ macro_rules! maplike_errors { #[test] fn attributes() { - let data = from_str::<$type>( + let data = from_str::<$attributes>( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ); @@ -4615,7 +4625,7 @@ macro_rules! maplike_errors { #[test] fn elements_root() { - let data = from_str::<$type>( + let data = from_str::<$mixed>( // Comment for prevent unnecessary formatting - we use the same style in all tests r#"answer"#, ); @@ -4628,7 +4638,7 @@ macro_rules! maplike_errors { #[test] fn elements_child() { - let data = from_str::<$type>( + let data = from_str::<$mixed>( // Comment for prevent unnecessary formatting - we use the same style in all tests r#"answer"#, ); @@ -4698,22 +4708,40 @@ mod struct_ { use super::*; use pretty_assertions::assert_eq; + /// Type where all struct fields represented by elements #[derive(Debug, Deserialize, PartialEq)] - struct Struct { + struct Elements { + float: f64, + string: String, + } + + /// Type where all struct fields represented by attributes + #[derive(Debug, Deserialize, PartialEq)] + struct Attributes { + #[serde(rename = "@float")] + float: f64, + #[serde(rename = "@string")] + string: String, + } + + /// Type where one field represented by an attribute and one by an element + #[derive(Debug, Deserialize, PartialEq)] + struct Mixed { + #[serde(rename = "@float")] float: f64, string: String, } #[test] fn elements() { - let data: Struct = from_str( + let data: Elements = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#"42answer"#, ) .unwrap(); assert_eq!( data, - Struct { + Elements { float: 42.0, string: "answer".into() } @@ -4722,7 +4750,7 @@ mod struct_ { #[test] fn excess_elements() { - let data: Struct = from_str( + let data: Elements = from_str( r#" @@ -4735,7 +4763,7 @@ mod struct_ { .unwrap(); assert_eq!( data, - Struct { + Elements { float: 42.0, string: "answer".into() } @@ -4744,14 +4772,14 @@ mod struct_ { #[test] fn attributes() { - let data: Struct = from_str( + let data: Attributes = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Struct { + Attributes { float: 42.0, string: "answer".into() } @@ -4760,13 +4788,13 @@ mod struct_ { #[test] fn excess_attributes() { - let data: Struct = from_str( + let data: Attributes = from_str( r#""#, ) .unwrap(); assert_eq!( data, - Struct { + Attributes { float: 42.0, string: "answer".into() } @@ -4775,7 +4803,7 @@ mod struct_ { #[test] fn attribute_and_element() { - let data: Struct = from_str( + let data: Mixed = from_str( r#" answer @@ -4786,7 +4814,7 @@ mod struct_ { assert_eq!( data, - Struct { + Mixed { float: 42.0, string: "answer".into() } @@ -4795,40 +4823,40 @@ mod struct_ { #[test] fn namespaces() { - let data: Struct = from_str( + let data: Elements = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#"42answer"#, ) .unwrap(); assert_eq!( data, - Struct { + Elements { float: 42.0, string: "answer".into() } ); } - maplike_errors!(Struct); + maplike_errors!(Attributes, Mixed); } mod nested_struct { use super::*; use pretty_assertions::assert_eq; - #[derive(Debug, Deserialize, PartialEq)] - struct Struct { - nested: Nested, - string: String, - } - - #[derive(Debug, Deserialize, PartialEq)] - struct Nested { - float: f32, - } - #[test] fn elements() { + #[derive(Debug, Deserialize, PartialEq)] + struct Struct { + nested: Nested, + string: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct Nested { + float: f32, + } + let data: Struct = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#"answer42"#, @@ -4845,6 +4873,19 @@ mod nested_struct { #[test] fn attributes() { + #[derive(Debug, Deserialize, PartialEq)] + struct Struct { + nested: Nested, + #[serde(rename = "@string")] + string: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct Nested { + #[serde(rename = "@float")] + float: f32, + } + let data: Struct = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, @@ -4864,22 +4905,22 @@ mod flatten_struct { use super::*; use pretty_assertions::assert_eq; - #[derive(Debug, Deserialize, PartialEq)] - struct Struct { - #[serde(flatten)] - nested: Nested, - string: String, - } - - #[derive(Debug, Deserialize, PartialEq)] - struct Nested { - //TODO: change to f64 after fixing https://github.com/serde-rs/serde/issues/1183 - float: String, - } - #[test] #[ignore = "Prime cause: deserialize_any under the hood + https://github.com/serde-rs/serde/issues/1183"] fn elements() { + #[derive(Debug, Deserialize, PartialEq)] + struct Struct { + #[serde(flatten)] + nested: Nested, + string: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct Nested { + //TODO: change to f64 after fixing https://github.com/serde-rs/serde/issues/1183 + float: String, + } + let data: Struct = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#"42answer"#, @@ -4896,6 +4937,21 @@ mod flatten_struct { #[test] fn attributes() { + #[derive(Debug, Deserialize, PartialEq)] + struct Struct { + #[serde(flatten)] + nested: Nested, + #[serde(rename = "@string")] + string: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct Nested { + //TODO: change to f64 after fixing https://github.com/serde-rs/serde/issues/1183 + #[serde(rename = "@float")] + float: String, + } + let data: Struct = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, @@ -4920,10 +4976,19 @@ mod enum_ { float: String, } + /// Type where all struct fields represented by attributes + #[derive(Debug, Deserialize, PartialEq)] + struct NestedAttr { + //TODO: change to f64 after fixing https://github.com/serde-rs/serde/issues/1183 + #[serde(rename = "@float")] + float: String, + } + mod externally_tagged { use super::*; use pretty_assertions::assert_eq; + /// Type where all fields of struct variants represented by elements #[derive(Debug, Deserialize, PartialEq)] enum Node { Unit, @@ -4945,6 +5010,28 @@ mod enum_ { }, } + /// Type where all fields of struct variants represented by attributes + #[derive(Debug, Deserialize, PartialEq)] + enum NodeAttr { + Struct { + #[serde(rename = "@float")] + float: f64, + #[serde(rename = "@string")] + string: String, + }, + Holder { + nested: NestedAttr, + #[serde(rename = "@string")] + string: String, + }, + Flatten { + #[serde(flatten)] + nested: NestedAttr, + #[serde(rename = "@string")] + string: String, + }, + } + /// Workaround for serde bug https://github.com/serde-rs/serde/issues/1904 #[derive(Debug, Deserialize, PartialEq)] enum Workaround { @@ -4991,14 +5078,14 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Struct { + NodeAttr::Struct { float: 42.0, string: "answer".into() } @@ -5043,15 +5130,15 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Holder { - nested: Nested { float: "42".into() }, + NodeAttr::Holder { + nested: NestedAttr { float: "42".into() }, string: "answer".into() } ); @@ -5081,15 +5168,15 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Flatten { - nested: Nested { float: "42".into() }, + NodeAttr::Flatten { + nested: NestedAttr { float: "42".into() }, string: "answer".into() } ); @@ -5100,6 +5187,7 @@ mod enum_ { mod internally_tagged { use super::*; + /// Type where all fields of struct variants and a tag represented by elements #[derive(Debug, Deserialize, PartialEq)] #[serde(tag = "tag")] enum Node { @@ -5107,8 +5195,8 @@ mod enum_ { /// Primitives (such as `bool`) are not supported by serde in the internally tagged mode Newtype(NewtypeContent), // Tuple(f64, String),// Tuples are not supported in the internally tagged mode - //TODO: change to f64 after fixing https://github.com/serde-rs/serde/issues/1183 Struct { + //TODO: change to f64 after fixing https://github.com/serde-rs/serde/issues/1183 float: String, string: String, }, @@ -5123,6 +5211,34 @@ mod enum_ { }, } + /// Type where all fields of struct variants and a tag represented by attributes + #[derive(Debug, Deserialize, PartialEq)] + #[serde(tag = "@tag")] + enum NodeAttr { + Unit, + /// Primitives (such as `bool`) are not supported by serde in the internally tagged mode + Newtype(NewtypeContent), + // Tuple(f64, String),// Tuples are not supported in the internally tagged mode + Struct { + //TODO: change to f64 after fixing https://github.com/serde-rs/serde/issues/1183 + #[serde(rename = "@float")] + float: String, + #[serde(rename = "@string")] + string: String, + }, + Holder { + nested: NestedAttr, + #[serde(rename = "@string")] + string: String, + }, + Flatten { + #[serde(flatten)] + nested: NestedAttr, + #[serde(rename = "@string")] + string: String, + }, + } + #[derive(Debug, Deserialize, PartialEq)] struct NewtypeContent { value: bool, @@ -5140,8 +5256,8 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str(r#""#).unwrap(); - assert_eq!(data, Node::Unit); + let data: NodeAttr = from_str(r#""#).unwrap(); + assert_eq!(data, NodeAttr::Unit); } } @@ -5163,12 +5279,12 @@ mod enum_ { #[test] #[ignore = "Prime cause: deserialize_any under the hood + https://github.com/serde-rs/serde/issues/1183"] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); - assert_eq!(data, Node::Newtype(NewtypeContent { value: true })); + assert_eq!(data, NodeAttr::Newtype(NewtypeContent { value: true })); } } @@ -5194,14 +5310,14 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Struct { + NodeAttr::Struct { float: "42".into(), string: "answer".into() } @@ -5230,15 +5346,15 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Holder { - nested: Nested { float: "42".into() }, + NodeAttr::Holder { + nested: NestedAttr { float: "42".into() }, string: "answer".into() } ); @@ -5267,15 +5383,15 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Flatten { - nested: Nested { float: "42".into() }, + NodeAttr::Flatten { + nested: NestedAttr { float: "42".into() }, string: "answer".into() } ); @@ -5286,6 +5402,8 @@ mod enum_ { mod adjacently_tagged { use super::*; + /// Type where all fields of struct variants, tag and content fields + /// represented by elements #[derive(Debug, Deserialize, PartialEq)] #[serde(tag = "tag", content = "content")] enum Node { @@ -5308,6 +5426,41 @@ mod enum_ { }, } + /// Type where all fields of struct variants, tag and content fields + /// represented by attributes + #[derive(Debug, Deserialize, PartialEq)] + #[serde(tag = "@tag", content = "@content")] + enum NodeAttrSimple { + Unit, + Newtype(bool), + } + + /// Type where all fields of struct variants and a tag represented by attributes + /// content cannot be represented by attribute because this is a complex struct + #[derive(Debug, Deserialize, PartialEq)] + #[serde(tag = "@tag", content = "content")] + enum NodeAttrComplex { + //TODO: serde bug https://github.com/serde-rs/serde/issues/1904 + // Tuple(f64, String), + Struct { + #[serde(rename = "@float")] + float: f64, + #[serde(rename = "@string")] + string: String, + }, + Holder { + nested: NestedAttr, + #[serde(rename = "@string")] + string: String, + }, + Flatten { + #[serde(flatten)] + nested: NestedAttr, + #[serde(rename = "@string")] + string: String, + }, + } + /// Workaround for serde bug https://github.com/serde-rs/serde/issues/1904 #[derive(Debug, Deserialize, PartialEq)] #[serde(tag = "tag", content = "content")] @@ -5315,6 +5468,13 @@ mod enum_ { Tuple(f64, String), } + /// Workaround for serde bug https://github.com/serde-rs/serde/issues/1904 + #[derive(Debug, Deserialize, PartialEq)] + #[serde(tag = "@tag", content = "@content")] + enum WorkaroundAttr { + Tuple(f64, String), + } + mod unit { use super::*; use pretty_assertions::assert_eq; @@ -5327,8 +5487,8 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str(r#""#).unwrap(); - assert_eq!(data, Node::Unit); + let data: NodeAttrSimple = from_str(r#""#).unwrap(); + assert_eq!(data, NodeAttrSimple::Unit); } } @@ -5348,8 +5508,12 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str(r#""#).unwrap(); - assert_eq!(data, Node::Newtype(true)); + let data: NodeAttrSimple = from_str( + // Comment for prevent unnecessary formatting - we use the same style in all tests + r#""#, + ) + .unwrap(); + assert_eq!(data, NodeAttrSimple::Newtype(true)); } } @@ -5368,12 +5532,12 @@ mod enum_ { #[test] #[ignore = "Prime cause: deserialize_any under the hood + https://github.com/serde-rs/serde/issues/1183"] fn attributes() { - let data: Workaround = from_str( - // Comment for prevent unnecessary formatting - we use the same style in all tests - r#"answer"#, + let data: WorkaroundAttr = from_str( + // We cannot have two attributes with the same name, so both values stored in one attribute + r#""#, ) .unwrap(); - assert_eq!(data, Workaround::Tuple(42.0, "answer".into())); + assert_eq!(data, WorkaroundAttr::Tuple(42.0, "answer".into())); } } @@ -5398,14 +5562,14 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttrComplex = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Struct { + NodeAttrComplex::Struct { float: 42.0, string: "answer".into() } @@ -5442,13 +5606,13 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttrComplex = from_str( r#""#, ).unwrap(); assert_eq!( data, - Node::Holder { - nested: Nested { float: "42".into() }, + NodeAttrComplex::Holder { + nested: NestedAttr { float: "42".into() }, string: "answer".into() } ); @@ -5476,15 +5640,15 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttrComplex = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Flatten { - nested: Nested { float: "42".into() }, + NodeAttrComplex::Flatten { + nested: NestedAttr { float: "42".into() }, string: "answer".into() } ); @@ -5496,6 +5660,7 @@ mod enum_ { use super::*; use pretty_assertions::assert_eq; + /// Type where all fields of struct variants represented by elements #[derive(Debug, Deserialize, PartialEq)] #[serde(untagged)] enum Node { @@ -5520,6 +5685,33 @@ mod enum_ { }, } + /// Type where all fields of struct variants represented by attributes + #[derive(Debug, Deserialize, PartialEq)] + #[serde(untagged)] + enum NodeAttr { + // serde bug https://github.com/serde-rs/serde/issues/1904 + // Tuple(f64, String), + Struct { + #[serde(rename = "@float")] + float: f64, + #[serde(rename = "@string")] + string: String, + }, + Holder { + nested: NestedAttr, + #[serde(rename = "@string")] + string: String, + }, + Flatten { + #[serde(flatten)] + nested: NestedAttr, + // Can't use "string" as name because in that case this variant + // will have no difference from `Struct` variant + #[serde(rename = "@string2")] + string2: String, + }, + } + /// Workaround for serde bug https://github.com/serde-rs/serde/issues/1904 #[derive(Debug, Deserialize, PartialEq)] #[serde(untagged)] @@ -5574,14 +5766,14 @@ mod enum_ { #[test] #[ignore = "Prime cause: deserialize_any under the hood + https://github.com/serde-rs/serde/issues/1183"] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Struct { + NodeAttr::Struct { float: 42.0, string: "answer".into() } @@ -5611,15 +5803,15 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Holder { - nested: Nested { float: "42".into() }, + NodeAttr::Holder { + nested: NestedAttr { float: "42".into() }, string: "answer".into() } ); @@ -5649,15 +5841,15 @@ mod enum_ { #[test] fn attributes() { - let data: Node = from_str( + let data: NodeAttr = from_str( // Comment for prevent unnecessary formatting - we use the same style in all tests r#""#, ) .unwrap(); assert_eq!( data, - Node::Flatten { - nested: Nested { float: "42".into() }, + NodeAttr::Flatten { + nested: NestedAttr { float: "42".into() }, string2: "answer".into() } ); @@ -5708,6 +5900,7 @@ mod xml_schema_lists { #[derive(Debug, Deserialize, PartialEq)] struct List { + #[serde(rename = "@list")] list: Vec, } @@ -5842,6 +6035,7 @@ fn from_str_should_ignore_encoding() { #[derive(Debug, PartialEq, Deserialize)] struct A { + #[serde(rename = "@a")] a: String, } From 8b50c64de3f309d670d3393f894b6cfe4b2aeca2 Mon Sep 17 00:00:00 2001 From: Mingun Date: Sun, 3 Jul 2022 02:18:58 +0500 Subject: [PATCH 21/21] Implement special deserializer for key names --- src/de/key.rs | 458 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/de/map.rs | 18 +- src/de/mod.rs | 3 +- src/se/mod.rs | 2 +- 4 files changed, 469 insertions(+), 12 deletions(-) create mode 100644 src/de/key.rs diff --git a/src/de/key.rs b/src/de/key.rs new file mode 100644 index 00000000..b4905d12 --- /dev/null +++ b/src/de/key.rs @@ -0,0 +1,458 @@ +use crate::de::str2bool; +use crate::encoding::Decoder; +use crate::errors::serialize::DeError; +use crate::name::QName; +use serde::de::{DeserializeSeed, Deserializer, EnumAccess, VariantAccess, Visitor}; +use serde::{forward_to_deserialize_any, serde_if_integer128}; +use std::borrow::Cow; + +macro_rules! deserialize_num { + ($method:ident, $visit:ident) => { + fn $method(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.$visit(self.name.parse()?) + } + }; +} + +/// Decodes raw bytes using the deserializer encoding. +/// The method will borrow if encoding is UTF-8 compatible and `name` contains +/// only UTF-8 compatible characters (usually only ASCII characters). +#[inline] +fn decode_name<'n>(name: QName<'n>, decoder: Decoder) -> Result, DeError> { + let local = name.local_name(); + Ok(decoder.decode(local.into_inner())?) +} + +/// A deserializer for xml names of elements and attributes. +/// +/// Used for deserializing values from: +/// - attribute names (`<... name="..." ...>`) +/// - element names (`...`) +/// +/// Converts a name to an identifier string using the following rules: +/// +/// - if it is an [`attribute`] name, put `@` in front of the identifier +/// - put the decoded [`local_name()`] of a name to the identifier +/// +/// The final identifier looks like `[@]local_name` +/// (where `[]` means optional element). +/// +/// The deserializer also supports deserializing names as other primitive types: +/// - numbers +/// - booleans +/// - unit (`()`) and unit structs +/// - unit variants of the enumerations +/// +/// Because `serde` does not define on which side type conversion should be +/// performed, and because [`Deserialize`] implementation for that primitives +/// in serde does not accept strings, the deserializer will perform conversion +/// by itself. +/// +/// The deserializer is able to deserialize unit and unit structs, but any name +/// will be converted to the same unit instance. This is asymmetry with a serializer, +/// which not able to serialize those types, because empty names are impossible +/// in XML. +/// +/// `deserialize_any()` returns the same result as `deserialize_identifier()`. +/// +/// # Lifetime +/// +/// - `'d`: lifetime of a deserializer that holds a buffer with content of events +/// +/// [`attribute`]: Self::from_attr +/// [`local_name()`]: QName::local_name +/// [`Deserialize`]: serde::Deserialize +pub struct QNameDeserializer<'d> { + name: Cow<'d, str>, +} + +impl<'d> QNameDeserializer<'d> { + /// Creates deserializer from name of an attribute + pub fn from_attr(name: QName<'d>, decoder: Decoder) -> Result { + let local = decode_name(name, decoder)?; + + Ok(Self { + name: Cow::Owned(format!("@{local}")), + }) + } + + /// Creates deserializer from name of an element + pub fn from_elem(name: QName<'d>, decoder: Decoder) -> Result { + let local = decode_name(name, decoder)?; + + Ok(Self { name: local }) + } +} + +impl<'de, 'd> Deserializer<'de> for QNameDeserializer<'d> { + type Error = DeError; + + forward_to_deserialize_any! { + char str string + bytes byte_buf + seq tuple tuple_struct + map struct + ignored_any + } + + /// According to the , + /// valid boolean representations are only `"true"`, `"false"`, `"1"`, + /// and `"0"`. But this method also handles following: + /// + /// |`bool` |XML content + /// |-------|------------------------------------------------------------- + /// |`true` |`"True"`, `"TRUE"`, `"t"`, `"Yes"`, `"YES"`, `"yes"`, `"y"` + /// |`false`|`"False"`, `"FALSE"`, `"f"`, `"No"`, `"NO"`, `"no"`, `"n"` + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + str2bool(self.name.as_ref(), visitor) + } + + deserialize_num!(deserialize_i8, visit_i8); + deserialize_num!(deserialize_i16, visit_i16); + deserialize_num!(deserialize_i32, visit_i32); + deserialize_num!(deserialize_i64, visit_i64); + + deserialize_num!(deserialize_u8, visit_u8); + deserialize_num!(deserialize_u16, visit_u16); + deserialize_num!(deserialize_u32, visit_u32); + deserialize_num!(deserialize_u64, visit_u64); + + serde_if_integer128! { + deserialize_num!(deserialize_i128, visit_i128); + deserialize_num!(deserialize_u128, visit_u128); + } + + deserialize_num!(deserialize_f32, visit_f32); + deserialize_num!(deserialize_f64, visit_f64); + + /// Calls [`Visitor::visit_unit`] + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + + /// Forwards deserialization to the [`Self::deserialize_unit`] + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + /// Forwards deserialization to the [`Self::deserialize_identifier`] + #[inline] + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_identifier(visitor) + } + + /// If `name` is an empty string then calls [`Visitor::visit_none`], + /// otherwise calls [`Visitor::visit_some`] with itself + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.name.is_empty() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + /// Calls a [`Visitor::visit_str`] if [`name`] contains only UTF-8 + /// compatible encoded characters and represents an element name and + /// a [`Visitor::visit_string`] in all other cases. + /// + /// [`name`]: Self::name + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.name { + Cow::Borrowed(name) => visitor.visit_str(name), + Cow::Owned(name) => visitor.visit_string(name), + } + } + + fn deserialize_enum( + self, + _name: &str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_enum(self) + } +} + +impl<'de, 'd> EnumAccess<'de> for QNameDeserializer<'d> { + type Error = DeError; + type Variant = QNameUnitOnly; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: DeserializeSeed<'de>, + { + let name = seed.deserialize(self)?; + Ok((name, QNameUnitOnly)) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Deserializer of variant data, that supports only unit variants. +/// Attempt to deserialize newtype, tuple or struct variant will return a +/// [`DeError::Unsupported`] error. +pub struct QNameUnitOnly; +impl<'de> VariantAccess<'de> for QNameUnitOnly { + type Error = DeError; + + #[inline] + fn unit_variant(self) -> Result<(), DeError> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + Err(DeError::Unsupported( + "enum newtype variants are not supported as an XML names".into(), + )) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(DeError::Unsupported( + "enum tuple variants are not supported as an XML names".into(), + )) + } + + fn struct_variant( + self, + _fields: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + Err(DeError::Unsupported( + "enum struct variants are not supported as an XML names".into(), + )) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use crate::se::key::QNameSerializer; + use crate::utils::{ByteBuf, Bytes}; + use pretty_assertions::assert_eq; + use serde::de::IgnoredAny; + use serde::{Deserialize, Serialize}; + use std::collections::HashMap; + + #[derive(Debug, Deserialize, Serialize, PartialEq)] + struct Unit; + + #[derive(Debug, Deserialize, Serialize, PartialEq)] + struct Newtype(String); + + #[derive(Debug, Deserialize, Serialize, PartialEq)] + struct Struct { + key: String, + val: usize, + } + + #[derive(Debug, Deserialize, Serialize, PartialEq)] + enum Enum { + Unit, + #[serde(rename = "@Attr")] + Attr, + Newtype(String), + Tuple(String, usize), + Struct { + key: String, + val: usize, + }, + } + + #[derive(Debug, Deserialize, PartialEq)] + #[serde(field_identifier)] + enum Id { + Field, + } + + #[derive(Debug, Deserialize)] + #[serde(transparent)] + struct Any(IgnoredAny); + impl PartialEq for Any { + fn eq(&self, _other: &Any) -> bool { + true + } + } + + /// Checks that given `$input` successfully deserializing into given `$result` + macro_rules! deserialized_to_only { + ($name:ident: $type:ty = $input:literal => $result:expr) => { + #[test] + fn $name() { + let de = QNameDeserializer { + name: Cow::Borrowed($input), + }; + let data: $type = Deserialize::deserialize(de).unwrap(); + + assert_eq!(data, $result); + } + }; + } + + /// Checks that given `$input` successfully deserializing into given `$result` + macro_rules! deserialized_to { + ($name:ident: $type:ty = $input:literal => $result:expr) => { + #[test] + fn $name() { + let de = QNameDeserializer { + name: Cow::Borrowed($input), + }; + let data: $type = Deserialize::deserialize(de).unwrap(); + + assert_eq!(data, $result); + + // Roundtrip to ensure that serializer corresponds to deserializer + assert_eq!( + data.serialize(QNameSerializer { + writer: String::new() + }) + .unwrap(), + $input + ); + } + }; + } + + /// Checks that attempt to deserialize given `$input` as a `$type` results to a + /// deserialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $type:ty = $input:literal => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let de = QNameDeserializer { + name: Cow::Borrowed($input), + }; + let err = <$type as Deserialize>::deserialize(de).unwrap_err(); + + match err { + DeError::$kind(e) => assert_eq!(e, $reason), + _ => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + err + ), + } + } + }; + } + + deserialized_to!(false_: bool = "false" => false); + deserialized_to!(true_: bool = "true" => true); + + deserialized_to!(i8_: i8 = "-2" => -2); + deserialized_to!(i16_: i16 = "-2" => -2); + deserialized_to!(i32_: i32 = "-2" => -2); + deserialized_to!(i64_: i64 = "-2" => -2); + + deserialized_to!(u8_: u8 = "3" => 3); + deserialized_to!(u16_: u16 = "3" => 3); + deserialized_to!(u32_: u32 = "3" => 3); + deserialized_to!(u64_: u64 = "3" => 3); + + serde_if_integer128! { + deserialized_to!(i128_: i128 = "-2" => -2); + deserialized_to!(u128_: u128 = "2" => 2); + } + + deserialized_to!(f32_: f32 = "1.23" => 1.23); + deserialized_to!(f64_: f64 = "1.23" => 1.23); + + deserialized_to!(char_unescaped: char = "h" => 'h'); + err!(char_escaped: char = "<" + => Custom("invalid value: string \"<\", expected a character")); + + deserialized_to!(string: String = "<escaped string" => "<escaped string"); + err!(borrowed_str: &str = "name" + => Custom("invalid type: string \"name\", expected a borrowed string")); + + err!(byte_buf: ByteBuf = "<escaped string" + => Custom("invalid type: string \"<escaped string\", expected byte data")); + err!(borrowed_bytes: Bytes = "name" + => Custom("invalid type: string \"name\", expected borrowed bytes")); + + deserialized_to!(option_none: Option = "" => None); + deserialized_to!(option_some: Option = "name" => Some("name".into())); + + // Unit structs cannot be represented in some meaningful way, but it meaningful + // to use them as a placeholder when we want to deserialize _something_ + deserialized_to_only!(unit: () = "anything" => ()); + deserialized_to_only!(unit_struct: Unit = "anything" => Unit); + + deserialized_to!(newtype: Newtype = "<escaped string" => Newtype("<escaped string".into())); + + err!(seq: Vec<()> = "name" + => Custom("invalid type: string \"name\", expected a sequence")); + err!(tuple: ((), ()) = "name" + => Custom("invalid type: string \"name\", expected a tuple of size 2")); + err!(tuple_struct: ((), ()) = "name" + => Custom("invalid type: string \"name\", expected a tuple of size 2")); + + err!(map: HashMap<(), ()> = "name" + => Custom("invalid type: string \"name\", expected a map")); + err!(struct_: Struct = "name" + => Custom("invalid type: string \"name\", expected struct Struct")); + + deserialized_to!(enum_unit: Enum = "Unit" => Enum::Unit); + deserialized_to!(enum_unit_for_attr: Enum = "@Attr" => Enum::Attr); + err!(enum_newtype: Enum = "Newtype" + => Unsupported("enum newtype variants are not supported as an XML names")); + err!(enum_tuple: Enum = "Tuple" + => Unsupported("enum tuple variants are not supported as an XML names")); + err!(enum_struct: Enum = "Struct" + => Unsupported("enum struct variants are not supported as an XML names")); + + // Field identifiers cannot be serialized, and IgnoredAny represented _something_ + // which is not concrete + deserialized_to_only!(identifier: Id = "Field" => Id::Field); + deserialized_to_only!(ignored_any: Any = "any-name" => Any(IgnoredAny)); +} diff --git a/src/de/map.rs b/src/de/map.rs index a7a957b5..84bea432 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -1,13 +1,14 @@ //! Serde `Deserializer` module use crate::{ - de::escape::EscapedDeserializer, + de::key::QNameDeserializer, de::seq::{not_in, TagFilter}, de::simple_type::SimpleTypeDeserializer, de::{str2bool, DeEvent, Deserializer, XmlRead, TEXT_KEY, VALUE_KEY}, errors::serialize::DeError, events::attributes::IterState, events::BytesStart, + name::QName, }; use serde::de::{self, DeserializeSeed, IntoDeserializer, SeqAccess, Visitor}; use serde::serde_if_integer128; @@ -227,12 +228,9 @@ where // try getting map from attributes (key= "value") let (key, value) = a.into(); self.source = ValueSource::Attribute(value.unwrap_or_default()); - seed.deserialize(EscapedDeserializer::new( - Cow::Borrowed(&slice[key]), - decoder, - false, - )) - .map(Some) + + let de = QNameDeserializer::from_attr(QName(&slice[key]), decoder)?; + seed.deserialize(de).map(Some) } else { // try getting from events (value) match self.de.peek()? { @@ -275,9 +273,9 @@ where } DeEvent::Start(e) => { self.source = ValueSource::Nested; - let name = Cow::Borrowed(e.local_name().into_inner()); - seed.deserialize(EscapedDeserializer::new(name, decoder, false)) - .map(Some) + + let de = QNameDeserializer::from_elem(e.name(), decoder)?; + seed.deserialize(de).map(Some) } // Stop iteration after reaching a closing tag DeEvent::End(e) if e.name() == self.start.name() => Ok(None), diff --git a/src/de/mod.rs b/src/de/mod.rs index 6e3bf7a1..e3b50ca6 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -331,6 +331,7 @@ macro_rules! deserialize_primitives { } mod escape; +mod key; mod map; mod seq; mod simple_type; @@ -549,7 +550,7 @@ where /// - `$text(with text)` /// - `` /// - `` (virtual start event) - /// - `` (vitrual end event) + /// - `` (virtual end event) /// - `` /// /// Note, that `` internally represented as 2 events: diff --git a/src/se/mod.rs b/src/se/mod.rs index 3df5b5f4..8e559d35 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -75,7 +75,7 @@ macro_rules! write_primitive { mod content; mod element; -mod key; +pub(crate) mod key; pub(crate) mod simple_type; use self::content::ContentSerializer;