Merge branch 'master' into refactor/web/rmap

This commit is contained in:
Rob Ede 2021-08-31 14:16:17 +01:00 committed by GitHub
commit 4bfaaf3df8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 337 additions and 206 deletions

View File

@ -16,7 +16,7 @@ jobs:
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version: version:
- 1.46.0 # MSRV - 1.51.0 # MSRV
- stable - stable
- nightly - nightly

View File

@ -4,6 +4,9 @@
### Added ### Added
* Re-export actix-service `ServiceFactory` in `dev` module. [#2325] * Re-export actix-service `ServiceFactory` in `dev` module. [#2325]
### Changes
* Minimum supported Rust version (MSRV) is now 1.51.
[#2325]: https://github.com/actix/actix-web/pull/2325 [#2325]: https://github.com/actix/actix-web/pull/2325

View File

@ -24,6 +24,7 @@ name = "actix_web"
path = "src/lib.rs" path = "src/lib.rs"
[workspace] [workspace]
resolver = "2"
members = [ members = [
".", ".",
"awc", "awc",

View File

@ -3,7 +3,8 @@
* The default `NormalizePath` behavior now strips trailing slashes by default. This was * The default `NormalizePath` behavior now strips trailing slashes by default. This was
previously documented to be the case in v3 but the behavior now matches. The effect is that previously documented to be the case in v3 but the behavior now matches. The effect is that
routes defined with trailing slashes will become inaccessible when routes defined with trailing slashes will become inaccessible when
using `NormalizePath::default()`. using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning.
It is advised that the `new` method be used instead.
Before: `#[get("/test/")]` Before: `#[get("/test/")]`
After: `#[get("/test")]` After: `#[get("/test")]`

View File

@ -7,7 +7,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web/4.0.0-beta.8) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web/4.0.0-beta.8)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.8) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.8)
<br /> <br />
@ -32,7 +32,7 @@
* SSL support using OpenSSL or Rustls * SSL support using OpenSSL or Rustls
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
* Includes an async [HTTP client](https://docs.rs/awc/) * Includes an async [HTTP client](https://docs.rs/awc/)
* Runs on stable Rust 1.46+ * Runs on stable Rust 1.51+
## Documentation ## Documentation

View File

@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.51.
## 0.6.0-beta.6 - 2021-06-26 ## 0.6.0-beta.6 - 2021-06-26

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.6)](https://docs.rs/actix-files/0.6.0-beta.6) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.6)](https://docs.rs/actix-files/0.6.0-beta.6)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
![License](https://img.shields.io/crates/l/actix-files.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.6/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.6) [![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.6/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.6)
@ -15,4 +15,4 @@
- [API Documentation](https://docs.rs/actix-files/) - [API Documentation](https://docs.rs/actix-files/)
- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index) - [Example Project](https://github.com/actix/examples/tree/master/basics/static_index)
- Minimum supported Rust version: 1.46 or later - Minimum supported Rust version: 1.51 or later

View File

@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.51.
## 3.0.0-beta.4 - 2021-04-02 ## 3.0.0-beta.4 - 2021-04-02

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http-test/3.0.0-beta.4) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.4)](https://docs.rs/actix-http-test/3.0.0-beta.4)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br> <br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.4) [![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.4/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.4)
@ -14,4 +14,4 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test) - [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.46.0 - Minimum Supported Rust Version (MSRV): 1.51.0

View File

@ -1,11 +1,16 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Changes
* Minimum supported Rust version (MSRV) is now 1.51.
### Fixed ### Fixed
* Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364] * Remove slice creation pointing to potential uninitialized data on h1 encoder. [#2364]
* Remove `Into<Error>` bound on `Encoder` body types. [#2375]
[#2364]: https://github.com/actix/actix-web/pull/2364 [#2364]: https://github.com/actix/actix-web/pull/2364
[#2375]: https://github.com/actix/actix-web/pull/2375
## 3.0.0-beta.8 - 2021-08-09 ## 3.0.0-beta.8 - 2021-08-09
### Fixed ### Fixed

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http/3.0.0-beta.9) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.9)](https://docs.rs/actix-http/3.0.0-beta.9)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.9) [![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.9/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.9)
@ -14,7 +14,7 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http) - [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.46.0 - Minimum Supported Rust Version (MSRV): 1.51.0
## Example ## Example

View File

@ -7,7 +7,7 @@ use std::{
}; };
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::{ready, Stream}; use futures_core::Stream;
use crate::error::Error; use crate::error::Error;
@ -74,14 +74,10 @@ impl MessageBody for AnyBody {
} }
} }
// TODO: MSRV 1.51: poll_map_err AnyBody::Message(body) => body
AnyBody::Message(body) => match ready!(body.as_pin_mut().poll_next(cx)) { .as_pin_mut()
Some(Err(err)) => { .poll_next(cx)
Poll::Ready(Some(Err(Error::new_body().with_cause(err)))) .map_err(|err| Error::new_body().with_cause(err)),
}
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
},
} }
} }
} }
@ -223,11 +219,9 @@ impl MessageBody for BoxAnyBody {
mut self: Pin<&mut Self>, mut self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
// TODO: MSRV 1.51: poll_map_err self.0
match ready!(self.0.as_mut().poll_next(cx)) { .as_mut()
Some(Err(err)) => Poll::Ready(Some(Err(Error::new_body().with_cause(err)))), .poll_next(cx)
Some(Ok(val)) => Poll::Ready(Some(Ok(val))), .map_err(|err| Error::new_body().with_cause(err))
None => Poll::Ready(None),
}
} }
} }

