mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into refactor/web/rmap
This commit is contained in:
commit
4bfaaf3df8
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ name = "actix_web"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
".",
|
".",
|
||||||
"awc",
|
"awc",
|
||||||
|
|
|
@ -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")]`
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web/4.0.0-beta.8)
|
[](https://docs.rs/actix-web/4.0.0-beta.8)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.8)
|
[](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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-files)
|
[](https://crates.io/crates/actix-files)
|
||||||
[](https://docs.rs/actix-files/0.6.0-beta.6)
|
[](https://docs.rs/actix-files/0.6.0-beta.6)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.6)
|
[](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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http-test)
|
[](https://crates.io/crates/actix-http-test)
|
||||||
[](https://docs.rs/actix-http-test/3.0.0-beta.4)
|
[](https://docs.rs/actix-http-test/3.0.0-beta.4)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br>
|
<br>
|
||||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.4)
|
[](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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-http)
|
[](https://crates.io/crates/actix-http)
|
||||||
[](https://docs.rs/actix-http/3.0.0-beta.9)
|
[](https://docs.rs/actix-http/3.0.0-beta.9)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.9)
|
[](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
|
||||||
|
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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))) => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-multipart)
|
[](https://crates.io/crates/actix-multipart)
|
||||||
[](https://docs.rs/actix-multipart/0.4.0-beta.5)
|
[](https://docs.rs/actix-multipart/0.4.0-beta.5)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.5)
|
[](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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-actors)
|
[](https://crates.io/crates/actix-web-actors)
|
||||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.6)
|
[](https://docs.rs/actix-web-actors/4.0.0-beta.6)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.6)
|
[](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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web-codegen)
|
[](https://crates.io/crates/actix-web-codegen)
|
||||||
[](https://docs.rs/actix-web-codegen/0.5.0-beta.3)
|
[](https://docs.rs/actix-web-codegen/0.5.0-beta.3)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.51.html)
|
||||||

|

|
||||||
<br />
|
<br />
|
||||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.3)
|
[](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
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {}
|
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
msrv = "1.46"
|
msrv = "1.51"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
Loading…
Reference in New Issue