Skip to content

Commit

Permalink
feat: add http body to the request data structure
Browse files Browse the repository at this point in the history
implements #5

done:
- [x] add `HttpVersion` to `HttpRequest`
- [x] implement http template file body parsing + test

todos:
- [ ] implement http body to curl parameter mapping
- [ ] implement external file enum behavior
- [ ] implement templating on body content

Signed-off-by: Sven Kanoldt <[email protected]>
  • Loading branch information
sassman committed Dec 16, 2022
1 parent 7aefda5 commit cfb5bec
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 63 deletions.
4 changes: 3 additions & 1 deletion src/curlz/cli/commands/request.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::data::{HttpHeaders, HttpMethod, HttpRequest, HttpUri};
use crate::data::{HttpBody, HttpHeaders, HttpMethod, HttpRequest, HttpUri};
use crate::interactive;
use crate::ops::{
LoadBookmark, MutOperation, Operation, OperationContext, RunCurlCommand, SaveBookmark,
Expand Down Expand Up @@ -103,6 +103,7 @@ impl MutOperation for RequestCli {
method,
version: Http11,
headers,
body: HttpBody::default(),
placeholders,
// todo: implement placeholder scanning..
curl_params: raw,
Expand All @@ -127,6 +128,7 @@ impl MutOperation for RequestCli {
method,
version: Http11,
headers,
body: HttpBody::default(),
placeholders,
// todo: implement placeholder scanning..
curl_params: raw,
Expand Down
28 changes: 28 additions & 0 deletions src/curlz/data/request/http_body.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum HttpBody {
InlineText(String),
InlineBinary(Vec<u8>),
Extern(PathBuf),
None,
}

impl Default for HttpBody {
fn default() -> Self {
Self::None
}
}

impl HttpBody {
pub fn contents(&self) -> std::io::Result<Option<String>> {
Ok(match self {
HttpBody::InlineText(c) => Some(c.to_owned()),
HttpBody::InlineBinary(_) => todo!("Binary data cannot be represented as string yet"),
HttpBody::Extern(f) => Some(std::fs::read_to_string(f)?),
HttpBody::None => None,
})
}
}
3 changes: 2 additions & 1 deletion src/curlz/data/request/http_request.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};

use crate::data::{HttpHeaders, HttpMethod, HttpUri, HttpVersion};
use crate::data::{HttpBody, HttpHeaders, HttpMethod, HttpUri, HttpVersion};
use crate::variables::Placeholder;

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
Expand All @@ -9,6 +9,7 @@ pub struct HttpRequest {
pub method: HttpMethod,
pub version: HttpVersion,
pub headers: HttpHeaders,
pub body: HttpBody,
pub curl_params: Vec<String>,
pub placeholders: Vec<Placeholder>,
}
Expand Down
2 changes: 2 additions & 0 deletions src/curlz/data/request/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod http_body;
mod http_headers;
mod http_method;
mod http_request;
mod http_uri;
mod http_version;

pub use http_body::*;
pub use http_headers::*;
pub use http_method::*;
pub use http_request::*;
Expand Down
67 changes: 59 additions & 8 deletions src/curlz/http.rs → src/curlz/http_file.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! a module for experimenting with the http language that the rest client uses
use crate::data::{Bookmark, HttpHeaders, HttpMethod, HttpRequest, HttpUri, HttpVersion};
use crate::data::{Bookmark, HttpBody, HttpHeaders, HttpMethod, HttpRequest, HttpUri, HttpVersion};
use anyhow::anyhow;
use pest::iterators::Pair;
use pest::Parser;
Expand All @@ -13,10 +13,7 @@ fn parse_request_file(req_file: impl AsRef<str>) -> Result<Vec<Bookmark>, anyhow
let mut requests = vec![];

let req_file = req_file.as_ref();
let file = HttpParser::parse(Rule::file, req_file)
.expect("parsing failed!")
.next()
.unwrap();
let file = HttpParser::parse(Rule::file, req_file)?.next().unwrap();

let mut slug: String = "".to_owned();
for line in file.into_inner() {
Expand All @@ -31,8 +28,9 @@ fn parse_request_file(req_file: impl AsRef<str>) -> Result<Vec<Bookmark>, anyhow
slug = line.as_str().trim().to_owned();
println!("delimiter = {:?}", line.as_str());
}
Rule::EOI => {}
x => {
println!("x = {:?}\n", x);
todo!("x = {:?}\n", x);
}
}
}
Expand Down Expand Up @@ -77,18 +75,25 @@ impl TryFrom<Pair<'_, Rule>> for HttpRequest {
.next()
.map(HttpHeaders::try_from)
.unwrap_or_else(|| Ok(HttpHeaders::default()))?;
let body = inner_rules
.next()
.map(HttpBody::try_from)
// todo: maybe an error on parsing remains an error
.map(|b| b.unwrap_or_default())
.unwrap_or_default();

dbg!(&method);
dbg!(&url);
dbg!(&version);
dbg!(&headers);
// dbg!(body);
dbg!(&body);

Ok(Self {
url,
method,
version,
headers,
body,
curl_params: Default::default(),
placeholders: Default::default(),
})
Expand Down Expand Up @@ -131,12 +136,23 @@ impl TryFrom<Pair<'_, Rule>> for HttpVersion {
}
}

