diff --git a/src/byte_str.rs b/src/byte_str.rs index 181ced9e..90872ecb 100644 --- a/src/byte_str.rs +++ b/src/byte_str.rs @@ -31,7 +31,7 @@ impl ByteStr { /// /// ## Safety /// `bytes` must contain valid UTF-8. In a release build it is undefined - /// behaviour to call this with `bytes` that is not valid UTF-8. + /// behavior to call this with `bytes` that is not valid UTF-8. pub unsafe fn from_utf8_unchecked(bytes: Bytes) -> ByteStr { if cfg!(debug_assertions) { match str::from_utf8(&bytes) { diff --git a/src/extensions.rs b/src/extensions.rs index 92c06f21..f8bf6fe2 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -215,7 +215,7 @@ impl Extensions { self.map.as_ref().map_or(true, |map| map.is_empty()) } - /// Get the numer of extensions available. + /// Get the number of extensions available. /// /// # Example /// diff --git a/src/header/map.rs b/src/header/map.rs index a668b311..7610175b 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -2020,7 +2020,7 @@ impl FromIterator<(HeaderName, T)> for HeaderMap { /// let headers: HeaderMap = (&map).try_into().expect("valid headers"); /// assert_eq!(headers["X-Custom-Header"], "my value"); /// ``` -impl<'a, K, V, T> TryFrom<&'a HashMap> for HeaderMap +impl<'a, K, V, S, T> TryFrom<&'a HashMap> for HeaderMap where K: Eq + Hash, HeaderName: TryFrom<&'a K>, @@ -2030,7 +2030,7 @@ where { type Error = Error; - fn try_from(c: &'a HashMap) -> Result { + fn try_from(c: &'a HashMap) -> Result { c.iter() .map(|(k, v)| -> crate::Result<(HeaderName, T)> { let name = TryFrom::try_from(k).map_err(Into::into)?; diff --git a/src/header/name.rs b/src/header/name.rs index 41320262..3d563f4e 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -676,7 +676,10 @@ standard_headers! { /// document. (IfUnmodifiedSince, IF_UNMODIFIED_SINCE, b"if-unmodified-since"); - /// Content-Types that are acceptable for the response. + /// The Last-Modified header contains the date and time when the origin believes + /// the resource was last modified. + /// + /// The value is a valid Date/Time string defined in [RFC9910](https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.7) (LastModified, LAST_MODIFIED, b"last-modified"); /// Allows the server to point an interested client to another resource @@ -1008,7 +1011,7 @@ const HEADER_CHARS: [u8; 256] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x - 0, 0, 0, b'!', b'"', b'#', b'$', b'%', b'&', b'\'', // 3x + 0, 0, 0, b'!', 0, b'#', b'$', b'%', b'&', b'\'', // 3x 0, 0, b'*', b'+', 0, b'-', b'.', 0, b'0', b'1', // 4x b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0, 0, // 5x 0, 0, 0, 0, 0, b'a', b'b', b'c', b'd', b'e', // 6x @@ -1656,13 +1659,13 @@ const SCRATCH_BUF_OVERFLOW: usize = SCRATCH_BUF_SIZE + 1; fn uninit_u8_array() -> [MaybeUninit; SCRATCH_BUF_SIZE] { let arr = MaybeUninit::<[MaybeUninit; SCRATCH_BUF_SIZE]>::uninit(); // Safety: assume_init() is claiming that an array of MaybeUninit<> - // has been initilized, but MaybeUninit<>'s do not require initilizaton. + // has been initialized, but MaybeUninit<>'s do not require initialization. unsafe { arr.assume_init() } } -// Assuming all the elements are initilized, get a slice of them. +// Assuming all the elements are initialized, get a slice of them. // -// Safety: All elements of `slice` must be initilized to prevent +// Safety: All elements of `slice` must be initialized to prevent // undefined behavior. unsafe fn slice_assume_init(slice: &[MaybeUninit]) -> &[T] { &*(slice as *const [MaybeUninit] as *const [T]) diff --git a/src/lib.rs b/src/lib.rs index 65d98721..0ab5bdfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,16 +6,13 @@ //! Notably you'll find `Uri` for what a [`Request`] is requesting, a [`Method`] //! for how it's being requested, a [`StatusCode`] for what sort of response came //! back, a [`Version`] for how this was communicated, and -//! [`HeaderName`][header::HeaderName]/[`HeaderValue`][header::HeaderName] definitions to get grouped in a [`HeaderMap`] to +//! [`HeaderName`]/[`HeaderValue`] definitions to get grouped in a [`HeaderMap`] to //! work with request/response headers. //! //! You will notably *not* find an implementation of sending requests or //! spinning up a server in this crate. It's intended that this crate is the //! "standard library" for HTTP clients and servers without dictating any -//! particular implementation. Note that this crate is still early on in its -//! lifecycle so the support libraries that integrate with the `http` crate are -//! a work in progress! Stay tuned and we'll be sure to highlight crates here -//! in the future. +//! particular implementation. //! //! ## Requests and Responses //! @@ -87,10 +84,10 @@ //! Accept: text/html //! ``` //! -//! Then `"Accept"` is a [`HeaderName`][header::HeaderName] while `"text/html"` is a [`HeaderValue`][header::HeaderValue]. +//! Then `"Accept"` is a [`HeaderName`] while `"text/html"` is a [`HeaderValue`]. //! Each of these is a dedicated type to allow for a number of interesting //! optimizations and to also encode the static guarantees of each type. For -//! example a [`HeaderName`][header::HeaderName] is always a valid `&str`, but a [`HeaderValue`] may +//! example a [`HeaderName`] is always a valid `&str`, but a [`HeaderValue`] may //! not be valid UTF-8. //! //! The most common header names are already defined for you as constant values @@ -134,7 +131,7 @@ //! Most HTTP requests and responses tend to come with more than one header, so //! it's not too useful to just work with names and values only! This crate also //! provides a [`HeaderMap`] type which is a specialized hash map for keys as -//! [`HeaderName`][header::HeaderName] and generic values. This type, like header names, is optimized +//! [`HeaderName`] and generic values. This type, like header names, is optimized //! for common usage but should continue to scale with your needs over time. //! //! # URIs diff --git a/src/method.rs b/src/method.rs index 94e4d4ec..7b4584ab 100644 --- a/src/method.rs +++ b/src/method.rs @@ -123,7 +123,7 @@ impl Method { _ => Method::extension_inline(src), }, _ => { - if src.len() < InlineExtension::MAX { + if src.len() <= InlineExtension::MAX { Method::extension_inline(src) } else { let allocated = AllocatedExtension::new(src)?; @@ -356,7 +356,7 @@ mod extension { } } - // From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can + // From the RFC 9110 HTTP Semantics, section 9.1, the HTTP method is case-sensitive and can // contain the following characters: // // ``` @@ -366,7 +366,7 @@ mod extension { // "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA // ``` // - // https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method + // https://datatracker.ietf.org/doc/html/rfc9110#section-9.1 // // 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 @@ -377,7 +377,7 @@ mod extension { b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // x b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 1x b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 2x - b'\0', b'\0', b'\0', b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 3x + b'\0', b'\0', b'\0', b'!', b'\0', b'#', b'$', b'%', b'&', b'\'', // 3x b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', // 4x b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', // 5x b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', // 6x @@ -465,5 +465,36 @@ mod test { let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely."; assert_eq!(Method::from_str(long_method).unwrap(), long_method); + + let longest_inline_method = [b'A'; InlineExtension::MAX]; + assert_eq!( + Method::from_bytes(&longest_inline_method).unwrap(), + Method(ExtensionInline( + InlineExtension::new(&longest_inline_method).unwrap() + )) + ); + let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1]; + assert_eq!( + Method::from_bytes(&shortest_allocated_method).unwrap(), + Method(ExtensionAllocated( + AllocatedExtension::new(&shortest_allocated_method).unwrap() + )) + ); + } + + #[test] + fn test_extension_method_chars() { + const VALID_METHOD_CHARS: &str = + "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + for c in VALID_METHOD_CHARS.chars() { + let c = c.to_string(); + + assert_eq!( + Method::from_str(&c).unwrap(), + c.as_str(), + "testing {c} is a valid method character" + ); + } } } diff --git a/src/request.rs b/src/request.rs index d4c5bf54..324b676c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -53,7 +53,7 @@ //! ``` use std::any::Any; -use std::convert::TryFrom; +use std::convert::TryInto; use std::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; @@ -231,8 +231,8 @@ impl Request<()> { /// ``` pub fn get(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::GET).uri(uri) } @@ -253,8 +253,8 @@ impl Request<()> { /// ``` pub fn put(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::PUT).uri(uri) } @@ -275,8 +275,8 @@ impl Request<()> { /// ``` pub fn post(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::POST).uri(uri) } @@ -297,8 +297,8 @@ impl Request<()> { /// ``` pub fn delete(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::DELETE).uri(uri) } @@ -320,8 +320,8 @@ impl Request<()> { /// ``` pub fn options(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::OPTIONS).uri(uri) } @@ -342,8 +342,8 @@ impl Request<()> { /// ``` pub fn head(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::HEAD).uri(uri) } @@ -364,8 +364,8 @@ impl Request<()> { /// ``` pub fn connect(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::CONNECT).uri(uri) } @@ -386,8 +386,8 @@ impl Request<()> { /// ``` pub fn patch(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::PATCH).uri(uri) } @@ -408,8 +408,8 @@ impl Request<()> { /// ``` pub fn trace(uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { Builder::new().method(Method::TRACE).uri(uri) } @@ -767,11 +767,11 @@ impl Builder { /// ``` pub fn method(self, method: T) -> Builder where - Method: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - let method = TryFrom::try_from(method).map_err(Into::into)?; + let method = method.try_into().map_err(Into::into)?; head.method = method; Ok(head) }) @@ -812,11 +812,11 @@ impl Builder { /// ``` pub fn uri(self, uri: T) -> Builder where - Uri: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - head.uri = TryFrom::try_from(uri).map_err(Into::into)?; + head.uri = uri.try_into().map_err(Into::into)?; Ok(head) }) } @@ -900,14 +900,14 @@ impl Builder { /// ``` pub fn header(self, key: K, value: V) -> Builder where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, + K: TryInto, + >::Error: Into, + V: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - let name = >::try_from(key).map_err(Into::into)?; - let value = >::try_from(value).map_err(Into::into)?; + let name = key.try_into().map_err(Into::into)?; + let value = value.try_into().map_err(Into::into)?; head.headers.try_append(name, value)?; Ok(head) }) diff --git a/src/response.rs b/src/response.rs index 312cc5f8..ab9e49bc 100644 --- a/src/response.rs +++ b/src/response.rs @@ -62,7 +62,7 @@ //! ``` use std::any::Any; -use std::convert::TryFrom; +use std::convert::TryInto; use std::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; @@ -559,11 +559,11 @@ impl Builder { /// ``` pub fn status(self, status: T) -> Builder where - StatusCode: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - head.status = TryFrom::try_from(status).map_err(Into::into)?; + head.status = status.try_into().map_err(Into::into)?; Ok(head) }) } @@ -610,14 +610,14 @@ impl Builder { /// ``` pub fn header(self, key: K, value: V) -> Builder where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, + K: TryInto, + >::Error: Into, + V: TryInto, + >::Error: Into, { self.and_then(move |mut head| { - let name = >::try_from(key).map_err(Into::into)?; - let value = >::try_from(value).map_err(Into::into)?; + let name = key.try_into().map_err(Into::into)?; + let value = value.try_into().map_err(Into::into)?; head.headers.try_append(name, value)?; Ok(head) }) diff --git a/src/status.rs b/src/status.rs index 88c556e2..16896e4a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -463,6 +463,10 @@ status_codes! { /// [[RFC4918, Section 11.4](https://tools.ietf.org/html/rfc4918#section-11.4)] (424, FAILED_DEPENDENCY, "Failed Dependency"); + /// 425 Too early + /// [[RFC8470, Section 5.2](https://httpwg.org/specs/rfc8470.html#status)] + (425, TOO_EARLY, "Too Early"); + /// 426 Upgrade Required /// [[RFC9110, Section 15.5.22](https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.22)] (426, UPGRADE_REQUIRED, "Upgrade Required"); diff --git a/src/uri/authority.rs b/src/uri/authority.rs index dab6dcd0..07aa6795 100644 --- a/src/uri/authority.rs +++ b/src/uri/authority.rs @@ -24,7 +24,7 @@ impl Authority { // Not public while `bytes` is unstable. pub(super) fn from_shared(s: Bytes) -> Result { // Precondition on create_authority: trivially satisfied by the - // identity clousre + // identity closure create_authority(s, |s| s) } diff --git a/src/uri/builder.rs b/src/uri/builder.rs index 9964d389..d5f7f49b 100644 --- a/src/uri/builder.rs +++ b/src/uri/builder.rs @@ -1,4 +1,4 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; use super::{Authority, Parts, PathAndQuery, Scheme}; use crate::Uri; @@ -44,8 +44,8 @@ impl Builder { /// ``` pub fn scheme(self, scheme: T) -> Self where - Scheme: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.map(move |mut parts| { let scheme = scheme.try_into().map_err(Into::into)?; @@ -68,8 +68,8 @@ impl Builder { /// ``` pub fn authority(self, auth: T) -> Self where - Authority: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.map(move |mut parts| { let auth = auth.try_into().map_err(Into::into)?; @@ -92,8 +92,8 @@ impl Builder { /// ``` pub fn path_and_query(self, p_and_q: T) -> Self where - PathAndQuery: TryFrom, - >::Error: Into, + T: TryInto, + >::Error: Into, { self.map(move |mut parts| { let p_and_q = p_and_q.try_into().map_err(Into::into)?; diff --git a/src/uri/scheme.rs b/src/uri/scheme.rs index c33ec41a..dbcc8c3f 100644 --- a/src/uri/scheme.rs +++ b/src/uri/scheme.rs @@ -302,7 +302,7 @@ impl Scheme2 { // Return scheme return Ok(Scheme2::Other(i)); } - // Invald scheme character, abort + // Invalid scheme character, abort 0 => break, _ => {} } @@ -349,10 +349,10 @@ mod test { #[test] fn invalid_scheme_is_error() { - Scheme::try_from("my_funky_scheme").expect_err("Unexpectly valid Scheme"); + Scheme::try_from("my_funky_scheme").expect_err("Unexpectedly valid Scheme"); // Invalid UTF-8 - Scheme::try_from([0xC0].as_ref()).expect_err("Unexpectly valid Scheme"); + Scheme::try_from([0xC0].as_ref()).expect_err("Unexpectedly valid Scheme"); } fn scheme(s: &str) -> Scheme {