From 1251953f9721f365802f173147665ffdf5b6bc5a Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 30 Oct 2025 09:21:08 +1100 Subject: [PATCH 1/2] Replace stale docs reference to minreq --- bitreq/src/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitreq/src/request.rs b/bitreq/src/request.rs index 6c55ac09..0352ab99 100644 --- a/bitreq/src/request.rs +++ b/bitreq/src/request.rs @@ -297,7 +297,7 @@ impl Request { /// Returns `Err` if we run into an error while sending the /// request, or receiving/parsing the response. The specific error /// is described in the `Err`, and it can be any - /// [`minreq::Error`](enum.Error.html) except + /// [`bitreq::Error`](enum.Error.html) except /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody). #[cfg(feature = "async")] pub async fn send_async(self) -> Result { From 99c948c98f7def225d7c9bc0b93130ac58161112 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 27 Oct 2025 10:23:10 +1100 Subject: [PATCH 2/2] Re-implement json-using-serde feature This feature was removed when we gutted `minreq` after forking it. Turns out we need it to be able to use `bitreq` in `jsonrpc`. Re-implement the feature by copying code from `minreq`. After copy, fix docs to mention `bitreq` and also refactor some error logic to use `as_str()?` directly (no logic change). --- Cargo-minimal.lock | 2 ++ Cargo-recent.lock | 2 ++ bitreq/Cargo.toml | 6 +++++- bitreq/src/error.rs | 7 +++++++ bitreq/src/request.rs | 18 ++++++++++++++++++ bitreq/src/response.rs | 35 +++++++++++++++++++++++++++++++++++ bitreq/tests/main.rs | 15 +++++++++++++++ 7 files changed, 84 insertions(+), 1 deletion(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index b6d159d1..89739476 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -165,6 +165,8 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-webpki", + "serde", + "serde_json", "tiny_http", "tokio", "tokio-rustls", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 53897d05..89365721 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -165,6 +165,8 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-webpki", + "serde", + "serde_json", "tiny_http", "tokio", "tokio-rustls", diff --git a/bitreq/Cargo.toml b/bitreq/Cargo.toml index bce0dfe6..c42d84b1 100644 --- a/bitreq/Cargo.toml +++ b/bitreq/Cargo.toml @@ -20,6 +20,9 @@ maintenance = { status = "experimental" } urlencoding = { version = "2.1.0", optional = true } # For the punycode feature: punycode = { version = "0.4.1", optional = true } +# For the json-using-serde feature: +serde = { version = "1.0.101", optional = true } +serde_json = { version = "1.0.0", optional = true } # For the proxy feature: base64 = { version = "0.22", optional = true } # For the https features: @@ -37,7 +40,7 @@ tiny_http = "0.12" chrono = "0.4.0" [package.metadata.docs.rs] -features = ["proxy", "https", "punycode"] +features = ["json-using-serde", "proxy", "https", "punycode"] [features] default = ["std"] @@ -46,6 +49,7 @@ log = ["dep:log"] https = ["https-rustls"] https-rustls = ["rustls", "webpki-roots", "rustls-webpki"] https-rustls-probe = ["rustls", "rustls-native-certs"] +json-using-serde = ["serde", "serde_json"] proxy = ["base64"] async = ["tokio", "std"] async-https = ["async", "https-rustls", "tokio-rustls"] diff --git a/bitreq/src/error.rs b/bitreq/src/error.rs index 0487fca7..dda103cd 100644 --- a/bitreq/src/error.rs +++ b/bitreq/src/error.rs @@ -9,6 +9,9 @@ use std::{error, io}; // what the user might want to handle? This error doesn't really invite graceful // handling. pub enum Error { + #[cfg(feature = "json-using-serde")] + /// Ran into a Serde error. + SerdeJsonError(serde_json::Error), /// The response body contains invalid UTF-8, so the `as_str()` /// conversion failed. InvalidUtf8InBody(str::Utf8Error), @@ -88,6 +91,8 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; match self { + #[cfg(feature = "json-using-serde")] + SerdeJsonError(err) => write!(f, "{}", err), #[cfg(feature = "std")] IoError(err) => write!(f, "{}", err), InvalidUtf8InBody(err) => write!(f, "{}", err), @@ -124,6 +129,8 @@ impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { use Error::*; match self { + #[cfg(feature = "json-using-serde")] + SerdeJsonError(err) => Some(err), #[cfg(feature = "std")] IoError(err) => Some(err), InvalidUtf8InBody(err) => Some(err), diff --git a/bitreq/src/request.rs b/bitreq/src/request.rs index 0352ab99..0cfbbaea 100644 --- a/bitreq/src/request.rs +++ b/bitreq/src/request.rs @@ -173,6 +173,24 @@ impl Request { self } + /// Converts given argument to JSON and sets it as body. + /// + /// # Errors + /// + /// Returns + /// [`SerdeJsonError`](enum.Error.html#variant.SerdeJsonError) if + /// Serde runs into a problem when converting `body` into a + /// string. + #[cfg(feature = "json-using-serde")] + pub fn with_json(mut self, body: &T) -> Result { + self.headers + .insert("Content-Type".to_string(), "application/json; charset=UTF-8".to_string()); + match serde_json::to_string(&body) { + Ok(json) => Ok(self.with_body(json)), + Err(err) => Err(Error::SerdeJsonError(err)), + } + } + /// Sets the request timeout in seconds. pub fn with_timeout(mut self, timeout: u64) -> Request { self.timeout = Some(timeout); diff --git a/bitreq/src/response.rs b/bitreq/src/response.rs index d9f13e89..fe9f674d 100644 --- a/bitreq/src/response.rs +++ b/bitreq/src/response.rs @@ -127,6 +127,41 @@ impl Response { /// # fn main() -> Result<(), Box> { Ok(()) } /// ``` pub fn into_bytes(self) -> Vec { self.body } + + /// Converts JSON body to a `struct` using Serde. + /// + /// # Errors + /// + /// Returns + /// [`SerdeJsonError`](enum.Error.html#variant.SerdeJsonError) if + /// Serde runs into a problem, or + /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody) + /// if the body is not UTF-8. + /// + /// # Example + /// In case compiler cannot figure out return type you might need to declare it explicitly: + /// + /// ```no_run + /// use serde_json::Value; + /// + /// # fn main() -> Result<(), bitreq::Error> { + /// # let url_to_json_resource = "http://example.org/resource.json"; + /// // Value could be any type that implements Deserialize! + /// let user = bitreq::get(url_to_json_resource).send()?.json::()?; + /// println!("User name is '{}'", user["name"]); + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "json-using-serde")] + pub fn json<'a, T>(&'a self) -> Result + where + T: serde::de::Deserialize<'a>, + { + match serde_json::from_str(self.as_str()?) { + Ok(json) => Ok(json), + Err(err) => Err(Error::SerdeJsonError(err)), + } + } } /// An HTTP response, which is loaded lazily. diff --git a/bitreq/tests/main.rs b/bitreq/tests/main.rs index 5331e246..88c40fbc 100644 --- a/bitreq/tests/main.rs +++ b/bitreq/tests/main.rs @@ -14,6 +14,21 @@ fn test_https() { assert_eq!(get_status_code(bitreq::get("https://example.com").send()), 200,); } +#[test] +#[cfg(feature = "json-using-serde")] +fn test_json_using_serde() { + const JSON_SRC: &str = r#"{ + "str": "Json test", + "num": 42 + }"#; + + setup(); + let original_json: serde_json::Value = serde_json::from_str(JSON_SRC).unwrap(); + let response = bitreq::post(url("/echo")).with_json(&original_json).unwrap().send().unwrap(); + let actual_json: serde_json::Value = response.json().unwrap(); + assert_eq!(actual_json, original_json); +} + #[test] fn test_timeout_too_low() { setup();