mirror of https://github.com/rwf2/Rocket.git
Remove 'testing' feature. Close stream on network error.
This is a breaking change. The `testing` feature no longer exists. Testing structures can now be accessed without any features enabled. Prior to this change, Rocket would panic when draining from a network stream failed. With this change, Rocket force closes the stream on any error. This change also ensures that the `Fairings` launch output only prints if at least one fairing has been attached.
This commit is contained in:
parent
ac0c78a0cd
commit
1e5a1b8940
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -9,6 +9,3 @@ rocket_codegen = { path = "../../codegen" }
|
||||||
serde = "0.9"
|
serde = "0.9"
|
||||||
serde_json = "0.9"
|
serde_json = "0.9"
|
||||||
serde_derive = "0.9"
|
serde_derive = "0.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -11,6 +11,3 @@ rocket_codegen = { path = "../../codegen" }
|
||||||
path = "../../contrib"
|
path = "../../contrib"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["handlebars_templates"]
|
features = ["handlebars_templates"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -14,6 +14,3 @@ serde_json = "0.9"
|
||||||
path = "../../contrib"
|
path = "../../contrib"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["handlebars_templates"]
|
features = ["handlebars_templates"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib", features = ["tls"] }
|
rocket = { path = "../../lib", features = ["tls"] }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -14,6 +14,3 @@ serde_derive = "0.9"
|
||||||
path = "../../contrib"
|
path = "../../contrib"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["json"]
|
features = ["json"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -7,6 +7,3 @@ workspace = "../.."
|
||||||
crossbeam = "*"
|
crossbeam = "*"
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -5,6 +5,3 @@ workspace = "../../"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -13,6 +13,3 @@ serde_derive = "0.9"
|
||||||
path = "../../contrib"
|
path = "../../contrib"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["msgpack"]
|
features = ["msgpack"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -7,6 +7,3 @@ workspace = "../../"
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -7,6 +7,3 @@ workspace = "../../"
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
rusqlite = "0.10"
|
rusqlite = "0.10"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -11,6 +11,3 @@ rocket_codegen = { path = "../../codegen" }
|
||||||
path = "../../contrib"
|
path = "../../contrib"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["handlebars_templates"]
|
features = ["handlebars_templates"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -6,6 +6,3 @@ workspace = "../../"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -13,6 +13,3 @@ lazy_static = "^0.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
path = "../../contrib"
|
path = "../../contrib"
|
||||||
features = ["uuid"]
|
features = ["uuid"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rocket = { path = "../../lib", features = ["testing"] }
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ build = "build.rs"
|
||||||
categories = ["web-programming::http-server"]
|
categories = ["web-programming::http-server"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
testing = []
|
|
||||||
tls = ["rustls", "hyper-rustls"]
|
tls = ["rustls", "hyper-rustls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -18,7 +18,6 @@ fn rocket() -> rocket::Rocket {
|
||||||
.mount("/", routes![get, post])
|
.mount("/", routes![get, post])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod benches {
|
mod benches {
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ fn rocket() -> rocket::Rocket {
|
||||||
.mount("/", routes![post, post2, post3])
|
.mount("/", routes![post, post2, post3])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod benches {
|
mod benches {
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ fn rocket() -> rocket::Rocket {
|
||||||
index_b, index_c, index_dyn_a])
|
index_b, index_c, index_dyn_a])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod benches {
|
mod benches {
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,9 @@ use std::mem::transmute;
|
||||||
|
|
||||||
#[cfg(feature = "tls")] use hyper_rustls::WrappedStream;
|
#[cfg(feature = "tls")] use hyper_rustls::WrappedStream;
|
||||||
|
|
||||||
|
use super::data_stream::{DataStream, StreamReader, kill_stream};
|
||||||
|
use super::net_stream::NetStream;
|
||||||
use ext::ReadExt;
|
use ext::ReadExt;
|
||||||
use super::data_stream::{DataStream, HyperNetStream, StreamReader, kill_stream};
|
|
||||||
|
|
||||||
use http::hyper::h1::HttpReader;
|
use http::hyper::h1::HttpReader;
|
||||||
use http::hyper::buffer;
|
use http::hyper::buffer;
|
||||||
|
@ -17,6 +18,9 @@ use http::hyper::net::{HttpStream, NetworkStream};
|
||||||
pub type BodyReader<'a, 'b> =
|
pub type BodyReader<'a, 'b> =
|
||||||
self::HttpReader<&'a mut self::buffer::BufReader<&'b mut NetworkStream>>;
|
self::HttpReader<&'a mut self::buffer::BufReader<&'b mut NetworkStream>>;
|
||||||
|
|
||||||
|
/// The number of bytes to read into the "peek" buffer.
|
||||||
|
const PEEK_BYTES: usize = 4096;
|
||||||
|
|
||||||
/// Type representing the data in the body of an incoming request.
|
/// Type representing the data in the body of an incoming request.
|
||||||
///
|
///
|
||||||
/// This type is the only means by which the body of a request can be retrieved.
|
/// This type is the only means by which the body of a request can be retrieved.
|
||||||
|
@ -90,19 +94,19 @@ impl Data {
|
||||||
let net_stream = h_body.get_ref().get_ref();
|
let net_stream = h_body.get_ref().get_ref();
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
fn concrete_stream(stream: &&mut NetworkStream) -> Option<HyperNetStream> {
|
fn concrete_stream(stream: &&mut NetworkStream) -> Option<NetStream> {
|
||||||
stream.downcast_ref::<HttpStream>()
|
stream.downcast_ref::<HttpStream>()
|
||||||
.map(|s| HyperNetStream::Http(s.clone()))
|
.map(|s| NetStream::Http(s.clone()))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
stream.downcast_ref::<WrappedStream>()
|
stream.downcast_ref::<WrappedStream>()
|
||||||
.map(|s| HyperNetStream::Https(s.clone()))
|
.map(|s| NetStream::Https(s.clone()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "tls"))]
|
#[cfg(not(feature = "tls"))]
|
||||||
fn concrete_stream(stream: &&mut NetworkStream) -> Option<HyperNetStream> {
|
fn concrete_stream(stream: &&mut NetworkStream) -> Option<NetStream> {
|
||||||
stream.downcast_ref::<HttpStream>()
|
stream.downcast_ref::<HttpStream>()
|
||||||
.map(|s| HyperNetStream::Http(s.clone()))
|
.map(|s| NetStream::Http(s.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the underlying HTTPStream from Hyper.
|
// Retrieve the underlying HTTPStream from Hyper.
|
||||||
|
@ -164,15 +168,15 @@ impl Data {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new data object with an internal buffer `buf`, where the cursor
|
// Creates a new data object with an internal buffer `buf`, where the cursor
|
||||||
// in the buffer is at `pos` and the buffer has `cap` valid bytes. The
|
// in the buffer is at `pos` and the buffer has `cap` valid bytes. Thus, the
|
||||||
// remainder of the data bytes can be read from `stream`.
|
// bytes `vec[pos..cap]` are buffered and unread. The remainder of the data
|
||||||
|
// bytes can be read from `stream`.
|
||||||
pub(crate) fn new(mut buf: Vec<u8>,
|
pub(crate) fn new(mut buf: Vec<u8>,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
mut cap: usize,
|
mut cap: usize,
|
||||||
mut stream: StreamReader
|
mut stream: StreamReader
|
||||||
) -> Data {
|
) -> Data {
|
||||||
// Make sure the buffer is large enough for the bytes we want to peek.
|
// Make sure the buffer is large enough for the bytes we want to peek.
|
||||||
const PEEK_BYTES: usize = 4096;
|
|
||||||
if buf.len() < PEEK_BYTES {
|
if buf.len() < PEEK_BYTES {
|
||||||
trace_!("Resizing peek buffer from {} to {}.", buf.len(), PEEK_BYTES);
|
trace_!("Resizing peek buffer from {} to {}.", buf.len(), PEEK_BYTES);
|
||||||
buf.resize(PEEK_BYTES, 0);
|
buf.resize(PEEK_BYTES, 0);
|
||||||
|
@ -203,6 +207,27 @@ impl Data {
|
||||||
capacity: cap,
|
capacity: cap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This creates a `data` object from a local data source `data`.
|
||||||
|
pub(crate) fn local(mut data: Vec<u8>) -> Data {
|
||||||
|
// Emulate peek buffering.
|
||||||
|
let (buf, rest) = if data.len() <= PEEK_BYTES {
|
||||||
|
(data, vec![])
|
||||||
|
} else {
|
||||||
|
let rest = data.split_off(PEEK_BYTES);
|
||||||
|
(data, rest)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (buf_len, stream_len) = (buf.len(), rest.len() as u64);
|
||||||
|
let stream = NetStream::Local(Cursor::new(rest));
|
||||||
|
Data {
|
||||||
|
buffer: buf,
|
||||||
|
stream: HttpReader::SizedReader(stream, stream_len),
|
||||||
|
is_done: stream_len == 0,
|
||||||
|
position: 0,
|
||||||
|
capacity: buf_len,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Data {
|
impl Drop for Data {
|
||||||
|
|
|
@ -1,80 +1,14 @@
|
||||||
use std::io::{self, BufRead, Read, Cursor, BufReader, Chain, Take};
|
use std::io::{self, BufRead, Read, Cursor, BufReader, Chain, Take};
|
||||||
use std::net::{SocketAddr, Shutdown};
|
use std::net::Shutdown;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[cfg(feature = "tls")] use hyper_rustls::WrappedStream as RustlsStream;
|
use super::net_stream::NetStream;
|
||||||
|
|
||||||
use http::hyper::net::{HttpStream, NetworkStream};
|
use http::hyper::net::NetworkStream;
|
||||||
use http::hyper::h1::HttpReader;
|
use http::hyper::h1::HttpReader;
|
||||||
|
|
||||||
pub type StreamReader = HttpReader<HyperNetStream>;
|
pub type StreamReader = HttpReader<NetStream>;
|
||||||
pub type InnerStream = Chain<Take<Cursor<Vec<u8>>>, BufReader<StreamReader>>;
|
pub type InnerStream = Chain<Take<Cursor<Vec<u8>>>, BufReader<StreamReader>>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum HyperNetStream {
|
|
||||||
Http(HttpStream),
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
Https(RustlsStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! with_inner {
|
|
||||||
($net:expr, |$stream:ident| $body:expr) => ({
|
|
||||||
trace!("{}:{}", file!(), line!());
|
|
||||||
match *$net {
|
|
||||||
HyperNetStream::Http(ref $stream) => $body,
|
|
||||||
#[cfg(feature = "tls")] HyperNetStream::Https(ref $stream) => $body
|
|
||||||
}
|
|
||||||
});
|
|
||||||
($net:expr, |mut $stream:ident| $body:expr) => ({
|
|
||||||
trace!("{}:{}", file!(), line!());
|
|
||||||
match *$net {
|
|
||||||
HyperNetStream::Http(ref mut $stream) => $body,
|
|
||||||
#[cfg(feature = "tls")] HyperNetStream::Https(ref mut $stream) => $body
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Read for HyperNetStream {
|
|
||||||
#[inline(always)]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
with_inner!(self, |mut stream| io::Read::read(stream, buf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Write for HyperNetStream {
|
|
||||||
#[inline(always)]
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
with_inner!(self, |mut stream| io::Write::write(stream, buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
with_inner!(self, |mut stream| io::Write::flush(stream))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkStream for HyperNetStream {
|
|
||||||
#[inline(always)]
|
|
||||||
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
|
||||||
with_inner!(self, |mut stream| NetworkStream::peer_addr(stream))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
with_inner!(self, |stream| NetworkStream::set_read_timeout(stream, dur))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
|
||||||
with_inner!(self, |stream| NetworkStream::set_write_timeout(stream, dur))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn close(&mut self, how: Shutdown) -> io::Result<()> {
|
|
||||||
with_inner!(self, |mut stream| NetworkStream::close(stream, how))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raw data stream of a request body.
|
/// Raw data stream of a request body.
|
||||||
///
|
///
|
||||||
/// This stream can only be obtained by calling
|
/// This stream can only be obtained by calling
|
||||||
|
@ -83,12 +17,12 @@ impl NetworkStream for HyperNetStream {
|
||||||
/// Instead, it must be used as an opaque `Read` or `BufRead` structure.
|
/// Instead, it must be used as an opaque `Read` or `BufRead` structure.
|
||||||
pub struct DataStream {
|
pub struct DataStream {
|
||||||
stream: InnerStream,
|
stream: InnerStream,
|
||||||
network: HyperNetStream,
|
network: NetStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataStream {
|
impl DataStream {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new(stream: InnerStream, network: HyperNetStream) -> DataStream {
|
pub(crate) fn new(stream: InnerStream, network: NetStream) -> DataStream {
|
||||||
DataStream { stream, network }
|
DataStream { stream, network }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,19 +46,18 @@ impl BufRead for DataStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn kill_stream<S: Read>(stream: &mut S, network: &mut HyperNetStream) {
|
|
||||||
pub fn kill_stream<S: Read, N: NetworkStream>(stream: &mut S, network: &mut N) {
|
|
||||||
io::copy(&mut stream.take(1024), &mut io::sink()).expect("kill_stream: sink");
|
|
||||||
|
|
||||||
// If there are any more bytes, kill it.
|
pub fn kill_stream<S: Read, N: NetworkStream>(stream: &mut S, network: &mut N) {
|
||||||
let mut buf = [0];
|
// Take <= 1k from the stream. If there might be more data, force close.
|
||||||
if let Ok(n) = stream.read(&mut buf) {
|
const FLUSH_LEN: u64 = 1024;
|
||||||
if n > 0 {
|
match io::copy(&mut stream.take(FLUSH_LEN), &mut io::sink()) {
|
||||||
|
Ok(FLUSH_LEN) | Err(_) => {
|
||||||
warn_!("Data left unread. Force closing network stream.");
|
warn_!("Data left unread. Force closing network stream.");
|
||||||
if let Err(e) = network.close(Shutdown::Both) {
|
if let Err(e) = network.close(Shutdown::Both) {
|
||||||
error_!("Failed to close network stream: {:?}", e);
|
error_!("Failed to close network stream: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(n) => debug!("flushed {} unread bytes", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,8 @@
|
||||||
//! Types and traits for reading and parsing request body data.
|
mod data;
|
||||||
|
mod data_stream;
|
||||||
#[cfg(any(test, feature = "testing"))]
|
mod net_stream;
|
||||||
#[path = "."]
|
|
||||||
mod items {
|
|
||||||
mod test_data;
|
|
||||||
|
|
||||||
pub use self::test_data::Data;
|
|
||||||
pub use self::test_data::DataStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "testing")))]
|
|
||||||
#[path = "."]
|
|
||||||
mod items {
|
|
||||||
mod data;
|
|
||||||
mod data_stream;
|
|
||||||
|
|
||||||
pub use self::data::Data;
|
|
||||||
pub use self::data_stream::DataStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
mod from_data;
|
mod from_data;
|
||||||
|
|
||||||
|
pub use self::data::Data;
|
||||||
|
pub use self::data_stream::DataStream;
|
||||||
pub use self::from_data::{FromData, Outcome};
|
pub use self::from_data::{FromData, Outcome};
|
||||||
pub use self::items::{Data, DataStream};
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
use std::io::{self, Cursor};
|
||||||
|
use std::net::{SocketAddr, Shutdown};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[cfg(feature = "tls")] use hyper_rustls::WrappedStream as RustlsStream;
|
||||||
|
use http::hyper::net::{HttpStream, NetworkStream};
|
||||||
|
|
||||||
|
use self::NetStream::*;
|
||||||
|
|
||||||
|
// This is a representation of all of the possible network streams we might get.
|
||||||
|
// This really shouldn't be necessary, but, you know, Hyper.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum NetStream {
|
||||||
|
Http(HttpStream),
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
Https(RustlsStream),
|
||||||
|
Local(Cursor<Vec<u8>>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Read for NetStream {
|
||||||
|
#[inline(always)]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match *self {
|
||||||
|
Http(ref mut stream) => stream.read(buf),
|
||||||
|
Local(ref mut stream) => stream.read(buf),
|
||||||
|
#[cfg(feature = "tls")] Https(ref mut stream) => stream.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for NetStream {
|
||||||
|
#[inline(always)]
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
match *self {
|
||||||
|
Http(ref mut stream) => stream.write(buf),
|
||||||
|
Local(ref mut stream) => stream.write(buf),
|
||||||
|
#[cfg(feature = "tls")] Https(ref mut stream) => stream.write(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match *self {
|
||||||
|
Http(ref mut stream) => stream.flush(),
|
||||||
|
Local(ref mut stream) => stream.flush(),
|
||||||
|
#[cfg(feature = "tls")] Https(ref mut stream) => stream.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkStream for NetStream {
|
||||||
|
#[inline(always)]
|
||||||
|
fn peer_addr(&mut self) -> io::Result<SocketAddr> {
|
||||||
|
match *self {
|
||||||
|
Http(ref mut stream) => stream.peer_addr(),
|
||||||
|
#[cfg(feature = "tls")] Https(ref mut stream) => stream.peer_addr(),
|
||||||
|
Local(_) => Err(io::Error::from(io::ErrorKind::AddrNotAvailable)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
||||||
|
match *self {
|
||||||
|
Http(ref stream) => stream.set_read_timeout(dur),
|
||||||
|
#[cfg(feature = "tls")] Https(ref stream) => stream.set_read_timeout(dur),
|
||||||
|
Local(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
|
||||||
|
match *self {
|
||||||
|
Http(ref stream) => stream.set_write_timeout(dur),
|
||||||
|
#[cfg(feature = "tls")] Https(ref stream) => stream.set_write_timeout(dur),
|
||||||
|
Local(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn close(&mut self, how: Shutdown) -> io::Result<()> {
|
||||||
|
match *self {
|
||||||
|
Http(ref mut stream) => stream.close(how),
|
||||||
|
#[cfg(feature = "tls")] Https(ref mut stream) => stream.close(how),
|
||||||
|
Local(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,75 +0,0 @@
|
||||||
use std::io::{self, Read, BufRead, Write, Cursor, BufReader};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
use http::hyper::h1::HttpReader;
|
|
||||||
use http::hyper::net::NetworkStream;
|
|
||||||
use http::hyper::buffer;
|
|
||||||
|
|
||||||
pub type BodyReader<'a, 'b> =
|
|
||||||
self::HttpReader<&'a mut self::buffer::BufReader<&'b mut NetworkStream>>;
|
|
||||||
|
|
||||||
const PEEK_BYTES: usize = 4096;
|
|
||||||
|
|
||||||
pub struct DataStream {
|
|
||||||
stream: BufReader<Cursor<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for DataStream {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.stream.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BufRead for DataStream {
|
|
||||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
|
||||||
self.stream.fill_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn consume(&mut self, amt: usize) {
|
|
||||||
self.stream.consume(amt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Data {
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data {
|
|
||||||
pub fn open(self) -> DataStream {
|
|
||||||
DataStream { stream: BufReader::new(Cursor::new(self.data)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn peek(&self) -> &[u8] {
|
|
||||||
&self.data[..::std::cmp::min(PEEK_BYTES, self.data.len())]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn peek_complete(&self) -> bool {
|
|
||||||
self.data.len() <= PEEK_BYTES
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn stream_to<W: Write>(self, writer: &mut W) -> io::Result<u64> {
|
|
||||||
io::copy(&mut self.open(), writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn stream_to_file<P: AsRef<Path>>(self, path: P) -> io::Result<u64> {
|
|
||||||
io::copy(&mut self.open(), &mut File::create(path)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_hyp(mut h_body: BodyReader) -> Result<Data, &'static str> {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
if let Err(_) = io::copy(&mut h_body, &mut vec) {
|
|
||||||
return Err("Reading from body failed.");
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Data::new(vec))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new(data: Vec<u8>) -> Data {
|
|
||||||
Data { data: data }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -173,9 +173,17 @@ impl Fairings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn num_attached(&self) -> usize {
|
||||||
|
self.launch.len() + self.request.len() + self.response.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pretty_print_counts(&self) {
|
pub fn pretty_print_counts(&self) {
|
||||||
use term_painter::ToStyle;
|
use term_painter::ToStyle;
|
||||||
use term_painter::Color::White;
|
use term_painter::Color::{White, Magenta};
|
||||||
|
|
||||||
|
if self.num_attached() > 0 {
|
||||||
|
info!("📡 {}:", Magenta.paint("Fairings"));
|
||||||
|
}
|
||||||
|
|
||||||
if !self.launch.is_empty() {
|
if !self.launch.is_empty() {
|
||||||
info_!("{} launch", White.paint(self.launch.len()));
|
info_!("{} launch", White.paint(self.launch.len()));
|
||||||
|
|
|
@ -117,7 +117,7 @@ extern crate smallvec;
|
||||||
#[cfg(test)] #[macro_use] extern crate lazy_static;
|
#[cfg(test)] #[macro_use] extern crate lazy_static;
|
||||||
|
|
||||||
#[doc(hidden)] #[macro_use] pub mod logger;
|
#[doc(hidden)] #[macro_use] pub mod logger;
|
||||||
#[cfg(any(test, feature = "testing"))] pub mod testing;
|
pub mod testing;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
|
@ -645,7 +645,6 @@ impl Rocket {
|
||||||
return LaunchError::from(LaunchErrorKind::Collision);
|
return LaunchError::from(LaunchErrorKind::Collision);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("📡 {}:", Magenta.paint("Fairings"));
|
|
||||||
self.fairings.pretty_print_counts();
|
self.fairings.pretty_print_counts();
|
||||||
|
|
||||||
let full_addr = format!("{}:{}", self.config.address, self.config.port);
|
let full_addr = format!("{}:{}", self.config.address, self.config.port);
|
||||||
|
|
|
@ -1,33 +1,5 @@
|
||||||
//! A tiny module for testing Rocket applications.
|
//! A tiny module for testing Rocket applications.
|
||||||
//!
|
//!
|
||||||
//! # Enabling
|
|
||||||
//!
|
|
||||||
//! The `testing` module is only available when Rocket is compiled with the
|
|
||||||
//! `testing` feature flag. The suggested way to enable the `testing` module is
|
|
||||||
//! through Cargo's `[dev-dependencies]` feature which allows features (and
|
|
||||||
//! other dependencies) to be enabled exclusively when testing/benchmarking your
|
|
||||||
//! application.
|
|
||||||
//!
|
|
||||||
//! To compile Rocket with the `testing` feature for testing/benchmarking, add
|
|
||||||
//! the following to your `Cargo.toml`:
|
|
||||||
//!
|
|
||||||
//! ```toml
|
|
||||||
//! [dev-dependencies]
|
|
||||||
//! rocket = { version = "*", features = ["testing"] }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Then, in your testing module, `use` the testing types. This typically looks
|
|
||||||
//! as follows:
|
|
||||||
//!
|
|
||||||
//! ```rust,ignore
|
|
||||||
//! #[cfg(test)]
|
|
||||||
//! mod test {
|
|
||||||
//! use super::rocket;
|
|
||||||
//! use rocket::testing::MockRequest;
|
|
||||||
//! use rocket::http::Method::*;
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Usage
|
//! # Usage
|
||||||
//!
|
//!
|
||||||
//! The testing methadology is simple:
|
//! The testing methadology is simple:
|
||||||
|
@ -51,9 +23,10 @@
|
||||||
//! builds a request for submitting a login form with three fields:
|
//! builds a request for submitting a login form with three fields:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # use rocket::http::Method::*;
|
//! use rocket::http::Method::*;
|
||||||
//! # use rocket::testing::MockRequest;
|
//! use rocket::http::ContentType;
|
||||||
//! # use rocket::http::ContentType;
|
//! use rocket::testing::MockRequest;
|
||||||
|
//!
|
||||||
//! let (username, password, age) = ("user", "password", 32);
|
//! let (username, password, age) = ("user", "password", 32);
|
||||||
//! MockRequest::new(Post, "/login")
|
//! MockRequest::new(Post, "/login")
|
||||||
//! .header(ContentType::Form)
|
//! .header(ContentType::Form)
|
||||||
|
@ -119,7 +92,7 @@ impl<'r> MockRequest<'r> {
|
||||||
pub fn new<S: AsRef<str>>(method: Method, uri: S) -> Self {
|
pub fn new<S: AsRef<str>>(method: Method, uri: S) -> Self {
|
||||||
MockRequest {
|
MockRequest {
|
||||||
request: Request::new(method, uri.as_ref().to_string()),
|
request: Request::new(method, uri.as_ref().to_string()),
|
||||||
data: Data::new(vec![])
|
data: Data::local(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +195,7 @@ impl<'r> MockRequest<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
|
pub fn body<S: AsRef<[u8]>>(mut self, body: S) -> Self {
|
||||||
self.data = Data::new(body.as_ref().into());
|
self.data = Data::local(body.as_ref().into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +234,7 @@ impl<'r> MockRequest<'r> {
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn dispatch_with<'s>(&'s mut self, rocket: &'r Rocket) -> Response<'s> {
|
pub fn dispatch_with<'s>(&'s mut self, rocket: &'r Rocket) -> Response<'s> {
|
||||||
let data = ::std::mem::replace(&mut self.data, Data::new(vec![]));
|
let data = ::std::mem::replace(&mut self.data, Data::local(vec![]));
|
||||||
rocket.dispatch(&mut self.request, data)
|
rocket.dispatch(&mut self.request, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ fn bug(form_data: Form<FormData>) -> &'static str {
|
||||||
"OK"
|
"OK"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
|
|
|
@ -15,7 +15,6 @@ fn bug(form_data: Form<FormData>) -> String {
|
||||||
form_data.into_inner().form_data
|
form_data.into_inner().form_data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
|
|
|
@ -20,7 +20,6 @@ fn other() -> content::JSON<()> {
|
||||||
content::JSON(())
|
content::JSON(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ fn index(form: Form<Simple>) -> String {
|
||||||
form.into_inner().value
|
form.into_inner().value
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use rocket;
|
use rocket;
|
||||||
use rocket::config::{Environment, Config, Limits};
|
use rocket::config::{Environment, Config, Limits};
|
||||||
|
|
|
@ -23,7 +23,6 @@ fn specified_html() -> &'static str {
|
||||||
"specified_html"
|
"specified_html"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ fn second() -> &'static str {
|
||||||
"no query"
|
"no query"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ fn not_found() -> Redirect {
|
||||||
Redirect::to("/")
|
Redirect::to("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
|
|
|
@ -10,7 +10,6 @@ fn get_ip(remote: SocketAddr) -> String {
|
||||||
remote.to_string()
|
remote.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod remote_rewrite_tests {
|
mod remote_rewrite_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
|
|
|
@ -30,7 +30,6 @@ fn dual(user: String, path: Segments) -> String {
|
||||||
user + "/is/" + &path.collect::<Vec<_>>().join("/")
|
user + "/is/" + &path.collect::<Vec<_>>().join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "testing")]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
|
|
Loading…
Reference in New Issue