From d6259a022f2f51ea9e8cc684f573b0d03a1b9618 Mon Sep 17 00:00:00 2001 From: alemi Date: Wed, 21 Jun 2023 00:24:12 +0200 Subject: [PATCH] feat: show requests in a tree, refactored collector --- Cargo.toml | 1 + src/main.rs | 85 ++++------------------------------- src/requestor.rs | 112 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 77 deletions(-) create mode 100644 src/requestor.rs diff --git a/Cargo.toml b/Cargo.toml index 9f3eea2..4b6620c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-recursion = "1.0.4" clap = { version = "4.3.0", features = ["derive"] } postman_collection = "0.2.0" regex = "1.8.4" diff --git a/src/main.rs b/src/main.rs index 7cbaec2..fe7d263 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,11 @@ -mod model; - -use std::sync::Arc; +pub mod model; +mod requestor; use clap::{Parser, Subcommand}; use regex::Regex; -use crate::model::PostWomanCollection; +use crate::{model::PostWomanCollection, requestor::{send_requests, show_results}}; /// API tester and debugger from your CLI #[derive(Parser, Debug)] @@ -65,19 +64,6 @@ pub enum PostWomanActions { Show {}, } -enum TestResult { - Success { - url: String, - method: String, - response: reqwest::Response, - }, - Failure { - url: String, - method: String, - err: reqwest::Error, - } -} - #[tokio::main] async fn main() -> Result<(), Box> { let args = PostWomanArgs::parse(); @@ -93,9 +79,10 @@ async fn main() -> Result<(), Box> { // if let Some(version) = &collection.version() { // println!(" │ {}", version); // } - println!(" │"); } + println!(" │"); + match args.action { // PostWomanActions::Send { // url, headers, method, data, save @@ -144,7 +131,7 @@ async fn main() -> Result<(), Box> { // if args.verbose { println!(" │╵") } // }, - PostWomanActions::Test { filter, isolated, pretty } => { + PostWomanActions::Test { filter, isolated: _, pretty } => { let reqs = collection.requests(); let matcher = match filter { @@ -152,66 +139,10 @@ async fn main() -> Result<(), Box> { None => None, }; - let mut tasks = Vec::new(); + let results = send_requests(reqs, matcher).await; - let client = Arc::new(reqwest::Client::default()); + show_results(results, args.verbose, pretty).await; - for req in reqs { - let url = req.url().as_str().to_string(); - if let Some(m) = &matcher { - if !m.is_match(&url) { - continue - } - } - let _c = client.clone(); - let t = tokio::spawn(async move { - let c = if isolated { - Arc::new(reqwest::Client::default()) - } else { _c }; - let method = req.method().as_str().to_string(); - // if args.verbose { - // println!(" ├─ {:?}", req); - // if let Some(body) = req.body() { - // println!(" ├── {}", std::str::from_utf8(body.as_bytes().unwrap()).unwrap()); - // } - // } - let response = c.execute(req).await; - match response { - Ok(response) => TestResult::Success { url, method, response }, - Err(err) => TestResult::Failure { url, method, err } - } - }); - tasks.push(t); - } - - for t in tasks { - match t.await? { - TestResult::Success { url, method, response } => { - let status_code = response.status().as_u16(); - let marker = if status_code < 400 { '✓' } else { '×' }; - println!(" ├ {} {} >> {} {}", marker, status_code, method, url); - if args.verbose { - let mut body = response.text().await?; - if pretty { - if let Ok(v) = serde_json::from_str::(&body) { - if let Ok(t) = serde_json::to_string_pretty(&v) { - body = t; - } - } - } - println!(" │ {}", body.replace("\n", "\n │ ")); - println!(" │"); - } - }, - TestResult::Failure { url, method, err } => { - println!(" ├ ! ERROR >> {} {}", method, url); - if args.verbose { - println!(" │ {}", err); - println!(" │"); - } - } - } - } }, PostWomanActions::Show { } => { println!(" ├ {:?}", collection); // TODO nicer print diff --git a/src/requestor.rs b/src/requestor.rs new file mode 100644 index 0000000..d197494 --- /dev/null +++ b/src/requestor.rs @@ -0,0 +1,112 @@ +use crate::model::collector::{RequestTree, RequestNode}; +use regex::Regex; +use tokio::sync::oneshot; + +#[async_recursion::async_recursion] +pub async fn send_requests(tree: RequestTree, filter: Option) -> TestResultTree { + let result : TestResultNode; + + match tree.request { + RequestNode::Leaf(req) => { + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + let request = req.build().unwrap(); + if filter.is_some() && !filter.unwrap().is_match(request.url().as_str()) { + tx.send(TestResultHolder { request, result: TestResultCase::Skip }).unwrap(); + } else { + let c = reqwest::Client::default(); + let res = match c.execute(request.try_clone().unwrap()).await { + Ok(res) => TestResultCase::Success(res), + Err(e) => TestResultCase::Failure(e), + }; + tx.send(TestResultHolder { request, result: res }).unwrap(); + } + }); + result = TestResultNode::Leaf(rx); + }, + RequestNode::Branch(requests) => { + let mut out = Vec::new(); + for req in requests { + out.push(send_requests(req, filter.clone()).await); + } + result = TestResultNode::Branch(out); + } + } + + TestResultTree { name: tree.name, result } +} + +pub async fn show_results(tree: TestResultTree, verbose: bool, pretty: bool) { + show_results_r(tree, verbose, pretty, 0).await +} + +#[async_recursion::async_recursion] +pub async fn show_results_r(tree: TestResultTree, verbose: bool, pretty: bool, depth: usize) { + let indent = " │".repeat(depth); + match tree.result { + TestResultNode::Leaf(rx) => { + let res = rx.await.unwrap(); + let method = res.request.method().as_str(); + let url = res.request.url().as_str(); + match res.result { + TestResultCase::Skip => {}, + TestResultCase::Success(response) => { + let status_code = response.status().as_u16(); + let marker = if status_code < 400 { '✓' } else { '×' }; + println!("{} ├ {} {} >> {} {}", indent, marker, status_code, method, url); + if verbose { + let mut body = response.text().await.unwrap(); + if pretty { + if let Ok(v) = serde_json::from_str::(&body) { + if let Ok(t) = serde_json::to_string_pretty(&v) { + body = t; + } + } + } + println!("{} │ {}", indent, body.replace("\n", &format!("\n │{} ", indent))); + println!("{} │", indent); + } + }, + TestResultCase::Failure(err) => { + println!("{} ├ ! ERROR >> {} {}", indent, method, url); + if verbose { + println!("{} │ {}", indent, err); + println!("{} │", indent); + } + } + } + }, + TestResultNode::Branch(results) => { + println!("{} ├─┐ {}", indent, tree.name); + for res in results { + show_results_r(res, verbose, pretty, depth + 1).await; + } + println!("{} │ ╵", indent); + }, + } +} + +#[derive(Debug)] +pub enum TestResultNode { + Leaf(oneshot::Receiver), + Branch(Vec), +} + +#[derive(Debug)] +pub struct TestResultTree { + name: String, + result: TestResultNode, +} + +#[derive(Debug)] +pub struct TestResultHolder { + request: reqwest::Request, + result: TestResultCase, +} + +#[derive(Debug)] +pub enum TestResultCase { + Skip, + Success(reqwest::Response), + Failure(reqwest::Error), +}