diff --git a/Cargo.lock b/Cargo.lock index f5b9100..57613ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.58" @@ -81,6 +90,22 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "ctor" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "dissimilar" version = "1.0.4" @@ -487,6 +512,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -534,6 +568,18 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "pretty_assertions" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + [[package]] name = "proc-macro2" version = "1.0.40" @@ -561,6 +607,7 @@ version = "0.3.2" dependencies = [ "anyhow", "case", + "pretty_assertions", "proc-macro2", "quote", "regex", diff --git a/qbittorrent-web-api-gen/Cargo.toml b/qbittorrent-web-api-gen/Cargo.toml index eaecf5f..2a6bb56 100644 --- a/qbittorrent-web-api-gen/Cargo.toml +++ b/qbittorrent-web-api-gen/Cargo.toml @@ -32,3 +32,4 @@ trybuild = { version = "1.0.63", features = ["diff"] } anyhow = "1.0.58" tokio = { version = "1.19.2", features = ["full"] } reqwest = { version = "0.11.11", features = ["json", "multipart"] } +pretty_assertions = "1.2.1" diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs index 48dcae4..9854f8c 100644 --- a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs @@ -73,93 +73,59 @@ impl TokenTreeFactory { mod tests { use super::*; + macro_rules! run_test { + ($test_file:expr) => { + use pretty_assertions::assert_eq; + + // given + let input = include_str!(concat!("token_tree_factory_tests/", $test_file, ".md")); + + // when + let tree = TokenTreeFactory::create(input); + + // then + let tree_as_str = format!("{tree:#?}"); + let should_be = + include_str!(concat!("token_tree_factory_tests/", $test_file, ".check")); + assert_eq!(tree_as_str, should_be); + }; + } + + // use this macro when creating/updating as test + #[allow(unused_macros)] + macro_rules! update_test { + ($test_file:expr) => { + use std::fs; + + let input = include_str!(concat!("token_tree_factory_tests/", $test_file, ".md")); + let tree = TokenTreeFactory::create(input); + let tree_as_str = format!("{tree:#?}"); + fs::write(concat!($test_file, ".check"), tree_as_str).unwrap(); + }; + } + #[test] fn should_remove_surrounding_asterix() { - // given - let input = r#" -# A -**B** - "# - .trim_matches('\n') - .trim(); - - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - let first = tree.children.first().unwrap(); - let content = first.content.first().unwrap(); - assert_eq!(*content, MdContent::Asterix("B".into())); + run_test!("should_remove_surrounding_asterix"); } #[test] fn should_remove_surrounding_hash() { - // given - let input = r#" -# A # - "# - .trim_matches('\n') - .trim(); - - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - assert_eq!(tree.children.first().unwrap().title, Some("A".into())); + run_test!("should_remove_surrounding_hash"); } #[test] fn single_level() { - // given - let input = r#" -# A -Foo - "# - .trim_matches('\n') - .trim(); - - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - assert_eq!(tree.title, None); - let first_child = tree.children.first().unwrap(); - assert_eq!(first_child.title, Some("A".into())); + run_test!("single_level"); } #[test] fn complex() { - // given - let input = r#" -# A -Foo -## B -# C -## D -Bar - "# - .trim_matches('\n') - .trim(); + run_test!("complex"); + } - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - assert_eq!(tree.title, None); - assert_eq!(tree.children.len(), 2); - - let first = tree.children.get(0).unwrap(); - assert_eq!(first.title, Some("A".into())); - assert_eq!(first.children.len(), 1); - assert_eq!(first.children.first().unwrap().title, Some("B".into())); - - let second = tree.children.get(1).unwrap(); - assert_eq!(second.title, Some("C".into())); - assert_eq!(second.children.len(), 1); - assert_eq!(second.children.first().unwrap().title, Some("D".into())); + #[test] + fn log() { + run_test!("log"); } } diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/complex.check b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/complex.check new file mode 100644 index 0000000..16be004 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/complex.check @@ -0,0 +1,44 @@ +TokenTree { + title: None, + content: [], + children: [ + TokenTree { + title: Some( + "A", + ), + content: [ + Text( + "Foo", + ), + ], + children: [ + TokenTree { + title: Some( + "B", + ), + content: [], + children: [], + }, + ], + }, + TokenTree { + title: Some( + "C", + ), + content: [], + children: [ + TokenTree { + title: Some( + "D", + ), + content: [ + Text( + "Bar", + ), + ], + children: [], + }, + ], + }, + ], +} \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/complex.md b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/complex.md new file mode 100644 index 0000000..cfaa8fb --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/complex.md @@ -0,0 +1,6 @@ +# A +Foo +## B +# C +## D +Bar \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.check b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.check new file mode 100644 index 0000000..0e7e5f1 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.check @@ -0,0 +1,503 @@ +TokenTree { + title: None, + content: [], + children: [ + TokenTree { + title: Some( + "Log", + ), + content: [ + Text( + "", + ), + Text( + "All Log API methods are under \"log\", e.g.: `/api/v2/log/methodName`.", + ), + Text( + "", + ), + ], + children: [ + TokenTree { + title: Some( + "Get log", + ), + content: [ + Text( + "", + ), + Text( + "Name: `main`", + ), + Text( + "", + ), + Asterix( + "Parameters:", + ), + Text( + "", + ), + Table( + Table { + header: TableRow { + raw: "Parameter | Type | Description", + columns: [ + "Parameter", + "Type", + "Description", + ], + }, + split: "----------------|---------|------------", + rows: [ + TableRow { + raw: "`normal` | bool | Include normal messages (default: `true`)", + columns: [ + "normal", + "bool", + "Include normal messages (default: true)", + ], + }, + TableRow { + raw: "`info` | bool | Include info messages (default: `true`)", + columns: [ + "info", + "bool", + "Include info messages (default: true)", + ], + }, + TableRow { + raw: "`warning` | bool | Include warning messages (default: `true`)", + columns: [ + "warning", + "bool", + "Include warning messages (default: true)", + ], + }, + TableRow { + raw: "`critical` | bool | Include critical messages (default: `true`)", + columns: [ + "critical", + "bool", + "Include critical messages (default: true)", + ], + }, + TableRow { + raw: "`last_known_id` | integer | Exclude messages with \"message id\" <= `last_known_id` (default: `-1`)", + columns: [ + "last_known_id", + "integer", + "Exclude messages with \"message id\" <= last_known_id (default: -1)", + ], + }, + ], + }, + ), + Text( + "Example:", + ), + Text( + "", + ), + Text( + "```http", + ), + Text( + "/api/v2/log/main?normal=true&info=true&warning=true&critical=true&last_known_id=-1", + ), + Text( + "```", + ), + Text( + "", + ), + Asterix( + "Returns:", + ), + Text( + "", + ), + Table( + Table { + header: TableRow { + raw: "HTTP Status Code | Scenario", + columns: [ + "HTTP Status Code", + "Scenario", + ], + }, + split: "----------------------------------|---------------------", + rows: [ + TableRow { + raw: "200 | All scenarios- see JSON below", + columns: [ + "200", + "All scenarios- see JSON below", + ], + }, + ], + }, + ), + Text( + "The response is a JSON array in which each element is an entry of the log.", + ), + Text( + "", + ), + Text( + "Each element of the array has the following properties:", + ), + Text( + "", + ), + Table( + Table { + header: TableRow { + raw: "Property | Type | Description", + columns: [ + "Property", + "Type", + "Description", + ], + }, + split: "------------|---------|------------", + rows: [ + TableRow { + raw: "`id` | integer | ID of the message", + columns: [ + "id", + "integer", + "ID of the message", + ], + }, + TableRow { + raw: "`message` | string | Text of the message", + columns: [ + "message", + "string", + "Text of the message", + ], + }, + TableRow { + raw: "`timestamp` | integer | Milliseconds since epoch", + columns: [ + "timestamp", + "integer", + "Milliseconds since epoch", + ], + }, + TableRow { + raw: "`type` | integer | Type of the message: Log::NORMAL: `1`, Log::INFO: `2`, Log::WARNING: `4`, Log::CRITICAL: `8`", + columns: [ + "type", + "integer", + "Type of the message: Log::NORMAL: 1, Log::INFO: 2, Log::WARNING: 4, Log::CRITICAL: 8", + ], + }, + ], + }, + ), + Text( + "Example:", + ), + Text( + "", + ), + Text( + "```JSON", + ), + Text( + "[", + ), + Text( + " {", + ), + Text( + " \"id\":0,", + ), + Text( + " \"message\":\"qBittorrent v3.4.0 started\",", + ), + Text( + " \"timestamp\":1507969127860,", + ), + Text( + " \"type\":1", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":1,", + ), + Text( + " \"message\":\"qBittorrent is trying to listen on any interface port: 19036\",", + ), + Text( + " \"timestamp\":1507969127869,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":2,", + ), + Text( + " \"message\":\"Peer ID: -qB3400-\",", + ), + Text( + " \"timestamp\":1507969127870,", + ), + Text( + " \"type\":1", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":3,", + ), + Text( + " \"message\":\"HTTP User-Agent is 'qBittorrent/3.4.0'\",", + ), + Text( + " \"timestamp\":1507969127870,", + ), + Text( + " \"type\":1", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":4,", + ), + Text( + " \"message\":\"DHT support [ON]\",", + ), + Text( + " \"timestamp\":1507969127871,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":5,", + ), + Text( + " \"message\":\"Local Peer Discovery support [ON]\",", + ), + Text( + " \"timestamp\":1507969127871,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":6,", + ), + Text( + " \"message\":\"PeX support [ON]\",", + ), + Text( + " \"timestamp\":1507969127871,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":7,", + ), + Text( + " \"message\":\"Anonymous mode [OFF]\",", + ), + Text( + " \"timestamp\":1507969127871,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":8,", + ), + Text( + " \"message\":\"Encryption support [ON]\",", + ), + Text( + " \"timestamp\":1507969127871,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":9,", + ), + Text( + " \"message\":\"Embedded Tracker [OFF]\",", + ), + Text( + " \"timestamp\":1507969127871,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":10,", + ), + Text( + " \"message\":\"UPnP / NAT-PMP support [ON]\",", + ), + Text( + " \"timestamp\":1507969127873,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":11,", + ), + Text( + " \"message\":\"Web UI: Now listening on port 8080\",", + ), + Text( + " \"timestamp\":1507969127883,", + ), + Text( + " \"type\":1", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":12,", + ), + Text( + " \"message\":\"Options were saved successfully.\",", + ), + Text( + " \"timestamp\":1507969128055,", + ), + Text( + " \"type\":1", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":13,", + ), + Text( + " \"message\":\"qBittorrent is successfully listening on interface :: port: TCP/19036\",", + ), + Text( + " \"timestamp\":1507969128270,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":14,", + ), + Text( + " \"message\":\"qBittorrent is successfully listening on interface 0.0.0.0 port: TCP/19036\",", + ), + Text( + " \"timestamp\":1507969128271,", + ), + Text( + " \"type\":2", + ), + Text( + " },", + ), + Text( + " {", + ), + Text( + " \"id\":15,", + ), + Text( + " \"message\":\"qBittorrent is successfully listening on interface 0.0.0.0 port: UDP/19036\",", + ), + Text( + " \"timestamp\":1507969128272,", + ), + Text( + " \"type\":2", + ), + ], + children: [], + }, + ], + }, + ], +} \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.md b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.md new file mode 100644 index 0000000..52df39e --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.md @@ -0,0 +1,143 @@ +# Log # + +All Log API methods are under "log", e.g.: `/api/v2/log/methodName`. + +## Get log ## + +Name: `main` + +**Parameters:** + +Parameter | Type | Description +----------------|---------|------------ +`normal` | bool | Include normal messages (default: `true`) +`info` | bool | Include info messages (default: `true`) +`warning` | bool | Include warning messages (default: `true`) +`critical` | bool | Include critical messages (default: `true`) +`last_known_id` | integer | Exclude messages with "message id" <= `last_known_id` (default: `-1`) + +Example: + +```http +/api/v2/log/main?normal=true&info=true&warning=true&critical=true&last_known_id=-1 +``` + +**Returns:** + +HTTP Status Code | Scenario +----------------------------------|--------------------- +200 | All scenarios- see JSON below + +The response is a JSON array in which each element is an entry of the log. + +Each element of the array has the following properties: + +Property | Type | Description +------------|---------|------------ +`id` | integer | ID of the message +`message` | string | Text of the message +`timestamp` | integer | Milliseconds since epoch +`type` | integer | Type of the message: Log::NORMAL: `1`, Log::INFO: `2`, Log::WARNING: `4`, Log::CRITICAL: `8` + +Example: + +```JSON +[ + { + "id":0, + "message":"qBittorrent v3.4.0 started", + "timestamp":1507969127860, + "type":1 + }, + { + "id":1, + "message":"qBittorrent is trying to listen on any interface port: 19036", + "timestamp":1507969127869, + "type":2 + }, + { + "id":2, + "message":"Peer ID: -qB3400-", + "timestamp":1507969127870, + "type":1 + }, + { + "id":3, + "message":"HTTP User-Agent is 'qBittorrent/3.4.0'", + "timestamp":1507969127870, + "type":1 + }, + { + "id":4, + "message":"DHT support [ON]", + "timestamp":1507969127871, + "type":2 + }, + { + "id":5, + "message":"Local Peer Discovery support [ON]", + "timestamp":1507969127871, + "type":2 + }, + { + "id":6, + "message":"PeX support [ON]", + "timestamp":1507969127871, + "type":2 + }, + { + "id":7, + "message":"Anonymous mode [OFF]", + "timestamp":1507969127871, + "type":2 + }, + { + "id":8, + "message":"Encryption support [ON]", + "timestamp":1507969127871, + "type":2 + }, + { + "id":9, + "message":"Embedded Tracker [OFF]", + "timestamp":1507969127871, + "type":2 + }, + { + "id":10, + "message":"UPnP / NAT-PMP support [ON]", + "timestamp":1507969127873, + "type":2 + }, + { + "id":11, + "message":"Web UI: Now listening on port 8080", + "timestamp":1507969127883, + "type":1 + }, + { + "id":12, + "message":"Options were saved successfully.", + "timestamp":1507969128055, + "type":1 + }, + { + "id":13, + "message":"qBittorrent is successfully listening on interface :: port: TCP/19036", + "timestamp":1507969128270, + "type":2 + }, + { + "id":14, + "message":"qBittorrent is successfully listening on interface 0.0.0.0 port: TCP/19036", + "timestamp":1507969128271, + "type":2 + }, + { + "id":15, + "message":"qBittorrent is successfully listening on interface 0.0.0.0 port: UDP/19036", + "timestamp":1507969128272, + "type":2 + } +] +``` diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_asterix.check b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_asterix.check new file mode 100644 index 0000000..2c66917 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_asterix.check @@ -0,0 +1,17 @@ +TokenTree { + title: None, + content: [], + children: [ + TokenTree { + title: Some( + "A", + ), + content: [ + Asterix( + "B", + ), + ], + children: [], + }, + ], +} \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_asterix.md b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_asterix.md new file mode 100644 index 0000000..318a664 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_asterix.md @@ -0,0 +1,2 @@ +# A +**B** \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_hash.check b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_hash.check new file mode 100644 index 0000000..bb00231 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_hash.check @@ -0,0 +1,13 @@ +TokenTree { + title: None, + content: [], + children: [ + TokenTree { + title: Some( + "A", + ), + content: [], + children: [], + }, + ], +} \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_hash.md b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_hash.md new file mode 100644 index 0000000..c3fd722 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/should_remove_surrounding_hash.md @@ -0,0 +1 @@ +# A # \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/single_level.check b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/single_level.check new file mode 100644 index 0000000..34e9f2c --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/single_level.check @@ -0,0 +1,17 @@ +TokenTree { + title: None, + content: [], + children: [ + TokenTree { + title: Some( + "A", + ), + content: [ + Text( + "Foo", + ), + ], + children: [], + }, + ], +} \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/single_level.md b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/single_level.md new file mode 100644 index 0000000..f5e1cf8 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/single_level.md @@ -0,0 +1,2 @@ +# A +Foo \ No newline at end of file