mirror of https://github.com/rwf2/Rocket.git
Document new 'local' structures.
This commit is contained in:
parent
03127f4dae
commit
050a2c6461
|
@ -6,10 +6,45 @@ use crate::rocket::{Rocket, Cargo};
|
|||
use crate::http::{Method, private::CookieJar};
|
||||
use crate::error::LaunchError;
|
||||
|
||||
struct_client! { [
|
||||
///
|
||||
/// ### Synchronization
|
||||
///
|
||||
/// While `Client` implements `Sync`, using it in a multithreaded environment
|
||||
/// while tracking cookies can result in surprising, non-deterministic behavior.
|
||||
/// This is because while cookie modifications are serialized, the exact
|
||||
/// ordering depends on when requests are dispatched. Specifically, when cookie
|
||||
/// tracking is enabled, all request dispatches are serialized, which in-turn
|
||||
/// serializes modifications to the internally tracked cookies.
|
||||
///
|
||||
/// If possible, refrain from sharing a single instance of `Client` across
|
||||
/// multiple threads. Instead, prefer to create a unique instance of `Client`
|
||||
/// per thread. If it's not possible, ensure that either you are not depending
|
||||
/// on cookies, the ordering of their modifications, or both, or have arranged
|
||||
/// for dispatches to occur in a deterministic ordering.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// The following snippet creates a `Client` from a `Rocket` instance and
|
||||
/// dispatches a local request to `POST /` with a body of `Hello, world!`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::local::asynchronous::Client;
|
||||
///
|
||||
/// # rocket::async_test(async {
|
||||
/// let rocket = rocket::ignite();
|
||||
/// let client = Client::new(rocket).await.expect("valid rocket");
|
||||
/// let response = client.post("/")
|
||||
/// .body("Hello, world!")
|
||||
/// .dispatch().await;
|
||||
/// # });
|
||||
/// ```
|
||||
]
|
||||
pub struct Client {
|
||||
cargo: Cargo,
|
||||
pub(crate) cookies: Option<RwLock<CookieJar>>,
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub(crate) async fn _new(
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
//! Structures for asynchronous local dispatching of requests, primarily for
|
||||
//! testing.
|
||||
//!
|
||||
//! This module contains the `asynchronous` variant of the `local` API: it can
|
||||
//! be used with `#[rocket::async_test]` or another asynchronous test harness.
|
||||
|
||||
mod client;
|
||||
mod request;
|
||||
mod response;
|
||||
|
|
|
@ -5,12 +5,33 @@ use crate::http::{Status, Method, uri::Origin, ext::IntoOwned};
|
|||
|
||||
use super::{Client, LocalResponse};
|
||||
|
||||
struct_request! { [
|
||||
/// ## Example
|
||||
///
|
||||
/// The following snippet uses the available builder methods to construct a
|
||||
/// `POST` request to `/` with a JSON body:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::local::asynchronous::{Client, LocalRequest};
|
||||
/// use rocket::http::{ContentType, Cookie};
|
||||
///
|
||||
/// # rocket::async_test(async {
|
||||
/// let client = Client::new(rocket::ignite()).await.expect("valid rocket");
|
||||
/// let req = client.post("/")
|
||||
/// .header(ContentType::JSON)
|
||||
/// .remote("127.0.0.1:8000".parse().unwrap())
|
||||
/// .cookie(Cookie::new("name", "value"))
|
||||
/// .body(r#"{ "value": 42 }"#);
|
||||
/// # });
|
||||
/// ```
|
||||
]
|
||||
pub struct LocalRequest<'c> {
|
||||
client: &'c Client,
|
||||
request: Request<'c>,
|
||||
data: Vec<u8>,
|
||||
uri: Cow<'c, str>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> LocalRequest<'c> {
|
||||
pub(crate) fn new(
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::{Request, Response};
|
||||
|
||||
struct_response! {
|
||||
pub struct LocalResponse<'c> {
|
||||
pub(in super) _request: Request<'c>,
|
||||
pub(in super) inner: Response<'c>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> LocalResponse<'c> {
|
||||
fn _response(&self) -> &Response<'c> {
|
||||
|
|
|
@ -5,10 +5,28 @@ use crate::http::Method;
|
|||
use crate::local::{asynchronous, blocking::LocalRequest};
|
||||
use crate::rocket::{Rocket, Cargo};
|
||||
|
||||
struct_client! { [
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// The following snippet creates a `Client` from a `Rocket` instance and
|
||||
/// dispatches a local request to `POST /` with a body of `Hello, world!`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::local::blocking::Client;
|
||||
///
|
||||
/// let rocket = rocket::ignite();
|
||||
/// let client = Client::new(rocket).expect("valid rocket");
|
||||
/// let response = client.post("/")
|
||||
/// .body("Hello, world!")
|
||||
/// .dispatch();
|
||||
/// ```
|
||||
]
|
||||
pub struct Client {
|
||||
pub(crate) inner: asynchronous::Client,
|
||||
runtime: RefCell<tokio::runtime::Runtime>,
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
|
||||
|
|
|
@ -1,103 +1,9 @@
|
|||
// TODO: Explain difference from async Client
|
||||
//! Structures for blocking local dispatching of requests, primarily for
|
||||
//! testing.
|
||||
//!
|
||||
//! Structures for local dispatching of requests, primarily for testing.
|
||||
//!
|
||||
//! This module allows for simple request dispatching against a local,
|
||||
//! non-networked instance of Rocket. The primary use of this module is to unit
|
||||
//! and integration test Rocket applications by crafting requests, dispatching
|
||||
//! them, and verifying the response.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! This module contains a [`Client`] structure that is used to create
|
||||
//! [`LocalRequest`] structures that can be dispatched against a given
|
||||
//! [`Rocket`](crate::Rocket) instance. Usage is straightforward:
|
||||
//!
|
||||
//! 1. Construct a `Rocket` instance that represents the application.
|
||||
//!
|
||||
//! ```rust
|
||||
//! let rocket = rocket::ignite();
|
||||
//! # let _ = rocket;
|
||||
//! ```
|
||||
//!
|
||||
//! 2. Construct a `Client` using the `Rocket` instance.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use rocket::local::blocking::Client;
|
||||
//! # let rocket = rocket::ignite();
|
||||
//! let client = Client::new(rocket).expect("valid rocket instance");
|
||||
//! # let _ = client;
|
||||
//! ```
|
||||
//!
|
||||
//! 3. Construct requests using the `Client` instance.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use rocket::local::blocking::Client;
|
||||
//! # let rocket = rocket::ignite();
|
||||
//! # let client = Client::new(rocket).unwrap();
|
||||
//! let req = client.get("/");
|
||||
//! # let _ = req;
|
||||
//! ```
|
||||
//!
|
||||
//! 3. Dispatch the request to retrieve the response.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use rocket::local::blocking::Client;
|
||||
//! # let rocket = rocket::ignite();
|
||||
//! # let client = Client::new(rocket).unwrap();
|
||||
//! # let req = client.get("/");
|
||||
//! let response = req.dispatch();
|
||||
//! # let _ = response;
|
||||
//! ```
|
||||
//!
|
||||
//! All together and in idiomatic fashion, this might look like:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rocket::local::blocking::Client;
|
||||
//!
|
||||
//! let client = Client::new(rocket::ignite()).expect("valid rocket");
|
||||
//! let response = client.post("/")
|
||||
//! .body("Hello, world!")
|
||||
//! .dispatch();
|
||||
//! # let _ = response;
|
||||
//! ```
|
||||
//!
|
||||
//! # Unit/Integration Testing
|
||||
//!
|
||||
//! This module can be used to test a Rocket application by constructing
|
||||
//! requests via `Client` and validating the resulting response. As an example,
|
||||
//! consider the following complete "Hello, world!" application, with testing.
|
||||
//!
|
||||
//! ```rust
|
||||
//! #![feature(proc_macro_hygiene)]
|
||||
//!
|
||||
//! #[macro_use] extern crate rocket;
|
||||
//!
|
||||
//! #[get("/")]
|
||||
//! fn hello() -> &'static str {
|
||||
//! "Hello, world!"
|
||||
//! }
|
||||
//!
|
||||
//! # fn main() { }
|
||||
//! #[cfg(test)]
|
||||
//! mod test {
|
||||
//! use super::{rocket, hello};
|
||||
//! use rocket::local::blocking::Client;
|
||||
//!
|
||||
//! fn test_hello_world() {
|
||||
//! // Construct a client to use for dispatching requests.
|
||||
//! let rocket = rocket::ignite().mount("/", routes![hello]);
|
||||
//! let client = Client::new(rocket).expect("valid rocket instance");
|
||||
//!
|
||||
//! // Dispatch a request to 'GET /' and validate the response.
|
||||
//! let mut response = client.get("/").dispatch();
|
||||
//! assert_eq!(response.into_string(), Some("Hello, world!".into()));
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [`Client`]: crate::local::blocking::Client
|
||||
//! [`LocalRequest`]: crate::local::blocking::LocalRequest
|
||||
//! This module contains the `blocking` variant of the `local` API: it can be
|
||||
//! used in Rust's synchronous `#[test]` harness. This is accomplished by
|
||||
//! starting and running an interal asynchronous Runtime as needed.
|
||||
|
||||
mod client;
|
||||
mod request;
|
||||
|
|
|
@ -3,11 +3,31 @@ use std::borrow::Cow;
|
|||
use crate::{Request, http::Method, local::asynchronous};
|
||||
|
||||
use super::{Client, LocalResponse};
|
||||
|
||||
struct_request! { [
|
||||
/// ## Example
|
||||
///
|
||||
/// The following snippet uses the available builder methods to construct a
|
||||
/// `POST` request to `/` with a JSON body:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::local::blocking::{Client, LocalRequest};
|
||||
/// use rocket::http::{ContentType, Cookie};
|
||||
///
|
||||
/// let client = Client::new(rocket::ignite()).expect("valid rocket");
|
||||
/// let req = client.post("/")
|
||||
/// .header(ContentType::JSON)
|
||||
/// .remote("127.0.0.1:8000".parse().unwrap())
|
||||
/// .cookie(Cookie::new("name", "value"))
|
||||
/// .body(r#"{ "value": 42 }"#);
|
||||
/// ```
|
||||
]
|
||||
#[derive(Clone)]
|
||||
pub struct LocalRequest<'c> {
|
||||
inner: asynchronous::LocalRequest<'c>,
|
||||
client: &'c Client,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> LocalRequest<'c> {
|
||||
#[inline]
|
||||
|
|
|
@ -2,10 +2,12 @@ use crate::{Response, local::asynchronous};
|
|||
|
||||
use super::Client;
|
||||
|
||||
struct_response! {
|
||||
pub struct LocalResponse<'c> {
|
||||
pub(in super) inner: asynchronous::LocalResponse<'c>,
|
||||
pub(in super) client: &'c Client,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c> LocalResponse<'c> {
|
||||
fn _response(&self) -> &Response<'c> {
|
||||
|
|
|
@ -1,66 +1,40 @@
|
|||
//! A structure to construct requests for local dispatching.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! A `Client` is constructed via the [`new()`] or [`untracked()`] methods from
|
||||
//! an already constructed `Rocket` instance. Once a value of `Client` has been
|
||||
//! constructed, the [`LocalRequest`] constructor methods ([`get()`], [`put()`],
|
||||
//! [`post()`], and so on) can be used to create a `LocalRequest` for
|
||||
//! dispatching.
|
||||
//!
|
||||
//! See the [top-level documentation](crate::local) for more usage information.
|
||||
//!
|
||||
//! ## Cookie Tracking
|
||||
//!
|
||||
//! A `Client` constructed using [`new()`] propagates cookie changes made by
|
||||
//! responses to previously dispatched requests. In other words, if a previously
|
||||
//! dispatched request resulted in a response that adds a cookie, any future
|
||||
//! requests will contain that cookie. Similarly, cookies removed by a response
|
||||
//! won't be propagated further.
|
||||
//!
|
||||
//! This is typically the desired mode of operation for a `Client` as it removes
|
||||
//! the burden of manually tracking cookies. Under some circumstances, however,
|
||||
//! disabling this tracking may be desired. In these cases, use the
|
||||
//! [`untracked()`](Client::untracked()) constructor to create a `Client` that
|
||||
//! _will not_ track cookies.
|
||||
//!
|
||||
//! ### Synchronization
|
||||
//!
|
||||
//! While `Client` implements `Sync`, using it in a multithreaded environment
|
||||
//! while tracking cookies can result in surprising, non-deterministic behavior.
|
||||
//! This is because while cookie modifications are serialized, the exact
|
||||
//! ordering depends on when requests are dispatched. Specifically, when cookie
|
||||
//! tracking is enabled, all request dispatches are serialized, which in-turn
|
||||
//! serializes modifications to the internally tracked cookies.
|
||||
//!
|
||||
//! If possible, refrain from sharing a single instance of `Client` across
|
||||
//! multiple threads. Instead, prefer to create a unique instance of `Client`
|
||||
//! per thread. If it's not possible, ensure that either you are not depending
|
||||
//! on cookies, the ordering of their modifications, or both, or have arranged
|
||||
//! for dispatches to occur in a deterministic ordering.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! The following snippet creates a `Client` from a `Rocket` instance and
|
||||
//! dispatches a local request to `POST /` with a body of `Hello, world!`.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rocket::local::asynchronous::Client;
|
||||
//!
|
||||
//! # rocket::async_test(async {
|
||||
//! let rocket = rocket::ignite();
|
||||
//! let client = Client::new(rocket).await.expect("valid rocket");
|
||||
//! let response = client.post("/")
|
||||
//! .body("Hello, world!")
|
||||
//! .dispatch().await;
|
||||
//! # });
|
||||
//! ```
|
||||
//!
|
||||
//! [`new()`]: #method.new
|
||||
//! [`untracked()`]: #method.untracked
|
||||
//! [`get()`]: #method.get
|
||||
//! [`put()`]: #method.put
|
||||
//! [`post()`]: #method.post
|
||||
macro_rules! struct_client {
|
||||
([$(#[$attr:meta])*] $item:item) =>
|
||||
{
|
||||
/// A structure to construct requests for local dispatching.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// A `Client` is constructed via the [`new()`] or [`untracked()`] methods from
|
||||
/// an already constructed `Rocket` instance. Once a value of `Client` has been
|
||||
/// constructed, the [`LocalRequest`] constructor methods ([`get()`], [`put()`],
|
||||
/// [`post()`], and so on) can be used to create a `LocalRequest` for
|
||||
/// dispatching.
|
||||
///
|
||||
/// See the [top-level documentation](crate::local) for more usage information.
|
||||
///
|
||||
/// ## Cookie Tracking
|
||||
///
|
||||
/// A `Client` constructed using [`new()`] propagates cookie changes made by
|
||||
/// responses to previously dispatched requests. In other words, if a previously
|
||||
/// dispatched request resulted in a response that adds a cookie, any future
|
||||
/// requests will contain that cookie. Similarly, cookies removed by a response
|
||||
/// won't be propagated further.
|
||||
///
|
||||
/// This is typically the desired mode of operation for a `Client` as it removes
|
||||
/// the burden of manually tracking cookies. Under some circumstances, however,
|
||||
/// disabling this tracking may be desired. In these cases, use the
|
||||
/// [`untracked()`](Client::untracked()) constructor to create a `Client` that
|
||||
/// _will not_ track cookies.
|
||||
$(#[$attr])*
|
||||
///
|
||||
/// [`new()`]: #method.new
|
||||
/// [`untracked()`]: #method.untracked
|
||||
/// [`get()`]: #method.get
|
||||
/// [`put()`]: #method.put
|
||||
/// [`post()`]: #method.post
|
||||
$item
|
||||
}}
|
||||
|
||||
macro_rules! req_method {
|
||||
($import:literal, $NAME:literal, $f:ident, $method:expr) => (
|
||||
|
|
|
@ -7,7 +7,14 @@
|
|||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! This module contains a [`Client`] structure that is used to create
|
||||
//! This module contains two variants of the local API: [`asynchronous`] and
|
||||
//! [`blocking`]. The primary difference between the two is in usage: the
|
||||
//! `asynchronous` API requires an asynchronous test entry point such as
|
||||
//! `#[rocket::async_test]`, while the `blocking` API can be used with
|
||||
//! `#[test]`. Additionally, several methods in the `asynchronous` API are
|
||||
//! `async` and must therefore be `await`ed.
|
||||
//!
|
||||
//! Both APIs include a [`Client`] structure that is used to create
|
||||
//! [`LocalRequest`] structures that can be dispatched against a given
|
||||
//! [`Rocket`](crate::Rocket) instance. Usage is straightforward:
|
||||
//!
|
||||
|
@ -104,7 +111,7 @@
|
|||
//! ```
|
||||
//!
|
||||
//! [`Client`]: crate::local::asynchronous::Client
|
||||
//! [`LocalRequest`]: crate::local::LocalRequest
|
||||
//! [`LocalRequest`]: crate::local::asynchronous::LocalRequest
|
||||
|
||||
#[macro_use] mod client;
|
||||
#[macro_use] mod request;
|
||||
|
|
|
@ -1,62 +1,38 @@
|
|||
//! A structure representing a local request as created by [`Client`].
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! A `LocalRequest` value is constructed via method constructors on [`Client`].
|
||||
//! Headers can be added via the [`header`] builder method and the
|
||||
//! [`add_header`] method. Cookies can be added via the [`cookie`] builder
|
||||
//! method. The remote IP address can be set via the [`remote`] builder method.
|
||||
//! The body of the request can be set via the [`body`] builder method or
|
||||
//! [`set_body`] method.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! The following snippet uses the available builder methods to construct a
|
||||
//! `POST` request to `/` with a JSON body:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use rocket::local::asynchronous::Client;
|
||||
//! use rocket::http::{ContentType, Cookie};
|
||||
//!
|
||||
//! # rocket::async_test(async {
|
||||
//! let client = Client::new(rocket::ignite()).await.expect("valid rocket");
|
||||
//! let req = client.post("/")
|
||||
//! .header(ContentType::JSON)
|
||||
//! .remote("127.0.0.1:8000".parse().unwrap())
|
||||
//! .cookie(Cookie::new("name", "value"))
|
||||
//! .body(r#"{ "value": 42 }"#);
|
||||
//! # });
|
||||
//! ```
|
||||
//!
|
||||
//! # Dispatching
|
||||
//!
|
||||
//! A `LocalRequest` can be dispatched in one of two ways:
|
||||
//!
|
||||
//! 1. [`dispatch`]
|
||||
//!
|
||||
//! This method should always be preferred. The `LocalRequest` is consumed
|
||||
//! and a response is returned.
|
||||
//!
|
||||
//! 2. [`mut_dispatch`]
|
||||
//!
|
||||
//! This method should _only_ be used when either it is known that the
|
||||
//! application will not modify the request, or it is desired to see
|
||||
//! modifications to the request. No cloning occurs, and the request is not
|
||||
//! consumed.
|
||||
//!
|
||||
//! Additionally, note that `LocalRequest` implements `Clone`. As such, if the
|
||||
//! same request needs to be dispatched multiple times, the request can first be
|
||||
//! cloned and then dispatched: `request.clone().dispatch()`.
|
||||
//!
|
||||
//! [`Client`]: crate::local::asynchronous::Client
|
||||
//! [`header`]: #method.header
|
||||
//! [`add_header`]: #method.add_header
|
||||
//! [`cookie`]: #method.cookie
|
||||
//! [`remote`]: #method.remote
|
||||
//! [`body`]: #method.body
|
||||
//! [`set_body`]: #method.set_body
|
||||
//! [`dispatch`]: #method.dispatch
|
||||
//! [`mut_dispatch`]: #method.mut_dispatch
|
||||
macro_rules! struct_request {
|
||||
([$(#[$attr:meta])*] $item:item) =>
|
||||
{
|
||||
/// A structure representing a local request as created by [`Client`].
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// A `LocalRequest` value is constructed via method constructors on [`Client`].
|
||||
/// Headers can be added via the [`header`] builder method and the
|
||||
/// [`add_header`] method. Cookies can be added via the [`cookie`] builder
|
||||
/// method. The remote IP address can be set via the [`remote`] builder method.
|
||||
/// The body of the request can be set via the [`body`] builder method or
|
||||
/// [`set_body`] method.
|
||||
///
|
||||
$(#[$attr])*
|
||||
///
|
||||
/// # Dispatching
|
||||
///
|
||||
/// A `LocalRequest` is dispatched by calling [`dispatch`].
|
||||
/// The `LocalRequest` is consumed and a response is returned.
|
||||
///
|
||||
/// Note that `LocalRequest` implements `Clone`. As such, if the
|
||||
/// same request needs to be dispatched multiple times, the request can first be
|
||||
/// cloned and then dispatched: `request.clone().dispatch()`.
|
||||
///
|
||||
/// [`Client`]: super::Client
|
||||
/// [`header`]: #method.header
|
||||
/// [`add_header`]: #method.add_header
|
||||
/// [`cookie`]: #method.cookie
|
||||
/// [`remote`]: #method.remote
|
||||
/// [`body`]: #method.body
|
||||
/// [`set_body`]: #method.set_body
|
||||
/// [`dispatch`]: #method.dispatch
|
||||
$item
|
||||
}}
|
||||
|
||||
macro_rules! impl_request {
|
||||
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
//! A structure representing a response from dispatching a local request.
|
||||
//!
|
||||
//! This structure is a thin wrapper around [`Response`]. It implements no
|
||||
//! methods of its own; all functionality is exposed via the [`Deref`] and
|
||||
//! [`DerefMut`] implementations with a target of `Response`. In other words,
|
||||
//! when invoking methods, a `LocalResponse` can be treated exactly as if it
|
||||
//! were a `Response`.
|
||||
macro_rules! struct_response {
|
||||
($item:item) =>
|
||||
{
|
||||
/// A structure representing a response from dispatching a local request.
|
||||
///
|
||||
/// This structure is a thin wrapper around [`Response`]. It implements no
|
||||
/// methods of its own; all functionality is exposed via the [`Deref`]
|
||||
/// implementation with a target of `Response`. In other words, when
|
||||
/// invoking methods, a `LocalResponse` can be treated exactly as if it were
|
||||
/// a (read-only) `Response`.
|
||||
///
|
||||
/// [`Deref`]: std::ops::Deref
|
||||
$item
|
||||
}}
|
||||
|
||||
macro_rules! impl_response {
|
||||
($import:literal $(@$prefix:tt $suffix:tt)? $name:ident) =>
|
||||
|
|
Loading…
Reference in New Issue