From 9de966bb297e529d87fd3a15212ebd3bcf7d3b92 Mon Sep 17 00:00:00 2001 From: Yong Wen Chua Date: Mon, 24 Jul 2017 11:31:49 +0800 Subject: [PATCH] Add documentation and example Change Fn to FnOnce to allow for moving and consuming --- examples/fairing.rs | 2 +- examples/guard.rs | 6 +- examples/manual.rs | 94 +++++++++++++++++++++ src/lib.rs | 194 ++++++++++++++++++++++++++++++++++++++++++-- tests/fairing.rs | 2 +- tests/guard.rs | 2 +- tests/headers.rs | 2 +- tests/manual.rs | 2 +- 8 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 examples/manual.rs diff --git a/examples/fairing.rs b/examples/fairing.rs index e666270..eca79b1 100644 --- a/examples/fairing.rs +++ b/examples/fairing.rs @@ -1,4 +1,4 @@ -#![feature(plugin, custom_derive)] +#![feature(plugin)] #![plugin(rocket_codegen)] extern crate rocket; extern crate rocket_cors; diff --git a/examples/guard.rs b/examples/guard.rs index 3b7c7b3..e014e8b 100644 --- a/examples/guard.rs +++ b/examples/guard.rs @@ -1,4 +1,4 @@ -#![feature(plugin, custom_derive)] +#![feature(plugin)] #![plugin(rocket_codegen)] extern crate rocket; extern crate rocket_cors; @@ -23,7 +23,7 @@ fn responder_options(cors: Guard) -> Responder<()> { } /// Using a `Response` instead of a `Responder`. You generally won't have to do this. -#[get("/responder")] +#[get("/response")] fn response(cors: Guard) -> Response { let mut response = Response::new(); response.set_sized_body(Cursor::new("Hello CORS!")); @@ -32,7 +32,7 @@ fn response(cors: Guard) -> Response { /// You need to define an OPTIONS route for preflight checks. /// These routes can just return the unit type `()` -#[options("/responder")] +#[options("/response")] fn response_options(cors: Guard) -> Response { let response = Response::new(); cors.response(response) diff --git a/examples/manual.rs b/examples/manual.rs new file mode 100644 index 0000000..67a3e65 --- /dev/null +++ b/examples/manual.rs @@ -0,0 +1,94 @@ +#![feature(plugin, conservative_impl_trait)] +#![plugin(rocket_codegen)] +extern crate rocket; +extern crate rocket_cors; + +use std::io::Cursor; + +use rocket::{State, Response}; +use rocket::http::Method; +use rocket::response::Responder; +use rocket_cors::{Cors, AllowedOrigins, AllowedHeaders}; + +/// Using a borrowed Cors +#[get("/")] +fn borrowed<'r>(options: State<'r, Cors>) -> impl Responder<'r> { + options.inner().respond_borrowed( + |guard| guard.responder("Hello CORS"), + ) +} + +/// You need to define an OPTIONS route for preflight checks. +/// These routes can just return the unit type `()` +#[options("/")] +fn borrowed_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> { + options.inner().respond_borrowed( + |guard| guard.responder(()), + ) +} + +/// Using a `Response` instead of a `Responder`. You generally won't have to do this. +#[get("/response")] +fn response<'r>(options: State<'r, Cors>) -> impl Responder<'r> { + let mut response = Response::new(); + response.set_sized_body(Cursor::new("Hello CORS!")); + + options.inner().respond_borrowed( + move |guard| guard.response(response), + ) +} + +/// You need to define an OPTIONS route for preflight checks. +/// These routes can just return the unit type `()` +#[options("/response")] +fn response_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> { + options.inner().respond_borrowed( + move |guard| guard.response(Response::new()), + ) +} + +/// Create and use an ad-hoc Cors +#[get("/owned")] +fn owned<'r>() -> impl Responder<'r> { + let options = cors_options(); + options.respond_owned(|guard| guard.responder("Hello CORS")) +} + +/// You need to define an OPTIONS route for preflight checks. +/// These routes can just return the unit type `()` +#[options("/owned")] +fn owned_options<'r>() -> impl Responder<'r> { + let options = cors_options(); + options.respond_owned(|guard| guard.responder(())) +} + +fn cors_options() -> Cors { + let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.acme.com"]); + assert!(failed_origins.is_empty()); + + // You can also deserialize this + rocket_cors::Cors { + allowed_origins: allowed_origins, + allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), + allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]), + allow_credentials: true, + ..Default::default() + } +} + +fn main() { + rocket::ignite() + .mount( + "/", + routes![ + borrowed, + borrowed_options, + response, + response_options, + owned, + owned_options, + ], + ) + .manage(cors_options()) + .launch(); +} diff --git a/src/lib.rs b/src/lib.rs index ecc19e3..9704943 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,7 @@ //! //! - Fairing (should only used exclusively) //! - Request Guard -//! - Truly Manual (not supported yet, [#13](https://github.com/lawliet89/rocket_cors/issues/13)) +//! - Truly Manual //! //! Unfortunately, you cannot mix and match Fairing with any other of the methods, due to the //! limitation of Rocket's fairing API. That is, the checks for Fairing will always happen first, @@ -213,7 +213,189 @@ //! //! ## Truly Manual //! -//! This is not supported yet. See [#13](https://github.com/lawliet89/rocket_cors/issues/13). +//! This mode is the most difficult to use but offers the most amount of flexibility. +//! You might have to understand how the library works internally to know how to use this mode. +//! In exchange, you can selectively choose which routes to offer CORS protection to, and you +//! can mix and match CORS settings for the routes. You can combine usage of this mode with +//! "guard" to offer a mix of ease of use and flexibility. +//! +//! You really do not need to use this unless you have a truly ad-hoc need to respond to CORS +//! differently in a route. For example, you have a `ping` endpoint that allows all origins but +//! the rest of your routes do not. +//! +//! ### Handler +//! +//! This mode requires that you pass in a closure that will be lazily evaluated once a CORS request +//! has been validated. If validation fails, the closure will not be run. You should put any code +//! that has any side effects or with an appreciable computation cost inside this handler. +//! +//! ### Steps to perform: +//! - Your crate will need to enable the +//! [`conservative_impl_trait`](https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md) +//! feature. You can use `#![feature(conservative_impl_trait)]` at your crate root. +//! Otherwise, the return type of your routes will be unspecifiable. +//! - You will first need to have a `Cors` struct ready. This struct can be borrowed with a lifetime +//! at least as long as `'r` which is the lifetime of a Rocket request. `'static` works too. +//! In this case, you might as well use the `Guard` method above and place the `Cors` struct in +//! Rocket's [state](https://rocket.rs/guide/state/). +//! Alternatively, you can create a `Cors` struct directly in the route. +//! - Your routes will need to have a `'r` lifetime and return `impl Responder<'r>`. +//! - Using the `Cors` struct, use either the +//! [`respond_owned`](struct.Cors.html#method.respond_owned) or +//! [`respond_borrowed`](struct.Cors.html#method.respond_borrowed) function and pass in a handler +//! that will be executed once CORS validation is successful. +//! - Your handler will be passed a [`Guard`](struct.Guard.html) which you will have to use to +//! add CORS headers into your own response. +//! - You will have to manually define your own `OPTIONS` routes. +//! +//! ### Notes about route lifetime +//! It is unfortunate that you have to manually specify the `'r` lifetimes in your routes. +//! Leaving out the lifetime will result in a +//! [compiler panic](https://github.com/rust-lang/rust/issues/43380). Even if the panic is fixed, +//! it is not known if we can exclude the lifetime because lifetimes are _elided_ in Rust, +//! not inferred. +//! +//! ### Owned example +//! This is the most likely scenario when you want to have manual CORS validation. You can use this +//! when the settings you want to use for a route is not the same as the rest of the application +//! (which you might have put in Rocket's state). +//! +//! ```rust,no_run +//! #![feature(plugin, conservative_impl_trait)] +//! #![plugin(rocket_codegen)] +//! extern crate rocket; +//! extern crate rocket_cors; +//! +//! use rocket::http::Method; +//! use rocket::response::Responder; +//! use rocket_cors::{Cors, AllowedOrigins, AllowedHeaders}; +//! +//! /// Create and use an ad-hoc Cors +//! #[get("/owned")] +//! fn owned<'r>() -> impl Responder<'r> { +//! let options = cors_options(); +//! options.respond_owned(|guard| guard.responder("Hello CORS")) +//! } +//! +//! /// You need to define an OPTIONS route for preflight checks. +//! /// These routes can just return the unit type `()` +//! #[options("/owned")] +//! fn owned_options<'r>() -> impl Responder<'r> { +//! let options = cors_options(); +//! options.respond_owned(|guard| guard.responder(())) +//! } +//! +//! fn cors_options() -> Cors { +//! let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.acme.com"]); +//! assert!(failed_origins.is_empty()); +//! +//! // You can also deserialize this +//! rocket_cors::Cors { +//! allowed_origins: allowed_origins, +//! allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), +//! allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]), +//! allow_credentials: true, +//! ..Default::default() +//! } +//! } +//! +//! fn main() { +//! rocket::ignite() +//! .mount( +//! "/", +//! routes![ +//! owned, +//! owned_options, +//! ], +//! ) +//! .manage(cors_options()) +//! .launch(); +//! } +//! ``` +//! +//! ### Borrowed Example +//! You might want to borrow the `Cors` struct from Rocket's state, for example. Unless you have +//! special handling, you might want to use the Guard method instead which has less hassle. +//! +//! ```rust,no_run +//! #![feature(plugin, conservative_impl_trait)] +//! #![plugin(rocket_codegen)] +//! extern crate rocket; +//! extern crate rocket_cors; +//! +//! use std::io::Cursor; +//! +//! use rocket::{State, Response}; +//! use rocket::http::Method; +//! use rocket::response::Responder; +//! use rocket_cors::{Cors, AllowedOrigins, AllowedHeaders}; +//! +//! /// Using a borrowed Cors +//! #[get("/")] +//! fn borrowed<'r>(options: State<'r, Cors>) -> impl Responder<'r> { +//! options.inner().respond_borrowed( +//! |guard| guard.responder("Hello CORS"), +//! ) +//! } +//! +//! /// You need to define an OPTIONS route for preflight checks. +//! /// These routes can just return the unit type `()` +//! #[options("/")] +//! fn borrowed_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> { +//! options.inner().respond_borrowed( +//! |guard| guard.responder(()), +//! ) +//! } +//! +//! /// Using a `Response` instead of a `Responder`. You generally won't have to do this. +//! #[get("/response")] +//! fn response<'r>(options: State<'r, Cors>) -> impl Responder<'r> { +//! let mut response = Response::new(); +//! response.set_sized_body(Cursor::new("Hello CORS!")); +//! +//! options.inner().respond_borrowed( +//! move |guard| guard.response(response), +//! ) +//! } +//! +//! /// You need to define an OPTIONS route for preflight checks. +//! /// These routes can just return the unit type `()` +//! #[options("/response")] +//! fn response_options<'r>(options: State<'r, Cors>) -> impl Responder<'r> { +//! options.inner().respond_borrowed( +//! move |guard| guard.response(Response::new()), +//! ) +//! } +//! +//! fn cors_options() -> Cors { +//! let (allowed_origins, failed_origins) = AllowedOrigins::some(&["https://www.acme.com"]); +//! assert!(failed_origins.is_empty()); +//! +//! // You can also deserialize this +//! rocket_cors::Cors { +//! allowed_origins: allowed_origins, +//! allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), +//! allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]), +//! allow_credentials: true, +//! ..Default::default() +//! } +//! } +//! +//! fn main() { +//! rocket::ignite() +//! .mount( +//! "/", +//! routes![ +//! borrowed, +//! borrowed_options, +//! response, +//! response_options, +//! ], +//! ) +//! .manage(cors_options()) +//! .launch(); +//! } +//! ``` #![allow( legacy_directory_ownership, @@ -850,7 +1032,7 @@ impl Cors { /// See the documentation at the [crate root](index.html) for usage information. pub fn respond_owned<'r, F, R>(self, handler: F) -> Result, Error> where - F: Fn(Guard<'r>) -> R + 'r, + F: FnOnce(Guard<'r>) -> R + 'r, R: response::Responder<'r>, { self.validate()?; @@ -874,7 +1056,7 @@ impl Cors { handler: F, ) -> Result, Error> where - F: Fn(Guard<'r>) -> R + 'r, + F: FnOnce(Guard<'r>) -> R + 'r, R: response::Responder<'r>, { self.validate()?; @@ -1178,7 +1360,7 @@ pub struct ManualResponder<'r, F, R> { impl<'r, F, R> ManualResponder<'r, F, R> where - F: Fn(Guard<'r>) -> R + 'r, + F: FnOnce(Guard<'r>) -> R + 'r, R: response::Responder<'r>, { /// Create a new manual responder by passing in either a borrowed or owned `Cors` option. @@ -1202,7 +1384,7 @@ where impl<'r, F, R> response::Responder<'r> for ManualResponder<'r, F, R> where - F: Fn(Guard<'r>) -> R + 'r, + F: FnOnce(Guard<'r>) -> R + 'r, R: response::Responder<'r>, { fn respond_to(self, request: &Request) -> response::Result<'r> { diff --git a/tests/fairing.rs b/tests/fairing.rs index c54f3d9..9d0c365 100644 --- a/tests/fairing.rs +++ b/tests/fairing.rs @@ -1,6 +1,6 @@ //! This crate tests using rocket_cors using Fairings -#![feature(plugin, custom_derive)] +#![feature(plugin)] #![plugin(rocket_codegen)] extern crate hyper; extern crate rocket; diff --git a/tests/guard.rs b/tests/guard.rs index ec17d8d..15c9901 100644 --- a/tests/guard.rs +++ b/tests/guard.rs @@ -1,6 +1,6 @@ //! This crate tests using rocket_cors using the per-route handling with request guard -#![feature(plugin, custom_derive)] +#![feature(plugin)] #![plugin(rocket_codegen)] extern crate hyper; extern crate rocket; diff --git a/tests/headers.rs b/tests/headers.rs index 544d823..573197a 100644 --- a/tests/headers.rs +++ b/tests/headers.rs @@ -1,5 +1,5 @@ //! This crate tests that all the request headers are parsed correctly in the round trip -#![feature(plugin, custom_derive)] +#![feature(plugin)] #![plugin(rocket_codegen)] extern crate hyper; extern crate rocket; diff --git a/tests/manual.rs b/tests/manual.rs index bea020b..cb21444 100644 --- a/tests/manual.rs +++ b/tests/manual.rs @@ -1,6 +1,6 @@ //! This crate tests using rocket_cors using manual mode -#![feature(plugin, custom_derive, conservative_impl_trait)] +#![feature(plugin, conservative_impl_trait)] #![plugin(rocket_codegen)] extern crate hyper; extern crate rocket;