feat: PoC with postman_collection crate

all these unions, ffs
This commit is contained in:
əlemi 2023-06-07 20:13:29 +02:00
parent c2fadf1f62
commit 65257b7e34
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 157 additions and 70 deletions

72
src/collector.rs Normal file
View file

@ -0,0 +1,72 @@
use std::str::FromStr;
use postman_collection::v2_0_0::{Spec, RequestClass, Items, Url, UrlClass, HeaderUnion, Body};
pub fn collect(collection: Spec) -> Vec<RequestClass> {
let mut reqs = Vec::new();
for item in collection.item {
reqs.append(&mut requests(item)); // TODO creating all these vectors is a waste!
}
reqs
}
pub fn requests(root: Items) -> Vec<RequestClass> {
let mut reqs = Vec::new();
if let Some(r) = root.request {
match r {
postman_collection::v2_0_0::RequestUnion::RequestClass(x) => reqs.push(x),
postman_collection::v2_0_0::RequestUnion::String(url) => reqs.push(
RequestClass {
method: Some("GET".into()),
url: Some(Url::String(url)),
..Default::default()
}
),
}
}
if let Some(items) = root.item {
for item in items {
reqs.append(&mut requests(item));
}
}
reqs
}
pub fn url(req: &RequestClass) -> String {
match &req.url {
Some(Url::String(x)) => x.clone(),
Some(Url::UrlClass(UrlClass { raw: Some(raw) , .. })) => raw.clone(),
// TODO compose UrlClass
_ => "".into(),
}
}
pub async fn send(req: RequestClass) -> reqwest::Result<reqwest::Response> {
let method = reqwest::Method::from_bytes(
&req.method.as_ref().unwrap_or(&"GET".into()).as_bytes() // TODO lol?
).unwrap_or(reqwest::Method::GET); // TODO throw an error rather than replacing it silently
let url = reqwest::Url::from_str(&url(&req)).unwrap();
let mut out = reqwest::Client::new().request(method, url);
match req.header {
Some(HeaderUnion::HeaderArray(x)) => {
for h in x {
out = out.header(h.key, h.value);
}
},
_ => {},
}
match req.body {
Some(Body { raw: Some(x), .. }) => {
out = out.body(x)
},
_ => {},
}
out.send().await
}

View file

