feat: show requests in a tree, refactored collector
This commit is contained in:
parent
15dca26744
commit
d6259a022f
3 changed files with 121 additions and 77 deletions
|
@ -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"
|
||||
|
|
85
src/main.rs
85
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<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
112
src/requestor.rs
Normal 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),
|
||||
}
|
Loading…
Reference in a new issue