feat: allow specifying path base, renamed url>path

This commit is contained in:
əlemi 2024-10-20 15:28:24 +02:00
parent 7d56ea3565
commit 8fbf224506
Signed by: alemi
GPG key ID: A4895B84D311642C
4 changed files with 38 additions and 26 deletions

View file

@ -2,17 +2,18 @@
user_agent = "postwoman@sample/0.3.1"
timeout = 60 # max time for each request to complete, in seconds
redirects = 5 # allow up to five redirects, defaults to none
base = "https://api.alemi.dev" # all route urls will be appended to this base
[env] # these will be replaced in routes options. environment vars overrule these
PW_TOKEN = "set-me-as-and-environment-variable!"
[route.healthcheck] # the simplest possible route: just name and url
url = "https://api.alemi.dev/"
[route.healthcheck] # the simplest possible route: just name and path
path = "/"
[route.debug]
url = "https://api.alemi.dev/debug"
path = "/debug"
method = "PUT" # specify request method
query = [ # specify query parameters in a more friendly way
"body=json",
@ -23,20 +24,20 @@ headers = [ # add custom headers to request
"Authorization: Bearer ${PW_TOKEN}",
]
body = { hello = "world!", success = true } # body can be a bare string, or an inline table (will be converted to json)
extract = ".path" # extract from json responses with JQ syntax
# note that a bare extractor string is equivalent to `{ type = "jq", query = ".path" }`
extract = { type = "body" } # get the whole response body, this is the default extractor
[route.benchmark]
url = "https://api.alemi.dev/look/into/the/void"
path = "/look/into/the/void"
extract = { type = "discard" } # if you don't care about the output, discard it!
[route.notfound]
url = "https://api.alemi.dev/not-found"
expect = 404 # it's possible to specify expected status code, will fail if doesn't match
path = "https://cdn.alemi.dev/does-not-exist"
absolute = true # mark as absolute to avoid composing with client base url
status = 404 # it's possible to specify expected status code, will fail if doesn't match
extract = { type = "regex", pattern = 'nginx/[0-9\.]+' } # extract from response with regex
[route.payload]
url = "https://api.alemi.dev/debug"
path = "/debug"
method = "POST"
body = '''{
"complex": {
@ -46,9 +47,10 @@ body = '''{
"way": true
}
}'''
extract = { type = "body" } # get the whole response body, this is the default extractor
extract = ".path" # extract from json responses with JQ syntax (default extractor), equivalent to `{ type = "jq", query = ".path" }`
expect = "/debug" # if extracted result doesn't match, this route will return an error
[route.cookie]
url = "https://api.alemi.dev/getcookie"
path = "/getcookie"
method = "GET"
extract = { type = "header", key = "Set-Cookie" } # get a specific response header, ignoring body

View file

@ -63,8 +63,10 @@ impl PrintableResult for ListResult {
println!(" + {key}={}", crate::ext::stringify_toml(&value));
}
for (name, mut endpoint) in collection.route {
println!(" - {name} \t{} \t{}", endpoint.method.as_deref().unwrap_or("GET"), endpoint.url);
for (name, endpoint) in collection.route {
let url = endpoint.url(collection.client.as_ref().and_then(|x| x.base.as_deref()));
let method = endpoint.method.as_deref().unwrap_or("GET");
println!(" - {name} \t{method} \t{url}");
if ! compact {
if let Some(ref query) = endpoint.query {
for query in query {

View file

@ -1,6 +1,8 @@
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ClientConfig {
/// base url for composing endpoints
pub base: Option<String>,
/// user agent for requests, defaults to 'postwoman/<version>'
pub user_agent: Option<String>,
/// max total duration of each request, in seconds. defaults to 30

View file

@ -14,8 +14,10 @@ use super::{ExtractorConfig, ClientConfig};
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct EndpointConfig {
/// endpoint url, required
pub url: String,
/// endpoint path, composed from client base and query params
pub path: String,
/// absolute url, don't compose with client base url
pub absolute: Option<bool>,
/// http method for request, default GET
pub method: Option<String>,
/// query parameters, appended to base url
@ -31,24 +33,24 @@ pub struct EndpointConfig {
}
impl EndpointConfig {
pub fn body(&mut self) -> Result<String, serde_json::Error> {
match self.body.take() {
pub fn body(&self) -> Result<String, serde_json::Error> {
match &self.body {
None => Ok("".to_string()),
Some(StringOr::Str(x)) => Ok(x.clone()),
Some(StringOr::T(json)) => Ok(serde_json::to_string(&json)?),
}
}
pub fn method(&mut self) -> Result<reqwest::Method, InvalidMethod> {
pub fn method(&self) -> Result<reqwest::Method, InvalidMethod> {
match self.method {
Some(ref m) => Ok(reqwest::Method::from_str(m)?),
None => Ok(reqwest::Method::GET),
}
}
pub fn headers(&mut self) -> Result<HeaderMap, InvalidHeaderError> {
pub fn headers(&self) -> Result<HeaderMap, InvalidHeaderError> {
let mut headers = HeaderMap::default();
for header in self.headers.take().unwrap_or_default() {
for header in self.headers.as_deref().unwrap_or(&[]) {
let (k, v) = header.split_once(':')
.ok_or_else(|| InvalidHeaderError::Format(header.clone()))?;
headers.insert(
@ -59,9 +61,13 @@ impl EndpointConfig {
Ok(headers)
}
pub fn url(&mut self) -> String {
let mut url = self.url.clone();
if let Some(query) = self.query.take() {
pub fn url(&self, base: Option<&str>) -> String {
let mut url = if self.absolute.unwrap_or(false) {
self.path.clone()
} else {
format!("{}{}", base.unwrap_or_default(), self.path)
};
if let Some(ref query) = self.query {
url = format!("{url}?{}", query.join("&"));
}
url
@ -82,7 +88,7 @@ impl EndpointConfig {
for (k, v) in vars {
let k_var = format!("${{{k}}}");
self.url = self.url.replace(&k_var, &v);
self.path = self.path.replace(&k_var, &v);
if let Some(method) = self.method {
self.method = Some(method.replace(&k_var, &v));
}
@ -118,11 +124,11 @@ impl EndpointConfig {
self
}
pub async fn execute(mut self, opts: &ClientConfig) -> Result<String, PostWomanError> {
let url = self.url();
pub async fn execute(self, opts: &ClientConfig) -> Result<String, PostWomanError> {
let body = self.body()?;
let method = self.method()?;
let headers = self.headers()?;
let url = self.url(opts.base.as_deref());
let client = reqwest::Client::builder()
.user_agent(opts.user_agent.as_deref().unwrap_or(APP_USER_AGENT))