feat: show requests in a tree, refactored collector

This commit is contained in:
əlemi 2023-06-21 00:24:12 +02:00
parent 15dca26744
commit d6259a022f
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 121 additions and 77 deletions

View file

@ -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"

View file

@ -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<dyn std::error::Error>> {
let args = PostWomanArgs::parse();
@ -93,9 +79,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
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::<serde_json::Value>(&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

112
src/requestor.rs Normal file
View file

@ -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<Regex>) -> 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::<serde_json::Value>(&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<TestResultHolder>),
Branch(Vec<TestResultTree>),
}
#[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),
}