zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use clap::Parser;
use reqwest::RequestBuilder;
use serde_json::{Map, Value};
use std::collections::HashMap;

use zero4rs::core::awsv2::pre_signed_url_v2;

#[derive(Parser)]
#[clap(author = "keesh.zhang", version, about = "Amazon aws v2 signture tool")]
pub struct Cli {
    #[clap(flatten)]
    access_key_id: AccessKeyId,
    #[clap(flatten)]
    secret_access_key: SecretAccessKey,
    #[clap(flatten)]
    method: Method,
    #[clap(flatten)]
    request_url: RequestUrl,
    #[clap(flatten)]
    expire_seconds: ExpireSeconds,
    #[clap(flatten)]
    time_format: TimeFormat,
    #[clap(flatten)]
    body: Body,
}

#[derive(clap::Parser)]
pub struct AccessKeyId {
    #[clap(short, long = "access_key_id", help = "*")]
    access_key_id: String,
}

#[derive(clap::Parser)]
pub struct SecretAccessKey {
    #[clap(short, long = "secret_access_key", help = "*")]
    secret_access_key: String,
}

#[derive(clap::Parser)]
pub struct Method {
    #[clap(
        short, long = "method",
        value_parser = clap::builder::PossibleValuesParser::new(["get", "post", "delete", "put", "patch"]),
        help = "*",
    )]
    method: String,
}

#[derive(clap::Parser)]
pub struct RequestUrl {
    #[clap(long, short, help = "*")]
    request_url: String,
}

#[derive(clap::Parser)]
pub struct ExpireSeconds {
    #[clap(long, short, default_value = "0")]
    expire_seconds: u32,
}

#[derive(clap::Parser)]
pub struct TimeFormat {
    #[clap(
        long, short,
        value_parser = clap::builder::PossibleValuesParser::new(["%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S"]),
        help = "*",
    )]
    time_format: String,
}

#[derive(clap::Parser)]
pub struct Body {
    #[clap(long, short, default_value = "")]
    body: String,
}

fn main() {
    let args = Cli::parse();

    let new_signed_url = pre_signed_url_v2(
        &args.access_key_id.access_key_id,
        &args.secret_access_key.secret_access_key,
        args.expire_seconds.expire_seconds,
        &args.method.method,
        &args.request_url.request_url,
        &args.time_format.time_format,
    );

    let request = if &args.method.method == "post" || &args.method.method == "POST" {
        reqwest::Client::builder()
            .build()
            .unwrap()
            .post(new_signed_url.clone())
            .header("Content-Type", "application/json")
    } else {
        reqwest::Client::builder()
            .build()
            .unwrap()
            .get(new_signed_url.clone())
            .header("Content-Type", "application/json")
    };

    let mut curl_command = curl(&request);

    if (&args.method.method == "post" || &args.method.method == "POST")
        && !args.body.body.is_empty()
    {
        curl_command = curl_command + " -d '" + &args.body.body + "'";
    }

    println!("curl_command:\n------------------\n{}\n", curl_command);
}

pub fn curl(req: &RequestBuilder) -> String {
    let req_info = format!(r#"{:?}"#, req);

    let url_regex = regex::Regex::new(
        r#"RequestBuilder \{ method: (\w+), url: Url \{ scheme: "(\w+)", cannot_be_a_base: (.*?), username: (.*?), password: (.*?), host: (.*?), port: (.*?), path: "(.*?)", query: (.*?), fragment: (.*?) \}, headers: (.*?) \}"#,
    ).unwrap();

    let captures = url_regex.captures(&req_info).unwrap();

    let method = captures.get(1).map_or("", |m| m.as_str());
    let scheme = captures.get(2).map_or("", |m| m.as_str());
    let domain = captures.get(6).map_or("", |m| m.as_str());
    let port = captures.get(7).map_or("", |m| m.as_str());
    let path = captures.get(8).map_or("", |m| m.as_str());
    let query = captures.get(9).map_or("", |m| m.as_str());
    let headers = captures.get(11).map_or("", |m| m.as_str());

    let domain = if domain != "None" {
        regex::Regex::new(r#"Some\(Domain\("(.*?)"\)\)"#)
            .unwrap()
            .captures(domain)
            .unwrap()
            .get(1)
            .map_or("", |m| m.as_str())
    } else {
        ""
    };

    let port = if port != "None" {
        format!(
            ":{}",
            regex::Regex::new(r#"Some\((.*?)\)"#)
                .unwrap()
                .captures(domain)
                .unwrap()
                .get(1)
                .map_or("", |m| m.as_str())
        )
    } else {
        "".to_string()
    };

    let query = if query != "None" {
        "?".to_owned()
            + regex::Regex::new(r#"Some\("(.*?)"\)"#)
                .unwrap()
                .captures(query)
                .unwrap()
                .get(1)
                .map_or("", |m| m.as_str())
    } else {
        "".to_string()
    };

    let result: Result<Map<String, Value>, _> = serde_json::from_str(headers);

    let headers = match result {
        Ok(map) => {
            let hashmap: HashMap<String, serde_json::Value> = map.into_iter().collect();

            let result: Vec<String> = hashmap
                .iter()
                .map(|(key, value)| format!("-H '{}:{}'", key, value.as_str().unwrap()))
                .collect();

            result.join(" ")
        }
        Err(_) => "".to_string(),
    };

    format!(
        "curl -vX {} '{}://{}{}{}{}' {}",
        method, scheme, domain, port, path, query, headers
    )
}