//! Test utilities to test your filters. //! //! [`Filter`](../trait.Filter.html)s can be easily tested without starting up an HTTP //! server, by making use of the [`RequestBuilder`](./struct.RequestBuilder.html) in this //! module. //! //! # Testing Filters //! //! It's easy to test filters, especially if smaller filters are used to build //! up your full set. Consider these example filters: //! //! ``` //! use warp::Filter; //! //! fn sum() -> impl Filter<Extract = (u32,), Error = warp::Rejection> + Copy { //! warp::path::param() //! .and(warp::path::param()) //! .map(|x: u32, y: u32| { //! x + y //! }) //! } //! //! fn math() -> impl Filter<Extract = (String,), Error = warp::Rejection> + Copy { //! warp::post() //! .and(sum()) //! .map(|z: u32| { //! format!("Sum = {}", z) //! }) //! } //! ``` //! //! We can test some requests against the `sum` filter like this: //! //! ``` //! # use warp::Filter; //! #[tokio::test] //! async fn test_sum() { //! # let sum = || warp::any().map(|| 3); //! let filter = sum(); //! //! // Execute `sum` and get the `Extract` back. //! let value = warp::test::request() //! .path("/1/2") //! .filter(&filter) //! .await //! .unwrap(); //! assert_eq!(value, 3); //! //! // Or simply test if a request matches (doesn't reject). //! assert!( //! warp::test::request() //! .path("/1/-5") //! .matches(&filter) //! .await //! ); //! } //! ``` //! //! If the filter returns something that implements `Reply`, and thus can be //! turned into a response sent back to the client, we can test what exact //! response is returned. The `math` filter uses the `sum` filter, but returns //! a `String` that can be turned into a response. //! //! ``` //! # use warp::Filter; //! #[test] //! fn test_math() { //! # let math = || warp::any().map(warp::reply); //! let filter = math(); //! //! let res = warp::test::request() //! .path("/1/2") //! .reply(&filter); //! assert_eq!(res.status(), 405, "GET is not allowed"); //! //! let res = warp::test::request() //! .method("POST") //! .path("/1/2") //! .reply(&filter); //! assert_eq!(res.status(), 200); //! assert_eq!(res.body(), "Sum is 3"); //! } //! ``` use std::convert::TryFrom; use std::error::Error as StdError; use std::fmt; use std::future::Future; use std::net::SocketAddr; #[cfg(feature = "websocket")] use std::pin::Pin; #[cfg(feature = "websocket")] use std::task::Context; #[cfg(feature = "websocket")] use std::task::{self, Poll};
use bytes::Bytes; #[cfg(feature = "websocket")] use futures_channel::mpsc; #[cfg(feature = "websocket")] use futures_util::StreamExt; use futures_util::{future, FutureExt, TryFutureExt}; use http::{
header::{HeaderName, HeaderValue},
Response,
}; use serde::Serialize; #[cfg(feature = "websocket")] use tokio::sync::oneshot;
/// Starts a new test `RequestBuilder`. pubfn request() -> RequestBuilder {
RequestBuilder {
remote_addr: None,
req: Request::default(),
}
}
/// Starts a new test `WsBuilder`. #[cfg(feature = "websocket")] pubfn ws() -> WsBuilder {
WsBuilder { req: request() }
}
/// A request builder for testing filters. /// /// See [module documentation](crate::test) for an overview. #[must_use = "RequestBuilder does nothing on its own"] #[derive(Debug)] pubstruct RequestBuilder {
remote_addr: Option<SocketAddr>,
req: Request,
}
/// A Websocket builder for testing filters. /// /// See [module documentation](crate::test) for an overview. #[cfg(feature = "websocket")] #[must_use = "WsBuilder does nothing on its own"] #[derive(Debug)] pubstruct WsBuilder {
req: RequestBuilder,
}
/// A test client for Websocket filters. #[cfg(feature = "websocket")] pubstruct WsClient {
tx: mpsc::UnboundedSender<crate::ws::Message>,
rx: mpsc::UnboundedReceiver<Result<crate::ws::Message, crate::error::Error>>,
}
/// An error from Websocket filter tests. #[derive(Debug)] pubstruct WsError {
cause: Box<dyn StdError + Send + Sync>,
}
impl RequestBuilder { /// Sets the method of this builder. /// /// The default if not set is `GET`. /// /// # Example /// /// ``` /// let req = warp::test::request() /// .method("POST"); /// ``` /// /// # Panic /// /// This panics if the passed string is not able to be parsed as a valid /// `Method`. pubfn method(mutself, method: &str) -> Self {
*self.req.method_mut() = method.parse().expect("valid method"); self
}
/// Sets the request path of this builder. /// /// The default is not set is `/`. /// /// # Example /// /// ``` /// let req = warp::test::request() /// .path("/todos/33"); /// ``` /// /// # Panic /// /// This panics if the passed string is not able to be parsed as a valid /// `Uri`. pubfn path(mutself, p: &str) -> Self { let uri = p.parse().expect("test request path invalid");
*self.req.uri_mut() = uri; self
}
/// Set a header for this request. /// /// # Example /// /// ``` /// let req = warp::test::request() /// .header("accept", "application/json"); /// ``` /// /// # Panic /// /// This panics if the passed strings are not able to be parsed as a valid /// `HeaderName` and `HeaderValue`. pubfn header<K, V>(mutself, key: K, value: V) -> Self where
HeaderName: TryFrom<K>,
HeaderValue: TryFrom<V>,
{ let name: HeaderName = TryFrom::try_from(key)
.map_err(|_| ())
.expect("invalid header name"); let value = TryFrom::try_from(value)
.map_err(|_| ())
.expect("invalid header value"); self.req.headers_mut().insert(name, value); self
}
/// Set the remote address of this request /// /// Default is no remote address. /// /// # Example /// ``` /// use std::net::{IpAddr, Ipv4Addr, SocketAddr}; /// /// let req = warp::test::request() /// .remote_addr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)); /// ``` pubfn remote_addr(mutself, addr: SocketAddr) -> Self { self.remote_addr = Some(addr); self
}
/// Add a type to the request's `http::Extensions`. pubfn extension<T>(mutself, ext: T) -> Self where
T: Send + Sync + 'static,
{ self.req.extensions_mut().insert(ext); self
}
/// Set the bytes of this request body. /// /// Default is an empty body. /// /// # Example /// /// ``` /// let req = warp::test::request() /// .body("foo=bar&baz=quux"); /// ``` pubfn body(mutself, body: impl AsRef<[u8]>) -> Self { let body = body.as_ref().to_vec(); let len = body.len();
*self.req.body_mut() = body.into(); self.header("content-length", len.to_string())
}
/// Set the bytes of this request body by serializing a value into JSON. /// /// # Example /// /// ``` /// let req = warp::test::request() /// .json(&true); /// ``` pubfn json(mutself, val: &impl Serialize) -> Self { let vec = serde_json::to_vec(val).expect("json() must serialize to JSON"); let len = vec.len();
*self.req.body_mut() = vec.into(); self.header("content-length", len.to_string())
.header("content-type", "application/json")
}
/// Tries to apply the `Filter` on this request. /// /// # Example /// /// ```no_run /// async { /// let param = warp::path::param::<u32>(); /// /// let ex = warp::test::request() /// .path("/41") /// .filter(¶m) /// .await /// .unwrap(); /// /// assert_eq!(ex, 41); /// /// assert!( /// warp::test::request() /// .path("/foo") /// .filter(¶m) /// .await /// .is_err() /// ); ///}; /// ``` pubasyncfn filter<F>(self, f: &F) -> Result<<F::Extract as OneOrTuple>::Output, F::Error> where
F: Filter,
F::Future: Send + 'static,
F::Extract: OneOrTuple + Send + 'static,
F::Error: Send + 'static,
{ self.apply_filter(f).await.map(|ex| ex.one_or_tuple())
}
/// Returns whether the `Filter` matches this request, or rejects it. /// /// # Example /// /// ```no_run /// async { /// let get = warp::get(); /// let post = warp::post(); /// /// assert!( /// warp::test::request() /// .method("GET") /// .matches(&get) /// .await /// ); /// /// assert!( /// !warp::test::request() /// .method("GET") /// .matches(&post) /// .await /// ); ///}; /// ``` pubasyncfn matches<F>(self, f: &F) -> bool where
F: Filter,
F::Future: Send + 'static,
F::Extract: Send + 'static,
F::Error: Send + 'static,
{ self.apply_filter(f).await.is_ok()
}
/// Returns `Response` provided by applying the `Filter`. /// /// This requires that the supplied `Filter` return a [`Reply`]. pubasyncfn reply<F>(self, f: &F) -> Response<Bytes> where
F: Filter + 'static,
F::Extract: Reply + Send,
F::Error: IsReject + Send,
{ // TODO: de-duplicate this and apply_filter()
assert!(!route::is_set(), "nested test filter calls");
let route = Route::new(self.req, self.remote_addr); letmut fut = Box::pin(
route::set(&route, move || f.filter(crate::filter::Internal)).then(|result| { let res = match result {
Ok(rep) => rep.into_response(),
Err(rej) => {
tracing::debug!("rejected: {:?}", rej);
rej.into_response()
}
}; let (parts, body) = res.into_parts();
hyper::body::to_bytes(body).map_ok(|chunk| Response::from_parts(parts, chunk))
}),
);
let fut = future::poll_fn(move |cx| route::set(&route, || fut.as_mut().poll(cx)));
#[cfg(feature = "websocket")] impl WsBuilder { /// Sets the request path of this builder. /// /// The default is not set is `/`. /// /// # Example /// /// ``` /// let req = warp::test::ws() /// .path("/chat"); /// ``` /// /// # Panic /// /// This panics if the passed string is not able to be parsed as a valid /// `Uri`. pubfn path(self, p: &str) -> Self {
WsBuilder {
req: self.req.path(p),
}
}
/// Set a header for this request. /// /// # Example /// /// ``` /// let req = warp::test::ws() /// .header("foo", "bar"); /// ``` /// /// # Panic /// /// This panics if the passed strings are not able to be parsed as a valid /// `HeaderName` and `HeaderValue`. pubfn header<K, V>(self, key: K, value: V) -> Self where
HeaderName: TryFrom<K>,
HeaderValue: TryFrom<V>,
{
WsBuilder {
req: self.req.header(key, value),
}
}
/// Execute this Websocket request against the provided filter. /// /// If the handshake succeeds, returns a `WsClient`. /// /// # Example /// /// ```no_run /// use futures_util::future; /// use warp::Filter; /// #[tokio::main] /// # async fn main() { /// /// // Some route that accepts websockets (but drops them immediately). /// let route = warp::ws() /// .map(|ws: warp::ws::Ws| { /// ws.on_upgrade(|_| future::ready(())) /// }); /// /// let client = warp::test::ws() /// .handshake(route) /// .await /// .expect("handshake"); /// # } /// ``` pubasyncfn handshake<F>(self, f: F) -> Result<WsClient, WsError> where
F: Filter + Clone + Send + Sync + 'static,
F::Extract: Reply + Send,
F::Error: IsReject + Send,
{ let (upgraded_tx, upgraded_rx) = oneshot::channel(); let (wr_tx, wr_rx) = mpsc::unbounded(); let (rd_tx, rd_rx) = mpsc::unbounded();
tokio::spawn(asyncmove { use tokio_tungstenite::tungstenite::protocol;
let (addr, srv) = crate::serve(f).bind_ephemeral(([127, 0, 0, 1], 0));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.