http-body-util-0.1.2/.cargo_vcs_info.json0000644000000001540000000000100137210ustar { "git": { "sha1": "482c3492b9f40fb850e555e75d6f892733c34c1d" }, "path_in_vcs": "http-body-util" }http-body-util-0.1.2/CHANGELOG.md000064400000000000000000000006751046102023000143320ustar 00000000000000# v0.1.2 - Add `BodyDataStream` type to convert a body to a stream of its data. # v0.1.1 - Add `BodyExt::with_trailers()` combinator. - Improve performance of `BodyExt::collect().to_bytes()`. # v0.1.0 - Update `http` to 1.0. - Update `http-body` to 1.0. # v0.1.0-rc.3 - Fix `BodyExt::collect()` from panicking on an empty frame. # v0.1.0-rc.2 - Update to `http-body` rc.2. # v0.1.0-rc.1 - Initial release, split from http-body 0.4.5. http-body-util-0.1.2/Cargo.toml0000644000000026660000000000100117310ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.49" name = "http-body-util" version = "0.1.2" authors = [ "Carl Lerche ", "Lucio Franco ", "Sean McArthur ", ] build = false autobins = false autoexamples = false autotests = false autobenches = false description = """ Combinators and adapters for HTTP request or response bodies. """ documentation = "https://docs.rs/http-body-util" readme = "README.md" keywords = ["http"] categories = ["web-programming"] license = "MIT" repository = "https://github.com/hyperium/http-body" [lib] name = "http_body_util" path = "src/lib.rs" [dependencies.bytes] version = "1" [dependencies.futures-util] version = "0.3" default-features = false [dependencies.http] version = "1" [dependencies.http-body] version = "1" [dependencies.pin-project-lite] version = "0.2" [dev-dependencies.tokio] version = "1" features = [ "macros", "rt", "sync", "rt-multi-thread", ] http-body-util-0.1.2/Cargo.toml.orig000064400000000000000000000017411046102023000154030ustar 00000000000000[package] name = "http-body-util" # When releasing to crates.io: # - Remove path dependencies # - Update html_root_url. # - Update doc url # - Cargo.toml # - README.md # - Update CHANGELOG.md. # - Create "http-body-util-x.y.z" git tag. version = "0.1.2" authors = [ "Carl Lerche ", "Lucio Franco ", "Sean McArthur ", ] edition = "2018" readme = "../README.md" documentation = "https://docs.rs/http-body-util" repository = "https://github.com/hyperium/http-body" license = "MIT" description = """ Combinators and adapters for HTTP request or response bodies. """ keywords = ["http"] categories = ["web-programming"] rust-version = "1.49" [dependencies] bytes = "1" futures-util = { version = "0.3", default-features = false } http = "1" http-body = { version = "1", path = "../http-body" } pin-project-lite = "0.2" [dev-dependencies] tokio = { version = "1", features = ["macros", "rt", "sync", "rt-multi-thread"] } http-body-util-0.1.2/LICENSE000064400000000000000000000020461046102023000135200ustar 00000000000000Copyright (c) 2019 Hyper Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. http-body-util-0.1.2/README.md000064400000000000000000000016511046102023000137730ustar 00000000000000# HTTP Body A trait representing asynchronous operations on an HTTP body. [![crates.io][crates-badge]][crates-url] [![documentation][docs-badge]][docs-url] [![MIT License][mit-badge]][mit-url] [![CI Status][ci-badge]][ci-url] [crates-badge]: https://img.shields.io/crates/v/http-body.svg [crates-url]: https://crates.io/crates/http-body [docs-badge]: https://docs.rs/http-body/badge.svg [docs-url]: https://docs.rs/http-body [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: LICENSE [ci-badge]: https://github.com/hyperium/http-body/workflows/CI/badge.svg [ci-url]: https://github.com/hyperium/http-body/actions?query=workflow%3ACI ## License This project is licensed under the [MIT license](LICENSE). ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `http-body` by you, shall be licensed as MIT, without any additional terms or conditions. http-body-util-0.1.2/src/collected.rs000064400000000000000000000114231046102023000156050ustar 00000000000000use std::{ convert::Infallible, pin::Pin, task::{Context, Poll}, }; use bytes::{Buf, Bytes}; use http::HeaderMap; use http_body::{Body, Frame}; use crate::util::BufList; /// A collected body produced by [`BodyExt::collect`] which collects all the DATA frames /// and trailers. /// /// [`BodyExt::collect`]: crate::BodyExt::collect #[derive(Debug)] pub struct Collected { bufs: BufList, trailers: Option, } impl Collected { /// If there is a trailers frame buffered, returns a reference to it. /// /// Returns `None` if the body contained no trailers. pub fn trailers(&self) -> Option<&HeaderMap> { self.trailers.as_ref() } /// Aggregate this buffered into a [`Buf`]. pub fn aggregate(self) -> impl Buf { self.bufs } /// Convert this body into a [`Bytes`]. pub fn to_bytes(mut self) -> Bytes { self.bufs.copy_to_bytes(self.bufs.remaining()) } pub(crate) fn push_frame(&mut self, frame: Frame) { let frame = match frame.into_data() { Ok(data) => { // Only push this frame if it has some data in it, to avoid crashing on // `BufList::push`. if data.has_remaining() { self.bufs.push(data); } return; } Err(frame) => frame, }; if let Ok(trailers) = frame.into_trailers() { if let Some(current) = &mut self.trailers { current.extend(trailers); } else { self.trailers = Some(trailers); } }; } } impl Body for Collected { type Data = B; type Error = Infallible; fn poll_frame( mut self: Pin<&mut Self>, _: &mut Context<'_>, ) -> Poll, Self::Error>>> { let frame = if let Some(data) = self.bufs.pop() { Frame::data(data) } else if let Some(trailers) = self.trailers.take() { Frame::trailers(trailers) } else { return Poll::Ready(None); }; Poll::Ready(Some(Ok(frame))) } } impl Default for Collected { fn default() -> Self { Self { bufs: BufList::default(), trailers: None, } } } impl Unpin for Collected {} #[cfg(test)] mod tests { use std::convert::TryInto; use futures_util::stream; use crate::{BodyExt, Full, StreamBody}; use super::*; #[tokio::test] async fn full_body() { let body = Full::new(&b"hello"[..]); let buffered = body.collect().await.unwrap(); let mut buf = buffered.to_bytes(); assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], &b"hello"[..]); } #[tokio::test] async fn segmented_body() { let bufs = [&b"hello"[..], &b"world"[..], &b"!"[..]]; let body = StreamBody::new(stream::iter(bufs.map(Frame::data).map(Ok::<_, Infallible>))); let buffered = body.collect().await.unwrap(); let mut buf = buffered.to_bytes(); assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], b"helloworld!"); } #[tokio::test] async fn delayed_segments() { let one = stream::once(async { Ok::<_, Infallible>(Frame::data(&b"hello "[..])) }); let two = stream::once(async { // a yield just so its not ready immediately tokio::task::yield_now().await; Ok::<_, Infallible>(Frame::data(&b"world!"[..])) }); let stream = futures_util::StreamExt::chain(one, two); let body = StreamBody::new(stream); let buffered = body.collect().await.unwrap(); let mut buf = buffered.to_bytes(); assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], b"hello world!"); } #[tokio::test] async fn trailers() { let mut trailers = HeaderMap::new(); trailers.insert("this", "a trailer".try_into().unwrap()); let bufs = [ Frame::data(&b"hello"[..]), Frame::data(&b"world!"[..]), Frame::trailers(trailers.clone()), ]; let body = StreamBody::new(stream::iter(bufs.map(Ok::<_, Infallible>))); let buffered = body.collect().await.unwrap(); assert_eq!(&trailers, buffered.trailers().unwrap()); let mut buf = buffered.to_bytes(); assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], b"helloworld!"); } /// Test for issue [#88](https://github.com/hyperium/http-body/issues/88). #[tokio::test] async fn empty_frame() { let bufs: [&[u8]; 1] = [&[]]; let body = StreamBody::new(stream::iter(bufs.map(Frame::data).map(Ok::<_, Infallible>))); let buffered = body.collect().await.unwrap(); assert_eq!(buffered.to_bytes().len(), 0); } } http-body-util-0.1.2/src/combinators/box_body.rs000064400000000000000000000050071046102023000177750ustar 00000000000000use crate::BodyExt as _; use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use std::{ fmt, pin::Pin, task::{Context, Poll}, }; /// A boxed [`Body`] trait object. pub struct BoxBody { inner: Pin + Send + Sync + 'static>>, } /// A boxed [`Body`] trait object that is !Sync. pub struct UnsyncBoxBody { inner: Pin + Send + 'static>>, } impl BoxBody { /// Create a new `BoxBody`. pub fn new(body: B) -> Self where B: Body + Send + Sync + 'static, D: Buf, { Self { inner: Box::pin(body), } } } impl fmt::Debug for BoxBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BoxBody").finish() } } impl Body for BoxBody where D: Buf, { type Data = D; type Error = E; fn poll_frame( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { self.inner.as_mut().poll_frame(cx) } fn is_end_stream(&self) -> bool { self.inner.is_end_stream() } fn size_hint(&self) -> SizeHint { self.inner.size_hint() } } impl Default for BoxBody where D: Buf + 'static, { fn default() -> Self { BoxBody::new(crate::Empty::new().map_err(|err| match err {})) } } // === UnsyncBoxBody === impl UnsyncBoxBody { /// Create a new `BoxBody`. pub fn new(body: B) -> Self where B: Body + Send + 'static, D: Buf, { Self { inner: Box::pin(body), } } } impl fmt::Debug for UnsyncBoxBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("UnsyncBoxBody").finish() } } impl Body for UnsyncBoxBody where D: Buf, { type Data = D; type Error = E; fn poll_frame( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { self.inner.as_mut().poll_frame(cx) } fn is_end_stream(&self) -> bool { self.inner.is_end_stream() } fn size_hint(&self) -> SizeHint { self.inner.size_hint() } } impl Default for UnsyncBoxBody where D: Buf + 'static, { fn default() -> Self { UnsyncBoxBody::new(crate::Empty::new().map_err(|err| match err {})) } } http-body-util-0.1.2/src/combinators/collect.rs000064400000000000000000000020471046102023000176160ustar 00000000000000use std::{ future::Future, pin::Pin, task::{Context, Poll}, }; use http_body::Body; use pin_project_lite::pin_project; pin_project! { /// Future that resolves into a [`Collected`]. /// /// [`Collected`]: crate::Collected pub struct Collect where T: Body, T: ?Sized, { pub(crate) collected: Option>, #[pin] pub(crate) body: T, } } impl Future for Collect { type Output = Result, T::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll { let mut me = self.project(); loop { let frame = futures_util::ready!(me.body.as_mut().poll_frame(cx)); let frame = if let Some(frame) = frame { frame? } else { return Poll::Ready(Ok(me.collected.take().expect("polled after complete"))); }; me.collected.as_mut().unwrap().push_frame(frame); } } } http-body-util-0.1.2/src/combinators/frame.rs000064400000000000000000000010561046102023000172620ustar 00000000000000use http_body::Body; use core::future::Future; use core::pin::Pin; use core::task; #[must_use = "futures don't do anything unless polled"] #[derive(Debug)] /// Future that resolves to the next frame from a [`Body`]. pub struct Frame<'a, T: ?Sized>(pub(crate) &'a mut T); impl<'a, T: Body + Unpin + ?Sized> Future for Frame<'a, T> { type Output = Option, T::Error>>; fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll { Pin::new(&mut self.0).poll_frame(ctx) } } http-body-util-0.1.2/src/combinators/map_err.rs000064400000000000000000000040171046102023000176150ustar 00000000000000use http_body::{Body, Frame, SizeHint}; use pin_project_lite::pin_project; use std::{ any::type_name, fmt, pin::Pin, task::{Context, Poll}, }; pin_project! { /// Body returned by the [`map_err`] combinator. /// /// [`map_err`]: crate::BodyExt::map_err #[derive(Clone, Copy)] pub struct MapErr { #[pin] inner: B, f: F } } impl MapErr { #[inline] pub(crate) fn new(body: B, f: F) -> Self { Self { inner: body, f } } /// Get a reference to the inner body pub fn get_ref(&self) -> &B { &self.inner } /// Get a mutable reference to the inner body pub fn get_mut(&mut self) -> &mut B { &mut self.inner } /// Get a pinned mutable reference to the inner body pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut B> { self.project().inner } /// Consume `self`, returning the inner body pub fn into_inner(self) -> B { self.inner } } impl Body for MapErr where B: Body, F: FnMut(B::Error) -> E, { type Data = B::Data; type Error = E; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { let this = self.project(); match this.inner.poll_frame(cx) { Poll::Pending => Poll::Pending, Poll::Ready(None) => Poll::Ready(None), Poll::Ready(Some(Ok(frame))) => Poll::Ready(Some(Ok(frame))), Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err((this.f)(err)))), } } fn is_end_stream(&self) -> bool { self.inner.is_end_stream() } fn size_hint(&self) -> SizeHint { self.inner.size_hint() } } impl fmt::Debug for MapErr where B: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("MapErr") .field("inner", &self.inner) .field("f", &type_name::()) .finish() } } http-body-util-0.1.2/src/combinators/map_frame.rs000064400000000000000000000037671046102023000201320ustar 00000000000000use bytes::Buf; use http_body::{Body, Frame}; use pin_project_lite::pin_project; use std::{ any::type_name, fmt, pin::Pin, task::{Context, Poll}, }; pin_project! { /// Body returned by the [`map_frame`] combinator. /// /// [`map_frame`]: crate::BodyExt::map_frame #[derive(Clone, Copy)] pub struct MapFrame { #[pin] inner: B, f: F } } impl MapFrame { #[inline] pub(crate) fn new(body: B, f: F) -> Self { Self { inner: body, f } } /// Get a reference to the inner body pub fn get_ref(&self) -> &B { &self.inner } /// Get a mutable reference to the inner body pub fn get_mut(&mut self) -> &mut B { &mut self.inner } /// Get a pinned mutable reference to the inner body pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut B> { self.project().inner } /// Consume `self`, returning the inner body pub fn into_inner(self) -> B { self.inner } } impl Body for MapFrame where B: Body, F: FnMut(Frame) -> Frame, B2: Buf, { type Data = B2; type Error = B::Error; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { let this = self.project(); match this.inner.poll_frame(cx) { Poll::Pending => Poll::Pending, Poll::Ready(None) => Poll::Ready(None), Poll::Ready(Some(Ok(frame))) => Poll::Ready(Some(Ok((this.f)(frame)))), Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))), } } fn is_end_stream(&self) -> bool { self.inner.is_end_stream() } } impl fmt::Debug for MapFrame where B: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("MapFrame") .field("inner", &self.inner) .field("f", &type_name::()) .finish() } } http-body-util-0.1.2/src/combinators/mod.rs000064400000000000000000000004571046102023000167530ustar 00000000000000//! Combinators for the `Body` trait. mod box_body; mod collect; mod frame; mod map_err; mod map_frame; mod with_trailers; pub use self::{ box_body::{BoxBody, UnsyncBoxBody}, collect::Collect, frame::Frame, map_err::MapErr, map_frame::MapFrame, with_trailers::WithTrailers, }; http-body-util-0.1.2/src/combinators/with_trailers.rs000064400000000000000000000142671046102023000210600ustar 00000000000000use std::{ future::Future, pin::Pin, task::{Context, Poll}, }; use futures_util::ready; use http::HeaderMap; use http_body::{Body, Frame}; use pin_project_lite::pin_project; pin_project! { /// Adds trailers to a body. /// /// See [`BodyExt::with_trailers`] for more details. pub struct WithTrailers { #[pin] state: State, } } impl WithTrailers { pub(crate) fn new(body: T, trailers: F) -> Self { Self { state: State::PollBody { body, trailers: Some(trailers), }, } } } pin_project! { #[project = StateProj] enum State { PollBody { #[pin] body: T, trailers: Option, }, PollTrailers { #[pin] trailers: F, prev_trailers: Option, }, Done, } } impl Body for WithTrailers where T: Body, F: Future>>, { type Data = T::Data; type Error = T::Error; fn poll_frame( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { loop { let mut this = self.as_mut().project(); match this.state.as_mut().project() { StateProj::PollBody { body, trailers } => match ready!(body.poll_frame(cx)?) { Some(frame) => match frame.into_trailers() { Ok(prev_trailers) => { let trailers = trailers.take().unwrap(); this.state.set(State::PollTrailers { trailers, prev_trailers: Some(prev_trailers), }); } Err(frame) => { return Poll::Ready(Some(Ok(frame))); } }, None => { let trailers = trailers.take().unwrap(); this.state.set(State::PollTrailers { trailers, prev_trailers: None, }); } }, StateProj::PollTrailers { trailers, prev_trailers, } => { let trailers = ready!(trailers.poll(cx)?); match (trailers, prev_trailers.take()) { (None, None) => return Poll::Ready(None), (None, Some(trailers)) | (Some(trailers), None) => { this.state.set(State::Done); return Poll::Ready(Some(Ok(Frame::trailers(trailers)))); } (Some(new_trailers), Some(mut prev_trailers)) => { prev_trailers.extend(new_trailers); this.state.set(State::Done); return Poll::Ready(Some(Ok(Frame::trailers(prev_trailers)))); } } } StateProj::Done => { return Poll::Ready(None); } } } } #[inline] fn size_hint(&self) -> http_body::SizeHint { match &self.state { State::PollBody { body, .. } => body.size_hint(), State::PollTrailers { .. } | State::Done => Default::default(), } } } #[cfg(test)] mod tests { use std::convert::Infallible; use bytes::Bytes; use http::{HeaderName, HeaderValue}; use crate::{BodyExt, Empty, Full}; #[allow(unused_imports)] use super::*; #[tokio::test] async fn works() { let mut trailers = HeaderMap::new(); trailers.insert( HeaderName::from_static("foo"), HeaderValue::from_static("bar"), ); let body = Full::::from("hello").with_trailers(std::future::ready(Some( Ok::<_, Infallible>(trailers.clone()), ))); futures_util::pin_mut!(body); let waker = futures_util::task::noop_waker(); let mut cx = Context::from_waker(&waker); let data = unwrap_ready(body.as_mut().poll_frame(&mut cx)) .unwrap() .unwrap() .into_data() .unwrap(); assert_eq!(data, "hello"); let body_trailers = unwrap_ready(body.as_mut().poll_frame(&mut cx)) .unwrap() .unwrap() .into_trailers() .unwrap(); assert_eq!(body_trailers, trailers); assert!(unwrap_ready(body.as_mut().poll_frame(&mut cx)).is_none()); } #[tokio::test] async fn merges_trailers() { let mut trailers_1 = HeaderMap::new(); trailers_1.insert( HeaderName::from_static("foo"), HeaderValue::from_static("bar"), ); let mut trailers_2 = HeaderMap::new(); trailers_2.insert( HeaderName::from_static("baz"), HeaderValue::from_static("qux"), ); let body = Empty::::new() .with_trailers(std::future::ready(Some(Ok::<_, Infallible>( trailers_1.clone(), )))) .with_trailers(std::future::ready(Some(Ok::<_, Infallible>( trailers_2.clone(), )))); futures_util::pin_mut!(body); let waker = futures_util::task::noop_waker(); let mut cx = Context::from_waker(&waker); let body_trailers = unwrap_ready(body.as_mut().poll_frame(&mut cx)) .unwrap() .unwrap() .into_trailers() .unwrap(); let mut all_trailers = HeaderMap::new(); all_trailers.extend(trailers_1); all_trailers.extend(trailers_2); assert_eq!(body_trailers, all_trailers); assert!(unwrap_ready(body.as_mut().poll_frame(&mut cx)).is_none()); } fn unwrap_ready(poll: Poll) -> T { match poll { Poll::Ready(t) => t, Poll::Pending => panic!("pending"), } } } http-body-util-0.1.2/src/either.rs000064400000000000000000000122201046102023000151230ustar 00000000000000use std::error::Error; use std::fmt::Debug; use std::pin::Pin; use std::task::{Context, Poll}; use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use proj::EitherProj; /// Sum type with two cases: [`Left`] and [`Right`], used if a body can be one of /// two distinct types. /// /// [`Left`]: Either::Left /// [`Right`]: Either::Right #[derive(Debug, Clone, Copy)] pub enum Either { /// A value of type `L` Left(L), /// A value of type `R` Right(R), } impl Either { /// This function is part of the generated code from `pin-project-lite`, /// for a more in depth explanation and the rest of the generated code refer /// to the [`proj`] module. pub(crate) fn project(self: Pin<&mut Self>) -> EitherProj { unsafe { match self.get_unchecked_mut() { Self::Left(left) => EitherProj::Left(Pin::new_unchecked(left)), Self::Right(right) => EitherProj::Right(Pin::new_unchecked(right)), } } } } impl Either { /// Convert [`Either`] into the inner type, if both `Left` and `Right` are /// of the same type. pub fn into_inner(self) -> L { match self { Either::Left(left) => left, Either::Right(right) => right, } } } impl Body for Either where L: Body, R: Body, L::Error: Into>, R::Error: Into>, Data: Buf, { type Data = Data; type Error = Box; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { match self.project() { EitherProj::Left(left) => left .poll_frame(cx) .map(|poll| poll.map(|opt| opt.map_err(Into::into))), EitherProj::Right(right) => right .poll_frame(cx) .map(|poll| poll.map(|opt| opt.map_err(Into::into))), } } fn is_end_stream(&self) -> bool { match self { Either::Left(left) => left.is_end_stream(), Either::Right(right) => right.is_end_stream(), } } fn size_hint(&self) -> SizeHint { match self { Either::Left(left) => left.size_hint(), Either::Right(right) => right.size_hint(), } } } pub(crate) mod proj { //! This code is the (cleaned output) generated by [pin-project-lite], as it //! does not support tuple variants. //! //! This is the altered expansion from the following snippet, expanded by //! `cargo-expand`: //! //! ```rust //! use pin_project_lite::pin_project; //! //! pin_project! { //! #[project = EitherProj] //! pub enum Either { //! Left {#[pin] left: L}, //! Right {#[pin] right: R} //! } //! } //! ``` //! //! [pin-project-lite]: https://docs.rs/pin-project-lite/latest/pin_project_lite/ use std::marker::PhantomData; use std::pin::Pin; use super::Either; #[allow(dead_code)] #[allow(single_use_lifetimes)] #[allow(unknown_lints)] #[allow(clippy::mut_mut)] #[allow(clippy::redundant_pub_crate)] #[allow(clippy::ref_option_ref)] #[allow(clippy::type_repetition_in_bounds)] pub(crate) enum EitherProj<'__pin, L, R> where Either: '__pin, { Left(Pin<&'__pin mut L>), Right(Pin<&'__pin mut R>), } #[allow(single_use_lifetimes)] #[allow(unknown_lints)] #[allow(clippy::used_underscore_binding)] #[allow(missing_debug_implementations)] const _: () = { #[allow(non_snake_case)] pub struct __Origin<'__pin, L, R> { __dummy_lifetime: PhantomData<&'__pin ()>, _Left: L, _Right: R, } impl<'__pin, L, R> Unpin for Either where __Origin<'__pin, L, R>: Unpin {} trait MustNotImplDrop {} #[allow(drop_bounds)] impl MustNotImplDrop for T {} impl MustNotImplDrop for Either {} }; } #[cfg(test)] mod tests { use super::*; use crate::{BodyExt, Empty, Full}; #[tokio::test] async fn data_left() { let full = Full::new(&b"hello"[..]); let mut value: Either<_, Empty<&[u8]>> = Either::Left(full); assert_eq!(value.size_hint().exact(), Some(b"hello".len() as u64)); assert_eq!( value.frame().await.unwrap().unwrap().into_data().unwrap(), &b"hello"[..] ); assert!(value.frame().await.is_none()); } #[tokio::test] async fn data_right() { let full = Full::new(&b"hello!"[..]); let mut value: Either, _> = Either::Right(full); assert_eq!(value.size_hint().exact(), Some(b"hello!".len() as u64)); assert_eq!( value.frame().await.unwrap().unwrap().into_data().unwrap(), &b"hello!"[..] ); assert!(value.frame().await.is_none()); } #[test] fn into_inner() { let a = Either::::Left(2); assert_eq!(a.into_inner(), 2) } } http-body-util-0.1.2/src/empty.rs000064400000000000000000000022211046102023000150010ustar 00000000000000use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use std::{ convert::Infallible, fmt, marker::PhantomData, pin::Pin, task::{Context, Poll}, }; /// A body that is always empty. pub struct Empty { _marker: PhantomData D>, } impl Empty { /// Create a new `Empty`. pub fn new() -> Self { Self::default() } } impl Body for Empty { type Data = D; type Error = Infallible; #[inline] fn poll_frame( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { Poll::Ready(None) } fn is_end_stream(&self) -> bool { true } fn size_hint(&self) -> SizeHint { SizeHint::with_exact(0) } } impl fmt::Debug for Empty { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Empty").finish() } } impl Default for Empty { fn default() -> Self { Self { _marker: PhantomData, } } } impl Clone for Empty { fn clone(&self) -> Self { *self } } impl Copy for Empty {} http-body-util-0.1.2/src/full.rs000064400000000000000000000060041046102023000146100ustar 00000000000000use bytes::{Buf, Bytes}; use http_body::{Body, Frame, SizeHint}; use pin_project_lite::pin_project; use std::borrow::Cow; use std::convert::{Infallible, TryFrom}; use std::pin::Pin; use std::task::{Context, Poll}; pin_project! { /// A body that consists of a single chunk. #[derive(Clone, Copy, Debug)] pub struct Full { data: Option, } } impl Full where D: Buf, { /// Create a new `Full`. pub fn new(data: D) -> Self { let data = if data.has_remaining() { Some(data) } else { None }; Full { data } } } impl Body for Full where D: Buf, { type Data = D; type Error = Infallible; fn poll_frame( mut self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { Poll::Ready(self.data.take().map(|d| Ok(Frame::data(d)))) } fn is_end_stream(&self) -> bool { self.data.is_none() } fn size_hint(&self) -> SizeHint { self.data .as_ref() .map(|data| SizeHint::with_exact(u64::try_from(data.remaining()).unwrap())) .unwrap_or_else(|| SizeHint::with_exact(0)) } } impl Default for Full where D: Buf, { /// Create an empty `Full`. fn default() -> Self { Full { data: None } } } impl From for Full where D: Buf + From, { fn from(bytes: Bytes) -> Self { Full::new(D::from(bytes)) } } impl From> for Full where D: Buf + From>, { fn from(vec: Vec) -> Self { Full::new(D::from(vec)) } } impl From<&'static [u8]> for Full where D: Buf + From<&'static [u8]>, { fn from(slice: &'static [u8]) -> Self { Full::new(D::from(slice)) } } impl From> for Full where D: Buf + From<&'static B> + From, B: ToOwned + ?Sized, { fn from(cow: Cow<'static, B>) -> Self { match cow { Cow::Borrowed(b) => Full::new(D::from(b)), Cow::Owned(o) => Full::new(D::from(o)), } } } impl From for Full where D: Buf + From, { fn from(s: String) -> Self { Full::new(D::from(s)) } } impl From<&'static str> for Full where D: Buf + From<&'static str>, { fn from(slice: &'static str) -> Self { Full::new(D::from(slice)) } } #[cfg(test)] mod tests { use super::*; use crate::BodyExt; #[tokio::test] async fn full_returns_some() { let mut full = Full::new(&b"hello"[..]); assert_eq!(full.size_hint().exact(), Some(b"hello".len() as u64)); assert_eq!( full.frame().await.unwrap().unwrap().into_data().unwrap(), &b"hello"[..] ); assert!(full.frame().await.is_none()); } #[tokio::test] async fn empty_full_returns_none() { assert!(Full::<&[u8]>::default().frame().await.is_none()); assert!(Full::new(&b""[..]).frame().await.is_none()); } } http-body-util-0.1.2/src/lib.rs000064400000000000000000000074121046102023000144200ustar 00000000000000#![deny(missing_debug_implementations, missing_docs, unreachable_pub)] #![cfg_attr(test, deny(warnings))] //! Utilities for [`http_body::Body`]. //! //! [`BodyExt`] adds extensions to the common trait. //! //! [`Empty`] and [`Full`] provide simple implementations. mod collected; pub mod combinators; mod either; mod empty; mod full; mod limited; mod stream; mod util; use self::combinators::{BoxBody, MapErr, MapFrame, UnsyncBoxBody}; pub use self::collected::Collected; pub use self::either::Either; pub use self::empty::Empty; pub use self::full::Full; pub use self::limited::{LengthLimitError, Limited}; pub use self::stream::{BodyDataStream, BodyStream, StreamBody}; /// An extension trait for [`http_body::Body`] adding various combinators and adapters pub trait BodyExt: http_body::Body { /// Returns a future that resolves to the next [`Frame`], if any. /// /// [`Frame`]: combinators::Frame fn frame(&mut self) -> combinators::Frame<'_, Self> where Self: Unpin, { combinators::Frame(self) } /// Maps this body's frame to a different kind. fn map_frame(self, f: F) -> MapFrame where Self: Sized, F: FnMut(http_body::Frame) -> http_body::Frame, B: bytes::Buf, { MapFrame::new(self, f) } /// Maps this body's error value to a different value. fn map_err(self, f: F) -> MapErr where Self: Sized, F: FnMut(Self::Error) -> E, { MapErr::new(self, f) } /// Turn this body into a boxed trait object. fn boxed(self) -> BoxBody where Self: Sized + Send + Sync + 'static, { BoxBody::new(self) } /// Turn this body into a boxed trait object that is !Sync. fn boxed_unsync(self) -> UnsyncBoxBody where Self: Sized + Send + 'static, { UnsyncBoxBody::new(self) } /// Turn this body into [`Collected`] body which will collect all the DATA frames /// and trailers. fn collect(self) -> combinators::Collect where Self: Sized, { combinators::Collect { body: self, collected: Some(crate::Collected::default()), } } /// Add trailers to the body. /// /// The trailers will be sent when all previous frames have been sent and the `trailers` future /// resolves. /// /// # Example /// /// ``` /// use http::HeaderMap; /// use http_body_util::{Full, BodyExt}; /// use bytes::Bytes; /// /// # #[tokio::main] /// async fn main() { /// let (tx, rx) = tokio::sync::oneshot::channel::(); /// /// let body = Full::::from("Hello, World!") /// // add trailers via a future /// .with_trailers(async move { /// match rx.await { /// Ok(trailers) => Some(Ok(trailers)), /// Err(_err) => None, /// } /// }); /// /// // compute the trailers in the background /// tokio::spawn(async move { /// let _ = tx.send(compute_trailers().await); /// }); /// /// async fn compute_trailers() -> HeaderMap { /// // ... /// # unimplemented!() /// } /// # } /// ``` fn with_trailers(self, trailers: F) -> combinators::WithTrailers where Self: Sized, F: std::future::Future>>, { combinators::WithTrailers::new(self, trailers) } /// Turn this body into [`BodyDataStream`]. fn into_data_stream(self) -> BodyDataStream where Self: Sized, { BodyDataStream::new(self) } } impl BodyExt for T where T: http_body::Body {} http-body-util-0.1.2/src/limited.rs000064400000000000000000000175011046102023000153010ustar 00000000000000use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use pin_project_lite::pin_project; use std::error::Error; use std::fmt; use std::pin::Pin; use std::task::{Context, Poll}; pin_project! { /// A length limited body. /// /// This body will return an error if more than the configured number /// of bytes are returned on polling the wrapped body. #[derive(Clone, Copy, Debug)] pub struct Limited { remaining: usize, #[pin] inner: B, } } impl Limited { /// Create a new `Limited`. pub fn new(inner: B, limit: usize) -> Self { Self { remaining: limit, inner, } } } impl Body for Limited where B: Body, B::Error: Into>, { type Data = B::Data; type Error = Box; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { let this = self.project(); let res = match this.inner.poll_frame(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(None) => None, Poll::Ready(Some(Ok(frame))) => { if let Some(data) = frame.data_ref() { if data.remaining() > *this.remaining { *this.remaining = 0; Some(Err(LengthLimitError.into())) } else { *this.remaining -= data.remaining(); Some(Ok(frame)) } } else { Some(Ok(frame)) } } Poll::Ready(Some(Err(err))) => Some(Err(err.into())), }; Poll::Ready(res) } fn is_end_stream(&self) -> bool { self.inner.is_end_stream() } fn size_hint(&self) -> SizeHint { use std::convert::TryFrom; match u64::try_from(self.remaining) { Ok(n) => { let mut hint = self.inner.size_hint(); if hint.lower() >= n { hint.set_exact(n) } else if let Some(max) = hint.upper() { hint.set_upper(n.min(max)) } else { hint.set_upper(n) } hint } Err(_) => self.inner.size_hint(), } } } /// An error returned when body length exceeds the configured limit. #[derive(Debug)] #[non_exhaustive] pub struct LengthLimitError; impl fmt::Display for LengthLimitError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("length limit exceeded") } } impl Error for LengthLimitError {} #[cfg(test)] mod tests { use super::*; use crate::{BodyExt, Full, StreamBody}; use bytes::Bytes; use std::convert::Infallible; #[tokio::test] async fn read_for_body_under_limit_returns_data() { const DATA: &[u8] = b"testing"; let inner = Full::new(Bytes::from(DATA)); let body = &mut Limited::new(inner, 8); let mut hint = SizeHint::new(); hint.set_upper(7); assert_eq!(body.size_hint().upper(), hint.upper()); let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); assert_eq!(data, DATA); hint.set_upper(0); assert_eq!(body.size_hint().upper(), hint.upper()); assert!(body.frame().await.is_none()); } #[tokio::test] async fn read_for_body_over_limit_returns_error() { const DATA: &[u8] = b"testing a string that is too long"; let inner = Full::new(Bytes::from(DATA)); let body = &mut Limited::new(inner, 8); let mut hint = SizeHint::new(); hint.set_upper(8); assert_eq!(body.size_hint().upper(), hint.upper()); let error = body.frame().await.unwrap().unwrap_err(); assert!(matches!(error.downcast_ref(), Some(LengthLimitError))); } fn body_from_iter(into_iter: I) -> impl Body where I: IntoIterator, I::Item: Into + 'static, I::IntoIter: Send + 'static, { let iter = into_iter .into_iter() .map(|it| Frame::data(it.into())) .map(Ok::<_, Infallible>); StreamBody::new(futures_util::stream::iter(iter)) } #[tokio::test] async fn read_for_chunked_body_around_limit_returns_first_chunk_but_returns_error_on_over_limit_chunk( ) { const DATA: [&[u8]; 2] = [b"testing ", b"a string that is too long"]; let inner = body_from_iter(DATA); let body = &mut Limited::new(inner, 8); let mut hint = SizeHint::new(); hint.set_upper(8); assert_eq!(body.size_hint().upper(), hint.upper()); let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); assert_eq!(data, DATA[0]); hint.set_upper(0); assert_eq!(body.size_hint().upper(), hint.upper()); let error = body.frame().await.unwrap().unwrap_err(); assert!(matches!(error.downcast_ref(), Some(LengthLimitError))); } #[tokio::test] async fn read_for_chunked_body_over_limit_on_first_chunk_returns_error() { const DATA: [&[u8]; 2] = [b"testing a string", b" that is too long"]; let inner = body_from_iter(DATA); let body = &mut Limited::new(inner, 8); let mut hint = SizeHint::new(); hint.set_upper(8); assert_eq!(body.size_hint().upper(), hint.upper()); let error = body.frame().await.unwrap().unwrap_err(); assert!(matches!(error.downcast_ref(), Some(LengthLimitError))); } #[tokio::test] async fn read_for_chunked_body_under_limit_is_okay() { const DATA: [&[u8]; 2] = [b"test", b"ing!"]; let inner = body_from_iter(DATA); let body = &mut Limited::new(inner, 8); let mut hint = SizeHint::new(); hint.set_upper(8); assert_eq!(body.size_hint().upper(), hint.upper()); let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); assert_eq!(data, DATA[0]); hint.set_upper(4); assert_eq!(body.size_hint().upper(), hint.upper()); let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); assert_eq!(data, DATA[1]); hint.set_upper(0); assert_eq!(body.size_hint().upper(), hint.upper()); assert!(body.frame().await.is_none()); } struct SomeTrailers; impl Body for SomeTrailers { type Data = Bytes; type Error = Infallible; fn poll_frame( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { Poll::Ready(Some(Ok(Frame::trailers(http::HeaderMap::new())))) } } #[tokio::test] async fn read_for_trailers_propagates_inner_trailers() { let body = &mut Limited::new(SomeTrailers, 8); let frame = body.frame().await.unwrap().unwrap(); assert!(frame.is_trailers()); } #[derive(Debug)] struct ErrorBodyError; impl fmt::Display for ErrorBodyError { fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { Ok(()) } } impl Error for ErrorBodyError {} struct ErrorBody; impl Body for ErrorBody { type Data = &'static [u8]; type Error = ErrorBodyError; fn poll_frame( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { Poll::Ready(Some(Err(ErrorBodyError))) } } #[tokio::test] async fn read_for_body_returning_error_propagates_error() { let body = &mut Limited::new(ErrorBody, 8); let error = body.frame().await.unwrap().unwrap_err(); assert!(matches!(error.downcast_ref(), Some(ErrorBodyError))); } } http-body-util-0.1.2/src/stream.rs000064400000000000000000000131771046102023000151520ustar 00000000000000use bytes::Buf; use futures_util::{ready, stream::Stream}; use http_body::{Body, Frame}; use pin_project_lite::pin_project; use std::{ pin::Pin, task::{Context, Poll}, }; pin_project! { /// A body created from a [`Stream`]. #[derive(Clone, Copy, Debug)] pub struct StreamBody { #[pin] stream: S, } } impl StreamBody { /// Create a new `StreamBody`. pub fn new(stream: S) -> Self { Self { stream } } } impl Body for StreamBody where S: Stream, E>>, D: Buf, { type Data = D; type Error = E; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { match self.project().stream.poll_next(cx) { Poll::Ready(Some(result)) => Poll::Ready(Some(result)), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, } } } impl Stream for StreamBody { type Item = S::Item; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().stream.poll_next(cx) } fn size_hint(&self) -> (usize, Option) { self.stream.size_hint() } } pin_project! { /// A stream created from a [`Body`]. #[derive(Clone, Copy, Debug)] pub struct BodyStream { #[pin] body: B, } } impl BodyStream { /// Create a new `BodyStream`. pub fn new(body: B) -> Self { Self { body } } } impl Body for BodyStream where B: Body, { type Data = B::Data; type Error = B::Error; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { self.project().body.poll_frame(cx) } } impl Stream for BodyStream where B: Body, { type Item = Result, B::Error>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.project().body.poll_frame(cx) { Poll::Ready(Some(frame)) => Poll::Ready(Some(frame)), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, } } } pin_project! { /// A data stream created from a [`Body`]. #[derive(Clone, Copy, Debug)] pub struct BodyDataStream { #[pin] body: B, } } impl BodyDataStream { /// Create a new `BodyDataStream` pub fn new(body: B) -> Self { Self { body } } } impl Stream for BodyDataStream where B: Body, { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { return match ready!(self.as_mut().project().body.poll_frame(cx)) { Some(Ok(frame)) => match frame.into_data() { Ok(bytes) => Poll::Ready(Some(Ok(bytes))), Err(_) => continue, }, Some(Err(err)) => Poll::Ready(Some(Err(err))), None => Poll::Ready(None), }; } } } #[cfg(test)] mod tests { use crate::{BodyExt, BodyStream, StreamBody}; use bytes::Bytes; use futures_util::StreamExt; use http_body::Frame; use std::convert::Infallible; #[tokio::test] async fn body_from_stream() { let chunks: Vec> = vec![ Ok(Frame::data(Bytes::from(vec![1]))), Ok(Frame::data(Bytes::from(vec![2]))), Ok(Frame::data(Bytes::from(vec![3]))), ]; let stream = futures_util::stream::iter(chunks); let mut body = StreamBody::new(stream); assert_eq!( body.frame() .await .unwrap() .unwrap() .into_data() .unwrap() .as_ref(), [1] ); assert_eq!( body.frame() .await .unwrap() .unwrap() .into_data() .unwrap() .as_ref(), [2] ); assert_eq!( body.frame() .await .unwrap() .unwrap() .into_data() .unwrap() .as_ref(), [3] ); assert!(body.frame().await.is_none()); } #[tokio::test] async fn stream_from_body() { let chunks: Vec> = vec![ Ok(Frame::data(Bytes::from(vec![1]))), Ok(Frame::data(Bytes::from(vec![2]))), Ok(Frame::data(Bytes::from(vec![3]))), ]; let stream = futures_util::stream::iter(chunks); let body = StreamBody::new(stream); let mut stream = BodyStream::new(body); assert_eq!( stream .next() .await .unwrap() .unwrap() .into_data() .unwrap() .as_ref(), [1] ); assert_eq!( stream .next() .await .unwrap() .unwrap() .into_data() .unwrap() .as_ref(), [2] ); assert_eq!( stream .next() .await .unwrap() .unwrap() .into_data() .unwrap() .as_ref(), [3] ); assert!(stream.next().await.is_none()); } } http-body-util-0.1.2/src/util.rs000064400000000000000000000104651046102023000146310ustar 00000000000000use std::collections::VecDeque; use std::io::IoSlice; use bytes::{Buf, BufMut, Bytes, BytesMut}; #[derive(Debug)] pub(crate) struct BufList { bufs: VecDeque, } impl BufList { #[inline] pub(crate) fn push(&mut self, buf: T) { debug_assert!(buf.has_remaining()); self.bufs.push_back(buf); } #[inline] pub(crate) fn pop(&mut self) -> Option { self.bufs.pop_front() } } impl Buf for BufList { #[inline] fn remaining(&self) -> usize { self.bufs.iter().map(|buf| buf.remaining()).sum() } #[inline] fn has_remaining(&self) -> bool { self.bufs.iter().any(|buf| buf.has_remaining()) } #[inline] fn chunk(&self) -> &[u8] { self.bufs.front().map(Buf::chunk).unwrap_or_default() } #[inline] fn advance(&mut self, mut cnt: usize) { while cnt > 0 { { let front = &mut self.bufs[0]; let rem = front.remaining(); if rem > cnt { front.advance(cnt); return; } else { front.advance(rem); cnt -= rem; } } self.bufs.pop_front(); } } #[inline] fn chunks_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize { if dst.is_empty() { return 0; } let mut vecs = 0; for buf in &self.bufs { vecs += buf.chunks_vectored(&mut dst[vecs..]); if vecs == dst.len() { break; } } vecs } #[inline] fn copy_to_bytes(&mut self, len: usize) -> Bytes { // Our inner buffer may have an optimized version of copy_to_bytes, and if the whole // request can be fulfilled by the front buffer, we can take advantage. match self.bufs.front_mut() { Some(front) if front.remaining() == len => { let b = front.copy_to_bytes(len); self.bufs.pop_front(); b } Some(front) if front.remaining() > len => front.copy_to_bytes(len), _ => { let rem = self.remaining(); assert!(len <= rem, "`len` greater than remaining"); let mut bm = BytesMut::with_capacity(len); if rem == len { // .take() costs a lot more, so skip it if we don't need it bm.put(self); } else { bm.put(self.take(len)); } bm.freeze() } } } } impl Default for BufList { fn default() -> Self { BufList { bufs: VecDeque::new(), } } } #[cfg(test)] mod tests { use std::ptr; use super::*; fn hello_world_buf() -> BufList { BufList { bufs: vec![Bytes::from("Hello"), Bytes::from(" "), Bytes::from("World")].into(), } } #[test] fn to_bytes_shorter() { let mut bufs = hello_world_buf(); let old_ptr = bufs.chunk().as_ptr(); let start = bufs.copy_to_bytes(4); assert_eq!(start, "Hell"); assert!(ptr::eq(old_ptr, start.as_ptr())); assert_eq!(bufs.chunk(), b"o"); assert!(ptr::eq(old_ptr.wrapping_add(4), bufs.chunk().as_ptr())); assert_eq!(bufs.remaining(), 7); } #[test] fn to_bytes_eq() { let mut bufs = hello_world_buf(); let old_ptr = bufs.chunk().as_ptr(); let start = bufs.copy_to_bytes(5); assert_eq!(start, "Hello"); assert!(ptr::eq(old_ptr, start.as_ptr())); assert_eq!(bufs.chunk(), b" "); assert_eq!(bufs.remaining(), 6); } #[test] fn to_bytes_longer() { let mut bufs = hello_world_buf(); let start = bufs.copy_to_bytes(7); assert_eq!(start, "Hello W"); assert_eq!(bufs.remaining(), 4); } #[test] fn one_long_buf_to_bytes() { let mut buf = BufList::default(); buf.push(b"Hello World" as &[_]); assert_eq!(buf.copy_to_bytes(5), "Hello"); assert_eq!(buf.chunk(), b" World"); } #[test] #[should_panic(expected = "`len` greater than remaining")] fn buf_to_bytes_too_many() { hello_world_buf().copy_to_bytes(42); } }