@ -1,11 +1,13 @@
// mod proto;
mod model;
// mod model;
mod collector;
use clap::{Parser, Subcommand};
use reqwest::Method;
use postman_collection::{PostmanCollection, v2_1_0::Spec};
use postman_collection::{PostmanCollection, v2_0_0::Spec};
use crate::collector::{collect, url, send};
// use crate::proto::{Item, Request, Header};
/// API tester and debugger from your CLI
@ -28,26 +30,26 @@ struct PostWomanArgs {
#[derive(Subcommand, Debug)]
pub enum PostWomanActions {
/// run a single request to given url
Send {
/// request URL
url: String,
// Send {
// /// request URL
// url: String,
/// request method
#[arg(short = 'X', long, default_value_t = Method::GET)]
method: Method,
// /// request method
// #[arg(short = 'X', long, default_value_t = Method::GET)]
// method: Method,
/// headers for request
#[arg(short = 'H', long, num_args = 0..)]
headers: Vec<String>,
// /// headers for request
// #[arg(short = 'H', long, num_args = 0..)]
// headers: Vec<String>,
/// request body
#[arg(short, long, default_value = "")]
data: String,
// /// request body
// #[arg(short, long, default_value = "")]
// data: String,
/// add action to collection items
#[arg(short = 'S', long, default_value_t = false)]
save: bool,
},
// /// add action to collection items
// #[arg(short = 'S', long, default_value_t = false)]
// save: bool,
// },
/// run all saved requests
Test {},
/// list saved requests
@ -59,81 +61,94 @@ pub enum PostWomanActions {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = PostWomanArgs::parse();
let collection =
let collection =
match postman_collection::from_path(args.collection) {
Ok(PostmanCollection::V2_1_0(spec)) => spec,
_ => Spec::default(), // TODO log what is happening here!
Ok(PostmanCollection::V2_0_0(spec)) => spec,
Ok(PostmanCollection::V1_0_0(_)) => {
eprintln!("collection using v1.0.0 format! only 2.0.0 is allowed");
Spec::default()
},
Ok(PostmanCollection::V2_1_0(_)) => {
eprintln!("collection using v2.1.0 format! only 2.0.0 is allowed");
Spec::default()
},
Err(e) => {
eprintln!("error loading collection: {}", e);
Spec::default()
}
};
if args.verbose {
println!("╶┐ * {}", collection.info.name);
if let Some(descr) = &collection.info.description {
match descr {
postman_collection::v2_1_0::DescriptionUnion::Description(x) => {
if let Some(d) = x.content { println!("{}", d) };
if let Some(v) = x.version { println!("{}", v) };
postman_collection::v2_0_0::DescriptionUnion::Description(x) => {
if let Some(d) = &x.content { println!("{}", d) };
if let Some(v) = &x.version { println!("{}", v) };
},
postman_collection::v2_1_0::DescriptionUnion::String(x) => println!("{}", x),
postman_collection::v2_0_0::DescriptionUnion::String(x) => println!("{}", x),
}
}
println!("");
}
match args.action {
PostWomanActions::Send {
url, headers, method, data, save
} => {
let req = Request::Object {
url: crate::proto::Url::String(url),
method: method.to_string(),
header: Some(
headers
.chunks(2)
.map(|x| Header {
key: x[0].clone(),
value: x[1].clone(), // TODO panics
})
.collect(),
),
body: if data.len() > 0 { Some(Body::String(data)) } else { None },
description: None,
};
// PostWomanActions::Send {
// url, headers, method, data, save
// } => {
// let req = Request::Object {
// url: crate::proto::Url::String(url),
// method: method.to_string(),
// header: Some(
// headers
// .chunks(2)
// .map(|x| Header {
// key: x[0].clone(),
// value: x[1].clone(), // TODO panics
// })
// .collect(),
// ),
// body: if data.len() > 0 { Some(Body::String(data)) } else { None },
// description: None,
// };
let res = req.clone().send().await?;
// let res = req.clone().send().await?;
if args.verbose {
println!(" ├┐ {}", res.status());
}
// if args.verbose {
// println!(" ├┐ {}", res.status());
// }
if args.verbose {
println!(" ││ {}", res.text().await?.replace("\n", "\n ││ "));
} else {
println!("{}", res.text().await?);
}
// if args.verbose {
// println!(" ││ {}", res.text().await?.replace("\n", "\n ││ "));
// } else {
// println!("{}", res.text().await?);
// }
if save {
// TODO prompt for name and descr
let item = Item {
name: "TODO!".into(),
event: None,
item: None,
request: Some(req),
response: Some(vec![]),
};
collection.item.push(item);
std::fs::write(&args.collection, serde_json::to_string(&collection)?)?;
if args.verbose { println!(" ││ * saved") }
}
// if save {
// // TODO prompt for name and descr
// let item = Item {
// name: "TODO!".into(),
// event: None,
// item: None,
// request: Some(req),
// response: Some(vec![]),
// };
// collection.item.push(item);
// std::fs::write(&args.collection, serde_json::to_string(&collection)?)?;
// if args.verbose { println!(" ││ * saved") }
// }
if args.verbose { println!(" │╵") }
},
// if args.verbose { println!(" │╵") }
// },
PostWomanActions::Test { } => {
let reqs = collect(collection);
let mut tasks = Vec::new();
for req in collection.collect() {
for req in reqs {
let t = tokio::spawn(async move {
let url = req.to_string();
let r = req.send().await?;
let url = url(&req);
let r = send(req).await?;
println!("{} >> {}", url, r.status());
if args.verbose {
println!("{}", r.text().await?.replace("\n", "\n"));