/// converts the body
impl TryFrom<Pair<'_, Rule>> for HttpBody {
type Error = anyhow::Error;
fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
match value.as_rule() {
Rule::body => Ok(HttpBody::InlineText(value.as_str().to_owned())),
_ => Err(anyhow!("The parsing result is not a valid `version`")),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::data::HttpVersion::Http11;
use crate::data::*;

use crate::data::HttpVersion::Http11;
use indoc::indoc;
use pretty_assertions::assert_eq;
use rstest::rstest;
Expand All @@ -155,6 +171,7 @@ mod tests {
method: HttpMethod::Get,
version: HttpVersion::Http11,
headers: HttpHeaders::from(["Accept: application/json".to_owned()].as_slice()),
body: HttpBody::default(),
curl_params: Default::default(),
placeholders: Default::default(),
}
Expand All @@ -172,6 +189,40 @@ mod tests {
method: HttpMethod::Get,
version: HttpVersion::Http11,
headers: Default::default(),
body: HttpBody::default(),
curl_params: Default::default(),
placeholders: Default::default(),
}
}
)]
#[case(
indoc! {r#"
### this is a POST request with a body
POST https://httpbin.org/anything HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"foo": "Bar",
"bool": true
}
"#},
Bookmark {
slug: "### this is a POST request with a body".into(),
request: HttpRequest {
url: "https://httpbin.org/anything".into(),
method: HttpMethod::Post,
version: HttpVersion::Http11,
headers: HttpHeaders::from([
"Accept: application/json".to_owned(),
"Content-Type: application/json".to_owned(),
].as_slice()),
body: HttpBody::InlineText(indoc! {r#"
{
"foo": "Bar",
"bool": true
}
"#}.to_owned()),
curl_params: Default::default(),
placeholders: Default::default(),
}
Expand Down
2 changes: 1 addition & 1 deletion src/curlz/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub mod utils;
pub mod variables;
pub mod workspace;

mod http;
mod http_file;
#[cfg(test)]
pub mod test_utils;

Expand Down
3 changes: 2 additions & 1 deletion src/curlz/workspace/bookmark_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ impl BookmarkCollection {
#[cfg(test)]
mod tests {
use super::*;
use crate::data::HttpMethod;
use crate::data::HttpVersion::Http11;
use crate::data::{HttpBody, HttpMethod};
use crate::data::{HttpHeaders, HttpRequest};
use crate::ops::SaveBookmark;
use crate::variables::Placeholder;
Expand All @@ -90,6 +90,7 @@ mod tests {
method: HttpMethod::Get,
version: Http11,
headers: HttpHeaders::default(),
body: HttpBody::default(),
curl_params: vec![],
placeholders: vec![email_placeholder(), protonmail_api_baseurl_placeholder()],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ request:
method: GET
version: HTTP/1.1
headers: []
body: None
curl_params: []
placeholders:
- name: email
Expand Down
67 changes: 16 additions & 51 deletions tests/basics.rs
Original file line number Diff line number Diff line change
@@ -1,69 +1,34 @@
use assert_cmd::assert::Assert;
use assert_cmd::prelude::*;
use dotenvy::dotenv;
use curlz::data::HttpMethod;
use predicates::prelude::*;
use std::process::Command;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

fn binary() -> Result<Command, assert_cmd::cargo::CargoError> {
Command::cargo_bin(env!("CARGO_PKG_NAME"))
}
use crate::testlib::{binary, CurlzTestSuite};

mod testlib;

#[test]
fn should_show_usage_when_no_args_passed() {
binary()
.unwrap()
.assert()
.failure()
.stderr(predicate::str::contains("USAGE:"));
}

#[tokio::test]
async fn should_request_a_url() {
CurlzTest::new()
async fn should_send_as_get() {
CurlzTestSuite::new()
.await
.with_url("/gitignore/templates/Rust")
.request()
.send_to_path("/gitignore/templates/Rust")
.issue_request()
.await;
}

struct CurlzTest {
mock_server: MockServer,
url_part: String,
payload: String,
}

impl CurlzTest {
pub async fn new() -> Self {
dotenv().ok();
Self {
mock_server: MockServer::start().await,
url_part: "".to_string(),
payload: "# curlz rocks".to_string(),
}
}

/// sets the target url to be requested
pub fn with_url(mut self, url_part: &str) -> Self {
self.url_part = url_part.to_string();
self
}

/// runs curlz and requests the given url
pub async fn request(self) -> Assert {
Mock::given(method("GET"))
.and(path(self.url_part.as_str()))
.respond_with(ResponseTemplate::new(200).set_body_string(&self.payload))
.mount(&self.mock_server)
.await;

let url = format!("{}{}", &self.mock_server.uri(), self.url_part);
binary()
.unwrap()
.args(["r", url.as_str()])
.assert()
.success()
.stdout(predicate::str::contains(&self.payload))
}
#[tokio::test]
async fn should_send_payload_as_post() {
CurlzTestSuite::new()
.await
.send_to_path("/anything")
.send_with_method(HttpMethod::Post)
.issue_request()
.await;
}
9 changes: 9 additions & 0 deletions tests/fixtures/send-payload-as-post.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### this is a POST request with a body
POST https://httpbin.org/anything
Accept: application/json
Content-Type: application/json

{
"foo": "Bar",
"bool": true
}
Loading

0 comments on commit cfb5bec

Please sign in to comment.