View File

@ -11,8 +11,6 @@ use bytes::{Bytes, BytesMut};
use futures_core::ready; use futures_core::ready;
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use crate::error::Error;
use super::BodySize; use super::BodySize;
/// An interface for response bodies. /// An interface for response bodies.
@ -47,7 +45,6 @@ impl MessageBody for () {
impl<B> MessageBody for Box<B> impl<B> MessageBody for Box<B>
where where
B: MessageBody + Unpin, B: MessageBody + Unpin,
B::Error: Into<Error>,
{ {
type Error = B::Error; type Error = B::Error;
@ -66,7 +63,6 @@ where
impl<B> MessageBody for Pin<Box<B>> impl<B> MessageBody for Pin<Box<B>>
where where
B: MessageBody, B: MessageBody,
B::Error: Into<Error>,
{ {
type Error = B::Error; type Error = B::Error;

View File

@ -80,7 +80,7 @@ mod tests {
impl Body { impl Body {
pub(crate) fn get_ref(&self) -> &[u8] { pub(crate) fn get_ref(&self) -> &[u8] {
match *self { match *self {
Body::Bytes(ref bin) => &bin, Body::Bytes(ref bin) => bin,
_ => panic!(), _ => panic!(),
} }
} }

View File

@ -5,7 +5,7 @@ use std::{
}; };
use bytes::Bytes; use bytes::Bytes;
use futures_core::{ready, Stream}; use futures_core::Stream;
use pin_project::pin_project; use pin_project::pin_project;
use crate::error::Error; use crate::error::Error;
@ -77,12 +77,7 @@ where
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> { ) -> Poll<Option<Self::Item>> {
match self.project() { match self.project() {
// TODO: MSRV 1.51: poll_map_err ResponseBodyProj::Body(body) => body.poll_next(cx).map_err(Into::into),
ResponseBodyProj::Body(body) => match ready!(body.poll_next(cx)) {
Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
},
ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx),
} }
} }

View File

@ -29,7 +29,7 @@ use crate::{
header::{ContentEncoding, CONTENT_ENCODING}, header::{ContentEncoding, CONTENT_ENCODING},
HeaderValue, StatusCode, HeaderValue, StatusCode,
}, },
Error, ResponseHead, ResponseHead,
}; };
use super::Writer; use super::Writer;
@ -107,7 +107,6 @@ enum EncoderBody<B> {
impl<B> MessageBody for EncoderBody<B> impl<B> MessageBody for EncoderBody<B>
where where
B: MessageBody, B: MessageBody,
B::Error: Into<Error>,
{ {
type Error = EncoderError<B::Error>; type Error = EncoderError<B::Error>;
@ -131,18 +130,9 @@ where
Poll::Ready(Some(Ok(std::mem::take(b)))) Poll::Ready(Some(Ok(std::mem::take(b))))
} }
} }
// TODO: MSRV 1.51: poll_map_err EncoderBodyProj::Stream(b) => b.poll_next(cx).map_err(EncoderError::Body),
EncoderBodyProj::Stream(b) => match ready!(b.poll_next(cx)) {
Some(Err(err)) => Poll::Ready(Some(Err(EncoderError::Body(err)))),
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
},
EncoderBodyProj::BoxedStream(ref mut b) => { EncoderBodyProj::BoxedStream(ref mut b) => {
match ready!(b.as_pin_mut().poll_next(cx)) { b.as_pin_mut().poll_next(cx).map_err(EncoderError::Boxed)
Some(Err(err)) => Poll::Ready(Some(Err(EncoderError::Boxed(err)))),
Some(Ok(val)) => Poll::Ready(Some(Ok(val))),
None => Poll::Ready(None),
}
} }
} }
} }
@ -151,7 +141,6 @@ where
impl<B> MessageBody for Encoder<B> impl<B> MessageBody for Encoder<B>
where where
B: MessageBody, B: MessageBody,
B::Error: Into<Error>,
{ {
type Error = EncoderError<B::Error>; type Error = EncoderError<B::Error>;

View File

@ -40,7 +40,7 @@ impl ChunkedState {
Size => ChunkedState::read_size(body, size), Size => ChunkedState::read_size(body, size),
SizeLws => ChunkedState::read_size_lws(body), SizeLws => ChunkedState::read_size_lws(body),
Extension => ChunkedState::read_extension(body), Extension => ChunkedState::read_extension(body),
SizeLf => ChunkedState::read_size_lf(body, size), SizeLf => ChunkedState::read_size_lf(body, *size),
Body => ChunkedState::read_body(body, size, buf), Body => ChunkedState::read_body(body, size, buf),
BodyCr => ChunkedState::read_body_cr(body), BodyCr => ChunkedState::read_body_cr(body),
BodyLf => ChunkedState::read_body_lf(body), BodyLf => ChunkedState::read_body_lf(body),
@ -113,11 +113,11 @@ impl ChunkedState {
} }
fn read_size_lf( fn read_size_lf(
rdr: &mut BytesMut, rdr: &mut BytesMut,
size: &mut u64, size: u64,
) -> Poll<Result<ChunkedState, io::Error>> { ) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\n' if *size > 0 => Poll::Ready(Ok(ChunkedState::Body)), b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
b'\n' if *size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
_ => Poll::Ready(Err(io::Error::new( _ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk size LF", "Invalid chunk size LF",

View File

@ -1060,7 +1060,7 @@ mod tests {
fn stabilize_date_header(payload: &mut [u8]) { fn stabilize_date_header(payload: &mut [u8]) {
let mut from = 0; let mut from = 0;
while let Some(pos) = find_slice(&payload, b"date", from) { while let Some(pos) = find_slice(payload, b"date", from) {
payload[(from + pos)..(from + pos + 35)] payload[(from + pos)..(from + pos + 35)]
.copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC");
from += 35; from += 35;

View File

@ -63,7 +63,6 @@ where
.is_write_buf_full() .is_write_buf_full()
{ {
let next = let next =
// TODO: MSRV 1.51: poll_map_err
match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) { match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)), Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => {

View File

@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.51.
## 0.4.0-beta.5 - 2021-06-17 ## 0.4.0-beta.5 - 2021-06-17

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.5)](https://docs.rs/actix-multipart/0.4.0-beta.5) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.5)](https://docs.rs/actix-multipart/0.4.0-beta.5)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.5/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.5) [![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.5/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.5)
@ -14,4 +14,4 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart) - [API Documentation](https://docs.rs/actix-multipart)
- Minimum Supported Rust Version (MSRV): 1.46.0 - Minimum Supported Rust Version (MSRV): 1.51.0

View File

@ -7,12 +7,17 @@
* Improve malformed path error message. [#384] * Improve malformed path error message. [#384]
* Prefix segments now always end with with a segment delimiter or end-of-input. [#2355] * Prefix segments now always end with with a segment delimiter or end-of-input. [#2355]
* Prefix segments with trailing slashes define a trailing empty segment. [#2355] * Prefix segments with trailing slashes define a trailing empty segment. [#2355]
* Support multi-pattern prefixes and joins. [#2356]
* `ResourceDef::pattern` now returns the first pattern in multi-pattern resources. [#2356]
* Support `build_resource_path` on multi-pattern resources. [#2356]
* Minimum supported Rust version (MSRV) is now 1.51.
[#378]: https://github.com/actix/actix-net/pull/378 [#378]: https://github.com/actix/actix-net/pull/378
[#379]: https://github.com/actix/actix-net/pull/379 [#379]: https://github.com/actix/actix-net/pull/379
[#380]: https://github.com/actix/actix-net/pull/380 [#380]: https://github.com/actix/actix-net/pull/380
[#384]: https://github.com/actix/actix-net/pull/384 [#384]: https://github.com/actix/actix-net/pull/384
[#2355]: https://github.com/actix/actix-web/pull/2355 [#2355]: https://github.com/actix/actix-web/pull/2355
[#2356]: https://github.com/actix/actix-web/pull/2356
## 0.5.0-beta.1 - 2021-07-20 ## 0.5.0-beta.1 - 2021-07-20

View File

@ -31,13 +31,13 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// # Pattern Format and Matching Behavior /// # Pattern Format and Matching Behavior
/// ///
/// Resource pattern is defined as a string of zero or more _segments_ where each segment is /// Resource pattern is defined as a string of zero or more _segments_ where each segment is
/// preceeded by a slash `/`. /// preceded by a slash `/`.
/// ///
/// This means that pattern string __must__ either be empty or begin with a slash (`/`). /// This means that pattern string __must__ either be empty or begin with a slash (`/`).
/// This also implies that a trailing slash in pattern defines an empty segment. /// This also implies that a trailing slash in pattern defines an empty segment.
/// For example, the pattern `"/user/"` has two segments: `["user", ""]` /// For example, the pattern `"/user/"` has two segments: `["user", ""]`
/// ///
/// A key point to undertand is that `ResourceDef` matches segments, not strings. /// A key point to underhand is that `ResourceDef` matches segments, not strings.
/// It matches segments individually. /// It matches segments individually.
/// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`, /// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`,
/// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`. /// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`.
@ -220,17 +220,15 @@ pub struct ResourceDef {
name: Option<String>, name: Option<String>,
/// Pattern that generated the resource definition. /// Pattern that generated the resource definition.
///
/// `None` when pattern type is `DynamicSet`.
patterns: Patterns, patterns: Patterns,
is_prefix: bool,
/// Pattern type. /// Pattern type.
pat_type: PatternType, pat_type: PatternType,
/// List of segments that compose the pattern, in order. /// List of segments that compose the pattern, in order.
/// segments: Vec<PatternSegment>,
/// `None` when pattern type is `DynamicSet`.
segments: Option<Vec<PatternSegment>>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -248,9 +246,6 @@ enum PatternType {
/// Single constant/literal segment. /// Single constant/literal segment.
Static(String), Static(String),
/// Single constant/literal prefix segment.
Prefix(String),
/// Single regular expression and list of dynamic segment names. /// Single regular expression and list of dynamic segment names.
Dynamic(Regex, Vec<&'static str>), Dynamic(Regex, Vec<&'static str>),
@ -284,45 +279,7 @@ impl ResourceDef {
/// ``` /// ```
pub fn new<T: IntoPatterns>(paths: T) -> Self { pub fn new<T: IntoPatterns>(paths: T) -> Self {
profile_method!(new); profile_method!(new);
Self::new2(paths, false)
match paths.patterns() {
Patterns::Single(pattern) => ResourceDef::from_single_pattern(&pattern, false),
// since zero length pattern sets are possible
// just return a useless `ResourceDef`
Patterns::List(patterns) if patterns.is_empty() => ResourceDef {
id: 0,
name: None,
patterns: Patterns::List(patterns),
pat_type: PatternType::DynamicSet(RegexSet::empty(), Vec::new()),
segments: None,
},
Patterns::List(patterns) => {
let mut re_set = Vec::with_capacity(patterns.len());
let mut pattern_data = Vec::new();
for pattern in &patterns {
match ResourceDef::parse(pattern, false, true) {
(PatternType::Dynamic(re, names), _) => {
re_set.push(re.as_str().to_owned());
pattern_data.push((re, names));
}
_ => unreachable!(),
}
}
let pattern_re_set = RegexSet::new(re_set).unwrap();
ResourceDef {
id: 0,
name: None,
patterns: Patterns::List(patterns),
pat_type: PatternType::DynamicSet(pattern_re_set, pattern_data),
segments: None,
}
}
}
} }
/// Constructs a new resource definition using a pattern that performs prefix matching. /// Constructs a new resource definition using a pattern that performs prefix matching.
@ -348,9 +305,9 @@ impl ResourceDef {
/// assert!(!resource.is_match("user/123/stars")); /// assert!(!resource.is_match("user/123/stars"));
/// assert!(!resource.is_match("/foo")); /// assert!(!resource.is_match("/foo"));
/// ``` /// ```
pub fn prefix(path: &str) -> Self { pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
profile_method!(prefix); profile_method!(prefix);
ResourceDef::from_single_pattern(path, true) ResourceDef::new2(paths, true)
} }
/// Constructs a new resource definition using a string pattern that performs prefix matching, /// Constructs a new resource definition using a string pattern that performs prefix matching,
@ -375,7 +332,7 @@ impl ResourceDef {
/// ``` /// ```
pub fn root_prefix(path: &str) -> Self { pub fn root_prefix(path: &str) -> Self {
profile_method!(root_prefix); profile_method!(root_prefix);
ResourceDef::prefix(&insert_slash(path)) ResourceDef::prefix(insert_slash(path).into_owned())
} }
/// Returns a numeric resource ID. /// Returns a numeric resource ID.
@ -453,17 +410,14 @@ impl ResourceDef {
/// assert!(!ResourceDef::new("/user").is_prefix()); /// assert!(!ResourceDef::new("/user").is_prefix());
/// ``` /// ```
pub fn is_prefix(&self) -> bool { pub fn is_prefix(&self) -> bool {
match &self.pat_type { self.is_prefix
PatternType::Prefix(_) => true,
PatternType::Dynamic(re, _) if !re.as_str().ends_with('$') => true,
_ => false,
}
} }
/// Returns the pattern string that generated the resource definition. /// Returns the pattern string that generated the resource definition.
/// ///
/// Returns `None` if definition was constructed with multiple patterns. /// If definition is constructed with multiple patterns, the first pattern is returned. To get
/// See [`patterns_iter`][Self::pattern_iter]. /// all patterns, use [`patterns_iter`][Self::pattern_iter]. If resource has 0 patterns,
/// returns `None`.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -472,11 +426,11 @@ impl ResourceDef {
/// assert_eq!(resource.pattern().unwrap(), "/user/{id}"); /// assert_eq!(resource.pattern().unwrap(), "/user/{id}");
/// ///
/// let mut resource = ResourceDef::new(["/profile", "/user/{id}"]); /// let mut resource = ResourceDef::new(["/profile", "/user/{id}"]);
/// assert!(resource.pattern().is_none()); /// assert_eq!(resource.pattern(), Some("/profile"));
pub fn pattern(&self) -> Option<&str> { pub fn pattern(&self) -> Option<&str> {
match &self.patterns { match &self.patterns {
Patterns::Single(pattern) => Some(pattern.as_str()), Patterns::Single(pattern) => Some(pattern.as_str()),
Patterns::List(_) => None, Patterns::List(patterns) => patterns.first().map(AsRef::as_ref),
} }
} }
@ -563,8 +517,8 @@ impl ResourceDef {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
match patterns.len() { match patterns.len() {
1 => ResourceDef::from_single_pattern(&patterns[0], other.is_prefix()), 1 => ResourceDef::new2(&patterns[0], other.is_prefix()),
_ => ResourceDef::new(patterns), _ => ResourceDef::new2(patterns, other.is_prefix()),
} }
} }
@ -609,11 +563,10 @@ impl ResourceDef {
// `self.find_match(path).is_some()` // `self.find_match(path).is_some()`
// but this skips some checks and uses potentially faster regex methods // but this skips some checks and uses potentially faster regex methods
match self.pat_type { match &self.pat_type {
PatternType::Static(ref s) => s == path, PatternType::Static(pattern) => self.static_match(pattern, path).is_some(),
PatternType::Prefix(ref prefix) => is_prefix(prefix, path), PatternType::Dynamic(re, _) => re.is_match(path),
PatternType::Dynamic(ref re, _) => re.is_match(path), PatternType::DynamicSet(re, _) => re.is_match(path),
PatternType::DynamicSet(ref re, _) => re.is_match(path),
} }
} }
@ -656,11 +609,7 @@ impl ResourceDef {
profile_method!(find_match); profile_method!(find_match);
match &self.pat_type { match &self.pat_type {
PatternType::Static(segment) if path == segment => Some(segment.len()), PatternType::Static(pattern) => self.static_match(pattern, path),
PatternType::Static(_) => None,
PatternType::Prefix(prefix) if is_prefix(prefix, path) => Some(prefix.len()),
PatternType::Prefix(_) => None,
PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()), PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()),
@ -753,10 +702,10 @@ impl ResourceDef {
let path_str = path.path(); let path_str = path.path();
let (matched_len, matched_vars) = match &self.pat_type { let (matched_len, matched_vars) = match &self.pat_type {
PatternType::Static(_) | PatternType::Prefix(_) => { PatternType::Static(pattern) => {
profile_section!(pattern_static_or_prefix); profile_section!(pattern_static_or_prefix);
match self.find_match(path_str) { match self.static_match(pattern, path_str) {
Some(len) => (len, None), Some(len) => (len, None),
None => return false, None => return false,
} }
@ -844,13 +793,10 @@ impl ResourceDef {
F: FnMut(&str) -> Option<I>, F: FnMut(&str) -> Option<I>,
I: AsRef<str>, I: AsRef<str>,
{ {
for el in match self.segments { for segment in &self.segments {
Some(ref segments) => segments, match segment {
None => return false, PatternSegment::Const(val) => path.push_str(val),
} { PatternSegment::Var(name) => match vars(name) {
match *el {
PatternSegment::Const(ref val) => path.push_str(val),
PatternSegment::Var(ref name) => match vars(name) {
Some(val) => path.push_str(val.as_ref()), Some(val) => path.push_str(val.as_ref()),
_ => return false, _ => return false,
}, },
@ -864,8 +810,8 @@ impl ResourceDef {
/// ///
/// Returns `true` on success. /// Returns `true` on success.
/// ///
/// Resource paths can not be built from multi-pattern resources; this call will always return /// For multi-pattern resources, the first pattern is used under the assumption that it would be
/// false and will not add anything to the string buffer. /// equivalent to any other choice.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -890,8 +836,8 @@ impl ResourceDef {
/// ///
/// Returns `true` on success. /// Returns `true` on success.
/// ///
/// Resource paths can not be built from multi-pattern resources; this call will always return /// For multi-pattern resources, the first pattern is used under the assumption that it would be
/// false and will not add anything to the string buffer. /// equivalent to any other choice.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -921,19 +867,69 @@ impl ResourceDef {
self.build_resource_path(path, |name| values.get(name).map(AsRef::<str>::as_ref)) self.build_resource_path(path, |name| values.get(name).map(AsRef::<str>::as_ref))
} }
/// Parse path pattern and create a new instance. /// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`.
fn from_single_pattern(pattern: &str, is_prefix: bool) -> Self { fn static_match(&self, pattern: &str, path: &str) -> Option<usize> {
profile_method!(from_single_pattern); let rem = path.strip_prefix(pattern)?;
let pattern = pattern.to_owned(); match self.is_prefix {
let (pat_type, segments) = ResourceDef::parse(&pattern, is_prefix, false); // resource is not a prefix so an exact match is needed
false if rem.is_empty() => Some(pattern.len()),
// resource is a prefix so rem should start with a path delimiter
true if rem.is_empty() || rem.starts_with('/') => Some(pattern.len()),
// otherwise, no match
_ => None,
}
}
fn new2<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
profile_method!(new2);
let patterns = paths.patterns();
let (pat_type, segments) = match &patterns {
Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false),
// since zero length pattern sets are possible
// just return a useless `ResourceDef`
Patterns::List(patterns) if patterns.is_empty() => (
PatternType::DynamicSet(RegexSet::empty(), Vec::new()),
Vec::new(),
),
Patterns::List(patterns) => {
let mut re_set = Vec::with_capacity(patterns.len());
let mut pattern_data = Vec::new();
let mut segments = None;
for pattern in patterns {
match ResourceDef::parse(pattern, is_prefix, true) {
(PatternType::Dynamic(re, names), segs) => {
re_set.push(re.as_str().to_owned());
pattern_data.push((re, names));
segments.get_or_insert(segs);
}
_ => unreachable!(),
}
}
let pattern_re_set = RegexSet::new(re_set).unwrap();
let segments = segments.unwrap_or_else(Vec::new);
(
PatternType::DynamicSet(pattern_re_set, pattern_data),
segments,
)
}
};
ResourceDef { ResourceDef {
id: 0, id: 0,
name: None, name: None,
patterns: Patterns::Single(pattern), patterns,
is_prefix,
pat_type, pat_type,
segments: Some(segments), segments,
} }
} }
@ -967,7 +963,10 @@ impl ResourceDef {
_ => false, _ => false,
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!(r#"path "{}" contains malformed dynamic segment"#, pattern) panic!(
r#"pattern "{}" contains malformed dynamic segment"#,
pattern
)
}); });
let (mut param, mut unprocessed) = pattern.split_at(close_idx + 1); let (mut param, mut unprocessed) = pattern.split_at(close_idx + 1);
@ -1020,20 +1019,15 @@ impl ResourceDef {
) -> (PatternType, Vec<PatternSegment>) { ) -> (PatternType, Vec<PatternSegment>) {
profile_method!(parse); profile_method!(parse);
let mut unprocessed = pattern; if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') {
if !force_dynamic && unprocessed.find('{').is_none() && !unprocessed.ends_with('*') {
// pattern is static // pattern is static
return (
let tp = if is_prefix { PatternType::Static(pattern.to_owned()),
PatternType::Prefix(unprocessed.to_owned()) vec![PatternSegment::Const(pattern.to_owned())],
} else { );
PatternType::Static(unprocessed.to_owned())
};
return (tp, vec![PatternSegment::Const(unprocessed.to_owned())]);
} }
let mut unprocessed = pattern;
let mut segments = Vec::new(); let mut segments = Vec::new();
let mut re = format!("{}^", REGEX_FLAGS); let mut re = format!("{}^", REGEX_FLAGS);
let mut dyn_segment_count = 0; let mut dyn_segment_count = 0;
@ -1134,18 +1128,7 @@ impl Eq for ResourceDef {}
impl PartialEq for ResourceDef { impl PartialEq for ResourceDef {
fn eq(&self, other: &ResourceDef) -> bool { fn eq(&self, other: &ResourceDef) -> bool {
self.patterns == other.patterns self.patterns == other.patterns && self.is_prefix == other.is_prefix
&& match &self.pat_type {
PatternType::Static(_) => matches!(&other.pat_type, PatternType::Static(_)),
PatternType::Prefix(_) => matches!(&other.pat_type, PatternType::Prefix(_)),
PatternType::Dynamic(re, _) => match &other.pat_type {
PatternType::Dynamic(other_re, _) => re.as_str() == other_re.as_str(),
_ => false,
},
PatternType::DynamicSet(_, _) => {
matches!(&other.pat_type, PatternType::DynamicSet(..))
}
}
} }
} }
@ -1180,15 +1163,6 @@ pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
} }
} }
/// Returns true if `prefix` acts as a proper prefix (i.e., separated by a slash) in `path`.
fn is_prefix(prefix: &str, path: &str) -> bool {
match path.strip_prefix(prefix) {
// Ensure the match ends at segment boundary
Some(rem) if rem.is_empty() || rem.starts_with('/') => true,
_ => false,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -1373,6 +1347,24 @@ mod tests {
assert!(!re.is_match("/user/2345/sdg")); assert!(!re.is_match("/user/2345/sdg"));
} }
#[test]
fn dynamic_set_prefix() {
let re = ResourceDef::prefix(vec!["/u/{id}", "/{id:[[:digit:]]{3}}"]);
assert_eq!(re.find_match("/u/abc"), Some(6));
assert_eq!(re.find_match("/u/abc/123"), Some(6));
assert_eq!(re.find_match("/s/user/profile"), None);
assert_eq!(re.find_match("/123"), Some(4));
assert_eq!(re.find_match("/123/456"), Some(4));
assert_eq!(re.find_match("/12345"), None);
let mut path = Path::new("/151/res");
assert!(re.capture_match_info(&mut path));
assert_eq!(path.get("id").unwrap(), "151");
assert_eq!(path.unprocessed(), "/res");
}
#[test] #[test]
fn parse_tail() { fn parse_tail() {
let re = ResourceDef::new("/user/-{id}*"); let re = ResourceDef::new("/user/-{id}*");
@ -1599,10 +1591,11 @@ mod tests {
} }
#[test] #[test]
fn multi_pattern_cannot_build_path() { fn multi_pattern_build_path() {
let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]); let resource = ResourceDef::new(["/user/{id}", "/profile/{id}"]);
let mut s = String::new(); let mut s = String::new();
assert!(!resource.resource_path_from_iter(&mut s, &mut ["123"].iter())); assert!(resource.resource_path_from_iter(&mut s, &mut ["123"].iter()));
assert_eq!(s, "/user/123");
} }
#[test] #[test]
@ -1737,6 +1730,10 @@ mod tests {
join_test!("/user", "" => "", "/user", "/user/123", "/user11", "user", "user/123"); join_test!("/user", "" => "", "/user", "/user/123", "/user11", "user", "user/123");
join_test!("", "/user" => "", "/user", "foo", "/user11", "user", "user/123"); join_test!("", "/user" => "", "/user", "foo", "/user11", "user", "user/123");
join_test!("/user", "/xx" => "", "", "/", "/user", "/xx", "/userxx", "/user/xx"); join_test!("/user", "/xx" => "", "", "/", "/user", "/xx", "/userxx", "/user/xx");
join_test!(["/ver/{v}", "/v{v}"], ["/req/{req}", "/{req}"] => "/v1/abc",
"/ver/1/abc", "/v1/req/abc", "/ver/1/req/abc", "/v1/abc/def",
"/ver1/req/abc/def", "", "/", "/v1/");
} }
#[test] #[test]
@ -1774,6 +1771,7 @@ mod tests {
match_methods_agree!(prefix "" => "", "/", "/foo"); match_methods_agree!(prefix "" => "", "/", "/foo");
match_methods_agree!(prefix "/user" => "user", "/user", "/users", "/user/123", "/foo"); match_methods_agree!(prefix "/user" => "user", "/user", "/users", "/user/123", "/foo");
match_methods_agree!(prefix r"/id/{id:\d{3}}" => "/id/123", "/id/1234"); match_methods_agree!(prefix r"/id/{id:\d{3}}" => "/id/123", "/id/1234");
match_methods_agree!(["/v{v}", "/ver/{v}"] => "", "s/v", "/v1", "/v1/xx", "/ver/i3/5", "/ver/1");
} }
#[test] #[test]

View File

@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.51.
## 0.1.0-beta.3 - 2021-06-20 ## 0.1.0-beta.3 - 2021-06-20

View File

@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* Minimum supported Rust version (MSRV) is now 1.51.
## 4.0.0-beta.6 - 2021-06-26 ## 4.0.0-beta.6 - 2021-06-26

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.6)](https://docs.rs/actix-web-actors/4.0.0-beta.6) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.6)](https://docs.rs/actix-web-actors/4.0.0-beta.6)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
![License](https://img.shields.io/crates/l/actix-web-actors.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6) [![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6)
@ -14,4 +14,4 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-actors) - [API Documentation](https://docs.rs/actix-web-actors)
- Minimum supported Rust version: 1.46 or later - Minimum supported Rust version: 1.51 or later

View File

@ -1,6 +1,10 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
* In routing macros, paths are now validated at compile time. [#2350]
* Minimum supported Rust version (MSRV) is now 1.51.
[#2350]: https://github.com/actix/actix-web/pull/2350
## 0.5.0-beta.3 - 2021-06-17 ## 0.5.0-beta.3 - 2021-06-17

View File

@ -17,6 +17,7 @@ proc-macro = true
quote = "1" quote = "1"
syn = { version = "1", features = ["full", "parsing"] } syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1" proc-macro2 = "1"
actix-router = "0.5.0-beta.1"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.3)](https://docs.rs/actix-web-codegen/0.5.0-beta.3) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.3)](https://docs.rs/actix-web-codegen/0.5.0-beta.3)
[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3) [![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3)
@ -14,7 +14,7 @@
## Documentation & Resources ## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-codegen) - [API Documentation](https://docs.rs/actix-web-codegen)
- Minimum supported Rust version: 1.46 or later. - Minimum supported Rust version: 1.51 or later.
## Compile Testing ## Compile Testing

View File

@ -3,6 +3,7 @@ extern crate proc_macro;
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom; use std::convert::TryFrom;
use actix_router::ResourceDef;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use quote::{format_ident, quote, ToTokens, TokenStreamExt};
@ -101,6 +102,7 @@ impl Args {
match arg { match arg {
NestedMeta::Lit(syn::Lit::Str(lit)) => match path { NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
None => { None => {
let _ = ResourceDef::new(lit.value());
path = Some(lit); path = Some(lit);
} }
_ => { _ => {

View File

@ -1,4 +1,4 @@
#[rustversion::stable(1.46)] // MSRV #[rustversion::stable(1.51)] // MSRV
#[test] #[test]
fn compile_macros() { fn compile_macros() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();
@ -10,6 +10,7 @@ fn compile_macros() {
t.compile_fail("tests/trybuild/route-missing-method-fail.rs"); t.compile_fail("tests/trybuild/route-missing-method-fail.rs");
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
t.compile_fail("tests/trybuild/route-malformed-path-fail.rs");
t.pass("tests/trybuild/docstring-ok.rs"); t.pass("tests/trybuild/docstring-ok.rs");
} }

View File

@ -0,0 +1,33 @@
use actix_web_codegen::get;
#[get("/{")]
async fn zero() -> &'static str {
"malformed resource def"
}
#[get("/{foo")]
async fn one() -> &'static str {
"malformed resource def"
}
#[get("/{}")]
async fn two() -> &'static str {
"malformed resource def"
}
#[get("/*")]
async fn three() -> &'static str {
"malformed resource def"
}
#[get("/{tail:\\d+}*")]
async fn four() -> &'static str {
"malformed resource def"
}
#[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")]
async fn five() -> &'static str {
"malformed resource def"
}
fn main() {}

View File

@ -0,0 +1,42 @@
error: custom attribute panicked
--> $DIR/route-malformed-path-fail.rs:3:1
|
3 | #[get("/{")]
| ^^^^^^^^^^^^
|
= help: message: pattern "{" contains malformed dynamic segment
error: custom attribute panicked
--> $DIR/route-malformed-path-fail.rs:8:1
|
8 | #[get("/{foo")]
| ^^^^^^^^^^^^^^^
|
= help: message: pattern "{foo" contains malformed dynamic segment
error: custom attribute panicked
--> $DIR/route-malformed-path-fail.rs:13:1
|
13 | #[get("/{}")]
| ^^^^^^^^^^^^^
|
= help: message: Wrong path pattern: "/{}" regex parse error:
((?s-m)^/(?P<>[^/]+))$
^
error: empty capture group name
error: custom attribute panicked
--> $DIR/route-malformed-path-fail.rs:23:1
|
23 | #[get("/{tail:\\d+}*")]
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: custom regex is not supported for tail match
error: custom attribute panicked
--> $DIR/route-malformed-path-fail.rs:28:1
|
28 | #[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: Only 16 dynamic segments are allowed, provided: 17

View File

@ -12,7 +12,7 @@
- [API Documentation](https://docs.rs/awc) - [API Documentation](https://docs.rs/awc)
- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https) - [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https)
- Minimum Supported Rust Version (MSRV): 1.46.0 - Minimum Supported Rust Version (MSRV): 1.51.0
## Example ## Example

View File

@ -1 +1 @@
msrv = "1.46" msrv = "1.51"

View File

@ -53,7 +53,7 @@
//! * SSL support using OpenSSL or Rustls //! * SSL support using OpenSSL or Rustls
//! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
//! * Includes an async [HTTP client](https://docs.rs/awc/) //! * Includes an async [HTTP client](https://docs.rs/awc/)
//! * Runs on stable Rust 1.46+ //! * Runs on stable Rust 1.51+
//! //!
//! # Crate Features //! # Crate Features
//! * `cookies` - cookies support (enabled by default) //! * `cookies` - cookies support (enabled by default)

View File

@ -341,7 +341,6 @@ where
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
let this = self.project(); let this = self.project();
// TODO: MSRV 1.51: poll_map_err
match ready!(this.body.poll_next(cx)) { match ready!(this.body.poll_next(cx)) {
Some(Ok(chunk)) => { Some(Ok(chunk)) => {
*this.size += chunk.len(); *this.size += chunk.len();

View File

@ -19,3 +19,43 @@ mod compress;
#[cfg(feature = "__compress")] #[cfg(feature = "__compress")]
pub use self::compress::Compress; pub use self::compress::Compress;
#[cfg(test)]
mod tests {
use crate::{http::StatusCode, App};
use super::*;
#[test]
fn common_combinations() {
// ensure there's no reason that the built-in middleware cannot compose
let _ = App::new()
.wrap(Compat::new(Logger::default()))
.wrap(Condition::new(true, DefaultHeaders::new()))
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
Ok(ErrorHandlerResponse::Response(res))
}))
.wrap(Logger::default())
.wrap(NormalizePath::new(TrailingSlash::Trim));
let _ = App::new()
.wrap(NormalizePath::new(TrailingSlash::Trim))
.wrap(Logger::default())
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
Ok(ErrorHandlerResponse::Response(res))
}))
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
.wrap(Condition::new(true, DefaultHeaders::new()))
.wrap(Compat::new(Logger::default()));
#[cfg(feature = "__compress")]
{
let _ = App::new().wrap(Compress::default()).wrap(Logger::default());
let _ = App::new().wrap(Logger::default()).wrap(Compress::default());
let _ = App::new().wrap(Compat::new(Compress::default()));
let _ = App::new().wrap(Condition::new(true, Compat::new(Compress::default())));
}
}
}

View File

@ -59,7 +59,7 @@ impl Default for TrailingSlash {
/// ///
/// # actix_web::rt::System::new().block_on(async { /// # actix_web::rt::System::new().block_on(async {
/// let app = App::new() /// let app = App::new()
/// .wrap(middleware::NormalizePath::default()) /// .wrap(middleware::NormalizePath::trim())
/// .route("/test", web::get().to(|| async { "test" })) /// .route("/test", web::get().to(|| async { "test" }))
/// .route("/unmatchable/", web::get().to(|| async { "unmatchable" })); /// .route("/unmatchable/", web::get().to(|| async { "unmatchable" }));
/// ///
@ -85,13 +85,31 @@ impl Default for TrailingSlash {
/// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// assert_eq!(res.status(), StatusCode::NOT_FOUND);
/// # }) /// # })
/// ``` /// ```
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy)]
pub struct NormalizePath(TrailingSlash); pub struct NormalizePath(TrailingSlash);
impl Default for NormalizePath {
fn default() -> Self {
log::warn!(
"`NormalizePath::default()` is deprecated. The default trailing slash behavior changed \
in v4 from `Always` to `Trim`. Update your call to `NormalizePath::new(...)`."
);
Self(TrailingSlash::Trim)
}
}
impl NormalizePath { impl NormalizePath {
/// Create new `NormalizePath` middleware with the specified trailing slash style. /// Create new `NormalizePath` middleware with the specified trailing slash style.
pub fn new(trailing_slash_style: TrailingSlash) -> Self { pub fn new(trailing_slash_style: TrailingSlash) -> Self {
NormalizePath(trailing_slash_style) Self(trailing_slash_style)
}
/// Constructs a new `NormalizePath` middleware with [trim](TrailingSlash::Trim) semantics.
///
/// Use this instead of `NormalizePath::default()` to avoid deprecation warning.
pub fn trim() -> Self {
Self::new(TrailingSlash::Trim)
} }
} }

View File

@ -270,7 +270,7 @@ pub(crate) mod tests {
impl BodyTest for Body { impl BodyTest for Body {
fn bin_ref(&self) -> &[u8] { fn bin_ref(&self) -> &[u8] {
match self { match self {
Body::Bytes(ref bin) => &bin, Body::Bytes(ref bin) => bin,
_ => unreachable!("bug in test impl"), _ => unreachable!("bug in test impl"),
} }
} }
@ -283,11 +283,11 @@ pub(crate) mod tests {
fn bin_ref(&self) -> &[u8] { fn bin_ref(&self) -> &[u8] {
match self { match self {
ResponseBody::Body(ref b) => match b { ResponseBody::Body(ref b) => match b {
Body::Bytes(ref bin) => &bin, Body::Bytes(ref bin) => bin,
_ => unreachable!("bug in test impl"), _ => unreachable!("bug in test impl"),
}, },
ResponseBody::Other(ref b) => match b { ResponseBody::Other(ref b) => match b {
Body::Bytes(ref bin) => &bin, Body::Bytes(ref bin) => bin,
_ => unreachable!("bug in test impl"), _ => unreachable!("bug in test impl"),
}, },
} }

View File

@ -213,10 +213,10 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_service_request_extract() { async fn test_service_request_extract() {
let req = TestRequest::with_uri("/name/user1/").to_srv_request(); let req = TestRequest::with_uri("/name/user1/").to_srv_request();
assert!(Query::<Id>::from_query(&req.query_string()).is_err()); assert!(Query::<Id>::from_query(req.query_string()).is_err());
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let mut s = Query::<Id>::from_query(&req.query_string()).unwrap(); let mut s = Query::<Id>::from_query(req.query_string()).unwrap();
assert_eq!(s.id, "test"); assert_eq!(s.id, "test");
assert_eq!( assert_eq!(