mirror of https://github.com/fafhrd91/actix-net
Merge remote-tracking branch 'origin/master' into feature/io-uring
This commit is contained in:
commit
f62fec2ccb
|
@ -1,3 +1,5 @@
|
||||||
[alias]
|
[alias]
|
||||||
chk = "check --workspace --all-features --tests --examples --bins"
|
chk = "check --workspace --all-features --tests --examples --bins"
|
||||||
lint = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
lint = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||||
|
ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture"
|
||||||
|
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||||
|
|
|
@ -104,9 +104,7 @@ jobs:
|
||||||
- name: tests
|
- name: tests
|
||||||
if: matrix.target.triple != 'x86_64-pc-windows-gnu'
|
if: matrix.target.triple != 'x86_64-pc-windows-gnu'
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with: { command: ci-test }
|
||||||
command: test
|
|
||||||
args: --workspace --all-features --no-fail-fast -- --nocapture
|
|
||||||
|
|
||||||
- name: Generate coverage file
|
- name: Generate coverage file
|
||||||
if: >
|
if: >
|
||||||
|
@ -129,3 +127,34 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
||||||
cargo-cache
|
cargo-cache
|
||||||
|
|
||||||
|
rustdoc:
|
||||||
|
name: rustdoc
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust (nightly)
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly-x86_64-unknown-linux-gnu
|
||||||
|
profile: minimal
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Generate Cargo.lock
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with: { command: generate-lockfile }
|
||||||
|
- name: Cache Dependencies
|
||||||
|
uses: Swatinem/rust-cache@v1.3.0
|
||||||
|
|
||||||
|
- name: Install cargo-hack
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cargo-hack
|
||||||
|
|
||||||
|
- name: doc tests
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
timeout-minutes: 40
|
||||||
|
with: { command: ci-doctest }
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
members = [
|
members = [
|
||||||
"actix-codec",
|
"actix-codec",
|
||||||
"actix-macros",
|
"actix-macros",
|
||||||
"actix-router",
|
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-server",
|
"actix-server",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
|
@ -17,7 +16,6 @@ members = [
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
actix-codec = { path = "actix-codec" }
|
actix-codec = { path = "actix-codec" }
|
||||||
actix-macros = { path = "actix-macros" }
|
actix-macros = { path = "actix-macros" }
|
||||||
actix-router = { path = "actix-router" }
|
|
||||||
actix-rt = { path = "actix-rt" }
|
actix-rt = { path = "actix-rt" }
|
||||||
actix-server = { path = "actix-server" }
|
actix-server = { path = "actix-server" }
|
||||||
actix-service = { path = "actix-service" }
|
actix-service = { path = "actix-service" }
|
||||||
|
|
|
@ -21,14 +21,13 @@ bitflags::bitflags! {
|
||||||
}
|
}
|
||||||
|
|
||||||
pin_project_lite::pin_project! {
|
pin_project_lite::pin_project! {
|
||||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
|
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using the `Encoder` and
|
||||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
/// `Decoder` traits to encode and decode frames.
|
||||||
///
|
///
|
||||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
/// Raw I/O objects work with byte sequences, but higher-level code usually wants to batch these
|
||||||
/// wants to batch these into meaningful chunks, called "frames". This
|
/// into meaningful chunks, called "frames". This method layers framing on top of an I/O object,
|
||||||
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
|
/// by using the `Encoder`/`Decoder` traits to handle encoding and decoding of message frames.
|
||||||
/// traits to handle encoding and decoding of message frames. Note that
|
/// Note that the incoming and outgoing frame types may be distinct.
|
||||||
/// the incoming and outgoing frame types may be distinct.
|
|
||||||
pub struct Framed<T, U> {
|
pub struct Framed<T, U> {
|
||||||
#[pin]
|
#[pin]
|
||||||
io: T,
|
io: T,
|
||||||
|
@ -44,10 +43,9 @@ where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
U: Decoder,
|
U: Decoder,
|
||||||
{
|
{
|
||||||
/// This function returns a *single* object that is both `Stream` and
|
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||||
/// `Sink`; grouping this into a single object is often useful for layering
|
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||||
/// things like gzip or TLS, which require both read and write access to the
|
/// read and write access to the underlying object.
|
||||||
/// underlying object.
|
|
||||||
pub fn new(io: T, codec: U) -> Framed<T, U> {
|
pub fn new(io: T, codec: U) -> Framed<T, U> {
|
||||||
Framed {
|
Framed {
|
||||||
io,
|
io,
|
||||||
|
@ -70,21 +68,18 @@ impl<T, U> Framed<T, U> {
|
||||||
&mut self.codec
|
&mut self.codec
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the underlying I/O stream wrapped by
|
/// Returns a reference to the underlying I/O stream wrapped by `Frame`.
|
||||||
/// `Frame`.
|
|
||||||
///
|
///
|
||||||
/// Note that care should be taken to not tamper with the underlying stream
|
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
/// it may corrupt the stream of frames otherwise being worked with.
|
||||||
/// being worked with.
|
|
||||||
pub fn io_ref(&self) -> &T {
|
pub fn io_ref(&self) -> &T {
|
||||||
&self.io
|
&self.io
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the underlying I/O stream.
|
/// Returns a mutable reference to the underlying I/O stream.
|
||||||
///
|
///
|
||||||
/// Note that care should be taken to not tamper with the underlying stream
|
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
/// it may corrupt the stream of frames otherwise being worked with.
|
||||||
/// being worked with.
|
|
||||||
pub fn io_mut(&mut self) -> &mut T {
|
pub fn io_mut(&mut self) -> &mut T {
|
||||||
&mut self.io
|
&mut self.io
|
||||||
}
|
}
|
||||||
|
@ -184,10 +179,9 @@ impl<T, U> Framed<T, U> {
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
let mut this = self.as_mut().project();
|
let mut this = self.as_mut().project();
|
||||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
// Repeatedly call `decode` or `decode_eof` as long as it is "readable". Readable is
|
||||||
// "readable". Readable is defined as not having returned `None`. If
|
// defined as not having returned `None`. If the upstream has returned EOF, and the
|
||||||
// the upstream has returned EOF, and the decoder is no longer
|
// decoder is no longer readable, it can be assumed that the decoder will never become
|
||||||
// readable, it can be assumed that the decoder will never become
|
|
||||||
// readable again, at which point the stream is terminated.
|
// readable again, at which point the stream is terminated.
|
||||||
|
|
||||||
if this.flags.contains(Flags::READABLE) {
|
if this.flags.contains(Flags::READABLE) {
|
||||||
|
@ -215,7 +209,7 @@ impl<T, U> Framed<T, U> {
|
||||||
|
|
||||||
debug_assert!(!this.flags.contains(Flags::EOF));
|
debug_assert!(!this.flags.contains(Flags::EOF));
|
||||||
|
|
||||||
// Otherwise, try to read more data and try again. Make sure we've got room
|
// Otherwise, try to read more data and try again. Make sure we've got room.
|
||||||
let remaining = this.read_buf.capacity() - this.read_buf.len();
|
let remaining = this.read_buf.capacity() - this.read_buf.len();
|
||||||
if remaining < LW {
|
if remaining < LW {
|
||||||
this.read_buf.reserve(HW - remaining)
|
this.read_buf.reserve(HW - remaining)
|
||||||
|
@ -341,13 +335,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Framed<T, U> {
|
impl<T, U> Framed<T, U> {
|
||||||
/// This function returns a *single* object that is both `Stream` and
|
/// This function returns a *single* object that is both `Stream` and `Sink`; grouping this into
|
||||||
/// `Sink`; grouping this into a single object is often useful for layering
|
/// a single object is often useful for layering things like gzip or TLS, which require both
|
||||||
/// things like gzip or TLS, which require both read and write access to the
|
/// read and write access to the underlying object.
|
||||||
/// underlying object.
|
|
||||||
///
|
///
|
||||||
/// These objects take a stream, a read buffer and a write buffer. These
|
/// These objects take a stream, a read buffer and a write buffer. These fields can be obtained
|
||||||
/// fields can be obtained from an existing `Framed` with the `into_parts` method.
|
/// from an existing `Framed` with the `into_parts` method.
|
||||||
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
||||||
Framed {
|
Framed {
|
||||||
io: parts.io,
|
io: parts.io,
|
||||||
|
@ -358,12 +351,11 @@ impl<T, U> Framed<T, U> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
|
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer with unprocessed data,
|
||||||
/// with unprocessed data, and the codec.
|
/// and the codec.
|
||||||
///
|
///
|
||||||
/// Note that care should be taken to not tamper with the underlying stream
|
/// Note that care should be taken to not tamper with the underlying stream of data coming in as
|
||||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
/// it may corrupt the stream of frames otherwise being worked with.
|
||||||
/// being worked with.
|
|
||||||
pub fn into_parts(self) -> FramedParts<T, U> {
|
pub fn into_parts(self) -> FramedParts<T, U> {
|
||||||
FramedParts {
|
FramedParts {
|
||||||
io: self.io,
|
io: self.io,
|
||||||
|
@ -376,14 +368,15 @@ impl<T, U> Framed<T, U> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `FramedParts` contains an export of the data of a Framed transport.
|
/// `FramedParts` contains an export of the data of a Framed transport.
|
||||||
/// It can be used to construct a new `Framed` with a different codec.
|
///
|
||||||
/// It contains all current buffers and the inner transport.
|
/// It can be used to construct a new `Framed` with a different codec. It contains all current
|
||||||
|
/// buffers and the inner transport.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FramedParts<T, U> {
|
pub struct FramedParts<T, U> {
|
||||||
/// The inner transport used to read bytes to and write bytes to
|
/// The inner transport used to read bytes to and write bytes to.
|
||||||
pub io: T,
|
pub io: T,
|
||||||
|
|
||||||
/// The codec
|
/// The codec object.
|
||||||
pub codec: U,
|
pub codec: U,
|
||||||
|
|
||||||
/// The buffer with read but unprocessed data.
|
/// The buffer with read but unprocessed data.
|
||||||
|
@ -396,7 +389,7 @@ pub struct FramedParts<T, U> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> FramedParts<T, U> {
|
impl<T, U> FramedParts<T, U> {
|
||||||
/// Create a new, default, `FramedParts`
|
/// Creates a new default `FramedParts`.
|
||||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||||
FramedParts {
|
FramedParts {
|
||||||
io,
|
io,
|
||||||
|
@ -407,7 +400,7 @@ impl<T, U> FramedParts<T, U> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `FramedParts` with read buffer
|
/// Creates a new `FramedParts` with read buffer.
|
||||||
pub fn with_read_buf(io: T, codec: U, read_buf: BytesMut) -> FramedParts<T, U> {
|
pub fn with_read_buf(io: T, codec: U, read_buf: BytesMut) -> FramedParts<T, U> {
|
||||||
FramedParts {
|
FramedParts {
|
||||||
io,
|
io,
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
# Changes
|
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
|
||||||
* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
|
|
||||||
* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
|
|
||||||
* Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
|
|
||||||
* Fix `ResourceDef` `PartialEq` implementation.
|
|
||||||
* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
|
|
||||||
* Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
|
|
||||||
* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
|
|
||||||
* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
|
|
||||||
* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
|
|
||||||
* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
|
|
||||||
* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
|
|
||||||
* Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
|
|
||||||
* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
|
|
||||||
* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
|
|
||||||
* Rename `Router::{*_checked => *_fn}`. [#373]
|
|
||||||
* Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
|
|
||||||
* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
|
|
||||||
|
|
||||||
[#368]: https://github.com/actix/actix-net/pull/368
|
|
||||||
[#366]: https://github.com/actix/actix-net/pull/366
|
|
||||||
[#368]: https://github.com/actix/actix-net/pull/368
|
|
||||||
[#370]: https://github.com/actix/actix-net/pull/370
|
|
||||||
[#371]: https://github.com/actix/actix-net/pull/371
|
|
||||||
[#372]: https://github.com/actix/actix-net/pull/372
|
|
||||||
[#373]: https://github.com/actix/actix-net/pull/373
|
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0 - 2021-06-06
|
|
||||||
* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
|
|
||||||
* Path tail patterns now match new lines (`\n`) in request URL. [#360]
|
|
||||||
* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
|
|
||||||
* Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
|
|
||||||
|
|
||||||
[#345]: https://github.com/actix/actix-net/pull/345
|
|
||||||
[#357]: https://github.com/actix/actix-net/pull/357
|
|
||||||
[#359]: https://github.com/actix/actix-net/pull/359
|
|
||||||
[#360]: https://github.com/actix/actix-net/pull/360
|
|
||||||
|
|
||||||
|
|
||||||
## 0.3.0 - 2019-12-31
|
|
||||||
* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.7 - 2021-02-06
|
|
||||||
* Add `Router::recognize_checked` [#247]
|
|
||||||
|
|
||||||
[#247]: https://github.com/actix/actix-net/pull/247
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.6 - 2021-01-09
|
|
||||||
* Use `bytestring` version range compatible with Bytes v1.0. [#246]
|
|
||||||
|
|
||||||
[#246]: https://github.com/actix/actix-net/pull/246
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.5 - 2020-09-20
|
|
||||||
* Fix `from_hex()` method
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.4 - 2019-12-31
|
|
||||||
* Add `ResourceDef::resource_path_named()` path generation method
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.3 - 2019-12-25
|
|
||||||
* Add impl `IntoPattern` for `&String`
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.2 - 2019-12-25
|
|
||||||
* Use `IntoPattern` for `RouterBuilder::path()`
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.1 - 2019-12-25
|
|
||||||
* Add `IntoPattern` trait
|
|
||||||
* Add multi-pattern resources
|
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0 - 2019-12-07
|
|
||||||
* Update http to 0.2
|
|
||||||
* Update regex to 1.3
|
|
||||||
* Use bytestring instead of string
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.5 - 2019-05-15
|
|
||||||
* Remove debug prints
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.4 - 2019-05-15
|
|
||||||
* Fix checked resource match
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.3 - 2019-04-22
|
|
||||||
* Added support for `remainder match` (i.e "/path/{tail}*")
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.2 - 2019-04-07
|
|
||||||
* Export `Quoter` type
|
|
||||||
* Allow to reset `Path` instance
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.1 - 2019-04-03
|
|
||||||
* Get dynamic segment by name instead of iterator.
|
|
||||||
|
|
||||||
|
|
||||||
## 0.1.0 - 2019-03-09
|
|
||||||
* Initial release
|
|
|
@ -1,38 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "actix-router"
|
|
||||||
version = "0.4.0"
|
|
||||||
authors = [
|
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
|
||||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
|
||||||
]
|
|
||||||
description = "Resource path matching library"
|
|
||||||
keywords = ["actix", "router", "routing"]
|
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "actix_router"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["http"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytestring = ">=0.1.5, <2"
|
|
||||||
firestorm = "0.4"
|
|
||||||
http = { version = "0.2.3", optional = true }
|
|
||||||
log = "0.4"
|
|
||||||
regex = "1.5"
|
|
||||||
serde = "1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
|
||||||
firestorm = { version = "0.4", features = ["enable_system_time"] }
|
|
||||||
http = "0.2.3"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "router"
|
|
||||||
harness = false
|
|
|
@ -1 +0,0 @@
|
||||||
../LICENSE-APACHE
|
|
|
@ -1 +0,0 @@
|
||||||
../LICENSE-MIT
|
|
|
@ -1,194 +0,0 @@
|
||||||
//! Based on https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs
|
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|
||||||
|
|
||||||
macro_rules! register {
|
|
||||||
(colon) => {{
|
|
||||||
register!(finish => ":p1", ":p2", ":p3", ":p4")
|
|
||||||
}};
|
|
||||||
(brackets) => {{
|
|
||||||
register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
|
|
||||||
}};
|
|
||||||
(regex) => {{
|
|
||||||
register!(finish => "(.*)", "(.*)", "(.*)", "(.*)")
|
|
||||||
}};
|
|
||||||
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
|
|
||||||
let arr = [
|
|
||||||
concat!("/authorizations"),
|
|
||||||
concat!("/authorizations/", $p1),
|
|
||||||
concat!("/applications/", $p1, "/tokens/", $p2),
|
|
||||||
concat!("/events"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/events"),
|
|
||||||
concat!("/networks/", $p1, "/", $p2, "/events"),
|
|
||||||
concat!("/orgs/", $p1, "/events"),
|
|
||||||
concat!("/users/", $p1, "/received_events"),
|
|
||||||
concat!("/users/", $p1, "/received_events/public"),
|
|
||||||
concat!("/users/", $p1, "/events"),
|
|
||||||
concat!("/users/", $p1, "/events/public"),
|
|
||||||
concat!("/users/", $p1, "/events/orgs/", $p2),
|
|
||||||
concat!("/feeds"),
|
|
||||||
concat!("/notifications"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/notifications"),
|
|
||||||
concat!("/notifications/threads/", $p1),
|
|
||||||
concat!("/notifications/threads/", $p1, "/subscription"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stargazers"),
|
|
||||||
concat!("/users/", $p1, "/starred"),
|
|
||||||
concat!("/user/starred"),
|
|
||||||
concat!("/user/starred/", $p1, "/", $p2),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/subscribers"),
|
|
||||||
concat!("/users/", $p1, "/subscriptions"),
|
|
||||||
concat!("/user/subscriptions"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/subscription"),
|
|
||||||
concat!("/user/subscriptions/", $p1, "/", $p2),
|
|
||||||
concat!("/users/", $p1, "/gists"),
|
|
||||||
concat!("/gists"),
|
|
||||||
concat!("/gists/", $p1),
|
|
||||||
concat!("/gists/", $p1, "/star"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/refs"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
|
|
||||||
concat!("/issues"),
|
|
||||||
concat!("/user/issues"),
|
|
||||||
concat!("/orgs/", $p1, "/issues"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/assignees"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
|
|
||||||
concat!("/emojis"),
|
|
||||||
concat!("/gitignore/templates"),
|
|
||||||
concat!("/gitignore/templates/", $p1),
|
|
||||||
concat!("/meta"),
|
|
||||||
concat!("/rate_limit"),
|
|
||||||
concat!("/users/", $p1, "/orgs"),
|
|
||||||
concat!("/user/orgs"),
|
|
||||||
concat!("/orgs/", $p1),
|
|
||||||
concat!("/orgs/", $p1, "/members"),
|
|
||||||
concat!("/orgs/", $p1, "/members", $p2),
|
|
||||||
concat!("/orgs/", $p1, "/public_members"),
|
|
||||||
concat!("/orgs/", $p1, "/public_members/", $p2),
|
|
||||||
concat!("/orgs/", $p1, "/teams"),
|
|
||||||
concat!("/teams/", $p1),
|
|
||||||
concat!("/teams/", $p1, "/members"),
|
|
||||||
concat!("/teams/", $p1, "/members", $p2),
|
|
||||||
concat!("/teams/", $p1, "/repos"),
|
|
||||||
concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
|
|
||||||
concat!("/user/teams"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
|
|
||||||
concat!("/user/repos"),
|
|
||||||
concat!("/users/", $p1, "/repos"),
|
|
||||||
concat!("/orgs/", $p1, "/repos"),
|
|
||||||
concat!("/repositories"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/contributors"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/languages"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/teams"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/tags"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/branches"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/collaborators"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/readme"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/keys"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/downloads"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/forks"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/hooks"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
|
|
||||||
concat!("/search/repositories"),
|
|
||||||
concat!("/search/code"),
|
|
||||||
concat!("/search/issues"),
|
|
||||||
concat!("/search/users"),
|
|
||||||
concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
|
|
||||||
concat!("/legacy/repos/search/", $p1),
|
|
||||||
concat!("/legacy/user/search/", $p1),
|
|
||||||
concat!("/legacy/user/email/", $p1),
|
|
||||||
concat!("/users/", $p1),
|
|
||||||
concat!("/user"),
|
|
||||||
concat!("/users"),
|
|
||||||
concat!("/user/emails"),
|
|
||||||
concat!("/users/", $p1, "/followers"),
|
|
||||||
concat!("/user/followers"),
|
|
||||||
concat!("/users/", $p1, "/following"),
|
|
||||||
concat!("/user/following"),
|
|
||||||
concat!("/user/following/", $p1),
|
|
||||||
concat!("/users/", $p1, "/following", $p2),
|
|
||||||
concat!("/users/", $p1, "/keys"),
|
|
||||||
concat!("/user/keys"),
|
|
||||||
concat!("/user/keys/", $p1),
|
|
||||||
];
|
|
||||||
std::array::IntoIter::new(arr)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call() -> impl Iterator<Item = &'static str> {
|
|
||||||
let arr = [
|
|
||||||
"/authorizations",
|
|
||||||
"/user/repos",
|
|
||||||
"/repos/rust-lang/rust/stargazers",
|
|
||||||
"/orgs/rust-lang/public_members/nikomatsakis",
|
|
||||||
"/repos/rust-lang/rust/releases/1.51.0",
|
|
||||||
];
|
|
||||||
|
|
||||||
std::array::IntoIter::new(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compare_routers(c: &mut Criterion) {
|
|
||||||
let mut group = c.benchmark_group("Compare Routers");
|
|
||||||
|
|
||||||
let mut actix = actix_router::Router::<bool>::build();
|
|
||||||
for route in register!(brackets) {
|
|
||||||
actix.path(route, true);
|
|
||||||
}
|
|
||||||
let actix = actix.finish();
|
|
||||||
group.bench_function("actix", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
for route in call() {
|
|
||||||
let mut path = actix_router::Path::new(route);
|
|
||||||
black_box(actix.recognize(&mut path).unwrap());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let regex_set = regex::RegexSet::new(register!(regex)).unwrap();
|
|
||||||
group.bench_function("regex", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
for route in call() {
|
|
||||||
black_box(regex_set.matches(route));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, compare_routers);
|
|
||||||
criterion_main!(benches);
|
|
|
@ -1,169 +0,0 @@
|
||||||
macro_rules! register {
|
|
||||||
(brackets) => {{
|
|
||||||
register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
|
|
||||||
}};
|
|
||||||
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
|
|
||||||
let arr = [
|
|
||||||
concat!("/authorizations"),
|
|
||||||
concat!("/authorizations/", $p1),
|
|
||||||
concat!("/applications/", $p1, "/tokens/", $p2),
|
|
||||||
concat!("/events"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/events"),
|
|
||||||
concat!("/networks/", $p1, "/", $p2, "/events"),
|
|
||||||
concat!("/orgs/", $p1, "/events"),
|
|
||||||
concat!("/users/", $p1, "/received_events"),
|
|
||||||
concat!("/users/", $p1, "/received_events/public"),
|
|
||||||
concat!("/users/", $p1, "/events"),
|
|
||||||
concat!("/users/", $p1, "/events/public"),
|
|
||||||
concat!("/users/", $p1, "/events/orgs/", $p2),
|
|
||||||
concat!("/feeds"),
|
|
||||||
concat!("/notifications"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/notifications"),
|
|
||||||
concat!("/notifications/threads/", $p1),
|
|
||||||
concat!("/notifications/threads/", $p1, "/subscription"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stargazers"),
|
|
||||||
concat!("/users/", $p1, "/starred"),
|
|
||||||
concat!("/user/starred"),
|
|
||||||
concat!("/user/starred/", $p1, "/", $p2),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/subscribers"),
|
|
||||||
concat!("/users/", $p1, "/subscriptions"),
|
|
||||||
concat!("/user/subscriptions"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/subscription"),
|
|
||||||
concat!("/user/subscriptions/", $p1, "/", $p2),
|
|
||||||
concat!("/users/", $p1, "/gists"),
|
|
||||||
concat!("/gists"),
|
|
||||||
concat!("/gists/", $p1),
|
|
||||||
concat!("/gists/", $p1, "/star"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/refs"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
|
|
||||||
concat!("/issues"),
|
|
||||||
concat!("/user/issues"),
|
|
||||||
concat!("/orgs/", $p1, "/issues"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/assignees"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
|
|
||||||
concat!("/emojis"),
|
|
||||||
concat!("/gitignore/templates"),
|
|
||||||
concat!("/gitignore/templates/", $p1),
|
|
||||||
concat!("/meta"),
|
|
||||||
concat!("/rate_limit"),
|
|
||||||
concat!("/users/", $p1, "/orgs"),
|
|
||||||
concat!("/user/orgs"),
|
|
||||||
concat!("/orgs/", $p1),
|
|
||||||
concat!("/orgs/", $p1, "/members"),
|
|
||||||
concat!("/orgs/", $p1, "/members", $p2),
|
|
||||||
concat!("/orgs/", $p1, "/public_members"),
|
|
||||||
concat!("/orgs/", $p1, "/public_members/", $p2),
|
|
||||||
concat!("/orgs/", $p1, "/teams"),
|
|
||||||
concat!("/teams/", $p1),
|
|
||||||
concat!("/teams/", $p1, "/members"),
|
|
||||||
concat!("/teams/", $p1, "/members", $p2),
|
|
||||||
concat!("/teams/", $p1, "/repos"),
|
|
||||||
concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
|
|
||||||
concat!("/user/teams"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
|
|
||||||
concat!("/user/repos"),
|
|
||||||
concat!("/users/", $p1, "/repos"),
|
|
||||||
concat!("/orgs/", $p1, "/repos"),
|
|
||||||
concat!("/repositories"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/contributors"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/languages"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/teams"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/tags"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/branches"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/collaborators"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/readme"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/keys"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/downloads"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/forks"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/hooks"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
|
|
||||||
concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
|
|
||||||
concat!("/search/repositories"),
|
|
||||||
concat!("/search/code"),
|
|
||||||
concat!("/search/issues"),
|
|
||||||
concat!("/search/users"),
|
|
||||||
concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
|
|
||||||
concat!("/legacy/repos/search/", $p1),
|
|
||||||
concat!("/legacy/user/search/", $p1),
|
|
||||||
concat!("/legacy/user/email/", $p1),
|
|
||||||
concat!("/users/", $p1),
|
|
||||||
concat!("/user"),
|
|
||||||
concat!("/users"),
|
|
||||||
concat!("/user/emails"),
|
|
||||||
concat!("/users/", $p1, "/followers"),
|
|
||||||
concat!("/user/followers"),
|
|
||||||
concat!("/users/", $p1, "/following"),
|
|
||||||
concat!("/user/following"),
|
|
||||||
concat!("/user/following/", $p1),
|
|
||||||
concat!("/users/", $p1, "/following", $p2),
|
|
||||||
concat!("/users/", $p1, "/keys"),
|
|
||||||
concat!("/user/keys"),
|
|
||||||
concat!("/user/keys/", $p1),
|
|
||||||
];
|
|
||||||
|
|
||||||
arr.to_vec()
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
static PATHS: [&str; 5] = [
|
|
||||||
"/authorizations",
|
|
||||||
"/user/repos",
|
|
||||||
"/repos/rust-lang/rust/stargazers",
|
|
||||||
"/orgs/rust-lang/public_members/nikomatsakis",
|
|
||||||
"/repos/rust-lang/rust/releases/1.51.0",
|
|
||||||
];
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut router = actix_router::Router::<bool>::build();
|
|
||||||
|
|
||||||
for route in register!(brackets) {
|
|
||||||
router.path(route, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actix = router.finish();
|
|
||||||
|
|
||||||
if firestorm::enabled() {
|
|
||||||
firestorm::bench("target", || {
|
|
||||||
for &route in &PATHS {
|
|
||||||
let mut path = actix_router::Path::new(route);
|
|
||||||
actix.recognize(&mut path).unwrap();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,723 +0,0 @@
|
||||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
|
||||||
use serde::forward_to_deserialize_any;
|
|
||||||
|
|
||||||
use crate::path::{Path, PathIter};
|
|
||||||
use crate::ResourcePath;
|
|
||||||
|
|
||||||
macro_rules! unsupported_type {
|
|
||||||
($trait_fn:ident, $name:expr) => {
|
|
||||||
fn $trait_fn<V>(self, _: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom(concat!(
|
|
||||||
"unsupported type: ",
|
|
||||||
$name
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! parse_single_value {
|
|
||||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
|
||||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.segment_count() != 1 {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!(
|
|
||||||
"wrong number of parameters: {} expected 1",
|
|
||||||
self.path.segment_count()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
let v = self.path[0].parse().map_err(|_| {
|
|
||||||
de::value::Error::custom(format!(
|
|
||||||
"can not parse {:?} to a {}",
|
|
||||||
&self.path[0], $tp
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
visitor.$visit_fn(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PathDeserializer<'de, T: ResourcePath> {
|
|
||||||
path: &'de Path<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath + 'de> PathDeserializer<'de, T> {
|
|
||||||
pub fn new(path: &'de Path<T>) -> Self {
|
|
||||||
PathDeserializer { path }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_map(ParamsDeserializer {
|
|
||||||
params: self.path.iter(),
|
|
||||||
current: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
self.deserialize_map(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
self.deserialize_unit(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_newtype_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_newtype_struct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.segment_count() < len {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!(
|
|
||||||
"wrong number of parameters: {} expected {}",
|
|
||||||
self.path.segment_count(),
|
|
||||||
len
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
visitor.visit_seq(ParamsSeq {
|
|
||||||
params: self.path.iter(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
len: usize,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.segment_count() < len {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!(
|
|
||||||
"wrong number of parameters: {} expected {}",
|
|
||||||
self.path.segment_count(),
|
|
||||||
len
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
visitor.visit_seq(ParamsSeq {
|
|
||||||
params: self.path.iter(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_enum<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.is_empty() {
|
|
||||||
Err(de::value::Error::custom("expected at least one parameters"))
|
|
||||||
} else {
|
|
||||||
visitor.visit_enum(ValueEnum {
|
|
||||||
value: &self.path[0],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.path.segment_count() != 1 {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!(
|
|
||||||
"wrong number of parameters: {} expected 1",
|
|
||||||
self.path.segment_count()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
visitor.visit_str(&self.path[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_seq(ParamsSeq {
|
|
||||||
params: self.path.iter(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
unsupported_type!(deserialize_any, "'any'");
|
|
||||||
unsupported_type!(deserialize_bytes, "bytes");
|
|
||||||
unsupported_type!(deserialize_option, "Option<T>");
|
|
||||||
unsupported_type!(deserialize_identifier, "identifier");
|
|
||||||
unsupported_type!(deserialize_ignored_any, "ignored_any");
|
|
||||||
|
|
||||||
parse_single_value!(deserialize_bool, visit_bool, "bool");
|
|
||||||
parse_single_value!(deserialize_i8, visit_i8, "i8");
|
|
||||||
parse_single_value!(deserialize_i16, visit_i16, "i16");
|
|
||||||
parse_single_value!(deserialize_i32, visit_i32, "i32");
|
|
||||||
parse_single_value!(deserialize_i64, visit_i64, "i64");
|
|
||||||
parse_single_value!(deserialize_u8, visit_u8, "u8");
|
|
||||||
parse_single_value!(deserialize_u16, visit_u16, "u16");
|
|
||||||
parse_single_value!(deserialize_u32, visit_u32, "u32");
|
|
||||||
parse_single_value!(deserialize_u64, visit_u64, "u64");
|
|
||||||
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
|
||||||
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
|
||||||
parse_single_value!(deserialize_string, visit_string, "String");
|
|
||||||
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
|
||||||
parse_single_value!(deserialize_char, visit_char, "char");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ParamsDeserializer<'de, T: ResourcePath> {
|
|
||||||
params: PathIter<'de, T>,
|
|
||||||
current: Option<(&'de str, &'de str)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath> de::MapAccess<'de> for ParamsDeserializer<'de, T> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
|
|
||||||
where
|
|
||||||
K: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
self.current = self.params.next().map(|ref item| (item.0, item.1));
|
|
||||||
match self.current {
|
|
||||||
Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
if let Some((_, value)) = self.current.take() {
|
|
||||||
seed.deserialize(Value { value })
|
|
||||||
} else {
|
|
||||||
Err(de::value::Error::custom("unexpected item"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Key<'de> {
|
|
||||||
key: &'de str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for Key<'de> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_str(self.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("Unexpected"))
|
|
||||||
}
|
|
||||||
|
|
||||||
forward_to_deserialize_any! {
|
|
||||||
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
|
||||||
byte_buf option unit unit_struct newtype_struct seq tuple
|
|
||||||
tuple_struct map struct enum ignored_any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! parse_value {
|
|
||||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
|
||||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
let v = self.value.parse().map_err(|_| {
|
|
||||||
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
|
|
||||||
})?;
|
|
||||||
visitor.$visit_fn(v)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Value<'de> {
|
|
||||||
value: &'de str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for Value<'de> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
parse_value!(deserialize_bool, visit_bool, "bool");
|
|
||||||
parse_value!(deserialize_i8, visit_i8, "i8");
|
|
||||||
parse_value!(deserialize_i16, visit_i16, "i16");
|
|
||||||
parse_value!(deserialize_i32, visit_i32, "i16");
|
|
||||||
parse_value!(deserialize_i64, visit_i64, "i64");
|
|
||||||
parse_value!(deserialize_u8, visit_u8, "u8");
|
|
||||||
parse_value!(deserialize_u16, visit_u16, "u16");
|
|
||||||
parse_value!(deserialize_u32, visit_u32, "u32");
|
|
||||||
parse_value!(deserialize_u64, visit_u64, "u64");
|
|
||||||
parse_value!(deserialize_f32, visit_f32, "f32");
|
|
||||||
parse_value!(deserialize_f64, visit_f64, "f64");
|
|
||||||
parse_value!(deserialize_string, visit_string, "String");
|
|
||||||
parse_value!(deserialize_byte_buf, visit_string, "String");
|
|
||||||
parse_value!(deserialize_char, visit_char, "char");
|
|
||||||
|
|
||||||
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_unit_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_unit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_borrowed_bytes(self.value.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_borrowed_str(self.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_enum<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_enum(ValueEnum { value: self.value })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_newtype_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
visitor: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
visitor.visit_newtype_struct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("unsupported type: tuple"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
_: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("unsupported type: struct"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_tuple_struct<V>(
|
|
||||||
self,
|
|
||||||
_: &'static str,
|
|
||||||
_: usize,
|
|
||||||
_: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("unsupported type: tuple struct"))
|
|
||||||
}
|
|
||||||
|
|
||||||
unsupported_type!(deserialize_any, "any");
|
|
||||||
unsupported_type!(deserialize_seq, "seq");
|
|
||||||
unsupported_type!(deserialize_map, "map");
|
|
||||||
unsupported_type!(deserialize_identifier, "identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ParamsSeq<'de, T: ResourcePath> {
|
|
||||||
params: PathIter<'de, T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, T: ResourcePath> de::SeqAccess<'de> for ParamsSeq<'de, T> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn next_element_seed<U>(&mut self, seed: U) -> Result<Option<U::Value>, Self::Error>
|
|
||||||
where
|
|
||||||
U: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
match self.params.next() {
|
|
||||||
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ValueEnum<'de> {
|
|
||||||
value: &'de str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
type Variant = UnitVariant;
|
|
||||||
|
|
||||||
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
|
|
||||||
where
|
|
||||||
V: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UnitVariant;
|
|
||||||
|
|
||||||
impl<'de> de::VariantAccess<'de> for UnitVariant {
|
|
||||||
type Error = de::value::Error;
|
|
||||||
|
|
||||||
fn unit_variant(self) -> Result<(), Self::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
|
|
||||||
where
|
|
||||||
T: de::DeserializeSeed<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("not supported"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("not supported"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn struct_variant<V>(
|
|
||||||
self,
|
|
||||||
_: &'static [&'static str],
|
|
||||||
_: V,
|
|
||||||
) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
Err(de::value::Error::custom("not supported"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use serde::{de, Deserialize};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::path::Path;
|
|
||||||
use crate::router::Router;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct MyStruct {
|
|
||||||
key: String,
|
|
||||||
value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Id {
|
|
||||||
_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Test1(String, u32);
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Test2 {
|
|
||||||
key: String,
|
|
||||||
value: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
enum TestEnum {
|
|
||||||
Val1,
|
|
||||||
Val2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Test3 {
|
|
||||||
val: TestEnum,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_request_extract() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{key}/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/user1/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
|
|
||||||
let s: MyStruct = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.key, "name");
|
|
||||||
assert_eq!(s.value, "user1");
|
|
||||||
|
|
||||||
let s: (String, String) =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.0, "name");
|
|
||||||
assert_eq!(s.1, "user1");
|
|
||||||
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{key}/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/32/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
|
|
||||||
let s: Test1 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.0, "name");
|
|
||||||
assert_eq!(s.1, 32);
|
|
||||||
|
|
||||||
let s: Test2 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.key, "name");
|
|
||||||
assert_eq!(s.value, 32);
|
|
||||||
|
|
||||||
let s: (String, u8) =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(s.0, "name");
|
|
||||||
assert_eq!(s.1, 32);
|
|
||||||
|
|
||||||
let res: Vec<String> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(res[0], "name".to_owned());
|
|
||||||
assert_eq!(res[1], "32".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_path_single() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/32/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: i8 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_enum() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{val}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/val1/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: TestEnum = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i, TestEnum::Val1);
|
|
||||||
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{val1}/{val2}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/val1/val2/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: (TestEnum, TestEnum) =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i, (TestEnum::Val1, TestEnum::Val2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_enum_value() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{val}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/val1/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: Test3 = de::Deserialize::deserialize(PathDeserializer::new(&path)).unwrap();
|
|
||||||
assert_eq!(i.val, TestEnum::Val1);
|
|
||||||
|
|
||||||
let mut path = Path::new("/val3/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
let i: Result<Test3, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(i.is_err());
|
|
||||||
assert!(format!("{:?}", i).contains("unknown variant"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extract_errors() {
|
|
||||||
let mut router = Router::<()>::build();
|
|
||||||
router.path("/{value}/", ());
|
|
||||||
let router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/");
|
|
||||||
assert!(router.recognize(&mut path).is_some());
|
|
||||||
|
|
||||||
let s: Result<Test1, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("wrong number of parameters"));
|
|
||||||
|
|
||||||
let s: Result<Test2, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("can not parse"));
|
|
||||||
|
|
||||||
let s: Result<(String, String), de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("wrong number of parameters"));
|
|
||||||
|
|
||||||
let s: Result<u32, de::value::Error> =
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&path));
|
|
||||||
assert!(s.is_err());
|
|
||||||
assert!(format!("{:?}", s).contains("can not parse"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_extract_path_decode() {
|
|
||||||
// let mut router = Router::<()>::default();
|
|
||||||
// router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
|
||||||
|
|
||||||
// macro_rules! test_single_value {
|
|
||||||
// ($value:expr, $expected:expr) => {{
|
|
||||||
// let req = TestRequest::with_uri($value).finish();
|
|
||||||
// let info = router.recognize(&req, &(), 0);
|
|
||||||
// let req = req.with_route_info(info);
|
|
||||||
// assert_eq!(
|
|
||||||
// *Path::<String>::from_request(&req, &PathConfig::default()).unwrap(),
|
|
||||||
// $expected
|
|
||||||
// );
|
|
||||||
// }};
|
|
||||||
// }
|
|
||||||
|
|
||||||
// test_single_value!("/%25/", "%");
|
|
||||||
// test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+=");
|
|
||||||
// test_single_value!("/%2B/", "+");
|
|
||||||
// test_single_value!("/%252B/", "%2B");
|
|
||||||
// test_single_value!("/%2F/", "/");
|
|
||||||
// test_single_value!("/%252F/", "%2F");
|
|
||||||
// test_single_value!(
|
|
||||||
// "/http%3A%2F%2Flocalhost%3A80%2Ffoo/",
|
|
||||||
// "http://localhost:80/foo"
|
|
||||||
// );
|
|
||||||
// test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog");
|
|
||||||
// test_single_value!(
|
|
||||||
// "/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/",
|
|
||||||
// "http://localhost:80/file/%2Fvar%2Flog%2Fsyslog"
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let req = TestRequest::with_uri("/%25/7/?id=test").finish();
|
|
||||||
|
|
||||||
// let mut router = Router::<()>::default();
|
|
||||||
// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
|
|
||||||
// let info = router.recognize(&req, &(), 0);
|
|
||||||
// let req = req.with_route_info(info);
|
|
||||||
|
|
||||||
// let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
|
|
||||||
// assert_eq!(s.key, "%");
|
|
||||||
// assert_eq!(s.value, 7);
|
|
||||||
|
|
||||||
// let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
|
|
||||||
// assert_eq!(s.0, "%");
|
|
||||||
// assert_eq!(s.1, "7");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_extract_path_no_decode() {
|
|
||||||
// let mut router = Router::<()>::default();
|
|
||||||
// router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
|
||||||
|
|
||||||
// let req = TestRequest::with_uri("/%25/").finish();
|
|
||||||
// let info = router.recognize(&req, &(), 0);
|
|
||||||
// let req = req.with_route_info(info);
|
|
||||||
// assert_eq!(
|
|
||||||
// *Path::<String>::from_request(&req, &&PathConfig::default().disable_decoding())
|
|
||||||
// .unwrap(),
|
|
||||||
// "%25"
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
//! Resource path matching library.
|
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
|
||||||
|
|
||||||
mod de;
|
|
||||||
mod path;
|
|
||||||
mod resource;
|
|
||||||
mod router;
|
|
||||||
|
|
||||||
pub use self::de::PathDeserializer;
|
|
||||||
pub use self::path::Path;
|
|
||||||
pub use self::resource::ResourceDef;
|
|
||||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
|
||||||
|
|
||||||
// TODO: this trait is necessary, document it
|
|
||||||
// see impl Resource for ServiceRequest
|
|
||||||
pub trait Resource<T: ResourcePath> {
|
|
||||||
fn resource_path(&mut self) -> &mut Path<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ResourcePath {
|
|
||||||
fn path(&self) -> &str;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for String {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ResourcePath for &'a str {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for bytestring::ByteString {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
&*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One or many patterns.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Patterns {
|
|
||||||
Single(String),
|
|
||||||
List(Vec<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper trait for type that could be converted to one or more path pattern.
|
|
||||||
pub trait IntoPatterns {
|
|
||||||
fn patterns(&self) -> Patterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoPatterns for String {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoPatterns for &'a String {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single((*self).clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoPatterns for &'a str {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single((*self).to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoPatterns for bytestring::ByteString {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single(self.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<str>> IntoPatterns for Vec<T> {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
let mut patterns = self.iter().map(|v| v.as_ref().to_owned());
|
|
||||||
|
|
||||||
match patterns.size_hint() {
|
|
||||||
(1, _) => Patterns::Single(patterns.next().unwrap()),
|
|
||||||
_ => Patterns::List(patterns.collect()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! array_patterns_single (($tp:ty) => {
|
|
||||||
impl IntoPatterns for [$tp; 1] {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::Single(self[0].to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => {
|
|
||||||
// for each array length specified in $num
|
|
||||||
$(
|
|
||||||
impl IntoPatterns for [$tp; $num] {
|
|
||||||
fn patterns(&self) -> Patterns {
|
|
||||||
Patterns::List(self.iter().map($str_fn).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
});
|
|
||||||
|
|
||||||
array_patterns_single!(&str);
|
|
||||||
array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
|
||||||
|
|
||||||
array_patterns_single!(String);
|
|
||||||
array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
mod url;
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
pub use self::url::{Quoter, Url};
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
|
||||||
mod http_impls {
|
|
||||||
use http::Uri;
|
|
||||||
|
|
||||||
use super::ResourcePath;
|
|
||||||
|
|
||||||
impl ResourcePath for Uri {
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,220 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::ops::Index;
|
|
||||||
|
|
||||||
use firestorm::profile_method;
|
|
||||||
use serde::de;
|
|
||||||
|
|
||||||
use crate::{de::PathDeserializer, Resource, ResourcePath};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) enum PathItem {
|
|
||||||
Static(Cow<'static, str>),
|
|
||||||
Segment(u16, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PathItem {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Static(Cow::Borrowed(""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource path match information.
|
|
||||||
///
|
|
||||||
/// If resource path contains variable patterns, `Path` stores them.
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct Path<T> {
|
|
||||||
path: T,
|
|
||||||
pub(crate) skip: u16,
|
|
||||||
pub(crate) segments: Vec<(Cow<'static, str>, PathItem)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ResourcePath> Path<T> {
|
|
||||||
pub fn new(path: T) -> Path<T> {
|
|
||||||
Path {
|
|
||||||
path,
|
|
||||||
skip: 0,
|
|
||||||
segments: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get reference to inner path instance.
|
|
||||||
#[inline]
|
|
||||||
pub fn get_ref(&self) -> &T {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get mutable reference to inner path instance.
|
|
||||||
#[inline]
|
|
||||||
pub fn get_mut(&mut self) -> &mut T {
|
|
||||||
&mut self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Path.
|
|
||||||
#[inline]
|
|
||||||
pub fn path(&self) -> &str {
|
|
||||||
profile_method!(path);
|
|
||||||
|
|
||||||
let skip = self.skip as usize;
|
|
||||||
let path = self.path.path();
|
|
||||||
if skip <= path.len() {
|
|
||||||
&path[skip..]
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set new path.
|
|
||||||
#[inline]
|
|
||||||
pub fn set(&mut self, path: T) {
|
|
||||||
self.skip = 0;
|
|
||||||
self.path = path;
|
|
||||||
self.segments.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset state.
|
|
||||||
#[inline]
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.skip = 0;
|
|
||||||
self.segments.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skip first `n` chars in path.
|
|
||||||
#[inline]
|
|
||||||
pub fn skip(&mut self, n: u16) {
|
|
||||||
self.skip += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
|
|
||||||
profile_method!(add);
|
|
||||||
|
|
||||||
match value {
|
|
||||||
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
|
|
||||||
PathItem::Segment(begin, end) => self.segments.push((
|
|
||||||
name.into(),
|
|
||||||
PathItem::Segment(self.skip + begin, self.skip + end),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn add_static(
|
|
||||||
&mut self,
|
|
||||||
name: impl Into<Cow<'static, str>>,
|
|
||||||
value: impl Into<Cow<'static, str>>,
|
|
||||||
) {
|
|
||||||
self.segments
|
|
||||||
.push((name.into(), PathItem::Static(value.into())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if there are any matched patterns.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.segments.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns number of interpolated segments.
|
|
||||||
#[inline]
|
|
||||||
pub fn segment_count(&self) -> usize {
|
|
||||||
self.segments.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get matched parameter by name without type conversion
|
|
||||||
pub fn get(&self, name: &str) -> Option<&str> {
|
|
||||||
profile_method!(get);
|
|
||||||
|
|
||||||
for (seg_name, val) in self.segments.iter() {
|
|
||||||
if name == seg_name {
|
|
||||||
return match val {
|
|
||||||
PathItem::Static(ref s) => Some(&s),
|
|
||||||
PathItem::Segment(s, e) => {
|
|
||||||
Some(&self.path.path()[(*s as usize)..(*e as usize)])
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get unprocessed part of the path
|
|
||||||
pub fn unprocessed(&self) -> &str {
|
|
||||||
&self.path.path()[(self.skip as usize)..]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get matched parameter by name.
|
|
||||||
///
|
|
||||||
/// If keyed parameter is not available empty string is used as default value.
|
|
||||||
pub fn query(&self, key: &str) -> &str {
|
|
||||||
profile_method!(query);
|
|
||||||
|
|
||||||
if let Some(s) = self.get(key) {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return iterator to items in parameter container.
|
|
||||||
pub fn iter(&self) -> PathIter<'_, T> {
|
|
||||||
PathIter {
|
|
||||||
idx: 0,
|
|
||||||
params: self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to deserialize matching parameters to a specified type `U`
|
|
||||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
|
||||||
profile_method!(load);
|
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PathIter<'a, T> {
|
|
||||||
idx: usize,
|
|
||||||
params: &'a Path<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
|
|
||||||
type Item = (&'a str, &'a str);
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Option<(&'a str, &'a str)> {
|
|
||||||
if self.idx < self.params.segment_count() {
|
|
||||||
let idx = self.idx;
|
|
||||||
let res = match self.params.segments[idx].1 {
|
|
||||||
PathItem::Static(ref s) => &s,
|
|
||||||
PathItem::Segment(s, e) => &self.params.path.path()[(s as usize)..(e as usize)],
|
|
||||||
};
|
|
||||||
self.idx += 1;
|
|
||||||
return Some((&self.params.segments[idx].0, res));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ResourcePath> Index<&'a str> for Path<T> {
|
|
||||||
type Output = str;
|
|
||||||
|
|
||||||
fn index(&self, name: &'a str) -> &str {
|
|
||||||
self.get(name)
|
|
||||||
.expect("Value for parameter is not available")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ResourcePath> Index<usize> for Path<T> {
|
|
||||||
type Output = str;
|
|
||||||
|
|
||||||
fn index(&self, idx: usize) -> &str {
|
|
||||||
match self.segments[idx].1 {
|
|
||||||
PathItem::Static(ref s) => &s,
|
|
||||||
PathItem::Segment(s, e) => &self.path.path()[(s as usize)..(e as usize)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ResourcePath> Resource<T> for Path<T> {
|
|
||||||
fn resource_path(&mut self) -> &mut Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,275 +0,0 @@
|
||||||
use firestorm::profile_method;
|
|
||||||
|
|
||||||
use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct ResourceId(pub u16);
|
|
||||||
|
|
||||||
/// Information about current resource
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ResourceInfo {
|
|
||||||
resource: ResourceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resource router.
|
|
||||||
pub struct Router<T, U = ()>(Vec<(ResourceDef, T, Option<U>)>);
|
|
||||||
|
|
||||||
impl<T, U> Router<T, U> {
|
|
||||||
pub fn build() -> RouterBuilder<T, U> {
|
|
||||||
RouterBuilder {
|
|
||||||
resources: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize<R, P>(&self, resource: &mut R) -> Option<(&T, ResourceId)>
|
|
||||||
where
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
profile_method!(recognize);
|
|
||||||
|
|
||||||
for item in self.0.iter() {
|
|
||||||
if item.0.capture_match_info(resource.resource_path()) {
|
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_mut<R, P>(&mut self, resource: &mut R) -> Option<(&mut T, ResourceId)>
|
|
||||||
where
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
profile_method!(recognize_mut);
|
|
||||||
|
|
||||||
for item in self.0.iter_mut() {
|
|
||||||
if item.0.capture_match_info(resource.resource_path()) {
|
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_fn<R, P, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
|
|
||||||
where
|
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
profile_method!(recognize_checked);
|
|
||||||
|
|
||||||
for item in self.0.iter() {
|
|
||||||
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recognize_mut_fn<R, P, F>(
|
|
||||||
&mut self,
|
|
||||||
resource: &mut R,
|
|
||||||
check: F,
|
|
||||||
) -> Option<(&mut T, ResourceId)>
|
|
||||||
where
|
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
|
||||||
R: Resource<P>,
|
|
||||||
P: ResourcePath,
|
|
||||||
{
|
|
||||||
profile_method!(recognize_mut_checked);
|
|
||||||
|
|
||||||
for item in self.0.iter_mut() {
|
|
||||||
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RouterBuilder<T, U = ()> {
|
|
||||||
resources: Vec<(ResourceDef, T, Option<U>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> RouterBuilder<T, U> {
|
|
||||||
/// Register resource for specified path.
|
|
||||||
pub fn path<P: IntoPatterns>(
|
|
||||||
&mut self,
|
|
||||||
path: P,
|
|
||||||
resource: T,
|
|
||||||
) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
profile_method!(path);
|
|
||||||
|
|
||||||
self.resources
|
|
||||||
.push((ResourceDef::new(path), resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register resource for specified path prefix.
|
|
||||||
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
profile_method!(prefix);
|
|
||||||
|
|
||||||
self.resources
|
|
||||||
.push((ResourceDef::prefix(prefix), resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register resource for ResourceDef
|
|
||||||
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
|
||||||
profile_method!(rdef);
|
|
||||||
|
|
||||||
self.resources.push((rdef, resource, None));
|
|
||||||
self.resources.last_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish configuration and create router instance.
|
|
||||||
pub fn finish(self) -> Router<T, U> {
|
|
||||||
Router(self.resources)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::path::Path;
|
|
||||||
use crate::router::{ResourceId, Router};
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
#[test]
|
|
||||||
fn test_recognizer_1() {
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/name", 10).0.set_id(0);
|
|
||||||
router.path("/name/{val}", 11).0.set_id(1);
|
|
||||||
router.path("/name/{val}/index.html", 12).0.set_id(2);
|
|
||||||
router.path("/file/{file}.{ext}", 13).0.set_id(3);
|
|
||||||
router.path("/v{val}/{val2}/index.html", 14).0.set_id(4);
|
|
||||||
router.path("/v/{tail:.*}", 15).0.set_id(5);
|
|
||||||
router.path("/test2/{test}.html", 16).0.set_id(6);
|
|
||||||
router.path("/{test}/index.html", 17).0.set_id(7);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/unknown");
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/name");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
assert_eq!(info, ResourceId(0));
|
|
||||||
assert!(path.is_empty());
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/value");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
assert_eq!(info, ResourceId(1));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "value");
|
|
||||||
assert_eq!(&path["val"], "value");
|
|
||||||
|
|
||||||
let mut path = Path::new("/name/value2/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 12);
|
|
||||||
assert_eq!(info, ResourceId(2));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "value2");
|
|
||||||
|
|
||||||
let mut path = Path::new("/file/file.gz");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 13);
|
|
||||||
assert_eq!(info, ResourceId(3));
|
|
||||||
assert_eq!(path.get("file").unwrap(), "file");
|
|
||||||
assert_eq!(path.get("ext").unwrap(), "gz");
|
|
||||||
|
|
||||||
let mut path = Path::new("/vtest/ttt/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 14);
|
|
||||||
assert_eq!(info, ResourceId(4));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "test");
|
|
||||||
assert_eq!(path.get("val2").unwrap(), "ttt");
|
|
||||||
|
|
||||||
let mut path = Path::new("/v/blah-blah/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 15);
|
|
||||||
assert_eq!(info, ResourceId(5));
|
|
||||||
assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html");
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 16);
|
|
||||||
assert_eq!(info, ResourceId(6));
|
|
||||||
assert_eq!(path.get("test").unwrap(), "index");
|
|
||||||
|
|
||||||
let mut path = Path::new("/bbb/index.html");
|
|
||||||
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 17);
|
|
||||||
assert_eq!(info, ResourceId(7));
|
|
||||||
assert_eq!(path.get("test").unwrap(), "bbb");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_recognizer_2() {
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/index.json", 10);
|
|
||||||
router.path("/{source}.json", 11);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/index.json");
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
|
|
||||||
let mut path = Path::new("/test.json");
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_recognizer_with_prefix() {
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/name", 10).0.set_id(0);
|
|
||||||
router.path("/name/{val}", 11).0.set_id(1);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name");
|
|
||||||
path.skip(5);
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/test/name");
|
|
||||||
path.skip(5);
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
|
|
||||||
let mut path = Path::new("/test/name/value");
|
|
||||||
path.skip(5);
|
|
||||||
let (h, id) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
assert_eq!(id, ResourceId(1));
|
|
||||||
assert_eq!(path.get("val").unwrap(), "value");
|
|
||||||
assert_eq!(&path["val"], "value");
|
|
||||||
|
|
||||||
// same patterns
|
|
||||||
let mut router = Router::<usize>::build();
|
|
||||||
router.path("/name", 10);
|
|
||||||
router.path("/name/{val}", 11);
|
|
||||||
let mut router = router.finish();
|
|
||||||
|
|
||||||
let mut path = Path::new("/name");
|
|
||||||
path.skip(6);
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/name");
|
|
||||||
path.skip(6);
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 10);
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/name-test");
|
|
||||||
path.skip(6);
|
|
||||||
assert!(router.recognize_mut(&mut path).is_none());
|
|
||||||
|
|
||||||
let mut path = Path::new("/test2/name/ttt");
|
|
||||||
path.skip(6);
|
|
||||||
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
||||||
assert_eq!(*h, 11);
|
|
||||||
assert_eq!(&path["val"], "ttt");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
use crate::ResourcePath;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~";
|
|
||||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~
|
|
||||||
!$'()*,";
|
|
||||||
const QS: &[u8] = b"+&=;b";
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
|
||||||
array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_bit(array: &mut [u8], ch: u8) {
|
|
||||||
array[(ch >> 3) as usize] |= 1 << (ch & 7)
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub struct Url {
|
|
||||||
uri: http::Uri,
|
|
||||||
path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Url {
|
|
||||||
pub fn new(uri: http::Uri) -> Url {
|
|
||||||
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
|
||||||
|
|
||||||
Url { uri, path }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
|
|
||||||
Url {
|
|
||||||
path: quoter.requote(uri.path().as_bytes()),
|
|
||||||
uri,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uri(&self) -> &http::Uri {
|
|
||||||
&self.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path(&self) -> &str {
|
|
||||||
if let Some(ref s) = self.path {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
self.uri.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn update(&mut self, uri: &http::Uri) {
|
|
||||||
self.uri = uri.clone();
|
|
||||||
self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) {
|
|
||||||
self.uri = uri.clone();
|
|
||||||
self.path = quoter.requote(uri.path().as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourcePath for Url {
|
|
||||||
#[inline]
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
self.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Quoter {
|
|
||||||
safe_table: [u8; 16],
|
|
||||||
protected_table: [u8; 16],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Quoter {
|
|
||||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
|
||||||
let mut q = Quoter {
|
|
||||||
safe_table: [0; 16],
|
|
||||||
protected_table: [0; 16],
|
|
||||||
};
|
|
||||||
|
|
||||||
// prepare safe table
|
|
||||||
for i in 0..128 {
|
|
||||||
if ALLOWED.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
if QS.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ch in safe {
|
|
||||||
set_bit(&mut q.safe_table, *ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare protected table
|
|
||||||
for ch in protected {
|
|
||||||
set_bit(&mut q.safe_table, *ch);
|
|
||||||
set_bit(&mut q.protected_table, *ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
q
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
|
||||||
let mut has_pct = 0;
|
|
||||||
let mut pct = [b'%', 0, 0];
|
|
||||||
let mut idx = 0;
|
|
||||||
let mut cloned: Option<Vec<u8>> = None;
|
|
||||||
|
|
||||||
let len = val.len();
|
|
||||||
while idx < len {
|
|
||||||
let ch = val[idx];
|
|
||||||
|
|
||||||
if has_pct != 0 {
|
|
||||||
pct[has_pct] = val[idx];
|
|
||||||
has_pct += 1;
|
|
||||||
if has_pct == 3 {
|
|
||||||
has_pct = 0;
|
|
||||||
let buf = cloned.as_mut().unwrap();
|
|
||||||
|
|
||||||
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
|
||||||
if ch < 128 {
|
|
||||||
if bit_at(&self.protected_table, ch) {
|
|
||||||
buf.extend_from_slice(&pct);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if bit_at(&self.safe_table, ch) {
|
|
||||||
buf.push(ch);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.push(ch);
|
|
||||||
} else {
|
|
||||||
buf.extend_from_slice(&pct[..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ch == b'%' {
|
|
||||||
has_pct = 1;
|
|
||||||
if cloned.is_none() {
|
|
||||||
let mut c = Vec::with_capacity(len);
|
|
||||||
c.extend_from_slice(&val[..idx]);
|
|
||||||
cloned = Some(c);
|
|
||||||
}
|
|
||||||
} else if let Some(ref mut cloned) = cloned {
|
|
||||||
cloned.push(ch)
|
|
||||||
}
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cloned.map(|data| String::from_utf8_lossy(&data).into_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn from_hex(v: u8) -> Option<u8> {
|
|
||||||
if (b'0'..=b'9').contains(&v) {
|
|
||||||
Some(v - 0x30) // ord('0') == 0x30
|
|
||||||
} else if (b'A'..=b'F').contains(&v) {
|
|
||||||
Some(v - 0x41 + 10) // ord('A') == 0x41
|
|
||||||
} else if (b'a'..=b'f').contains(&v) {
|
|
||||||
Some(v - 0x61 + 10) // ord('a') == 0x61
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
|
||||||
from_hex(d1).and_then(|d1| from_hex(d2).map(move |d2| d1 << 4 | d2))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use http::Uri;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::{Path, ResourceDef};
|
|
||||||
|
|
||||||
const PROTECTED: &[u8] = b"%/+";
|
|
||||||
|
|
||||||
fn match_url(pattern: &'static str, url: impl AsRef<str>) -> Path<Url> {
|
|
||||||
let re = ResourceDef::new(pattern);
|
|
||||||
let uri = Uri::try_from(url.as_ref()).unwrap();
|
|
||||||
let mut path = Path::new(Url::new(uri));
|
|
||||||
assert!(re.capture_match_info(&mut path));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn percent_encode(data: &[u8]) -> String {
|
|
||||||
data.iter().map(|c| format!("%{:02X}", c)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_url() {
|
|
||||||
let re = "/user/{id}/test";
|
|
||||||
|
|
||||||
let path = match_url(re, "/user/2345/test");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "2345");
|
|
||||||
|
|
||||||
// "%25" should never be decoded into '%' to guarantee the output is a valid
|
|
||||||
// percent-encoded format
|
|
||||||
let path = match_url(re, "/user/qwe%25/test");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
|
||||||
|
|
||||||
let path = match_url(re, "/user/qwe%25rty/test");
|
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%25rty");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_protected_chars() {
|
|
||||||
let encoded = percent_encode(PROTECTED);
|
|
||||||
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
|
||||||
assert_eq!(path.get("id").unwrap(), &encoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_non_protecteed_ascii() {
|
|
||||||
let nonprotected_ascii = ('\u{0}'..='\u{7F}')
|
|
||||||
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
|
|
||||||
.collect::<String>();
|
|
||||||
let encoded = percent_encode(nonprotected_ascii.as_bytes());
|
|
||||||
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
|
||||||
assert_eq!(path.get("id").unwrap(), &nonprotected_ascii);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_valid_utf8_multibyte() {
|
|
||||||
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
|
||||||
let encoded = percent_encode(test.as_bytes());
|
|
||||||
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
|
||||||
assert_eq!(path.get("id").unwrap(), &test);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_utf8() {
|
|
||||||
let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice());
|
|
||||||
let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap();
|
|
||||||
let path = Path::new(Url::new(uri));
|
|
||||||
|
|
||||||
// We should always get a valid utf8 string
|
|
||||||
assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_hex() {
|
|
||||||
let hex = b"0123456789abcdefABCDEF";
|
|
||||||
|
|
||||||
for i in 0..256 {
|
|
||||||
let c = i as u8;
|
|
||||||
if hex.contains(&c) {
|
|
||||||
assert!(from_hex(c).is_some())
|
|
||||||
} else {
|
|
||||||
assert!(from_hex(c).is_none())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected = [
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15,
|
|
||||||
];
|
|
||||||
for i in 0..hex.len() {
|
|
||||||
assert_eq!(from_hex(hex[i]).unwrap(), expected[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
* Add `io-uring` feature for enabling async file Io on linux system. [#374]
|
* Add `io-uring` feature for enabling async file Io on linux system. [#374]
|
||||||
|
* The `spawn` method can now resolve with non-unit outputs. [#369]
|
||||||
|
|
||||||
|
[#369]: https://github.com/actix/actix-net/pull/369
|
||||||
[#374]: https://github.com/actix/actix-net/pull/374
|
[#374]: https://github.com/actix/actix-net/pull/374
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,10 @@ use tokio::task::JoinHandle;
|
||||||
// Cannot define a main macro when compiled into test harness.
|
// Cannot define a main macro when compiled into test harness.
|
||||||
// Workaround for https://github.com/rust-lang/rust/issues/62127.
|
// Workaround for https://github.com/rust-lang/rust/issues/62127.
|
||||||
#[cfg(all(feature = "macros", not(test)))]
|
#[cfg(all(feature = "macros", not(test)))]
|
||||||
pub use actix_macros::{main, test};
|
pub use actix_macros::main;
|
||||||
|
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
pub use actix_macros::test;
|
||||||
|
|
||||||
mod arbiter;
|
mod arbiter;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
@ -158,14 +161,41 @@ pub mod task {
|
||||||
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
|
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a future on the current thread.
|
/// Spawns a future on the current thread as a new task.
|
||||||
|
///
|
||||||
|
/// If not immediately awaited, the task can be cancelled using [`JoinHandle::abort`].
|
||||||
|
///
|
||||||
|
/// The provided future is spawned as a new task; therefore, panics are caught.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if Actix system is not running.
|
/// Panics if Actix system is not running.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # actix_rt::Runtime::new().unwrap().block_on(async {
|
||||||
|
/// // task resolves successfully
|
||||||
|
/// assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||||
|
///
|
||||||
|
/// // task panics
|
||||||
|
/// assert!(actix_rt::spawn(async {
|
||||||
|
/// panic!("panic is caught at task boundary");
|
||||||
|
/// })
|
||||||
|
/// .await
|
||||||
|
/// .unwrap_err()
|
||||||
|
/// .is_panic());
|
||||||
|
///
|
||||||
|
/// // task is cancelled before completion
|
||||||
|
/// let handle = actix_rt::spawn(actix_rt::time::sleep(Duration::from_secs(100)));
|
||||||
|
/// handle.abort();
|
||||||
|
/// assert!(handle.await.unwrap_err().is_cancelled());
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn spawn<Fut>(f: Fut) -> JoinHandle<()>
|
pub fn spawn<Fut>(f: Fut) -> JoinHandle<Fut::Output>
|
||||||
where
|
where
|
||||||
Fut: Future<Output = ()> + 'static,
|
Fut: Future + 'static,
|
||||||
|
Fut::Output: 'static,
|
||||||
{
|
{
|
||||||
tokio::task::spawn_local(f)
|
tokio::task::spawn_local(f)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
future::Future,
|
||||||
sync::mpsc::channel,
|
sync::mpsc::channel,
|
||||||
thread,
|
thread,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_rt::{Arbiter, System};
|
use actix_rt::{task::JoinError, Arbiter, System};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -302,21 +303,56 @@ fn try_current_with_system() {
|
||||||
System::new().block_on(async { assert!(System::try_current().is_some()) });
|
System::new().block_on(async { assert!(System::try_current().is_some()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
#[allow(clippy::unit_cmp)]
|
||||||
#[test]
|
#[test]
|
||||||
fn tokio_uring_arbiter() {
|
fn spawn_local() {
|
||||||
|
System::new().block_on(async {
|
||||||
|
// demonstrate that spawn -> R is strictly more capable than spawn -> ()
|
||||||
|
|
||||||
|
assert_eq!(actix_rt::spawn(async {}).await.unwrap(), ());
|
||||||
|
assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1);
|
||||||
|
assert!(actix_rt::spawn(async { panic!("") }).await.is_err());
|
||||||
|
|
||||||
|
actix_rt::spawn(async { tokio::time::sleep(Duration::from_millis(50)).await })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
fn g<F: Future<Output = Result<(), JoinError>>>(_f: F) {}
|
||||||
|
g(actix_rt::spawn(async {}));
|
||||||
|
// g(actix_rt::spawn(async { 1 })); // compile err
|
||||||
|
|
||||||
|
fn h<F: Future<Output = Result<R, JoinError>>, R>(_f: F) {}
|
||||||
|
h(actix_rt::spawn(async {}));
|
||||||
|
h(actix_rt::spawn(async { 1 }));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "linux", feature = "io-uring"))]
|
||||||
|
mod linux_only {
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokio_uring_arbiter() {
|
||||||
let system = System::new();
|
let system = System::new();
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
Arbiter::new().spawn(async move {
|
Arbiter::new().spawn(async move {
|
||||||
let handle = actix_rt::spawn(async move {
|
let handle = actix_rt::spawn(async move {
|
||||||
let f = tokio_uring::fs::File::create("test.txt").await.unwrap();
|
let f = tokio_uring::fs::File::create("test.txt").await.unwrap();
|
||||||
let buf = b"Hello World!";
|
let buf = b"Hello World!";
|
||||||
|
|
||||||
let (res, _) = f.write_at(&buf[..], 0).await;
|
let (res, _) = f.write_at(&buf[..], 0).await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
||||||
f.sync_all().await.unwrap();
|
f.sync_all().await.unwrap();
|
||||||
f.close().await.unwrap();
|
f.close().await.unwrap();
|
||||||
|
|
||||||
std::fs::remove_file("test.txt").unwrap();
|
std::fs::remove_file("test.txt").unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
handle.await.unwrap();
|
handle.await.unwrap();
|
||||||
tx.send(true).unwrap();
|
tx.send(true).unwrap();
|
||||||
});
|
});
|
||||||
|
@ -324,4 +360,5 @@ fn tokio_uring_arbiter() {
|
||||||
assert!(rx.recv().unwrap());
|
assert!(rx.recv().unwrap());
|
||||||
|
|
||||||
drop(system);
|
drop(system);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub trait Address: Unpin + 'static {
|
||||||
|
|
||||||
impl Address for String {
|
impl Address for String {
|
||||||
fn hostname(&self) -> &str {
|
fn hostname(&self) -> &str {
|
||||||
&self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ impl<T: Address> Service<Connect<T>> for Resolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Custom(resolver) => {
|
Self::Custom(resolver) => {
|
||||||
let resolver = Rc::clone(&resolver);
|
let resolver = Rc::clone(resolver);
|
||||||
ResolverFuture::LookupCustom(Box::pin(async move {
|
ResolverFuture::LookupCustom(Box::pin(async move {
|
||||||
let addrs = resolver
|
let addrs = resolver
|
||||||
.lookup(req.hostname(), req.port())
|
.lookup(req.hostname(), req.port())
|
||||||
|
|
Loading…
Reference in New Issue