Skip to content

Commit d384a89

Browse files
authored
Merge pull request #102 from http-rs/more-conversions
Match Surf's Body conversions
2 parents b9129e4 + 960df9f commit d384a89

File tree

5 files changed

+337
-57
lines changed

5 files changed

+337
-57
lines changed

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ features = ["docs"]
1616
rustdoc-args = ["--cfg", "feature=\"docs\""]
1717

1818
[features]
19-
default = []
19+
default = ["async_std"]
2020
docs = ["unstable"]
2121
unstable = []
2222
hyperium_http = ["http"]
23+
async_std = [] # "async-std" when it is not default
2324

2425
[dependencies]
25-
2626
# Note(yoshuawuyts): used for async_std's `channel` only; use "core" once possible.
27+
# features: async_std
2728
async-std = { version = "1.4.0", features = ["unstable"] }
2829

2930
# features: hyperium/http
@@ -36,7 +37,8 @@ omnom = "2.1.1"
3637
pin-project-lite = "0.1.0"
3738
url = "2.1.0"
3839
serde_json = "1.0.51"
39-
serde = { version = "1.0", features = ["derive"] }
40+
serde = { version = "1.0.106", features = ["derive"] }
41+
serde_urlencoded = "0.6.1"
4042

4143
[dev-dependencies]
4244
http = "0.2.0"

src/body.rs

Lines changed: 204 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
use async_std::fs;
12
use async_std::io::prelude::*;
2-
use async_std::io::{self, BufRead, Read};
3+
use async_std::io::{self, Cursor};
4+
use serde::{de::DeserializeOwned, Serialize};
35

46
use std::fmt::{self, Debug};
7+
use std::path::Path;
58
use std::pin::Pin;
69
use std::task::{Context, Poll};
710

@@ -110,6 +113,23 @@ impl Body {
110113
}
111114
}
112115

116+
/// Get the inner reader from the `Body`
117+
///
118+
/// # Examples
119+
///
120+
/// ```
121+
/// # use std::io::prelude::*;
122+
/// use http_types::Body;
123+
/// use async_std::io::Cursor;
124+
///
125+
/// let cursor = Cursor::new("Hello Nori");
126+
/// let body = Body::from_reader(cursor, None);
127+
/// let _ = body.into_reader();
128+
/// ```
129+
pub fn into_reader(self) -> Box<dyn BufRead + Unpin + Send + 'static> {
130+
self.reader
131+
}
132+
113133
/// Create a `Body` from a Vec of bytes.
114134
///
115135
/// The Mime type is set to `application/octet-stream` if no other mime type has been set or can
@@ -157,43 +177,30 @@ impl Body {
157177
Ok(buf)
158178
}
159179

160-
/// Get the length of the body in bytes.
161-
///
162-
/// # Examples
163-
///
164-
/// ```
165-
/// use http_types::Body;
166-
/// use async_std::io::Cursor;
180+
/// Create a `Body` from a String
167181
///
168-
/// let cursor = Cursor::new("Hello Nori");
169-
/// let len = 10;
170-
/// let body = Body::from_reader(cursor, Some(len));
171-
/// assert_eq!(body.len(), Some(10));
172-
/// ```
173-
pub fn len(&self) -> Option<usize> {
174-
self.length
175-
}
176-
177-
/// Returns `true` if the body has a length of zero, and `false` otherwise.
178-
pub fn is_empty(&self) -> Option<bool> {
179-
self.length.map(|length| length == 0)
180-
}
181-
182-
/// Get the inner reader from the `Body`
182+
/// The Mime type is set to `text/plain` if no other mime type has been set or can
183+
/// be sniffed. If a `Body` has no length, HTTP implementations will often switch over to
184+
/// framed messages such as [Chunked
185+
/// Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding).
183186
///
184187
/// # Examples
185188
///
186189
/// ```
187-
/// # use std::io::prelude::*;
188-
/// use http_types::Body;
190+
/// use http_types::{Body, Response, StatusCode};
189191
/// use async_std::io::Cursor;
190192
///
191-
/// let cursor = Cursor::new("Hello Nori");
192-
/// let body = Body::from_reader(cursor, None);
193-
/// let _ = body.into_reader();
193+
/// let mut req = Response::new(StatusCode::Ok);
194+
///
195+
/// let input = String::from("hello Nori!");
196+
/// req.set_body(Body::from_string(input));
194197
/// ```
195-
pub fn into_reader(self) -> Box<dyn BufRead + Unpin + Send + 'static> {
196-
self.reader
198+
pub fn from_string(s: String) -> Self {
199+
Self {
200+
mime: mime::PLAIN,
201+
length: Some(s.len()),
202+
reader: Box::new(io::Cursor::new(s.into_bytes())),
203+
}
197204
}
198205

