Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bitreq/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ features = ["json-using-serde", "proxy", "https"]
default = ["std"]
std = []

forms = ["serde/std", "std"]
log = ["dep:log"]
json-using-serde = ["serde", "serde_json"]
proxy = ["base64", "std"]
Expand Down
7 changes: 7 additions & 0 deletions bitreq/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ pub enum Error {
#[cfg(feature = "json-using-serde")]
/// Ran into a Serde error.
SerdeJsonError(serde_json::Error),

#[cfg(feature = "forms")]
/// Ran into a URL encoding error.
SerdeUrlencodeError(crate::urlencode::Error),

/// The response body contains invalid UTF-8, so the `as_str()`
/// conversion failed.
InvalidUtf8InBody(str::Utf8Error),
Expand Down Expand Up @@ -95,6 +100,8 @@ impl fmt::Display for Error {
match self {
#[cfg(feature = "json-using-serde")]
SerdeJsonError(err) => write!(f, "{}", err),
#[cfg(feature = "forms")]
SerdeUrlencodeError(err) => write!(f, "{}", err),
#[cfg(feature = "std")]
IoError(err) => write!(f, "{}", err),
InvalidUtf8InBody(err) => write!(f, "{}", err),
Expand Down
2 changes: 2 additions & 0 deletions bitreq/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ mod http_url;
mod proxy;
mod request;
mod response;
#[cfg(feature = "forms")]
mod urlencode;

#[cfg(feature = "async")]
pub use client::{Client, RequestExt};
Expand Down
85 changes: 85 additions & 0 deletions bitreq/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ impl Request {
self.with_header("Content-Length", format!("{}", body_length))
}

/// Add support for form url encode
#[cfg(feature = "forms")]
pub fn with_form<T: serde::ser::Serialize>(mut self, body: &T) -> Result<Request, Error> {
self.headers
.insert("Content-Type".to_string(), "application/x-www-form-urlencoded".to_string());

match crate::urlencode::to_string(body) {
Ok(json) => Ok(self.with_body(json)),
Err(err) => Err(Error::SerdeUrlencodeError(err)),
}
}

/// Adds given key and value as query parameter to request url
/// (resource).
///
Expand Down Expand Up @@ -711,3 +723,76 @@ mod encoding_tests {
assert_eq!(&req.url.path_and_query, "/?%C3%B3w%C3%B2=what%27s%20this?%20%F0%9F%91%80");
}
}

#[cfg(all(test, feature = "forms"))]
mod form_tests {
use alloc::collections::BTreeMap;

use super::post;

#[test]
fn test_with_form_sets_content_type() {
let mut form_data = BTreeMap::new();
form_data.insert("key", "value");

let req =
post("http://www.example.org").with_form(&form_data).expect("form encoding failed");
assert_eq!(
req.headers.get("Content-Type"),
Some(&"application/x-www-form-urlencoded".to_string())
);
}

#[test]
fn test_with_form_sets_content_length() {
let mut form_data = BTreeMap::new();
form_data.insert("key", "value");

let req =
post("http://www.example.org").with_form(&form_data).expect("form encoding failed");
// "key=value" is 9 bytes
assert_eq!(req.headers.get("Content-Length"), Some(&"9".to_string()));
}

#[test]
fn test_with_form_encodes_body() {
let mut form_data = BTreeMap::new();
form_data.insert("name", "test");
form_data.insert("value", "42");

let req =
post("http://www.example.org").with_form(&form_data).expect("form encoding failed");
let body = req.body.expect("body should be set");
let body_str = String::from_utf8(body).expect("body should be valid UTF-8");
// BTreeMap provides ordered iteration
assert_eq!(body_str, "name=test&value=42");
}

#[test]
fn test_with_form_encodes_special_characters() {
let mut form_data = BTreeMap::new();
form_data.insert("message", "hello world");
form_data.insert("special", "a&b=c");

let req =
post("http://www.example.org").with_form(&form_data).expect("form encoding failed");
let body = req.body.expect("body should be set");
let body_str = String::from_utf8(body).expect("body should be valid UTF-8");
// Spaces are encoded as + and special chars are percent-encoded
assert!(
body_str.contains("message=hello+world") || body_str.contains("message=hello%20world")
);
assert!(body_str.contains("special=a%26b%3Dc"));
}

#[test]
fn test_with_form_empty() {
let form_data: BTreeMap<&str, &str> = BTreeMap::new();

let req =
post("http://www.example.org").with_form(&form_data).expect("form encoding failed");
let body = req.body.expect("body should be set");
assert!(body.is_empty());
assert_eq!(req.headers.get("Content-Length"), Some(&"0".to_string()));
}
}
Loading
Loading