Skip to content

Commit

Permalink
Support HTTP methods (#2)
Browse files Browse the repository at this point in the history
* 0.0.2

* try optional positonal argument for http method, doesn't work

* add optional method argument that defaults to GET

* 0.0.3

Co-authored-by: Ridwan Hoq <ridhoq@users.noreply.github.com>
  • Loading branch information
ridhoq and ridhoq committed Feb 8, 2021
1 parent 2b1e078 commit f8b7918
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 7 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "af"
version = "0.0.1"
version = "0.0.3"
authors = ["Ridwan Hoq <ridhoq@users.noreply.github.com>"]
edition = "2018"
description = "A (http) fetch CLI πŸ˜€πŸ‘πŸ½"
Expand All @@ -10,6 +10,7 @@ repository = "https://github.com/ridhoq/af"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
http = "0.2.2"
hyper = { version = "0.14", features = ["full"] }
hyper-tls = "0.5.0"
structopt = "0.3.21"
Expand Down
81 changes: 75 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
use hyper::{body::HttpBody as _, Client, http::Uri};
use std::error;
use std::fmt;

use http::{Method, Uri};
use hyper::{body::HttpBody as _, Client, Request};
use hyper_tls::HttpsConnector;
use structopt::clap::AppSettings;
use structopt::StructOpt;
use tokio::io::{self, AsyncWriteExt as _};

#[derive(StructOpt)]
#[derive(Debug, Clone)]
struct InvalidHttpMethodError;

impl fmt::Display for InvalidHttpMethodError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid or unknown HTTP Method")
}
}

/// Parse HTTP method from string. Throws error if invalid/unknown HTTP method
fn parse_method(src: &str) -> Result<Method, InvalidHttpMethodError> {
match src.to_uppercase().as_str() {
"GET" => Ok(Method::GET),
"PUT" => Ok(Method::PUT),
"POST" => Ok(Method::POST),
"HEAD" => Ok(Method::HEAD),
"PATCH" => Ok(Method::PATCH),
"TRACE" => Ok(Method::TRACE),
"DELETE" => Ok(Method::DELETE),
"OPTIONS" => Ok(Method::OPTIONS),
"CONNECT" => Ok(Method::CONNECT),
_ => Err(InvalidHttpMethodError),
}
}

#[derive(StructOpt, Debug)]
#[structopt(setting = AppSettings::AllowMissingPositional)]
/// A (http) fetch CLI πŸ˜€πŸ‘
struct Cli {
// The URI to fetch
uri: Uri
/// HTTP method. If no HTTP method is provided, GET is used by default
#[structopt(name = "METHOD", index = 1, default_value = "GET", parse(try_from_str = parse_method))]
method: Method,

/// URI to fetch
#[structopt(name = "URI", index = 2, required = true, parse(try_from_str))]
uri: Uri,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>>{
async fn main() -> Result<(), Box<dyn error::Error>> {
let args = Cli::from_args();

// Set up the HTTPS connector with the client
let https = HttpsConnector::new();
let client = Client::builder().build::<_, hyper::Body>(https);

let mut res = client.get(args.uri).await?;
let req = Request::builder()
.method(args.method)
.uri(args.uri)
// TODO: couldn't figure out how to not pass a body with the Request::builder
// TODO: pass in a real body when doing POST/PUT/etc
.body(hyper::Body::from(""))
.unwrap();

let mut res = client.request(req).await?;

println!("Response: {}", res.status());
println!("Headers: {:#?}\n", res.headers());
Expand All @@ -31,3 +76,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>>{

Ok(())
}

#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;

#[test]
fn test_valid_http_method() {
let get_lower = "get";
let get_upper = "GET";

assert_eq!(parse_method(get_lower).unwrap(), Method::GET);
assert_eq!(parse_method(get_upper).unwrap(), Method::GET);
}

#[test]
fn test_invalid_http_method() {
let poop = "poop";
let empty = "";

assert!(parse_method(poop).is_err());
assert!(parse_method(empty).is_err());
}
}

0 comments on commit f8b7918

Please sign in to comment.