Compare commits
No commits in common. "3a966bdc8bf5107815b6fb125eff5a0caccd111f" and "fe7bc43c5711a397e244327531096a7dc41c6da0" have entirely different histories.
3a966bdc8b
...
fe7bc43c57
6 changed files with 141 additions and 54 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -507,7 +507,6 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
|||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -754,7 +753,6 @@ version = "0.2.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"http",
|
||||
"indexmap",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
|
@ -1252,7 +1250,6 @@ version = "0.8.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
|
|
|
@ -8,12 +8,11 @@ edition = "2021"
|
|||
[dependencies]
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
http = "1.1.0"
|
||||
indexmap = { version = "2.6", features = ["serde"] }
|
||||
regex = "1.11"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0.64"
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
toml = { version = "0.8", features = ["preserve_order"] }
|
||||
toml = "0.8"
|
||||
toml_edit = { version = "0.22", features = ["serde"] } # only to pretty print tables ...
|
||||
|
|
85
postwoman.json
Normal file
85
postwoman.json
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"variables": [],
|
||||
"info": {
|
||||
"name": "Sample Postman Collection",
|
||||
"_postman_id": "35567af6-6b92-26c2-561a-21fe8aeeb1ea",
|
||||
"description": "A sample collection to demonstrate collections as a set of related requests",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "GET request",
|
||||
"request": {
|
||||
"url": {
|
||||
"raw": "https://api.alemi.dev/dump?source=sample-collection",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"alemi",
|
||||
"dev"
|
||||
],
|
||||
"path": [
|
||||
"dump"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "source",
|
||||
"value": "sample-collection",
|
||||
"equals": true,
|
||||
"description": ""
|
||||
}
|
||||
],
|
||||
"variable": []
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"body": {},
|
||||
"description": ""
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "POST requests",
|
||||
"item": [
|
||||
{
|
||||
"name": "Text body",
|
||||
"request": {
|
||||
"url": "https://api.alemi.dev/dump",
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "text/plain",
|
||||
"type": "default"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium..."
|
||||
},
|
||||
"description": ""
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Json body",
|
||||
"request": {
|
||||
"url": "https://api.alemi.dev/dump",
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\"text\":\"Lorem ipsum\", \"length\":100}"
|
||||
},
|
||||
"description": ""
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
[client]
|
||||
user_agent = "postwoman@sample/0.2.0"
|
||||
|
||||
[route.healthcheck]
|
||||
url = "https://api.alemi.dev/"
|
||||
|
||||
[route.debug]
|
||||
url = "https://api.alemi.dev/debug"
|
||||
method = "PUT"
|
||||
headers = [
|
||||
"Content-Type: application/json",
|
||||
"Authorization: Bearer ${PW_TOKEN}",
|
||||
]
|
||||
body = { hello = "world!", success = true }
|
||||
extract = { type = "body" }
|
||||
|
||||
[route.payload]
|
||||
url = "https://api.alemi.dev/debug"
|
||||
method = "POST"
|
||||
body = '''{
|
||||
"complex": {
|
||||
"json": "payloads",
|
||||
"can": "be",
|
||||
"expressed": "this",
|
||||
"way": true
|
||||
}
|
||||
}'''
|
||||
extract = { type = "body" }
|
||||
|
||||
[route.cookie]
|
||||
url = "https://api.alemi.dev/getcookie"
|
||||
method = "GET"
|
||||
extract = { type = "header", key = "Set-Cookie" }
|
50
src/main.rs
50
src/main.rs
|
@ -1,7 +1,9 @@
|
|||
mod model;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use model::{PostWomanConfig, PostWomanError};
|
||||
use model::{Endpoint, Extractor, PostWomanClient, PostWomanConfig, PostWomanError, StringOr};
|
||||
|
||||
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
|
@ -20,6 +22,9 @@ struct PostWomanArgs {
|
|||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum PostWomanActions {
|
||||
/// print an example configragion, pipe to file and start editing
|
||||
Sample,
|
||||
|
||||
/// execute specific endpoint requests
|
||||
Run {
|
||||
/// regex query filter, run all with '.*'
|
||||
|
@ -44,6 +49,45 @@ pub enum PostWomanActions {
|
|||
async fn main() -> Result<(), PostWomanError> {
|
||||
let args = PostWomanArgs::parse();
|
||||
|
||||
if matches!(args.action, PostWomanActions::Sample) {
|
||||
let a = Endpoint {
|
||||
url: "https://api.alemi.dev/debug".into(),
|
||||
query: None,
|
||||
method: None,
|
||||
headers: None,
|
||||
body: None,
|
||||
extract: None,
|
||||
};
|
||||
|
||||
let b = Endpoint {
|
||||
url: "https://api.alemi.dev/debug".into(),
|
||||
query: None,
|
||||
method: Some("PUT".into()),
|
||||
headers: Some(vec![
|
||||
"Authorization: Bearer asdfg".into(),
|
||||
"Cache: skip".into(),
|
||||
]),
|
||||
body: Some(StringOr::T(toml::Table::from_iter([("hello".into(), toml::Value::String("world".into()))]))),
|
||||
extract: Some(StringOr::T(Extractor::Body)),
|
||||
};
|
||||
|
||||
let client = PostWomanClient {
|
||||
user_agent: Some(APP_USER_AGENT.into()),
|
||||
};
|
||||
|
||||
let cfg = PostWomanConfig {
|
||||
client,
|
||||
route: HashMap::from_iter([
|
||||
("simple".to_string(), a),
|
||||
("json".to_string(), b),
|
||||
]),
|
||||
};
|
||||
|
||||
println!("{}", toml_edit::ser::to_string_pretty(&cfg)?);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collection = std::fs::read_to_string(args.collection)?;
|
||||
let config: PostWomanConfig = toml::from_str(&collection)?;
|
||||
|
||||
|
@ -55,7 +99,7 @@ async fn main() -> Result<(), PostWomanError> {
|
|||
eprintln!("> executing {name}");
|
||||
let res = endpoint
|
||||
.fill()
|
||||
.execute(&config.client)
|
||||
.execute()
|
||||
.await?;
|
||||
println!("{res}");
|
||||
}
|
||||
|
@ -65,6 +109,8 @@ async fn main() -> Result<(), PostWomanError> {
|
|||
// PostWomanActions::Save { name, url, method, headers, body } => {
|
||||
// todo!();
|
||||
// },
|
||||
|
||||
PostWomanActions::Sample => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
21
src/model.rs
21
src/model.rs
|
@ -1,8 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
|
||||
use crate::APP_USER_AGENT;
|
||||
use reqwest::{header::{HeaderMap, HeaderName, HeaderValue}};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PostWomanError {
|
||||
|
@ -87,7 +85,7 @@ fn replace_recursive(element: toml::Value, from: &str, to: &str) -> toml::Value
|
|||
impl Endpoint {
|
||||
pub fn fill(mut self) -> Self {
|
||||
for (k, v) in std::env::vars() {
|
||||
let k_var = format!("${{{k}}}");
|
||||
let k_var = format!("${k}");
|
||||
self.url = self.url.replace(&k_var, &v);
|
||||
if let Some(method) = self.method {
|
||||
self.method = Some(method.replace(&k_var, &v));
|
||||
|
@ -117,7 +115,7 @@ impl Endpoint {
|
|||
self
|
||||
}
|
||||
|
||||
pub async fn execute(self, opts: &PostWomanClient) -> Result<String, PostWomanError> {
|
||||
pub async fn execute(self) -> Result<String, PostWomanError> {
|
||||
let method = match self.method {
|
||||
Some(m) => reqwest::Method::from_str(&m)?,
|
||||
None => reqwest::Method::GET,
|
||||
|
@ -135,12 +133,7 @@ impl Endpoint {
|
|||
StringOr::Str(x) => x,
|
||||
StringOr::T(json) => serde_json::to_string(&json)?,
|
||||
};
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent(opts.user_agent.as_deref().unwrap_or(APP_USER_AGENT))
|
||||
.build()?;
|
||||
|
||||
let res = client
|
||||
let res = reqwest::Client::new()
|
||||
.request(method, self.url)
|
||||
.headers(headers)
|
||||
.body(body)
|
||||
|
@ -149,7 +142,7 @@ impl Endpoint {
|
|||
.error_for_status()?;
|
||||
|
||||
Ok(match self.extract.unwrap_or_default() {
|
||||
StringOr::Str(_query) => todo!(),
|
||||
StringOr::Str(query) => todo!(),
|
||||
StringOr::T(Extractor::Debug) => format!("{res:#?}"),
|
||||
StringOr::T(Extractor::Body) => res.text().await?,
|
||||
StringOr::T(Extractor::Header { key }) => res
|
||||
|
@ -195,5 +188,5 @@ pub struct PostWomanClient {
|
|||
pub struct PostWomanConfig {
|
||||
pub client: PostWomanClient,
|
||||
// it's weird to name it singular but makes more sense in config
|
||||
pub route: indexmap::IndexMap<String, Endpoint>,
|
||||
pub route: HashMap<String, Endpoint>,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue