reqwest_pretty_json/
lib.rs

1#![cfg_attr(feature = "pedantic", warn(clippy::pedantic))]
2#![warn(clippy::use_self)]
3#![warn(clippy::map_flatten)]
4#![warn(clippy::map_unwrap_or)]
5#![warn(deprecated_in_future)]
6#![warn(future_incompatible)]
7#![warn(unreachable_pub)]
8#![warn(missing_debug_implementations)]
9#![warn(rust_2018_compatibility)]
10#![warn(rust_2021_compatibility)]
11#![warn(rust_2018_idioms)]
12#![warn(noop_method_call)]
13#![warn(trivial_casts)]
14#![warn(unused)]
15#![deny(warnings)]
16
17//! [`reqwest`] provides an easy way of sending JSON-formatted body in the HTTP request and
18//! it always emits terse on-line JSON representation.
19//!
20//! Most of the time it is exactly what you need. However, in some cases you may prefer
21//! to emit "pretty" JSON representation of your data structures. Key-Value data stores are one
22//! such use case and there may be others as well.
23//!
24//! In this case you won't be able to use [`reqwest::RequestBuilder::json`] method and will have to
25//! manually serialize your data and set both the body of the request and Content-Type HTTP header.
26//!
27//! This crate provides convenient method to do just that.
28//! It exports trait [`PrettyJson`] that extends [`reqwest::RequestBuilder`] with
29//! [`PrettyJson::pretty_json`] method (in addition to the original
30//! [`reqwest::RequestBuilder::json`]).
31//!
32//! This method serializes your data structures as "pretty" JSON
33//! (using [`serde_json::to_vec_pretty`]) and lets [`reqwest::RequestBuilder::json`] do the rest.
34//!
35//!
36//! ```rust
37//! use reqwest::Client;
38//! use reqwest_pretty_json::PrettyJson;
39//!
40//! let data = vec![1, 2, 3];
41//! let client = Client::new();
42//! let request = client
43//!     .post("http://httpbin.org/post")
44//!     .pretty_json(&data)
45//!     .build();
46//! ```
47
48use serde::Serialize;
49use serde_json::to_vec_pretty;
50
51/// A trait to set HTTP request body to a "prettified" JSON-formatted representation of the data.
52pub trait PrettyJson<T>: Sized
53where
54    T: Serialize + ?Sized,
55{
56    /// Send a "pretty" JSON body.
57    ///
58    /// Set the HTTP request body to the "pretty" (human-friendly) JSON serialization
59    /// of the passed value, and also set the `Content-Type: application/json` header.
60    ///
61    /// ```no_run
62    /// # use reqwest::Error;
63    /// # use std::collections::HashMap;
64    /// use reqwest_pretty_json::PrettyJson;
65    ///
66    /// # async fn run() -> Result<(), Error> {
67    /// let mut map = HashMap::new();
68    /// map.insert("lang", "rust");
69    ///
70    /// let client = reqwest::Client::new();
71    /// let res = client.post("http://httpbin.org")
72    ///     .pretty_json(&map)
73    ///     .send()
74    ///     .await?;
75    /// # Ok(())
76    /// # }
77    /// ```
78    ///
79    /// # Errors
80    ///
81    /// Same as [`reqwest::RequestBuilder::json`]. See [`reqwest`] for more details.
82    fn pretty_json(self, json: &T) -> Self;
83}
84
85impl<T> PrettyJson<T> for reqwest::RequestBuilder
86where
87    T: Serialize + ?Sized,
88{
89    fn pretty_json(self, json: &T) -> Self {
90        let builder = self.json(json);
91        match to_vec_pretty(json) {
92            Ok(body) => builder.body(body),
93            Err(_) => builder,
94        }
95    }
96}
97
98impl<T> PrettyJson<T> for reqwest::blocking::RequestBuilder
99where
100    T: Serialize + ?Sized,
101{
102    fn pretty_json(self, json: &T) -> Self {
103        let builder = self.json(json);
104        match to_vec_pretty(json) {
105            Ok(body) => builder.body(body),
106            Err(_) => builder,
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use std::collections::HashMap;
114    use std::error::Error;
115
116    use reqwest::StatusCode;
117    use serde_json::{to_string, to_string_pretty, to_value, Value};
118
119    use super::*;
120
121    #[tokio::test]
122    async fn pretty_json_async() -> Result<(), Box<dyn Error>> {
123        let mut data = HashMap::<_, Vec<u8>>::new();
124        data.insert("foo", vec![1, 2, 3]);
125
126        let body_should_be = to_string_pretty(&data)?;
127        let body_shouldnt_be = to_string(&data)?;
128        let value = to_value(&data)?;
129
130        let client = reqwest::Client::new();
131        let response = client
132            .post("http://httpbin.org/post")
133            .pretty_json(&data)
134            .send()
135            .await?;
136
137        assert_eq!(response.status(), StatusCode::OK);
138
139        let result: Value = response.json().await?;
140
141        assert_eq!(result["data"], body_should_be);
142        assert_ne!(result["data"], body_shouldnt_be);
143        assert_eq!(result["headers"]["Content-Type"], "application/json");
144        assert_eq!(result["json"], value);
145
146        Ok(())
147    }
148
149    #[test]
150    fn pretty_json_blocking() -> Result<(), Box<dyn Error>> {
151        let mut data = HashMap::new();
152        data.insert("foo", vec![1, 2, 3]);
153
154        let body_should_be = to_string_pretty(&data)?;
155        let body_shouldnt_be = to_string(&data)?;
156        let value = to_value(&data)?;
157
158        let client = reqwest::blocking::Client::new();
159        let response = client
160            .post("http://httpbin.org/post")
161            .pretty_json(&data)
162            .send()?;
163
164        assert_eq!(response.status(), StatusCode::OK);
165
166        let result: Value = response.json().unwrap();
167
168        assert_eq!(result["data"], body_should_be);
169        assert_ne!(result["data"], body_shouldnt_be);
170        assert_eq!(result["headers"]["Content-Type"], "application/json");
171        assert_eq!(result["json"], value);
172
173        Ok(())
174    }
175}