199206
/// Read the body as a string
@@ -218,6 +225,169 @@ impl Body {
218225
Ok(result)
219226
}
220227

228+
/// Creates a `Body` from a type, serializing it as JSON.
229+
///
230+
/// # Mime
231+
///
232+
/// The encoding is set to `application/json`.
233+
///
234+
/// # Examples
235+
///
236+
/// ```
237+
/// use http_types::{Body, convert::json};
238+
///
239+
/// let body = Body::from_json(&json!({ "name": "Chashu" }));
240+
/// # drop(body);
241+
/// ```
242+
pub fn from_json(json: &impl Serialize) -> crate::Result<Self> {
243+
let bytes = serde_json::to_vec(&json)?;
244+
let body = Self {
245+
length: Some(bytes.len()),
246+
reader: Box::new(Cursor::new(bytes)),
247+
mime: mime::JSON,
248+
};
249+
Ok(body)
250+
}
251+
252+
/// Parse the body as JSON, serializing it to a struct.
253+
///
254+
/// # Examples
255+
///
256+
/// ```
257+
/// # fn main() -> Result<(), http_types::Error> { async_std::task::block_on(async {
258+
/// use http_types::Body;
259+
/// use http_types::convert::{Serialize, Deserialize};
260+
///
261+
/// #[derive(Debug, Serialize, Deserialize)]
262+
/// struct Cat { name: String }
263+
///
264+
/// let cat = Cat { name: String::from("chashu") };
265+
/// let body = Body::from_json(&cat)?;
266+
///
267+
/// let cat: Cat = body.into_json().await?;
268+
/// assert_eq!(&cat.name, "chashu");
269+
/// # Ok(()) }) }
270+
/// ```
271+
pub async fn into_json<T: DeserializeOwned>(mut self) -> crate::Result<T> {
272+
let mut buf = Vec::with_capacity(1024);
273+
self.read_to_end(&mut buf).await?;
274+
Ok(serde_json::from_slice(&buf).map_err(io::Error::from)?)
275+
}
276+
277+
/// Creates a `Body` from a type, serializing it using form encoding.
278+
///
279+
/// # Mime
280+
///
281+
/// The encoding is set to `application/x-www-form-urlencoded`.
282+
///
283+
/// # Errors
284+
///
285+
/// An error will be returned if the encoding failed.
286+
///
287+
/// # Examples
288+
///
289+
/// ```
290+
/// # fn main() -> Result<(), http_types::Error> { async_std::task::block_on(async {
291+
/// use http_types::Body;
292+
/// use http_types::convert::{Serialize, Deserialize};
293+
///
294+
/// #[derive(Debug, Serialize, Deserialize)]
295+
/// struct Cat { name: String }
296+
///
297+
/// let cat = Cat { name: String::from("chashu") };
298+
/// let body = Body::from_form(&cat)?;
299+
///
300+
/// let cat: Cat = body.into_form().await?;
301+
/// assert_eq!(&cat.name, "chashu");
302+
/// # Ok(()) }) }
303+
/// ```
304+
pub fn from_form(form: &impl Serialize) -> crate::Result<Self> {
305+
let query = serde_urlencoded::to_string(form)?;
306+
let bytes = query.into_bytes();
307+
308+
let body = Self {
309+
length: Some(bytes.len()),
310+
reader: Box::new(Cursor::new(bytes)),
311+
mime: mime::FORM,
312+
};
313+
Ok(body)
314+
}
315+
316+
/// Parse the body from form encoding into a type.
317+
///
318+
/// # Errors
319+
///
320+
/// An error is returned if the underlying IO stream errors, or if the body
321+
/// could not be deserialized into the type.
322+
///
323+
/// # Examples
324+
///
325+
/// ```
326+
/// # fn main() -> Result<(), http_types::Error> { async_std::task::block_on(async {
327+
/// use http_types::Body;
328+
/// use http_types::convert::{Serialize, Deserialize};
329+
///
330+
/// #[derive(Debug, Serialize, Deserialize)]
331+
/// struct Cat { name: String }
332+
///
333+
/// let cat = Cat { name: String::from("chashu") };
334+
/// let body = Body::from_form(&cat)?;
335+
///
336+
/// let cat: Cat = body.into_form().await?;
337+
/// assert_eq!(&cat.name, "chashu");
338+
/// # Ok(()) }) }
339+
/// ```
340+
pub async fn into_form<T: DeserializeOwned>(self) -> crate::Result<T> {
341+
let s = self.into_string().await?;
342+
Ok(serde_urlencoded::from_str(&s)?)
343+
}
344+
345+
/// Create a `Body` from a file.
346+
///
347+
/// The Mime type set to `application/octet-stream` if no other mime type has
348+
/// been set or can be sniffed.
349+
///
350+
/// # Examples
351+
///
352+
/// ```no_run
353+
/// # fn main() -> Result<(), http_types::Error> { async_std::task::block_on(async {
354+
/// use http_types::{Body, Response, StatusCode};
355+
///
356+
/// let mut res = Response::new(StatusCode::Ok);
357+
/// res.set_body(Body::from_file("/path/to/file").await?);
358+
/// # Ok(()) }) }
359+
/// ```
360+
#[cfg(feature = "async_std")]
361+
pub async fn from_file<P>(file: P) -> io::Result<Self>
362+
where
363+
P: AsRef<Path>,
364+
{
365+
let file = fs::read(file.as_ref()).await?;
366+
Ok(file.into())
367+
}
368+
369+
/// Get the length of the body in bytes.
370+
///
371+
/// # Examples
372+
///
373+
/// ```
374+
/// use http_types::Body;
375+
/// use async_std::io::Cursor;
376+
///
377+
/// let cursor = Cursor::new("Hello Nori");
378+
/// let len = 10;
379+
/// let body = Body::from_reader(cursor, Some(len));
380+
/// assert_eq!(body.len(), Some(10));
381+
/// ```
382+
pub fn len(&self) -> Option<usize> {
383+
self.length
384+
}
385+
386+
/// Returns `true` if the body has a length of zero, and `false` otherwise.
387+
pub fn is_empty(&self) -> Option<bool> {
388+
self.length.map(|length| length == 0)
389+
}
390+
221391
pub(crate) fn mime(&self) -> &Mime {
222392
&self.mime
223393
}
@@ -234,41 +404,25 @@ impl Debug for Body {
234404

235405
impl From<String> for Body {
236406
fn from(s: String) -> Self {
237-
Self {
238-
length: Some(s.len()),
239-
reader: Box::new(io::Cursor::new(s.into_bytes())),
240-
mime: mime::PLAIN,
241-
}
407+
Self::from_string(s)
242408
}
243409
}
244410

245411
impl<'a> From<&'a str> for Body {
246412
fn from(s: &'a str) -> Self {
247-
Self {
248-
length: Some(s.len()),
249-
reader: Box::new(io::Cursor::new(s.to_owned().into_bytes())),
250-
mime: mime::PLAIN,
251-
}
413+
Self::from_string(s.to_owned())
252414
}
253415
}
254416

255417
impl From<Vec<u8>> for Body {
256418
fn from(b: Vec<u8>) -> Self {
257-
Self {
258-
length: Some(b.len()),
259-
reader: Box::new(io::Cursor::new(b)),
260-
mime: mime::BYTE_STREAM,
261-
}
419+
Self::from_bytes(b.to_owned())
262420
}
263421
}
264422

265423
impl<'a> From<&'a [u8]> for Body {
266424
fn from(b: &'a [u8]) -> Self {
267-
Self {
268-
length: Some(b.len()),
269-
reader: Box::new(io::Cursor::new(b.to_owned())),
270-
mime: mime::BYTE_STREAM,
271-
}
425+
Self::from_bytes(b.to_owned())
272426
}
273427
}
274428

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ mod hyperium_http;
173173
#[doc(inline)]
174174
pub use crate::type_map::TypeMap;
175175

176+
/// Traits for conversions between types.
177+
pub mod convert {
178+
pub use serde::{de::DeserializeOwned, Deserialize, Serialize};
179+
#[doc(inline)]
180+
pub use serde_json::json;
181+
}
182+
176183
// Not public API. Referenced by macro-generated code.
177184
#[doc(hidden)]
178185
pub mod private {

0 commit comments

Comments
 (0)