From 9ace925aaa0000ce470411b1cc09edadf70463a6 Mon Sep 17 00:00:00 2001 From: Daniel Olano Date: Fri, 29 Jul 2022 00:03:18 +0200 Subject: [PATCH] Turn crate into no_std using core + alloc Extensions are also made an optional feature to not require depending on HashMap --- Cargo.toml | 8 +++ src/byte_str.rs | 4 +- src/convert.rs | 8 +-- src/error.rs | 35 ++++++++--- src/extensions.rs | 26 ++++----- src/header/map.rs | 123 ++++++++++++++++++++------------------- src/header/name.rs | 134 ++++++++++++++++++++++++++++--------------- src/header/value.rs | 32 +++++++---- src/lib.rs | 8 ++- src/method.rs | 25 ++++---- src/request.rs | 34 +++++------ src/response.rs | 27 +++++---- src/status.rs | 24 ++++---- src/uri/authority.rs | 13 +++-- src/uri/builder.rs | 5 +- src/uri/mod.rs | 18 +++--- src/uri/path.rs | 32 +++++------ src/uri/port.rs | 4 +- src/uri/scheme.rs | 12 ++-- src/uri/tests.rs | 6 +- src/version.rs | 2 +- 21 files changed, 338 insertions(+), 242 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6fe4b13c..09a2c983 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,14 @@ rust-version = "1.49.0" bytes = "1" fnv = "1.0.5" itoa = "1" +ahash = { version = "0.7", default-features = false } +hashbrown = { version = "0.12", optional = true } + +[features] +default = ["std", "extensions"] +std = [] +hashmap = ["hashbrown"] +extensions = ["hashmap"] [dev-dependencies] indexmap = "<=1.8" diff --git a/src/byte_str.rs b/src/byte_str.rs index e83ff75d..ea386d06 100644 --- a/src/byte_str.rs +++ b/src/byte_str.rs @@ -1,6 +1,6 @@ +use alloc::string::String; use bytes::Bytes; - -use std::{ops, str}; +use core::{ops, str}; #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub(crate) struct ByteStr { diff --git a/src/convert.rs b/src/convert.rs index aeee2219..633c0568 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,11 +1,11 @@ macro_rules! if_downcast_into { - ($in_ty:ty, $out_ty:ty, $val:ident, $body:expr) => ({ - if std::any::TypeId::of::<$in_ty>() == std::any::TypeId::of::<$out_ty>() { + ($in_ty:ty, $out_ty:ty, $val:ident, $body:expr) => {{ + if core::any::TypeId::of::<$in_ty>() == core::any::TypeId::of::<$out_ty>() { // Store the value in an `Option` so we can `take` // it after casting to `&mut dyn Any`. let mut slot = Some($val); // Re-write the `$val` ident with the downcasted value. - let $val = (&mut slot as &mut dyn std::any::Any) + let $val = (&mut slot as &mut dyn core::any::Any) .downcast_mut::>() .unwrap() .take() @@ -13,5 +13,5 @@ macro_rules! if_downcast_into { // Run the $body in scope of the replaced val. $body } - }) + }}; } diff --git a/src/error.rs b/src/error.rs index ba690841..e8cc904f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ +use core::fmt; +use core::result; +#[cfg(feature = "std")] use std::error; -use std::fmt; -use std::result; use crate::header; use crate::method; @@ -31,19 +32,38 @@ enum ErrorKind { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ErrorKind::*; + let e: &dyn fmt::Debug = match self.inner { + StatusCode(ref e) => e, + Method(ref e) => e, + Uri(ref e) => e, + UriParts(ref e) => e, + HeaderName(ref e) => e, + HeaderValue(ref e) => e, + }; f.debug_tuple("http::Error") // Skip the noise of the ErrorKind enum - .field(&self.get_ref()) + .field(e) .finish() } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.get_ref(), f) + use ErrorKind::*; + let e: &dyn fmt::Display = match self.inner { + StatusCode(ref e) => e, + Method(ref e) => e, + Uri(ref e) => e, + UriParts(ref e) => e, + HeaderName(ref e) => e, + HeaderValue(ref e) => e, + }; + fmt::Display::fmt(e, f) } } +#[cfg(feature = "std")] impl Error { /// Return true if the underlying error has the same type as T. pub fn is(&self) -> bool { @@ -65,6 +85,7 @@ impl Error { } } +#[cfg(feature = "std")] impl error::Error for Error { // Return any available cause from the inner error. Note the inner error is // not itself the cause. @@ -121,13 +142,13 @@ impl From for Error { } } -impl From for Error { - fn from(err: std::convert::Infallible) -> Error { +impl From for Error { + fn from(err: core::convert::Infallible) -> Error { match err {} } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use super::*; diff --git a/src/extensions.rs b/src/extensions.rs index 7e815df7..412836f2 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,7 +1,9 @@ -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::fmt; -use std::hash::{BuildHasherDefault, Hasher}; +use core::any::{Any, TypeId}; +use core::fmt; +use core::hash::{BuildHasherDefault, Hasher}; +use hashbrown::HashMap; + +use alloc::boxed::Box; type AnyMap = HashMap, BuildHasherDefault>; @@ -166,9 +168,7 @@ impl Extensions { /// ``` #[inline] pub fn is_empty(&self) -> bool { - self.map - .as_ref() - .map_or(true, |map| map.is_empty()) + self.map.as_ref().map_or(true, |map| map.is_empty()) } /// Get the numer of extensions available. @@ -184,28 +184,26 @@ impl Extensions { /// ``` #[inline] pub fn len(&self) -> usize { - self.map - .as_ref() - .map_or(0, |map| map.len()) + self.map.as_ref().map_or(0, |map| map.len()) } /// Extends `self` with another `Extensions`. /// /// If an instance of a specific type exists in both, the one in `self` is overwritten with the /// one from `other`. - /// + /// /// # Example - /// + /// /// ``` /// # use http::Extensions; /// let mut ext_a = Extensions::new(); /// ext_a.insert(8u8); /// ext_a.insert(16u16); - /// + /// /// let mut ext_b = Extensions::new(); /// ext_b.insert(4u8); /// ext_b.insert("hello"); - /// + /// /// ext_a.extend(ext_b); /// assert_eq!(ext_a.len(), 3); /// assert_eq!(ext_a.get::(), Some(&4u8)); diff --git a/src/header/map.rs b/src/header/map.rs index 13cbc3a3..ad8471ab 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -1,15 +1,17 @@ -use std::collections::HashMap; -use std::collections::hash_map::RandomState; -use std::convert::TryFrom; -use std::hash::{BuildHasher, Hash, Hasher}; -use std::iter::{FromIterator, FusedIterator}; -use std::marker::PhantomData; -use std::{fmt, mem, ops, ptr, vec}; - -use crate::Error; +use ahash::RandomState; +use alloc::vec; +use alloc::{boxed::Box, vec::Vec}; +#[cfg(feature = "hashmap")] +use core::convert::TryFrom; +use core::hash::{BuildHasher, Hash, Hasher}; +use core::iter::{FromIterator, FusedIterator}; +use core::marker::PhantomData; +use core::{fmt, mem, ops, ptr}; +#[cfg(feature = "hashmap")] +use hashbrown::HashMap; -use super::HeaderValue; use super::name::{HdrName, HeaderName, InvalidHeaderName}; +use super::HeaderValue; pub use self::as_header_name::AsHeaderName; pub use self::into_header_name::IntoHeaderName; @@ -114,7 +116,7 @@ pub struct IntoIter { /// associated value. #[derive(Debug)] pub struct Keys<'a, T> { - inner: ::std::slice::Iter<'a, Bucket>, + inner: ::core::slice::Iter<'a, Bucket>, } /// `HeaderMap` value iterator. @@ -207,7 +209,7 @@ pub struct ValueIterMut<'a, T> { #[derive(Debug)] pub struct ValueDrain<'a, T> { first: Option, - next: Option<::std::vec::IntoIter>, + next: Option<::alloc::vec::IntoIter>, lt: PhantomData<&'a mut HeaderMap>, } @@ -961,7 +963,9 @@ impl HeaderMap { let entries = &mut self.entries[..] as *mut _; let extra_values = &mut self.extra_values as *mut _; let len = self.entries.len(); - unsafe { self.entries.set_len(0); } + unsafe { + self.entries.set_len(0); + } Drain { idx: 0, @@ -992,7 +996,7 @@ impl HeaderMap { } else { ValueIter { map: self, - index: ::std::usize::MAX, + index: ::core::usize::MAX, front: None, back: None, } @@ -1193,10 +1197,8 @@ impl HeaderMap { let raw_links = self.raw_links(); let extra_values = &mut self.extra_values; - let next = links.map(|l| { - drain_all_extra_values(raw_links, extra_values, l.next) - .into_iter() - }); + let next = + links.map(|l| drain_all_extra_values(raw_links, extra_values, l.next).into_iter()); ValueDrain { first: Some(old), @@ -1595,9 +1597,8 @@ impl HeaderMap { fn remove_extra_value( mut raw_links: RawLinks, extra_values: &mut Vec>, - idx: usize) - -> ExtraValue -{ + idx: usize, +) -> ExtraValue { let prev; let next; @@ -1618,8 +1619,7 @@ fn remove_extra_value( (Link::Entry(prev), Link::Extra(next)) => { debug_assert!(raw_links[prev].is_some()); - raw_links[prev].as_mut().unwrap() - .next = next; + raw_links[prev].as_mut().unwrap().next = next; debug_assert!(extra_values.len() > next); extra_values[next].prev = Link::Entry(prev); @@ -1627,8 +1627,7 @@ fn remove_extra_value( (Link::Extra(prev), Link::Entry(next)) => { debug_assert!(raw_links[next].is_some()); - raw_links[next].as_mut().unwrap() - .tail = prev; + raw_links[next].as_mut().unwrap().tail = prev; debug_assert!(extra_values.len() > prev); extra_values[prev].next = Link::Entry(next); @@ -1716,9 +1715,8 @@ fn remove_extra_value( fn drain_all_extra_values( raw_links: RawLinks, extra_values: &mut Vec>, - mut head: usize) - -> Vec -{ + mut head: usize, +) -> Vec { let mut vec = Vec::new(); loop { let extra = remove_extra_value(raw_links, extra_values, head); @@ -1826,28 +1824,34 @@ impl FromIterator<(HeaderName, T)> for HeaderMap { /// Try to convert a `HashMap` into a `HeaderMap`. /// -/// # Examples -/// -/// ``` -/// use std::collections::HashMap; -/// use std::convert::TryInto; -/// use http::HeaderMap; -/// -/// let mut map = HashMap::new(); -/// map.insert("X-Custom-Header".to_string(), "my value".to_string()); -/// -/// let headers: HeaderMap = (&map).try_into().expect("valid headers"); -/// assert_eq!(headers["X-Custom-Header"], "my value"); -/// ``` +#[cfg_attr( + feature = "std", + doc = r##" +# Examples + +``` +use std::collections::HashMap; +use std::convert::TryInto; +use http::HeaderMap; + +let mut map = HashMap::new(); +map.insert("X-Custom-Header".to_string(), "my value".to_string()); + +let headers: HeaderMap = (&map).try_into().expect("valid headers"); +assert_eq!(headers["X-Custom-Header"], "my value"); +``` +"## +)] +#[cfg(feature = "hashmap")] impl<'a, K, V, T> TryFrom<&'a HashMap> for HeaderMap - where - K: Eq + Hash, - HeaderName: TryFrom<&'a K>, - >::Error: Into, - T: TryFrom<&'a V>, - T::Error: Into, +where + K: Eq + Hash, + HeaderName: TryFrom<&'a K>, + >::Error: Into, + T: TryFrom<&'a V>, + T::Error: Into, { - type Error = Error; + type Error = crate::Error; fn try_from(c: &'a HashMap) -> Result { c.into_iter() @@ -2204,9 +2208,7 @@ impl<'a, T> Iterator for Drain<'a, T> { // Remove the extra value let raw_links = RawLinks(self.entries); - let extra = unsafe { - remove_extra_value(raw_links, &mut *self.extra_values, next) - }; + let extra = unsafe { remove_extra_value(raw_links, &mut *self.extra_values, next) }; match extra.next { Link::Extra(idx) => self.next = Some(idx), @@ -2969,10 +2971,9 @@ impl<'a, T> OccupiedEntry<'a, T> { let raw_links = self.map.raw_links(); let extra_values = &mut self.map.extra_values; - let next = self.map.entries[self.index].links.map(|l| { - drain_all_extra_values(raw_links, extra_values, l.next) - .into_iter() - }); + let next = self.map.entries[self.index] + .links + .map(|l| drain_all_extra_values(raw_links, extra_values, l.next).into_iter()); let entry = self.map.remove_found(self.probe, self.index); @@ -3086,7 +3087,7 @@ impl<'a, T> Iterator for ValueDrain<'a, T> { (&Some(_), &Some(ref extras)) => { let (l, u) = extras.size_hint(); (l + 1, u.map(|u| u + 1)) - }, + } // Extras only (&None, &Some(ref extras)) => extras.size_hint(), // No more @@ -3120,17 +3121,13 @@ impl ops::Index for RawLinks { type Output = Option; fn index(&self, idx: usize) -> &Self::Output { - unsafe { - &(*self.0)[idx].links - } + unsafe { &(*self.0)[idx].links } } } impl ops::IndexMut for RawLinks { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { - unsafe { - &mut (*self.0)[idx].links - } + unsafe { &mut (*self.0)[idx].links } } } @@ -3349,6 +3346,8 @@ mod into_header_name { } mod as_header_name { + use alloc::string::String; + use super::{Entry, HdrName, HeaderMap, HeaderName, InvalidHeaderName}; /// A marker trait used to identify values that can be used as search keys diff --git a/src/header/name.rs b/src/header/name.rs index f0eaeb77..d0900d88 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1,13 +1,14 @@ use crate::byte_str::ByteStr; +use alloc::string::String; +use alloc::vec::Vec; use bytes::{Bytes, BytesMut}; -use std::borrow::Borrow; -use std::error::Error; -use std::convert::{TryFrom}; -use std::hash::{Hash, Hasher}; -use std::mem::MaybeUninit; -use std::str::FromStr; -use std::fmt; +use core::borrow::Borrow; +use core::convert::TryFrom; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::mem::MaybeUninit; +use core::str::FromStr; /// Represents an HTTP header field name /// @@ -990,6 +991,7 @@ standard_headers! { /// ``` // HEADER_CHARS maps every byte that is 128 or larger to 0 so everything that is // mapped by HEADER_CHARS, maps to a valid single-byte UTF-8 codepoint. +#[rustfmt::skip] const HEADER_CHARS: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x @@ -1023,6 +1025,7 @@ const HEADER_CHARS: [u8; 256] = [ /// Valid header name characters for HTTP/2.0 and HTTP/3.0 // HEADER_CHARS_H2 maps every byte that is 128 or larger to 0 so everything that is // mapped by HEADER_CHARS_H2, maps to a valid single-byte UTF-8 codepoint. +#[rustfmt::skip] const HEADER_CHARS_H2: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x @@ -1083,11 +1086,11 @@ fn parse_hdr<'a>( } } - - impl<'a> From for HdrName<'a> { fn from(hdr: StandardHeader) -> HdrName<'a> { - HdrName { inner: Repr::Standard(hdr) } + HdrName { + inner: Repr::Standard(hdr), + } } } @@ -1107,7 +1110,7 @@ impl HeaderName { Ok(Custom(val).into()) } Repr::Custom(MaybeLower { buf, lower: false }) => { - use bytes::{BufMut}; + use bytes::BufMut; let mut dst = BytesMut::with_capacity(buf.len()); for b in buf.iter() { @@ -1239,7 +1242,7 @@ impl HeaderName { pub const fn from_static(src: &'static str) -> HeaderName { let name_bytes = src.as_bytes(); if let Some(standard) = StandardHeader::from_bytes(name_bytes) { - return HeaderName{ + return HeaderName { inner: Repr::Standard(standard), }; } @@ -1259,7 +1262,7 @@ impl HeaderName { } HeaderName { - inner: Repr::Custom(Custom(ByteStr::from_static(src))) + inner: Repr::Custom(Custom(ByteStr::from_static(src))), } } @@ -1493,7 +1496,8 @@ impl fmt::Display for InvalidHeaderName { } } -impl Error for InvalidHeaderName {} +#[cfg(feature = "std")] +impl std::error::Error for InvalidHeaderName {} // ===== HdrName ===== @@ -1510,7 +1514,8 @@ impl<'a> HdrName<'a> { } pub fn from_bytes(hdr: &[u8], f: F) -> Result - where F: FnOnce(HdrName<'_>) -> U, + where + F: FnOnce(HdrName<'_>) -> U, { let mut buf = uninit_u8_array(); // Precondition: HEADER_CHARS is a valid table for parse_hdr(). @@ -1624,9 +1629,10 @@ fn eq_ignore_ascii_case(lower: &[u8], s: &[u8]) -> bool { return false; } - lower.iter().zip(s).all(|(a, b)| { - *a == HEADER_CHARS[*b as usize] - }) + lower + .iter() + .zip(s) + .all(|(a, b)| *a == HEADER_CHARS[*b as usize]) } // Utility functions for MaybeUninit<>. These are drawn from unstable API's on @@ -1651,8 +1657,10 @@ unsafe fn slice_assume_init(slice: &[MaybeUninit]) -> &[T] { #[cfg(test)] mod tests { - use super::*; + use alloc::vec; + use self::StandardHeader::Vary; + use super::*; #[test] fn test_bounds() { @@ -1664,11 +1672,15 @@ mod tests { fn test_parse_invalid_headers() { for i in 0..128 { let hdr = vec![1u8; i]; - assert!(HeaderName::from_bytes(&hdr).is_err(), "{} invalid header chars did not fail", i); + assert!( + HeaderName::from_bytes(&hdr).is_err(), + "{} invalid header chars did not fail", + i + ); } } - const ONE_TOO_LONG: &[u8] = &[b'a'; super::super::MAX_HEADER_NAME_LEN+1]; + const ONE_TOO_LONG: &[u8] = &[b'a'; super::super::MAX_HEADER_NAME_LEN + 1]; #[test] fn test_invalid_name_lengths() { @@ -1716,7 +1728,10 @@ mod tests { }), }); - assert_eq!(name.inner, Repr::Custom(Custom(ByteStr::from_static("hello-world")))); + assert_eq!( + name.inner, + Repr::Custom(Custom(ByteStr::from_static("hello-world"))) + ); let name = HeaderName::from(HdrName { inner: Repr::Custom(MaybeLower { @@ -1725,49 +1740,68 @@ mod tests { }), }); - assert_eq!(name.inner, Repr::Custom(Custom(ByteStr::from_static("hello-world")))); + assert_eq!( + name.inner, + Repr::Custom(Custom(ByteStr::from_static("hello-world"))) + ); } #[test] fn test_eq_hdr_name() { use self::StandardHeader::Vary; - let a = HeaderName { inner: Repr::Standard(Vary) }; - let b = HdrName { inner: Repr::Standard(Vary) }; + let a = HeaderName { + inner: Repr::Standard(Vary), + }; + let b = HdrName { + inner: Repr::Standard(Vary), + }; assert_eq!(a, b); - let a = HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static("vaary"))) }; + let a = HeaderName { + inner: Repr::Custom(Custom(ByteStr::from_static("vaary"))), + }; assert_ne!(a, b); - let b = HdrName { inner: Repr::Custom(MaybeLower { - buf: b"vaary", - lower: true, - })}; + let b = HdrName { + inner: Repr::Custom(MaybeLower { + buf: b"vaary", + lower: true, + }), + }; assert_eq!(a, b); - let b = HdrName { inner: Repr::Custom(MaybeLower { - buf: b"vaary", - lower: false, - })}; + let b = HdrName { + inner: Repr::Custom(MaybeLower { + buf: b"vaary", + lower: false, + }), + }; assert_eq!(a, b); - let b = HdrName { inner: Repr::Custom(MaybeLower { - buf: b"VAARY", - lower: false, - })}; + let b = HdrName { + inner: Repr::Custom(MaybeLower { + buf: b"VAARY", + lower: false, + }), + }; assert_eq!(a, b); - let a = HeaderName { inner: Repr::Standard(Vary) }; + let a = HeaderName { + inner: Repr::Standard(Vary), + }; assert_ne!(a, b); } #[test] fn test_from_static_std() { - let a = HeaderName { inner: Repr::Standard(Vary) }; + let a = HeaderName { + inner: Repr::Standard(Vary), + }; let b = HeaderName::from_static("vary"); assert_eq!(a, b); @@ -1791,7 +1825,9 @@ mod tests { // MaybeLower { lower: true } #[test] fn test_from_static_custom_short() { - let a = HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static("customheader"))) }; + let a = HeaderName { + inner: Repr::Custom(Custom(ByteStr::from_static("customheader"))), + }; let b = HeaderName::from_static("customheader"); assert_eq!(a, b); } @@ -1811,11 +1847,13 @@ mod tests { // MaybeLower { lower: false } #[test] fn test_from_static_custom_long() { - let a = HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static( - "longer-than-63--thisheaderislongerthansixtythreecharactersandthushandleddifferent" - ))) }; + let a = HeaderName { + inner: Repr::Custom(Custom(ByteStr::from_static( + "longer-than-63--thisheaderislongerthansixtythreecharactersandthushandleddifferent", + ))), + }; let b = HeaderName::from_static( - "longer-than-63--thisheaderislongerthansixtythreecharactersandthushandleddifferent" + "longer-than-63--thisheaderislongerthansixtythreecharactersandthushandleddifferent", ); assert_eq!(a, b); } @@ -1824,7 +1862,7 @@ mod tests { #[should_panic] fn test_from_static_custom_long_uppercase() { HeaderName::from_static( - "Longer-Than-63--ThisHeaderIsLongerThanSixtyThreeCharactersAndThusHandledDifferent" + "Longer-Than-63--ThisHeaderIsLongerThanSixtyThreeCharactersAndThusHandledDifferent", ); } @@ -1838,7 +1876,9 @@ mod tests { #[test] fn test_from_static_custom_single_char() { - let a = HeaderName { inner: Repr::Custom(Custom(ByteStr::from_static("a"))) }; + let a = HeaderName { + inner: Repr::Custom(Custom(ByteStr::from_static("a"))), + }; let b = HeaderName::from_static("a"); assert_eq!(a, b); } diff --git a/src/header/value.rs b/src/header/value.rs index bf05f16f..bc362246 100644 --- a/src/header/value.rs +++ b/src/header/value.rs @@ -1,10 +1,10 @@ use bytes::{Bytes, BytesMut}; -use std::convert::TryFrom; -use std::error::Error; -use std::fmt::Write; -use std::str::FromStr; -use std::{cmp, fmt, mem, str}; +use alloc::{string::String, vec::Vec}; +use core::convert::TryFrom; +use core::fmt::Write; +use core::str::FromStr; +use core::{cmp, fmt, mem, str}; use crate::header::name::HeaderName; @@ -203,7 +203,6 @@ impl HeaderValue { } } } else { - if_downcast_into!(T, Bytes, src, { return HeaderValue { inner: src, @@ -220,10 +219,13 @@ impl HeaderValue { } fn from_shared(src: Bytes) -> Result { - HeaderValue::try_from_generic(src, std::convert::identity) + HeaderValue::try_from_generic(src, core::convert::identity) } - fn try_from_generic, F: FnOnce(T) -> Bytes>(src: T, into: F) -> Result { + fn try_from_generic, F: FnOnce(T) -> Bytes>( + src: T, + into: F, + ) -> Result { for &b in src.as_ref() { if !is_valid(b) { return Err(InvalidHeaderValue { _priv: () }); @@ -438,11 +440,13 @@ macro_rules! from_integers { #[test] fn $name() { + use alloc::string::ToString; + let n: $t = 55; let val = HeaderValue::from(n); assert_eq!(val, &n.to_string()); - let n = ::std::$t::MAX; + let n = ::core::$t::MAX; let val = HeaderValue::from(n); assert_eq!(val, &n.to_string()); } @@ -603,7 +607,8 @@ impl fmt::Display for InvalidHeaderValue { } } -impl Error for InvalidHeaderValue {} +#[cfg(feature = "std")] +impl std::error::Error for InvalidHeaderValue {} impl fmt::Display for ToStrError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -611,7 +616,8 @@ impl fmt::Display for ToStrError { } } -impl Error for ToStrError {} +#[cfg(feature = "std")] +impl std::error::Error for ToStrError {} // ===== PartialEq / PartialOrd ===== @@ -772,11 +778,13 @@ impl<'a> PartialOrd for &'a str { #[test] fn test_try_from() { - HeaderValue::try_from(vec![127]).unwrap_err(); + HeaderValue::try_from(alloc::vec![127]).unwrap_err(); } #[test] fn test_debug() { + use alloc::format; + let cases = &[ ("hello", "\"hello\""), ("hello \"world\"", "\"hello \\\"world\\\"\""), diff --git a/src/lib.rs b/src/lib.rs index 42118296..3dced6dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![no_std] #![doc(html_root_url = "https://docs.rs/http/0.2.8")] //! A general purpose library of common HTTP types @@ -157,9 +158,12 @@ //! assert_eq!(uri.path(), "/index.html"); //! assert_eq!(uri.query(), None); //! ``` - #![deny(warnings, missing_docs, missing_debug_implementations)] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + #[cfg(test)] #[macro_use] extern crate doc_comment; @@ -180,9 +184,11 @@ pub mod version; mod byte_str; mod error; +#[cfg(feature = "extensions")] mod extensions; pub use crate::error::{Error, Result}; +#[cfg(feature = "extensions")] pub use crate::extensions::Extensions; #[doc(no_inline)] pub use crate::header::{HeaderMap, HeaderName, HeaderValue}; diff --git a/src/method.rs b/src/method.rs index b7b3b357..761cafd9 100644 --- a/src/method.rs +++ b/src/method.rs @@ -15,14 +15,14 @@ //! assert_eq!(Method::POST.as_str(), "POST"); //! ``` +use self::extension::{AllocatedExtension, InlineExtension}; use self::Inner::*; -use self::extension::{InlineExtension, AllocatedExtension}; -use std::convert::AsRef; -use std::error::Error; -use std::str::FromStr; -use std::convert::TryFrom; -use std::{fmt, str}; +use core::convert::AsRef; +use core::convert::TryFrom; +// use std::error::Error; +use core::str::FromStr; +use core::{fmt, str}; /// The Request Method (VERB) /// @@ -67,7 +67,6 @@ enum Inner { ExtensionAllocated(AllocatedExtension), } - impl Method { /// GET pub const GET: Method = Method(Get); @@ -307,11 +306,14 @@ impl fmt::Display for InvalidMethod { } } -impl Error for InvalidMethod {} +#[cfg(feature = "std")] +impl std::error::Error for InvalidMethod {} mod extension { use super::InvalidMethod; - use std::str; + use alloc::vec; + use alloc::{boxed::Box, vec::Vec}; + use core::str; #[derive(Clone, PartialEq, Eq, Hash)] // Invariant: the first self.1 bytes of self.0 are valid UTF-8. @@ -339,7 +341,7 @@ mod extension { let InlineExtension(ref data, len) = self; // Safety: the invariant of InlineExtension ensures that the first // len bytes of data contain valid UTF-8. - unsafe {str::from_utf8_unchecked(&data[..*len as usize])} + unsafe { str::from_utf8_unchecked(&data[..*len as usize]) } } } @@ -357,7 +359,7 @@ mod extension { pub fn as_str(&self) -> &str { // Safety: the invariant of AllocatedExtension ensures that self.0 // contains valid UTF-8. - unsafe {str::from_utf8_unchecked(&self.0)} + unsafe { str::from_utf8_unchecked(&self.0) } } } @@ -376,6 +378,7 @@ mod extension { // Note that this definition means that any &[u8] that consists solely of valid // characters is also valid UTF-8 because the valid method characters are a // subset of the valid 1 byte UTF-8 encoding. + #[rustfmt::skip] const METHOD_CHARS: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // x diff --git a/src/request.rs b/src/request.rs index 2f662e9e..7057c098 100644 --- a/src/request.rs +++ b/src/request.rs @@ -52,14 +52,15 @@ //! } //! ``` -use std::any::Any; -use std::convert::{TryFrom}; -use std::fmt; +use core::convert::TryFrom; +use core::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; use crate::method::Method; use crate::version::Version; -use crate::{Extensions, Result, Uri}; +#[cfg(feature = "extensions")] +use crate::Extensions; +use crate::{Result, Uri}; /// Represents an HTTP request. /// @@ -177,6 +178,7 @@ pub struct Parts { pub headers: HeaderMap, /// The request's extensions + #[cfg(feature = "extensions")] pub extensions: Extensions, _priv: (), @@ -231,7 +233,6 @@ impl Request<()> { where Uri: TryFrom, >::Error: Into, - { Builder::new().method(Method::GET).uri(uri) } @@ -254,7 +255,6 @@ impl Request<()> { where Uri: TryFrom, >::Error: Into, - { Builder::new().method(Method::PUT).uri(uri) } @@ -277,7 +277,6 @@ impl Request<()> { where Uri: TryFrom, >::Error: Into, - { Builder::new().method(Method::POST).uri(uri) } @@ -300,7 +299,6 @@ impl Request<()> { where Uri: TryFrom, >::Error: Into, - { Builder::new().method(Method::DELETE).uri(uri) } @@ -324,7 +322,6 @@ impl Request<()> { where Uri: TryFrom, >::Error: Into, - { Builder::new().method(Method::OPTIONS).uri(uri) } @@ -347,7 +344,6 @@ impl Request<()> { where Uri: TryFrom, >::Error: Into, - { Builder::new().method(Method::HEAD).uri(uri) } @@ -370,7 +366,6 @@ impl Request<()> { where Uri: TryFrom, >::Error: Into, - { Builder::new().method(Method::CONNECT).uri(uri) } @@ -590,6 +585,7 @@ impl Request { /// assert!(request.extensions().get::().is_none()); /// ``` #[inline] + #[cfg(feature = "extensions")] pub fn extensions(&self) -> &Extensions { &self.head.extensions } @@ -606,6 +602,7 @@ impl Request { /// assert_eq!(request.extensions().get(), Some(&"hello")); /// ``` #[inline] + #[cfg(feature = "extensions")] pub fn extensions_mut(&mut self) -> &mut Extensions { &mut self.head.extensions } @@ -722,6 +719,7 @@ impl Parts { uri: Uri::default(), version: Version::default(), headers: HeaderMap::default(), + #[cfg(feature = "extensions")] extensions: Extensions::default(), _priv: (), } @@ -985,9 +983,10 @@ impl Builder { /// assert_eq!(req.extensions().get::<&'static str>(), /// Some(&"My Extension")); /// ``` + #[cfg(feature = "extensions")] pub fn extension(self, extension: T) -> Builder where - T: Any + Send + Sync + 'static, + T: core::any::Any + Send + Sync + 'static, { self.and_then(move |mut head| { head.extensions.insert(extension); @@ -1008,6 +1007,7 @@ impl Builder { /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` + #[cfg(feature = "extensions")] pub fn extensions_ref(&self) -> Option<&Extensions> { self.inner.as_ref().ok().map(|h| &h.extensions) } @@ -1026,6 +1026,7 @@ impl Builder { /// extensions.insert(5u32); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` + #[cfg(feature = "extensions")] pub fn extensions_mut(&mut self) -> Option<&mut Extensions> { self.inner.as_mut().ok().map(|h| &mut h.extensions) } @@ -1051,19 +1052,14 @@ impl Builder { /// .unwrap(); /// ``` pub fn body(self, body: T) -> Result> { - self.inner.map(move |head| { - Request { - head, - body, - } - }) + self.inner.map(move |head| Request { head, body }) } // private fn and_then(self, func: F) -> Self where - F: FnOnce(Parts) -> Result + F: FnOnce(Parts) -> Result, { Builder { inner: self.inner.and_then(func), diff --git a/src/response.rs b/src/response.rs index 7233cdb7..ca8d7510 100644 --- a/src/response.rs +++ b/src/response.rs @@ -61,14 +61,15 @@ //! // ... //! ``` -use std::any::Any; -use std::convert::TryFrom; -use std::fmt; +use core::convert::TryFrom; +use core::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; use crate::status::StatusCode; use crate::version::Version; -use crate::{Extensions, Result}; +#[cfg(feature = "extensions")] +use crate::Extensions; +use crate::Result; /// Represents an HTTP response /// @@ -196,6 +197,7 @@ pub struct Parts { pub headers: HeaderMap, /// The response's extensions + #[cfg(feature = "extensions")] pub extensions: Extensions, _priv: (), @@ -376,6 +378,7 @@ impl Response { /// assert!(response.extensions().get::().is_none()); /// ``` #[inline] + #[cfg(feature = "extensions")] pub fn extensions(&self) -> &Extensions { &self.head.extensions } @@ -392,6 +395,7 @@ impl Response { /// assert_eq!(response.extensions().get(), Some(&"hello")); /// ``` #[inline] + #[cfg(feature = "extensions")] pub fn extensions_mut(&mut self) -> &mut Extensions { &mut self.head.extensions } @@ -507,6 +511,7 @@ impl Parts { status: StatusCode::default(), version: Version::default(), headers: HeaderMap::default(), + #[cfg(feature = "extensions")] extensions: Extensions::default(), _priv: (), } @@ -688,9 +693,10 @@ impl Builder { /// assert_eq!(response.extensions().get::<&'static str>(), /// Some(&"My Extension")); /// ``` + #[cfg(feature = "extensions")] pub fn extension(self, extension: T) -> Builder where - T: Any + Send + Sync + 'static, + T: core::any::Any + Send + Sync + 'static, { self.and_then(move |mut head| { head.extensions.insert(extension); @@ -711,6 +717,7 @@ impl Builder { /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` + #[cfg(feature = "extensions")] pub fn extensions_ref(&self) -> Option<&Extensions> { self.inner.as_ref().ok().map(|h| &h.extensions) } @@ -729,6 +736,7 @@ impl Builder { /// extensions.insert(5u32); /// assert_eq!(extensions.get::(), Some(&5u32)); /// ``` + #[cfg(feature = "extensions")] pub fn extensions_mut(&mut self) -> Option<&mut Extensions> { self.inner.as_mut().ok().map(|h| &mut h.extensions) } @@ -754,19 +762,14 @@ impl Builder { /// .unwrap(); /// ``` pub fn body(self, body: T) -> Result> { - self.inner.map(move |head| { - Response { - head, - body, - } - }) + self.inner.map(move |head| Response { head, body }) } // private fn and_then(self, func: F) -> Self where - F: FnOnce(Parts) -> Result + F: FnOnce(Parts) -> Result, { Builder { inner: self.inner.and_then(func), diff --git a/src/status.rs b/src/status.rs index d98d24c3..7ebc34c3 100644 --- a/src/status.rs +++ b/src/status.rs @@ -14,11 +14,10 @@ //! assert!(StatusCode::OK.is_success()); //! ``` -use std::convert::TryFrom; -use std::num::NonZeroU16; -use std::error::Error; -use std::fmt; -use std::str::FromStr; +use core::convert::TryFrom; +use core::fmt; +use core::num::NonZeroU16; +use core::str::FromStr; /// An HTTP status code (`status-code` in RFC 7230 et al.). /// @@ -140,10 +139,14 @@ impl StatusCode { // ASCII-only, of length 900 * 3 = 2700 bytes #[cfg(debug_assertions)] - { &CODE_DIGITS[offset..offset+3] } + { + &CODE_DIGITS[offset..offset + 3] + } #[cfg(not(debug_assertions))] - unsafe { CODE_DIGITS.get_unchecked(offset..offset+3) } + unsafe { + CODE_DIGITS.get_unchecked(offset..offset + 3) + } } /// Get the standardised `reason-phrase` for this status code. @@ -516,9 +519,7 @@ status_codes! { impl InvalidStatusCode { fn new() -> InvalidStatusCode { - InvalidStatusCode { - _priv: (), - } + InvalidStatusCode { _priv: () } } } @@ -536,7 +537,8 @@ impl fmt::Display for InvalidStatusCode { } } -impl Error for InvalidStatusCode {} +#[cfg(feature = "std")] +impl std::error::Error for InvalidStatusCode {} // A string of packed 3-ASCII-digit status code values for the supported range // of [100, 999] (900 codes, 2700 bytes). diff --git a/src/uri/authority.rs b/src/uri/authority.rs index 7a43bc10..881a54b4 100644 --- a/src/uri/authority.rs +++ b/src/uri/authority.rs @@ -1,8 +1,9 @@ -use std::convert::TryFrom; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; -use std::{cmp, fmt, str}; +use core::convert::TryFrom; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; +use core::{cmp, fmt, str}; +use alloc::{string::String, vec::Vec}; use bytes::Bytes; use super::{ErrorKind, InvalidUri, Port, URI_CHARS}; @@ -527,6 +528,7 @@ where #[cfg(test)] mod tests { use super::*; + use alloc::string::ToString; #[test] fn parse_empty_string_is_error() { @@ -658,8 +660,7 @@ mod tests { let err = Authority::try_from([0xc0u8].as_ref()).unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidUriChar); - let err = Authority::from_shared(Bytes::from_static([0xc0u8].as_ref())) - .unwrap_err(); + let err = Authority::from_shared(Bytes::from_static([0xc0u8].as_ref())).unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidUriChar); } diff --git a/src/uri/builder.rs b/src/uri/builder.rs index 825c0faf..4b00dcea 100644 --- a/src/uri/builder.rs +++ b/src/uri/builder.rs @@ -1,4 +1,4 @@ -use std::convert::{TryFrom, TryInto}; +use core::convert::{TryFrom, TryInto}; use super::{Authority, Parts, PathAndQuery, Scheme}; use crate::Uri; @@ -137,7 +137,6 @@ impl Builder { where F: FnOnce(Parts) -> Result, { - Builder { parts: self.parts.and_then(func), } @@ -155,6 +154,8 @@ impl Default for Builder { #[cfg(test)] mod tests { + use alloc::format; + use super::*; #[test] diff --git a/src/uri/mod.rs b/src/uri/mod.rs index 30be83b5..a85c3f2c 100644 --- a/src/uri/mod.rs +++ b/src/uri/mod.rs @@ -23,14 +23,15 @@ //! ``` use crate::byte_str::ByteStr; -use std::convert::TryFrom; +use core::convert::TryFrom; +use alloc::{boxed::Box, string::String, vec::Vec}; use bytes::Bytes; -use std::error::Error; -use std::hash::{Hash, Hasher}; -use std::str::{self, FromStr}; -use std::{fmt, u16, u8}; +// use std::error::Error; +use core::hash::{Hash, Hasher}; +use core::str::{self, FromStr}; +use core::{fmt, u16, u8}; use self::scheme::Scheme2; @@ -149,6 +150,7 @@ const MAX_LEN: usize = (u16::MAX - 1) as usize; // of this table is that all entries above 127 are invalid. This makes all of the // valid entries a valid single-byte UTF-8 code point. This means that a slice // of such valid entries is valid UTF-8. +#[rustfmt::skip] const URI_CHARS: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x @@ -1084,7 +1086,8 @@ impl fmt::Display for InvalidUri { } } -impl Error for InvalidUri {} +#[cfg(feature = "std")] +impl std::error::Error for InvalidUri {} impl fmt::Display for InvalidUriParts { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -1092,7 +1095,8 @@ impl fmt::Display for InvalidUriParts { } } -impl Error for InvalidUriParts {} +#[cfg(feature = "std")] +impl std::error::Error for InvalidUriParts {} impl Hash for Uri { fn hash(&self, state: &mut H) diff --git a/src/uri/path.rs b/src/uri/path.rs index 9dc9406a..5a316f38 100644 --- a/src/uri/path.rs +++ b/src/uri/path.rs @@ -1,7 +1,8 @@ -use std::convert::TryFrom; -use std::str::FromStr; -use std::{cmp, fmt, str}; +use core::convert::TryFrom; +use core::str::FromStr; +use core::{cmp, fmt, str}; +use alloc::{string::String, vec::Vec}; use bytes::Bytes; use super::{ErrorKind, InvalidUri}; @@ -14,7 +15,7 @@ pub struct PathAndQuery { pub(super) query: u16, } -const NONE: u16 = ::std::u16::MAX; +const NONE: u16 = ::core::u16::MAX; impl PathAndQuery { // Not public while `bytes` is unstable. @@ -43,13 +44,7 @@ impl PathAndQuery { // This is the range of bytes that don't need to be // percent-encoded in the path. If it should have been // percent-encoded, then error. - 0x21 | - 0x24..=0x3B | - 0x3D | - 0x40..=0x5F | - 0x61..=0x7A | - 0x7C | - 0x7E => {}, + 0x21 | 0x24..=0x3B | 0x3D | 0x40..=0x5F | 0x61..=0x7A | 0x7C | 0x7E => {} // These are code points that are supposed to be // percent-encoded in the path but there are clients @@ -60,8 +55,7 @@ impl PathAndQuery { // For reference, those are code points that are used // to send requests with JSON directly embedded in // the URI path. Yes, those things happen for real. - b'"' | - b'{' | b'}' => {}, + b'"' | b'{' | b'}' => {} _ => return Err(ErrorKind::InvalidUriChar.into()), } @@ -76,10 +70,7 @@ impl PathAndQuery { // See https://url.spec.whatwg.org/#query-state // // Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E - 0x21 | - 0x24..=0x3B | - 0x3D | - 0x3F..=0x7E => {}, + 0x21 | 0x24..=0x3B | 0x3D | 0x3F..=0x7E => {} b'#' => { fragment = Some(i); @@ -446,6 +437,8 @@ impl PartialOrd for String { #[cfg(test)] mod tests { + use alloc::{format, string::ToString}; + use super::*; #[test] @@ -549,7 +542,10 @@ mod tests { #[test] fn json_is_fine() { - assert_eq!(r#"/{"bread":"baguette"}"#, pq(r#"/{"bread":"baguette"}"#).path()); + assert_eq!( + r#"/{"bread":"baguette"}"#, + pq(r#"/{"bread":"baguette"}"#).path() + ); } fn pq(s: &str) -> PathAndQuery { diff --git a/src/uri/port.rs b/src/uri/port.rs index 8f5c5f3f..b695c550 100644 --- a/src/uri/port.rs +++ b/src/uri/port.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; use super::{ErrorKind, InvalidUri}; @@ -112,6 +112,8 @@ impl PartialEq> for u16 { #[cfg(test)] mod tests { + use alloc::string::String; + use super::*; #[test] diff --git a/src/uri/scheme.rs b/src/uri/scheme.rs index 682b11ee..600817cf 100644 --- a/src/uri/scheme.rs +++ b/src/uri/scheme.rs @@ -1,7 +1,8 @@ -use std::convert::TryFrom; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; +use alloc::boxed::Box; +use core::convert::TryFrom; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; use bytes::Bytes; @@ -204,6 +205,7 @@ const MAX_SCHEME_LEN: usize = 64; // important characteristic of this table is that all entries above 127 are // invalid. This makes all of the valid entries a valid single-byte UTF-8 code // point. This means that a slice of such valid entries is valid UTF-8. +#[rustfmt::skip] const SCHEME_CHARS: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x @@ -339,6 +341,8 @@ impl From for Scheme { #[cfg(test)] mod test { + use alloc::format; + use super::*; #[test] diff --git a/src/uri/tests.rs b/src/uri/tests.rs index 719cb94e..cb7f7736 100644 --- a/src/uri/tests.rs +++ b/src/uri/tests.rs @@ -1,4 +1,8 @@ -use std::str::FromStr; +use alloc::{ + string::{String, ToString}, + vec, +}; +use core::str::FromStr; use super::{ErrorKind, InvalidUri, Port, Uri, URI_CHARS}; diff --git a/src/version.rs b/src/version.rs index d8b71306..436ca7df 100644 --- a/src/version.rs +++ b/src/version.rs @@ -19,7 +19,7 @@ //! println!("{:?}", http2); //! ``` -use std::fmt; +use core::fmt; /// Represents a version of the HTTP spec. #[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash)]