Split 'rocket_contrib' into distinct crates.

This follows the completed graduation of stable contrib features into
core, removing 'rocket_contrib' in its entirety in favor of two new
crates. These crates are versioned independently of Rocket's core
libraries, allowing upgrades to dependencies without consideration for
versions in core libraries.

'rocket_dyn_templates' replaces the contrib 'templates' features. While
largely a 1-to-1 copy, it makes the following changes:

  * the 'tera_templates' feature is now 'tera'
  * the 'handlebars_templates' feature is now 'handlebars'
  * fails to compile if neither 'tera' nor 'handlebars' is enabled

'rocket_sync_db_pools' replaces the contrib 'database' features. It
makes no changes to the replaced features except that the `database`
attribute is properly documented at the crate root.
This commit is contained in:
Sergio Benitez 2021-05-24 18:58:05 -07:00
parent b2519208a7
commit 5a4e66ec43
81 changed files with 837 additions and 1671 deletions

View File

@ -3,7 +3,8 @@ members = [
"core/lib/", "core/lib/",
"core/codegen/", "core/codegen/",
"core/http/", "core/http/",
"contrib/lib", "contrib/sync_db_pools/codegen/",
"contrib/codegen", "contrib/sync_db_pools/lib/",
"contrib/dyn_templates/",
"site/tests", "site/tests",
] ]

View File

@ -69,29 +69,25 @@ You should see `Hello, world!` by visiting `http://localhost:8000`.
## Building and Testing ## Building and Testing
### Core and Contrib
The `core` directory contains the three core libraries: `lib`, `codegen`, and The `core` directory contains the three core libraries: `lib`, `codegen`, and
`http`. The `contrib` directory contains officially supported community `http` published as `rocket`, `rocket_codegen` and `rocket_http`, respectively.
contributions and similarly consists of `lib` and `codegen`. The latter two are implementations details and are reexported from `rocket`.
Public APIs are exposed via `lib` packages: `core/lib` is distributed as the ### Testing
`rocket` crate while `contrib/lib` is distributed as the `rocket_contrib` crate.
The remaining crates are implementation details.
### Library Testing
Rocket's complete test suite can be run with `./scripts/test.sh` from the root Rocket's complete test suite can be run with `./scripts/test.sh` from the root
of the source tree. The script builds and tests all libraries and examples. It of the source tree. The script builds and tests all libraries and examples in
accepts the following flags: all configurations. It accepts the following flags:
* `--contrib`: tests each `contrib` feature individually * `--examples`: tests all examples in `examples/`
* `--core`: tests each `core` feature individually * `--contrib`: tests each `contrib` library and feature individually
* `--release`: runs the testing suite in `release` mode * `--core`: tests each `core/lib` feature individually
* `--benchmarks`: runs all benchmarks
* `--all`: runs all tests in all configurations
Additionally, a `+${toolchain}` flag, where `${toolchain}` is a valid `rustup` Additionally, a `+${toolchain}` flag, where `${toolchain}` is a valid `rustup`
toolchain string, can be passed as the first parameter. The flag is forwarded to toolchain string, can be passed as the first parameter. The flag is forwarded to
`cargo` commands. `cargo` commands. Any other extra parameters are passed directly to `cargo`.
To test crates individually, simply run `cargo test --all-features` in the To test crates individually, simply run `cargo test --all-features` in the
crate's directory. crate's directory.
@ -99,10 +95,11 @@ crate's directory.
### Codegen Testing ### Codegen Testing
Code generation diagnostics are tested using [`trybuild`]; tests can be found in Code generation diagnostics are tested using [`trybuild`]; tests can be found in
the `codegen/tests/ui-fail` directory of both `core` and `contrib`. Each test is the `codegen/tests/ui-fail` directories of respective `codegen` crates. Each
symlinked into sibling `ui-fail-stable` and `ui-fail-nightly` directories which test is symlinked into sibling `ui-fail-stable` and `ui-fail-nightly`
contain the expected error output for stable and nightly compilers, directories which contain the expected error output for stable and nightly
respectively. compilers, respectively. To update codegen test UI output, run a codegen test
suite with `TRYBUILD=overwrite` and inspect the `diff` of `.std*` files.
[`trybuild`]: https://docs.rs/trybuild/1 [`trybuild`]: https://docs.rs/trybuild/1

View File

@ -1,28 +0,0 @@
[package]
name = "rocket_contrib_codegen"
version = "0.5.0-dev"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Procedural macros for the Rocket contrib libraries."
documentation = "https://api.rocket.rs/master/rocket_contrib/"
homepage = "https://rocket.rs"
repository = "https://github.com/SergioBenitez/Rocket"
readme = "../../README.md"
keywords = ["rocket", "contrib", "code", "generation", "proc-macro"]
license = "MIT OR Apache-2.0"
edition = "2018"
[features]
database_attribute = []
[lib]
proc-macro = true
[dependencies]
quote = "1.0"
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "df00b5" }
[dev-dependencies]
rocket = { version = "0.5.0-dev", path = "../../core/lib" }
rocket_contrib = { version = "0.5.0-dev", path = "../lib", features = ["diesel_sqlite_pool"] }
trybuild = "1.0"
version_check = "0.9"

View File

@ -1,46 +0,0 @@
#![recursion_limit="256"]
#![warn(rust_2018_idioms)]
//! # Rocket Contrib - Code Generation
//!
//! This crate implements the code generation portion of the Rocket Contrib
//! crate. This is for officially sanctioned contributor libraries that require
//! code generation of some kind.
//!
//! This crate includes custom derives and procedural macros and will expand
//! as-needed if future `rocket_contrib` features require code generation
//! facilities.
//!
//! ## Procedural Macros
//!
//! This crate implements the following procedural macros:
//!
//! * **databases**
//!
//! The syntax for the `databases` macro is:
//!
//! <pre>
//! macro := database(DATABASE_NAME)
//! DATABASE_NAME := (string literal)
//! </pre>
#[allow(unused_imports)]
#[macro_use] extern crate quote;
#[allow(unused_imports)]
use devise::{syn, proc_macro2};
#[cfg(feature = "database_attribute")]
mod database;
#[allow(unused_imports)]
use proc_macro::TokenStream;
/// The procedural macro for the `databases` annotation.
#[cfg(feature = "database_attribute")]
#[proc_macro_attribute]
pub fn database(attr: TokenStream, input: TokenStream) -> TokenStream {
crate::database::database_attr(attr, input)
.unwrap_or_else(|diag| diag.emit_as_item_tokens().into())
}

View File

@ -0,0 +1,40 @@
[package]
name = "rocket_dyn_templates"
version = "0.1.0-dev"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Dynamic templating engine integration for Rocket."
documentation = "https://api.rocket.rs/master/rocket_dyn_templates/"
homepage = "https://rocket.rs"
repository = "https://github.com/SergioBenitez/Rocket/contrib/dyn_templates"
readme = "README.md"
keywords = ["rocket", "framework", "templates", "templating", "engine"]
license = "MIT OR Apache-2.0"
edition = "2018"
[features]
tera = ["_tera"]
handlebars = ["_handlebars"]
[dependencies]
serde = "1.0"
serde_json = "1.0.26"
glob = "0.3"
notify = "4.0.6"
normpath = "0.2"
[dependencies.rocket]
path = "../../core/lib"
default-features = false
[dependencies._tera]
package = "tera"
version = "1.10.0"
optional = true
[dependencies._handlebars]
package = "handlebars"
version = "3.0"
optional = true
[package.metadata.docs.rs]
all-features = true

View File

@ -0,0 +1,52 @@
# `dyn_templates` [![ci.svg]][ci] [![crates.io]][crate] [![docs.svg]][crate docs]
[crates.io]: https://img.shields.io/crates/v/rocket_dyn_templates.svg
[crate]: https://crates.io/crates/rocket_dyn_templates
[docs.svg]: https://img.shields.io/badge/web-master-red.svg?style=flat&label=docs&colorB=d33847
[crate docs]: https://api.rocket.rs/master/rocket_dyn_templates
[ci.svg]: https://github.com/SergioBenitez/Rocket/workflows/CI/badge.svg
[ci]: https://github.com/SergioBenitez/Rocket/actions
This crate adds support for dynamic template rendering to Rocket. It
automatically discovers templates, provides a `Responder` to render templates,
and automatically reloads templates when compiled in debug mode. At present, it
supports [Handlebars] and [Tera].
[Tera]: https://docs.rs/crate/tera/1
[Handlebars]: https://docs.rs/crate/handlebars/3
# Usage
1. Enable the `rocket_dyn_templates` feature corresponding to your templating
engine(s) of choice:
```toml
[dependencies.rocket_dyn_templates]
version = "0.1.0-dev"
default-features = false
features = ["handlebars", "tera"]
```
1. Write your template files in Handlebars (`.hbs`) and/or Tera (`.tera`) in
the configurable `template_dir` directory (default:
`{rocket_root}/templates`).
2. Attach `Template::fairing()` and return a `Template` using
`Template::render()`, supplying the name of the template file **minus the
last two extensions**:
```rust
use rocket_dyn_templates::Template;
#[launch]
fn rocket() -> _ {
rocket::build().attach(Template::fairing())
}
#[get("/")]
fn index() -> Template {
Template::render("template-name", &context)
}
```
See the [crate docs] for full details.

View File

@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use crate::templates::{Engines, TemplateInfo}; use crate::{Engines, TemplateInfo};
use rocket::http::ContentType; use rocket::http::ContentType;
use normpath::PathExt; use normpath::PathExt;
@ -88,7 +88,7 @@ impl Context {
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
mod manager { mod manager {
use std::ops::Deref; use std::ops::Deref;
use crate::templates::Context; use crate::Context;
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure /// Wraps a Context. With `cfg(debug_assertions)` active, this structure
/// additionally provides a method to reload the context at runtime. /// additionally provides a method to reload the context at runtime.

View File

@ -3,10 +3,10 @@ use std::collections::HashMap;
use serde::Serialize; use serde::Serialize;
use crate::templates::TemplateInfo; use crate::TemplateInfo;
#[cfg(feature = "tera_templates")] use crate::templates::tera::Tera; #[cfg(feature = "tera")] use crate::tera::Tera;
#[cfg(feature = "handlebars_templates")] use crate::templates::handlebars::Handlebars; #[cfg(feature = "handlebars")] use crate::handlebars::Handlebars;
pub(crate) trait Engine: Send + Sync + Sized + 'static { pub(crate) trait Engine: Send + Sync + Sized + 'static {
const EXT: &'static str; const EXT: &'static str;
@ -19,17 +19,17 @@ pub(crate) trait Engine: Send + Sync + Sized + 'static {
/// ///
/// Calling methods on the exposed template engine types may require importing /// Calling methods on the exposed template engine types may require importing
/// types from the respective templating engine library. These types should be /// types from the respective templating engine library. These types should be
/// imported from the reexported crate at the root of `rocket_contrib` to avoid /// imported from the reexported crate at the root of `rocket_dyn_templates` to
/// version mismatches. For instance, when registering a Tera filter, the /// avoid version mismatches. For instance, when registering a Tera filter, the
/// [`tera::Value`] and [`tera::Result`] types are required. Import them from /// [`tera::Value`] and [`tera::Result`] types are required. Import them from
/// `rocket_contrib::templates::tera`. The example below illustrates this: /// `rocket_dyn_templates::tera`. The example below illustrates this:
/// ///
/// ```rust /// ```rust
/// # #[cfg(feature = "tera_templates")] { /// # #[cfg(feature = "tera")] {
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// ///
/// use rocket_contrib::templates::{Template, Engines}; /// use rocket_dyn_templates::{Template, Engines};
/// use rocket_contrib::templates::tera::{self, Value}; /// use rocket_dyn_templates::tera::{self, Value};
/// ///
/// fn my_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> { /// fn my_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
/// # /* /// # /*
@ -49,27 +49,27 @@ pub(crate) trait Engine: Send + Sync + Sized + 'static {
/// # } /// # }
/// ``` /// ```
/// ///
/// [`tera::Value`]: crate::templates::tera::Value /// [`tera::Value`]: crate::tera::Value
/// [`tera::Result`]: crate::templates::tera::Result /// [`tera::Result`]: crate::tera::Result
pub struct Engines { pub struct Engines {
/// A `Tera` templating engine. This field is only available when the /// A `Tera` templating engine. This field is only available when the
/// `tera_templates` feature is enabled. When calling methods on the `Tera` /// `tera_templates` feature is enabled. When calling methods on the `Tera`
/// instance, ensure you use types imported from /// instance, ensure you use types imported from
/// `rocket_contrib::templates::tera` to avoid version mismatches. /// `rocket_dyn_templates::tera` to avoid version mismatches.
#[cfg(feature = "tera_templates")] #[cfg(feature = "tera")]
pub tera: Tera, pub tera: Tera,
/// The Handlebars templating engine. This field is only available when the /// The Handlebars templating engine. This field is only available when the
/// `handlebars_templates` feature is enabled. When calling methods on the /// `handlebars_templates` feature is enabled. When calling methods on the
/// `Tera` instance, ensure you use types imported from /// `Tera` instance, ensure you use types imported from
/// `rocket_contrib::templates::handlebars` to avoid version mismatches. /// `rocket_dyn_templates::handlebars` to avoid version mismatches.
#[cfg(feature = "handlebars_templates")] #[cfg(feature = "handlebars")]
pub handlebars: Handlebars<'static>, pub handlebars: Handlebars<'static>,
} }
impl Engines { impl Engines {
pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[ pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
#[cfg(feature = "tera_templates")] Tera::EXT, #[cfg(feature = "tera")] Tera::EXT,
#[cfg(feature = "handlebars_templates")] Handlebars::EXT, #[cfg(feature = "handlebars")] Handlebars::EXT,
]; ];
pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> { pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
@ -83,12 +83,12 @@ impl Engines {
} }
Some(Engines { Some(Engines {
#[cfg(feature = "tera_templates")] #[cfg(feature = "tera")]
tera: match inner::<Tera>(templates) { tera: match inner::<Tera>(templates) {
Some(tera) => tera, Some(tera) => tera,
None => return None None => return None
}, },
#[cfg(feature = "handlebars_templates")] #[cfg(feature = "handlebars")]
handlebars: match inner::<Handlebars<'static>>(templates) { handlebars: match inner::<Handlebars<'static>>(templates) {
Some(hb) => hb, Some(hb) => hb,
None => return None None => return None
@ -102,13 +102,13 @@ impl Engines {
info: &TemplateInfo, info: &TemplateInfo,
context: C context: C
) -> Option<String> { ) -> Option<String> {
#[cfg(feature = "tera_templates")] { #[cfg(feature = "tera")] {
if info.engine_ext == Tera::EXT { if info.engine_ext == Tera::EXT {
return Engine::render(&self.tera, name, context); return Engine::render(&self.tera, name, context);
} }
} }
#[cfg(feature = "handlebars_templates")] { #[cfg(feature = "handlebars")] {
if info.engine_ext == Handlebars::EXT { if info.engine_ext == Handlebars::EXT {
return Engine::render(&self.handlebars, name, context); return Engine::render(&self.handlebars, name, context);
} }
@ -119,20 +119,24 @@ impl Engines {
/// Returns iterator over template (name, engine_extension). /// Returns iterator over template (name, engine_extension).
pub(crate) fn templates(&self) -> impl Iterator<Item = (&str, &'static str)> { pub(crate) fn templates(&self) -> impl Iterator<Item = (&str, &'static str)> {
#[cfg(all(feature = "tera_templates", feature = "handlebars_templates"))] { #[cfg(all(feature = "tera", feature = "handlebars"))] {
self.tera.get_template_names() self.tera.get_template_names()
.map(|name| (name, Tera::EXT)) .map(|name| (name, Tera::EXT))
.chain(self.handlebars.get_templates().keys() .chain(self.handlebars.get_templates().keys()
.map(|name| (name.as_str(), Handlebars::EXT))) .map(|name| (name.as_str(), Handlebars::EXT)))
} }
#[cfg(all(feature = "tera_templates", not(feature = "handlebars_templates")))] { #[cfg(all(feature = "tera", not(feature = "handlebars")))] {
self.tera.get_template_names().map(|name| (name, Tera::EXT)) self.tera.get_template_names().map(|name| (name, Tera::EXT))
} }
#[cfg(all(feature = "handlebars_templates", not(feature = "tera_templates")))] { #[cfg(all(feature = "handlebars", not(feature = "tera")))] {
self.handlebars.get_templates().keys() self.handlebars.get_templates().keys()
.map(|name| (name.as_str(), Handlebars::EXT)) .map(|name| (name.as_str(), Handlebars::EXT))
} }
#[cfg(not(any(feature = "tera", feature = "handlebars")))] {
None.into_iter()
}
} }
} }

View File

@ -1,5 +1,5 @@
use crate::templates::{DEFAULT_TEMPLATE_DIR, Context, Engines}; use crate::{DEFAULT_TEMPLATE_DIR, Context, Engines};
use crate::templates::context::{Callback, ContextManager}; use crate::context::{Callback, ContextManager};
use rocket::{Rocket, Build, Orbit}; use rocket::{Rocket, Build, Orbit};
use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::fairing::{self, Fairing, Info, Kind};

View File

@ -1,9 +1,9 @@
use std::path::Path; use std::path::Path;
use serde::Serialize; use serde::Serialize;
use crate::templates::Engine;
pub use crate::templates::handlebars::Handlebars; use crate::engine::Engine;
pub use crate::handlebars::Handlebars;
impl Engine for Handlebars<'static> { impl Engine for Handlebars<'static> {
const EXT: &'static str = "hbs"; const EXT: &'static str = "hbs";

View File

@ -1,50 +1,42 @@
//! Dynamic template engine support for handlebars and tera. //! Dynamic templating engine support for Rocket.
//! //!
//! # Overview //! This crate adds support for dynamic template rendering to Rocket. It
//! automatically discovers templates, provides a `Responder` to render
//! templates, and automatically reloads templates when compiled in debug mode.
//! At present, it supports [Handlebars] and [Tera].
//! //!
//! The general outline for using templates in Rocket is: //! # Usage
//! //!
//! 0. Enable the `rocket_contrib` feature corresponding to your templating //! 1. Enable the `rocket_dyn_templates` feature corresponding to your
//! engine(s) of choice: //! templating engine(s) of choice:
//! //!
//! ```toml //! ```toml
//! [dependencies.rocket_contrib] //! [dependencies.rocket_dyn_templates]
//! version = "0.5.0-dev" //! version = "0.1.0-dev"
//! default-features = false //! default-features = false
//! features = ["handlebars_templates", "tera_templates"] //! features = ["handlebars", "tera"]
//! ``` //! ```
//! //!
//! 1. Write your template files in Handlebars (extension: `.hbs`) and/or tera //! 1. Write your template files in Handlebars (`.hbs`) and/or Tera (`.tera`)
//! (extensions: `.tera`) in the templates directory (default: //! in the configurable `template_dir` directory (default:
//! `{rocket_root}/templates`). //! `{rocket_root}/templates`).
//! //!
//! 2. Attach the template fairing, [`Template::fairing()`]: //! 2. Attach `Template::fairing()` return a `Template` using
//! //! `Template::render()`, supplying the name of the template file **minus
//! ```rust //! the last two extensions**:
//! # extern crate rocket;
//! # extern crate rocket_contrib;
//! use rocket_contrib::templates::Template;
//!
//! fn main() {
//! rocket::build()
//! .attach(Template::fairing())
//! // ...
//! # ;
//! }
//! ```
//!
//! 3. Return a [`Template`] using [`Template::render()`], supplying the name
//! of the template file **minus the last two extensions**, from a handler.
//! //!
//! ```rust //! ```rust
//! # #[macro_use] extern crate rocket; //! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib; //! use rocket_dyn_templates::Template;
//! # fn context() { } //!
//! use rocket_contrib::templates::Template; //! #[launch]
//! fn rocket() -> _ {
//! rocket::build().attach(Template::fairing())
//! }
//! //!
//! #[get("/")] //! #[get("/")]
//! fn index() -> Template { //! fn index() -> Template {
//! let context = context(); //! # let context = ();
//! Template::render("template-name", &context) //! Template::render("template-name", &context)
//! } //! }
//! ``` //! ```
@ -57,7 +49,8 @@
//! [Discovery](#discovery) for more. //! [Discovery](#discovery) for more.
//! //!
//! Templates that are _not_ discovered by Rocket, such as those registered //! Templates that are _not_ discovered by Rocket, such as those registered
//! directly via [`Template::custom()`], are _not_ renamed. //! directly via [`Template::custom()`], are _not_ renamed. Use the name with
//! which the template was orginally registered.
//! //!
//! ## Content Type //! ## Content Type
//! //!
@ -66,7 +59,7 @@
//! extension or the extension is unknown. For example, for a discovered //! extension or the extension is unknown. For example, for a discovered
//! template with file name `foo.html.hbs` or a manually registered template //! template with file name `foo.html.hbs` or a manually registered template
//! with name ending in `foo.html`, the `Content-Type` is automatically set to //! with name ending in `foo.html`, the `Content-Type` is automatically set to
//! [`ContentType::HTML`]. //! `ContentType::HTML`.
//! //!
//! ## Discovery //! ## Discovery
//! //!
@ -75,8 +68,8 @@
//! template directory is configured via the `template_dir` configuration //! template directory is configured via the `template_dir` configuration
//! parameter and defaults to `templates/`. The path set in `template_dir` is //! parameter and defaults to `templates/`. The path set in `template_dir` is
//! relative to the Rocket configuration file. See the [configuration //! relative to the Rocket configuration file. See the [configuration
//! chapter](https://rocket.rs/master/guide/configuration/#extras) of the guide //! chapter](https://rocket.rs/master/guide/configuration) of the guide for more
//! for more information on configuration. //! information on configuration.
//! //!
//! The corresponding templating engine used for a given template is based on a //! The corresponding templating engine used for a given template is based on a
//! template's extension. At present, this library supports the following //! template's extension. At present, this library supports the following
@ -93,9 +86,10 @@
//! Any file that ends with one of these extension will be discovered and //! Any file that ends with one of these extension will be discovered and
//! rendered with the corresponding templating engine. The _name_ of the //! rendered with the corresponding templating engine. The _name_ of the
//! template will be the path to the template file relative to `template_dir` //! template will be the path to the template file relative to `template_dir`
//! minus at most two extensions. The following table illustrates this mapping: //! minus at most two extensions. The following table contains examples of this
//! mapping:
//! //!
//! | path | name | //! | example template path | template name |
//! |-----------------------------------------------|-----------------------| //! |-----------------------------------------------|-----------------------|
//! | {template_dir}/index.html.hbs | index | //! | {template_dir}/index.html.hbs | index |
//! | {template_dir}/index.tera | index | //! | {template_dir}/index.tera | index |
@ -109,32 +103,58 @@
//! type, and one for the template extension. This means that template //! type, and one for the template extension. This means that template
//! extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc. //! extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc.
//! //!
//! ## Template Fairing //! ## Template Fairing and Customization
//! //!
//! Template discovery is actualized by the template fairing, which itself is //! Template discovery is actualized by the template fairing, which itself is
//! created via [`Template::fairing()`], [`Template::custom()`], or //! created via [`Template::fairing()`], [`Template::custom()`], or
//! [`Template::try_custom()`], the latter two allowing customizations to //! [`Template::try_custom()`], the latter two allowing customizations to
//! enabled templating engines. In order for _any_ templates to be rendered, the //! templating engines such as registering template helpers and register
//! template fairing _must_ be [attached](rocket::Rocket::attach()) to the //! templates from strings.
//! running Rocket instance. Failure to do so will result in a run-time error. //!
//! In order for _any_ templates to be rendered, the template fairing _must_ be
//! [attached](rocket::Rocket::attach()) to the running Rocket instance. Failure
//! to do so will result in an ignite-time error.
//!
//! ## Rendering
//! //!
//! Templates are rendered with the `render` method. The method takes in the //! Templates are rendered with the `render` method. The method takes in the
//! name of a template and a context to render the template with. The context //! name of a template and a context to render the template with. The context
//! can be any type that implements [`Serialize`] from [`serde`] and would //! can be any type that implements [`Serialize`] from [`serde`] and would
//! serialize to an `Object` value. //! serialize to an `Object` value.
//! //!
//! In debug mode (without the `--release` flag passed to `rocket`), templates //! ## Automatic Reloading
//!
//! In debug mode (without the `--release` flag passed to `cargo`), templates
//! will be automatically reloaded from disk if any changes have been made to //! will be automatically reloaded from disk if any changes have been made to
//! the templates directory since the previous request. In release builds, //! the templates directory since the previous request. In release builds,
//! template reloading is disabled to improve performance and cannot be enabled. //! template reloading is disabled to improve performance and cannot be enabled.
//! //!
//! [`Serialize`]: serde::Serialize //! [`Serialize`]: serde::Serialize
#[cfg(feature = "tera_templates")] pub extern crate tera; #![doc(html_root_url = "https://api.rocket.rs/master/rocket_dyn_templates")]
#[cfg(feature = "tera_templates")] mod tera_templates; #![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
#[cfg(feature = "handlebars_templates")] pub extern crate handlebars; #[macro_use] extern crate rocket;
#[cfg(feature = "handlebars_templates")] mod handlebars_templates;
#[cfg(not(any(feature = "tera", feature = "handlebars")))]
compile_error!("at least one of \"tera\" or \"handlebars\" features must be enabled");
/// The tera templating engine library, reexported.
#[doc(inline)]
#[cfg(feature = "tera")]
pub use _tera as tera;
#[cfg(feature = "tera")]
mod tera_templates;
/// The handlebars templating engine library, reexported.
#[doc(inline)]
#[cfg(feature = "handlebars")]
pub use _handlebars as handlebars;
#[cfg(feature = "handlebars")]
mod handlebars_templates;
mod engine; mod engine;
mod fairing; mod fairing;
@ -144,7 +164,6 @@ mod metadata;
pub use self::engine::Engines; pub use self::engine::Engines;
pub use self::metadata::Metadata; pub use self::metadata::Metadata;
use self::engine::Engine;
use self::fairing::TemplateFairing; use self::fairing::TemplateFairing;
use self::context::{Context, ContextManager}; use self::context::{Context, ContextManager};
@ -169,56 +188,7 @@ const DEFAULT_TEMPLATE_DIR: &str = "templates";
/// contain the rendered template itself. The template is lazily rendered, at /// contain the rendered template itself. The template is lazily rendered, at
/// response time. To render a template greedily, use [`Template::show()`]. /// response time. To render a template greedily, use [`Template::show()`].
/// ///
/// # Usage /// See the [crate root](crate) for usage details.
///
/// To use, add the `handlebars_templates` feature, the `tera_templates`
/// feature, or both, to the `rocket_contrib` dependencies section of your
/// `Cargo.toml`:
///
/// ```toml
/// [dependencies.rocket_contrib]
/// version = "0.5.0-dev"
/// default-features = false
/// features = ["handlebars_templates", "tera_templates"]
/// ```
///
/// Then, ensure that the template [`Fairing`] is attached to your Rocket
/// application:
///
/// ```rust
/// # extern crate rocket;
/// # extern crate rocket_contrib;
/// use rocket_contrib::templates::Template;
///
/// fn main() {
/// rocket::build()
/// .attach(Template::fairing())
/// // ...
/// # ;
/// }
/// ```
///
/// The `Template` type implements Rocket's [`Responder`] trait, so it can be
/// returned from a request handler directly:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// # fn context() { }
/// use rocket_contrib::templates::Template;
///
/// #[get("/")]
/// fn index() -> Template {
/// let context = context();
/// Template::render("index", &context)
/// }
/// ```
///
/// # Helpers, Filters, and Customization
///
/// You may use the [`Template::custom()`] method to construct a fairing with
/// customized templating engines. Among other things, this method allows you to
/// register template helpers and register templates from strings.
#[derive(Debug)] #[derive(Debug)]
pub struct Template { pub struct Template {
name: Cow<'static, str>, name: Cow<'static, str>,
@ -254,9 +224,9 @@ impl Template {
/// ///
/// ```rust /// ```rust
/// extern crate rocket; /// extern crate rocket;
/// extern crate rocket_contrib; /// extern crate rocket_dyn_templates;
/// ///
/// use rocket_contrib::templates::Template; /// use rocket_dyn_templates::Template;
/// ///
/// fn main() { /// fn main() {
/// rocket::build() /// rocket::build()
@ -283,9 +253,9 @@ impl Template {
/// ///
/// ```rust /// ```rust
/// extern crate rocket; /// extern crate rocket;
/// extern crate rocket_contrib; /// extern crate rocket_dyn_templates;
/// ///
/// use rocket_contrib::templates::Template; /// use rocket_dyn_templates::Template;
/// ///
/// fn main() { /// fn main() {
/// rocket::build() /// rocket::build()
@ -314,9 +284,9 @@ impl Template {
/// ///
/// ```rust /// ```rust
/// extern crate rocket; /// extern crate rocket;
/// extern crate rocket_contrib; /// extern crate rocket_dyn_templates;
/// ///
/// use rocket_contrib::templates::Template; /// use rocket_dyn_templates::Template;
/// ///
/// fn main() { /// fn main() {
/// rocket::build() /// rocket::build()
@ -343,7 +313,7 @@ impl Template {
/// ///
/// ```rust /// ```rust
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// use rocket_contrib::templates::Template; /// use rocket_dyn_templates::Template;
/// ///
/// // Create a `context`. Here, just an empty `HashMap`. /// // Create a `context`. Here, just an empty `HashMap`.
/// let mut context = HashMap::new(); /// let mut context = HashMap::new();
@ -376,10 +346,10 @@ impl Template {
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # extern crate rocket; /// # extern crate rocket;
/// # extern crate rocket_contrib; /// # extern crate rocket_dyn_templates;
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// ///
/// use rocket_contrib::templates::Template; /// use rocket_dyn_templates::Template;
/// use rocket::local::blocking::Client; /// use rocket::local::blocking::Client;
/// ///
/// fn main() { /// fn main() {

View File

@ -2,7 +2,7 @@ use rocket::{Request, Rocket, Ignite, Sentinel};
use rocket::http::Status; use rocket::http::Status;
use rocket::request::{self, FromRequest}; use rocket::request::{self, FromRequest};
use crate::templates::context::ContextManager; use crate::context::ContextManager;
/// Request guard for dynamically querying template metadata. /// Request guard for dynamically querying template metadata.
/// ///
@ -13,8 +13,8 @@ use crate::templates::context::ContextManager;
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib; /// # #[macro_use] extern crate rocket_dyn_templates;
/// use rocket_contrib::templates::{Template, Metadata}; /// use rocket_dyn_templates::{Template, Metadata};
/// ///
/// #[get("/")] /// #[get("/")]
/// fn homepage(metadata: Metadata) -> Template { /// fn homepage(metadata: Metadata) -> Template {
@ -45,9 +45,9 @@ impl Metadata<'_> {
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib; /// # extern crate rocket_dyn_templates;
/// # /// #
/// use rocket_contrib::templates::Metadata; /// use rocket_dyn_templates::Metadata;
/// ///
/// #[get("/")] /// #[get("/")]
/// fn handler(metadata: Metadata) { /// fn handler(metadata: Metadata) {
@ -65,9 +65,9 @@ impl Metadata<'_> {
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib; /// # extern crate rocket_dyn_templates;
/// # /// #
/// use rocket_contrib::templates::Metadata; /// use rocket_dyn_templates::Metadata;
/// ///
/// #[get("/")] /// #[get("/")]
/// fn handler(metadata: Metadata) { /// fn handler(metadata: Metadata) {

View File

@ -2,9 +2,10 @@ use std::path::Path;
use std::error::Error; use std::error::Error;
use serde::Serialize; use serde::Serialize;
use crate::templates::Engine;
pub use crate::templates::tera::{Context, Tera}; use crate::engine::Engine;
pub use crate::tera::{Context, Tera};
impl Engine for Tera { impl Engine for Tera {
const EXT: &'static str = "tera"; const EXT: &'static str = "tera";

View File

@ -0,0 +1,238 @@
#[macro_use] extern crate rocket;
use std::path::{Path, PathBuf};
use rocket::{Rocket, Build};
use rocket::config::Config;
use rocket_dyn_templates::{Template, Metadata};
#[get("/<engine>/<name>")]
fn template_check(md: Metadata<'_>, engine: &str, name: &str) -> Option<()> {
match md.contains_template(&format!("{}/{}", engine, name)) {
true => Some(()),
false => None
}
}
#[get("/is_reloading")]
fn is_reloading(md: Metadata<'_>) -> Option<()> {
if md.reloading() { Some(()) } else { None }
}
fn template_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("templates")
}
fn rocket() -> Rocket<Build> {
rocket::custom(Config::figment().merge(("template_dir", template_root())))
.attach(Template::fairing())
.mount("/", routes![template_check, is_reloading])
}
#[test]
fn test_callback_error() {
use rocket::{local::blocking::Client, error::ErrorKind::FailedFairings};
let rocket = rocket::build().attach(Template::try_custom(|_| {
Err("error reloading templates!".into())
}));
let error = Client::debug(rocket).expect_err("client failure");
match error.kind() {
FailedFairings(failures) => assert_eq!(failures[0].name, "Templating"),
_ => panic!("Wrong kind of launch error"),
}
}
#[test]
fn test_sentinel() {
use rocket::{local::blocking::Client, error::ErrorKind::SentinelAborts};
let err = Client::debug_with(routes![is_reloading]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
let err = Client::debug_with(routes![is_reloading, template_check]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 2));
#[get("/")]
fn return_template() -> Template {
Template::render("foo", ())
}
let err = Client::debug_with(routes![return_template]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[get("/")]
fn return_opt_template() -> Option<Template> {
Some(Template::render("foo", ()))
}
let err = Client::debug_with(routes![return_opt_template]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[derive(rocket::Responder)]
struct MyThing<T>(T);
#[get("/")]
fn return_custom_template() -> MyThing<Template> {
MyThing(Template::render("foo", ()))
}
let err = Client::debug_with(routes![return_custom_template]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[derive(rocket::Responder)]
struct MyOkayThing<T>(Option<T>);
impl<T> rocket::Sentinel for MyOkayThing<T> {
fn abort(_: &Rocket<rocket::Ignite>) -> bool {
false
}
}
#[get("/")]
fn always_ok_sentinel() -> MyOkayThing<Template> {
MyOkayThing(None)
}
Client::debug_with(routes![always_ok_sentinel]).expect("no sentinel abort");
}
#[cfg(feature = "tera")]
mod tera_tests {
use super::*;
use std::collections::HashMap;
use rocket::http::Status;
use rocket::local::blocking::Client;
const UNESCAPED_EXPECTED: &'static str
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
const ESCAPED_EXPECTED: &'static str
= "\nh_start\ntitle: _test_\nh_end\n\n\n&lt;script &#x2F;&gt;\n\nfoot\n";
#[test]
fn test_tera_templates() {
let client = Client::debug(rocket()).unwrap();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script />");
// Test with a txt file, which shouldn't escape.
let template = Template::show(client.rocket(), "tera/txt_test", &map);
assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
// Now with an HTML file, which should.
let template = Template::show(client.rocket(), "tera/html_test", &map);
assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
}
#[test]
fn test_template_metadata_with_tera() {
let client = Client::debug(rocket()).unwrap();
let response = client.get("/tera/txt_test").dispatch();
assert_eq!(response.status(), Status::Ok);
let response = client.get("/tera/html_test").dispatch();
assert_eq!(response.status(), Status::Ok);
let response = client.get("/tera/not_existing").dispatch();
assert_eq!(response.status(), Status::NotFound);
let response = client.get("/hbs/txt_test").dispatch();
assert_eq!(response.status(), Status::NotFound);
}
}
#[cfg(feature = "handlebars")]
mod handlebars_tests {
use super::*;
use std::collections::HashMap;
use rocket::http::Status;
use rocket::local::blocking::Client;
const EXPECTED: &'static str
= "Hello _test_!\n\n<main> &lt;script /&gt; hi </main>\nDone.\n\n";
#[test]
fn test_handlebars_templates() {
let client = Client::debug(rocket()).unwrap();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script /> hi");
// Test with a txt file, which shouldn't escape.
let template = Template::show(client.rocket(), "hbs/test", &map);
assert_eq!(template, Some(EXPECTED.into()));
}
#[test]
fn test_template_metadata_with_handlebars() {
let client = Client::debug(rocket()).unwrap();
let response = client.get("/hbs/test").dispatch();
assert_eq!(response.status(), Status::Ok);
let response = client.get("/hbs/not_existing").dispatch();
assert_eq!(response.status(), Status::NotFound);
let response = client.get("/tera/test").dispatch();
assert_eq!(response.status(), Status::NotFound);
}
#[test]
#[cfg(debug_assertions)]
fn test_template_reload() {
use std::fs::File;
use std::io::Write;
use std::time::Duration;
use rocket::local::blocking::Client;
const RELOAD_TEMPLATE: &str = "hbs/reload";
const INITIAL_TEXT: &str = "initial";
const NEW_TEXT: &str = "reload";
fn write_file(path: &Path, text: &str) {
let mut file = File::create(path).expect("open file");
file.write_all(text.as_bytes()).expect("write file");
file.sync_all().expect("sync file");
}
// set up the template before initializing the Rocket instance so
// that it will be picked up in the initial loading of templates.
let reload_path = template_root().join("hbs").join("reload.txt.hbs");
write_file(&reload_path, INITIAL_TEXT);
// set up the client. if we can't reload templates, then just quit
let client = Client::debug(rocket()).unwrap();
let res = client.get("/is_reloading").dispatch();
if res.status() != Status::Ok {
return;
}
// verify that the initial content is correct
let initial_rendered = Template::show(client.rocket(), RELOAD_TEMPLATE, ());
assert_eq!(initial_rendered, Some(INITIAL_TEXT.into()));
// write a change to the file
write_file(&reload_path, NEW_TEXT);
for _ in 0..6 {
// dispatch any request to trigger a template reload
client.get("/").dispatch();
// if the new content is correct, we are done
let new_rendered = Template::show(client.rocket(), RELOAD_TEMPLATE, ());
if new_rendered == Some(NEW_TEXT.into()) {
write_file(&reload_path, INITIAL_TEXT);
return;
}
// otherwise, retry a few times, waiting 250ms in between
std::thread::sleep(Duration::from_millis(250));
}
panic!("failed to reload modified template in 1.5s");
}
}

View File

@ -1,74 +0,0 @@
[package]
name = "rocket_contrib"
version = "0.5.0-dev"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Community contributed libraries for the Rocket web framework."
documentation = "https://api.rocket.rs/master/rocket_contrib/"
homepage = "https://rocket.rs"
repository = "https://github.com/SergioBenitez/Rocket"
readme = "../../README.md"
keywords = ["rocket", "web", "framework", "contrib", "contributed"]
license = "MIT OR Apache-2.0"
edition = "2018"
[features]
# Internal use only.
templates = ["serde", "serde_json", "glob", "notify", "normpath"]
databases = [
"serde", "r2d2", "tokio/rt", "tokio/rt-multi-thread",
"rocket_contrib_codegen/database_attribute"
]
# User-facing features.
default = []
tera_templates = ["tera", "templates"]
handlebars_templates = ["handlebars", "templates"]
compression = ["brotli_compression", "gzip_compression"]
brotli_compression = ["brotli"]
gzip_compression = ["flate2"]
# The barage of user-facing database features.
diesel_sqlite_pool = ["databases", "diesel/sqlite", "diesel/r2d2"]
diesel_postgres_pool = ["databases", "diesel/postgres", "diesel/r2d2"]
postgres_pool = ["databases", "postgres", "r2d2_postgres"]
sqlite_pool = ["databases", "rusqlite", "r2d2_sqlite"]
memcache_pool = ["databases", "memcache", "r2d2-memcache"]
diesel_mysql_pool = ["databases", "diesel/mysql", "diesel/r2d2"]
[dependencies]
# Global dependencies.
tokio = { version = "1.4", optional = true }
rocket_contrib_codegen = { version = "0.5.0-dev", path = "../codegen", optional = true }
rocket = { version = "0.5.0-dev", path = "../../core/lib/", default-features = false }
log = "0.4"
# Serialization and templating dependencies.
serde = { version = "1.0", optional = true, features = ["derive"] }
serde_json = { version = "1.0.26", optional = true }
# Templating dependencies.
handlebars = { version = "3.0", optional = true }
glob = { version = "0.3", optional = true }
tera = { version = "1.10.0", optional = true }
notify = { version = "4.0.6", optional = true }
normpath = { version = "0.2", optional = true }
# Database dependencies
diesel = { version = "1.0", default-features = false, optional = true }
postgres = { version = "0.19", optional = true }
r2d2 = { version = "0.8", optional = true }
r2d2_postgres = { version = "0.18", optional = true }
rusqlite = { version = "0.24", optional = true }
r2d2_sqlite = { version = "0.17", optional = true }
memcache = { version = "0.15", optional = true }
r2d2-memcache = { version = "0.6", optional = true }
# Compression dependencies
brotli = { version = "3.3", optional = true }
flate2 = { version = "1.0", optional = true }
[dev-dependencies]
serde_test = "1.0.114"
[package.metadata.docs.rs]
all-features = true

View File

@ -1,154 +0,0 @@
use rocket::config::{ConfigError, Value};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::MediaType;
use rocket::Rocket;
use rocket::{Request, Response};
struct Context {
exclusions: Vec<MediaType>,
}
impl Default for Context {
fn default() -> Context {
Context {
exclusions: vec![
MediaType::parse_flexible("application/gzip").unwrap(),
MediaType::parse_flexible("application/zip").unwrap(),
MediaType::parse_flexible("image/*").unwrap(),
MediaType::parse_flexible("video/*").unwrap(),
MediaType::parse_flexible("application/wasm").unwrap(),
MediaType::parse_flexible("application/octet-stream").unwrap(),
],
}
}
}
/// Compresses all responses with Brotli or Gzip compression.
///
/// Compression is done in the same manner as the [`Compress`](super::Compress)
/// responder.
///
/// By default, the fairing does not compress responses with a `Content-Type`
/// matching any of the following:
///
/// - `application/gzip`
/// - `application/zip`
/// - `image/*`
/// - `video/*`
/// - `application/wasm`
/// - `application/octet-stream`
///
/// The excluded types can be changed changing the `compress.exclude` Rocket
/// configuration property in Rocket.toml. The default `Content-Type` exclusions
/// will be ignored if this is set, and must be added back in one by one if
/// desired.
///
/// ```toml
/// [global.compress]
/// exclude = ["video/*", "application/x-xz"]
/// ```
///
/// # Usage
///
/// Attach the compression [fairing](/rocket/fairing/) to your Rocket
/// application:
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
///
/// use rocket_contrib::compression::Compression;
///
/// fn main() {
/// rocket::build()
/// // ...
/// .attach(Compression::fairing())
/// // ...
/// # ;
/// }
/// ```
pub struct Compression(());
impl Compression {
/// Returns a fairing that compresses outgoing requests.
///
/// ## Example
/// To attach this fairing, simply call `attach` on the application's
/// `Rocket` instance with `Compression::fairing()`:
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
///
/// use rocket_contrib::compression::Compression;
///
/// fn main() {
/// rocket::build()
/// // ...
/// .attach(Compression::fairing())
/// // ...
/// # ;
/// }
/// ```
pub fn fairing() -> Compression {
Compression(())
}
}
impl Fairing for Compression {
fn info(&self) -> Info {
Info {
name: "Response compression",
kind: Kind::Attach | Kind::Response,
}
}
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
let mut ctxt = Context::default();
match rocket.config().get_table("compress").and_then(|t| {
t.get("exclude").ok_or_else(|| ConfigError::Missing(String::from("exclude")))
}) {
Ok(excls) => match excls.as_array() {
Some(excls) => {
ctxt.exclusions = excls.iter().flat_map(|ex| {
if let Value::String(s) = ex {
let mt = MediaType::parse_flexible(s);
if mt.is_none() {
warn_!("Ignoring invalid media type '{:?}'", s);
}
mt
} else {
warn_!("Ignoring non-string media type '{:?}'", ex);
None
}
}).collect();
}
None => {
warn_!(
"Exclusions is not an array; using default compression exclusions '{:?}'",
ctxt.exclusions
);
}
},
Err(ConfigError::Missing(_)) => { /* ignore missing */ }
Err(e) => {
e.pretty_print();
warn_!(
"Using default compression exclusions '{:?}'",
ctxt.exclusions
);
}
};
Ok(rocket.manage(ctxt))
}
fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
let context = request
.guard::<rocket::State<'_, Context>>()
.expect("Compression Context registered in on_attach");
super::CompressionUtils::compress_response(request, response, &context.exclusions);
}
}

View File

@ -1,139 +0,0 @@
//! Gzip and Brotli response compression.
//!
//! See the [`Compression`] and [`Compress`] types for further details.
//!
//! # Enabling
//!
//! This module is only available when one of the `brotli_compression`,
//! `gzip_compression`, or `compression` features is enabled. Enable
//! one of these in `Cargo.toml` as follows:
//!
//! ```toml
//! [dependencies.rocket_contrib]
//! version = "0.5.0-dev"
//! default-features = false
//! features = ["compression"]
//! ```
//!
//! # Security Implications
//!
//! In some cases, HTTP compression on a site served over HTTPS can make a web
//! application vulnerable to attacks including BREACH. These risks should be
//! evaluated in the context of your application before enabling compression.
//!
mod fairing;
mod responder;
pub use self::fairing::Compression;
pub use self::responder::Compress;
use std::io::Read;
use rocket::http::MediaType;
use rocket::http::hyper::header::{ContentEncoding, Encoding};
use rocket::{Request, Response};
#[cfg(feature = "brotli_compression")]
use brotli::enc::backward_references::BrotliEncoderMode;
#[cfg(feature = "gzip_compression")]
use flate2::read::GzEncoder;
struct CompressionUtils;
impl CompressionUtils {
fn accepts_encoding(request: &Request<'_>, encoding: &str) -> bool {
request
.headers()
.get("Accept-Encoding")
.flat_map(|accept| accept.split(','))
.map(|accept| accept.trim())
.any(|accept| accept == encoding)
}
fn already_encoded(response: &Response<'_>) -> bool {
response.headers().get("Content-Encoding").next().is_some()
}
fn set_body_and_encoding<'r, B: Read + 'r>(
response: &mut Response<'r>,
body: B,
encoding: Encoding,
) {
response.set_header(ContentEncoding(vec![encoding]));
response.set_streamed_body(body);
}
fn skip_encoding(
content_type: &Option<rocket::http::ContentType>,
exclusions: &[MediaType],
) -> bool {
match content_type {
Some(content_type) => exclusions.iter().any(|exc_media_type| {
if exc_media_type.sub() == "*" {
*exc_media_type.top() == *content_type.top()
} else {
*exc_media_type == *content_type.media_type()
}
}),
None => false,
}
}
fn compress_response(
request: &Request<'_>,
response: &mut Response<'_>,
exclusions: &[MediaType]
) {
if CompressionUtils::already_encoded(response) {
return;
}
let content_type = response.content_type();
if CompressionUtils::skip_encoding(&content_type, exclusions) {
return;
}
// Compression is done when the request accepts brotli or gzip encoding
// and the corresponding feature is enabled
if cfg!(feature = "brotli_compression") && CompressionUtils::accepts_encoding(request, "br")
{
#[cfg(feature = "brotli_compression")]
{
if let Some(plain) = response.take_body() {
let content_type_top = content_type.as_ref().map(|ct| ct.top());
let mut params = brotli::enc::BrotliEncoderInitParams();
params.quality = 2;
if content_type_top == Some("text".into()) {
params.mode = BrotliEncoderMode::BROTLI_MODE_TEXT;
} else if content_type_top == Some("font".into()) {
params.mode = BrotliEncoderMode::BROTLI_MODE_FONT;
}
let compressor =
brotli::CompressorReader::with_params(plain.into_inner(), 4096, &params);
CompressionUtils::set_body_and_encoding(
response,
compressor,
Encoding::EncodingExt("br".into()),
);
}
}
} else if cfg!(feature = "gzip_compression")
&& CompressionUtils::accepts_encoding(request, "gzip")
{
#[cfg(feature = "gzip_compression")]
{
if let Some(plain) = response.take_body() {
let compressor =
GzEncoder::new(plain.into_inner(), flate2::Compression::default());
CompressionUtils::set_body_and_encoding(response, compressor, Encoding::Gzip);
}
}
}
}
}

View File

@ -1,47 +0,0 @@
use rocket::response::{self, Responder, Response};
use rocket::Request;
use super::CompressionUtils;
/// Compresses responses with Brotli or Gzip compression.
///
/// The `Compress` type implements brotli and gzip compression for responses in
/// accordance with the `Accept-Encoding` header. If accepted, brotli
/// compression is preferred over gzip.
///
/// In the brotli compression mode (using the
/// [rust-brotli](https://github.com/dropbox/rust-brotli) crate), quality is set
/// to 2 in order to achieve fast compression with a compression ratio similar
/// to gzip. When appropriate, brotli's text and font compression modes are
/// used.
///
/// In the gzip compression mode (using the
/// [flate2](https://github.com/alexcrichton/flate2-rs) crate), quality is set
/// to the default (9) in order to have good compression ratio.
///
/// Responses that already have a `Content-Encoding` header are not compressed.
///
/// # Usage
///
/// Compress responses by wrapping a `Responder` inside `Compress`:
///
/// ```rust
/// use rocket_contrib::compression::Compress;
///
/// # #[allow(unused_variables)]
/// let response = Compress("Hi.");
/// ```
#[derive(Debug)]
pub struct Compress<R>(pub R);
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Compress<R> {
#[inline(always)]
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o> {
let mut response = Response::build()
.merge(self.0.respond_to(request)?)
.finalize();
CompressionUtils::compress_response(request, &mut response, &[]);
Ok(response)
}
}

View File

@ -1,46 +0,0 @@
#![doc(html_root_url = "https://api.rocket.rs/master")]
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
#![warn(rust_2018_idioms)]
#![allow(unused_extern_crates)]
//! This crate contains officially sanctioned contributor libraries that provide
//! functionality commonly used by Rocket applications.
//!
//! These libraries are always kept in-sync with the core Rocket library. They
//! provide common, but not fundamental, abstractions to be used by Rocket
//! applications.
//!
//! Each module in this library is held behind a feature flag, with the most
//! common modules exposed by default. The present feature list is below, with
//! an asterisk next to the features that are enabled by default:
//!
//! * [handlebars_templates](templates) - Handlebars Templating
//! * [tera_templates](templates) - Tera Templating
//! * [${database}_pool](databases) - Database Configuration and Pooling
//!
//! The recommend way to include features from this crate via Rocket in your
//! project is by adding a `[dependencies.rocket_contrib]` section to your
//! `Cargo.toml` file, setting `default-features` to false, and specifying
//! features manually. For example, to use the `tera_templates` module, you
//! would add:
//!
//! ```toml
//! [dependencies.rocket_contrib]
//! version = "0.5.0-dev"
//! default-features = false
//! features = ["tera_templates"]
//! ```
//!
//! This crate is expected to grow with time, bringing in outside crates to be
//! officially supported by Rocket.
#[allow(unused_imports)] #[macro_use] extern crate rocket;
#[cfg(feature="templates")] pub mod templates;
#[cfg(feature="databases")] pub mod databases;
// TODO.async: Migrate compression, reenable this, tests, and add to docs.
//#[cfg(any(feature="brotli_compression", feature="gzip_compression"))] pub mod compression;
#[cfg(feature="databases")] #[doc(hidden)] pub use rocket_contrib_codegen::*;

View File

@ -1,238 +0,0 @@
#[macro_use]
#[cfg(all(feature = "brotli_compression", feature = "gzip_compression"))]
extern crate rocket;
#[cfg(all(feature = "brotli_compression", feature = "gzip_compression"))]
mod compress_responder_tests {
use rocket::http::hyper::header::{ContentEncoding, Encoding};
use rocket::http::Status;
use rocket::http::{ContentType, Header};
use rocket::local::blocking::Client;
use rocket::response::{Content, Response};
use rocket_contrib::compression::Compress;
use std::io::Cursor;
use std::io::Read;
use flate2::read::{GzDecoder, GzEncoder};
const HELLO: &str = r"This is a message to hello with more than 100 bytes \
in order to have to read more than one buffer when gzipping. こんにちは!";
#[get("/")]
pub fn index() -> Compress<String> {
Compress(String::from(HELLO))
}
#[get("/font")]
pub fn font() -> Compress<Content<&'static str>> {
Compress(Content(ContentType::WOFF, HELLO))
}
#[get("/image")]
pub fn image() -> Compress<Content<&'static str>> {
Compress(Content(ContentType::PNG, HELLO))
}
#[get("/already_encoded")]
pub fn already_encoded() -> Compress<Response<'static>> {
let mut encoder = GzEncoder::new(
Cursor::new(String::from(HELLO)),
flate2::Compression::default(),
);
let mut encoded = Vec::new();
encoder.read_to_end(&mut encoded).unwrap();
Compress(
Response::build()
.header(ContentEncoding(vec![Encoding::Gzip]))
.sized_body(Cursor::new(encoded))
.finalize(),
)
}
#[get("/identity")]
pub fn identity() -> Compress<Response<'static>> {
Compress(
Response::build()
.header(ContentEncoding(vec![Encoding::Identity]))
.sized_body(Cursor::new(String::from(HELLO)))
.finalize(),
)
}
fn rocket() -> rocket::Rocket {
rocket::build().mount("/", routes![index, font, image, already_encoded, identity])
}
#[test]
fn test_prioritizes_brotli() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
assert_eq!(
String::from_utf8(body_plain.get_mut().to_vec()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_br_font() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/font")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
assert_eq!(
String::from_utf8(body_plain.get_mut().to_vec()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_fallback_gzip() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
}
#[test]
fn test_does_not_recompress() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/already_encoded")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
}
#[test]
fn test_does_not_compress_explicit_identity() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/identity")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_ignore_exceptions() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
assert_eq!(
String::from_utf8(body_plain.get_mut().to_vec()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_ignores_unimplemented_encodings() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_respects_identity_only() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "identity"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
}

View File

@ -1,293 +0,0 @@
#[macro_use]
#[cfg(all(feature = "brotli_compression", feature = "gzip_compression"))]
extern crate rocket;
#[cfg(all(feature = "brotli_compression", feature = "gzip_compression"))]
mod compression_fairing_tests {
use rocket::config::{Config, Environment};
use rocket::http::hyper::header::{ContentEncoding, Encoding};
use rocket::http::Status;
use rocket::http::{ContentType, Header};
use rocket::local::blocking::Client;
use rocket::response::{Content, Response};
use rocket_contrib::compression::Compression;
use std::io::Cursor;
use std::io::Read;
use flate2::read::{GzDecoder, GzEncoder};
const HELLO: &str = r"This is a message to hello with more than 100 bytes \
in order to have to read more than one buffer when gzipping. こんにちは!";
#[get("/")]
pub fn index() -> String {
String::from(HELLO)
}
#[get("/font")]
pub fn font() -> Content<&'static str> {
Content(ContentType::WOFF, HELLO)
}
#[get("/image")]
pub fn image() -> Content<&'static str> {
Content(ContentType::PNG, HELLO)
}
#[get("/tar")]
pub fn tar() -> Content<&'static str> {
Content(ContentType::TAR, HELLO)
}
#[get("/already_encoded")]
pub fn already_encoded() -> Response<'static> {
let mut encoder = GzEncoder::new(
Cursor::new(String::from(HELLO)),
flate2::Compression::default(),
);
let mut encoded = Vec::new();
encoder.read_to_end(&mut encoded).unwrap();
Response::build()
.header(ContentEncoding(vec![Encoding::Gzip]))
.sized_body(Cursor::new(encoded))
.finalize()
}
#[get("/identity")]
pub fn identity() -> Response<'static> {
Response::build()
.header(ContentEncoding(vec![Encoding::Identity]))
.sized_body(Cursor::new(String::from(HELLO)))
.finalize()
}
fn rocket() -> rocket::Rocket {
rocket::build()
.mount(
"/",
routes![index, font, image, tar, already_encoded, identity],
)
.attach(Compression::fairing())
}
fn rocket_tar_exception() -> rocket::Rocket {
let mut table = std::collections::BTreeMap::new();
table.insert("exclude".to_string(), vec!["application/x-tar"]);
let config = Config::build(Environment::Development)
.extra("compress", table)
.expect("valid configuration");
rocket::custom(config)
.mount("/", routes![image, tar])
.attach(Compression::fairing())
}
#[test]
fn test_prioritizes_brotli() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
assert_eq!(
String::from_utf8(body_plain.get_mut().to_vec()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_br_font() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/font")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
assert_eq!(
String::from_utf8(body_plain.get_mut().to_vec()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_fallback_gzip() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
}
#[test]
fn test_does_not_recompress() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/already_encoded")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "gzip"));
let mut s = String::new();
GzDecoder::new(&response.into_bytes().unwrap()[..])
.read_to_string(&mut s)
.expect("decompress response");
assert_eq!(s, String::from(HELLO));
}
#[test]
fn test_does_not_compress_explicit_identity() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/identity")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_does_not_compress_image() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_ignores_unimplemented_encodings() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "deflate"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_respects_identity_only() {
let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client
.get("/")
.header(Header::new("Accept-Encoding", "identity"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_does_not_compress_custom_exception() {
let client = Client::debug(rocket_tar_exception()).expect("valid rocket instance");
let mut response = client
.get("/tar")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(!response
.headers()
.get("Content-Encoding")
.any(|x| x != "identity"));
assert_eq!(
String::from_utf8(response.into_bytes().unwrap()).unwrap(),
String::from(HELLO)
);
}
#[test]
fn test_compress_custom_removed_exception() {
let client = Client::debug(rocket_tar_exception()).expect("valid rocket instance");
let mut response = client
.get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br"))
.dispatch();
assert_eq!(response.status(), Status::Ok);
assert!(response
.headers()
.get("Content-Encoding")
.any(|x| x == "br"));
let mut body_plain = Cursor::new(Vec::<u8>::new());
brotli::BrotliDecompress(
&mut Cursor::new(response.into_bytes().unwrap()),
&mut body_plain,
)
.expect("decompress response");
assert_eq!(
String::from_utf8(body_plain.get_mut().to_vec()).unwrap(),
String::from(HELLO)
);
}
}

View File

@ -1,242 +0,0 @@
#[cfg(feature = "templates")]
#[macro_use] extern crate rocket;
#[cfg(feature = "templates")]
mod templates_tests {
use std::path::{Path, PathBuf};
use rocket::{Rocket, Build};
use rocket::config::Config;
use rocket_contrib::templates::{Template, Metadata};
#[get("/<engine>/<name>")]
fn template_check(md: Metadata<'_>, engine: &str, name: &str) -> Option<()> {
match md.contains_template(&format!("{}/{}", engine, name)) {
true => Some(()),
false => None
}
}
#[get("/is_reloading")]
fn is_reloading(md: Metadata<'_>) -> Option<()> {
if md.reloading() { Some(()) } else { None }
}
fn template_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("templates")
}
fn rocket() -> Rocket<Build> {
rocket::custom(Config::figment().merge(("template_dir", template_root())))
.attach(Template::fairing())
.mount("/", routes![template_check, is_reloading])
}
#[test]
fn test_callback_error() {
use rocket::{local::blocking::Client, error::ErrorKind::FailedFairings};
let rocket = rocket::build().attach(Template::try_custom(|_| {
Err("error reloading templates!".into())
}));
let error = Client::debug(rocket).expect_err("client failure");
match error.kind() {
FailedFairings(failures) => assert_eq!(failures[0].name, "Templating"),
_ => panic!("Wrong kind of launch error"),
}
}
#[test]
fn test_sentinel() {
use rocket::{local::blocking::Client, error::ErrorKind::SentinelAborts};
let err = Client::debug_with(routes![is_reloading]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
let err = Client::debug_with(routes![is_reloading, template_check]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 2));
#[get("/")]
fn return_template() -> Template {
Template::render("foo", ())
}
let err = Client::debug_with(routes![return_template]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[get("/")]
fn return_opt_template() -> Option<Template> {
Some(Template::render("foo", ()))
}
let err = Client::debug_with(routes![return_opt_template]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[derive(rocket::Responder)]
struct MyThing<T>(T);
#[get("/")]
fn return_custom_template() -> MyThing<Template> {
MyThing(Template::render("foo", ()))
}
let err = Client::debug_with(routes![return_custom_template]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[derive(rocket::Responder)]
struct MyOkayThing<T>(Option<T>);
impl<T> rocket::Sentinel for MyOkayThing<T> {
fn abort(_: &Rocket<rocket::Ignite>) -> bool {
false
}
}
#[get("/")]
fn always_ok_sentinel() -> MyOkayThing<Template> {
MyOkayThing(None)
}
Client::debug_with(routes![always_ok_sentinel]).expect("no sentinel abort");
}
#[cfg(feature = "tera_templates")]
mod tera_tests {
use super::*;
use std::collections::HashMap;
use rocket::http::Status;
use rocket::local::blocking::Client;
const UNESCAPED_EXPECTED: &'static str
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
const ESCAPED_EXPECTED: &'static str
= "\nh_start\ntitle: _test_\nh_end\n\n\n&lt;script &#x2F;&gt;\n\nfoot\n";
#[test]
fn test_tera_templates() {
let client = Client::debug(rocket()).unwrap();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script />");
// Test with a txt file, which shouldn't escape.
let template = Template::show(client.rocket(), "tera/txt_test", &map);
assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
// Now with an HTML file, which should.
let template = Template::show(client.rocket(), "tera/html_test", &map);
assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
}
#[test]
fn test_template_metadata_with_tera() {
let client = Client::debug(rocket()).unwrap();
let response = client.get("/tera/txt_test").dispatch();
assert_eq!(response.status(), Status::Ok);
let response = client.get("/tera/html_test").dispatch();
assert_eq!(response.status(), Status::Ok);
let response = client.get("/tera/not_existing").dispatch();
assert_eq!(response.status(), Status::NotFound);
let response = client.get("/hbs/txt_test").dispatch();
assert_eq!(response.status(), Status::NotFound);
}
}
#[cfg(feature = "handlebars_templates")]
mod handlebars_tests {
use super::*;
use std::collections::HashMap;
use rocket::http::Status;
use rocket::local::blocking::Client;
const EXPECTED: &'static str
= "Hello _test_!\n\n<main> &lt;script /&gt; hi </main>\nDone.\n\n";
#[test]
fn test_handlebars_templates() {
let client = Client::debug(rocket()).unwrap();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script /> hi");
// Test with a txt file, which shouldn't escape.
let template = Template::show(client.rocket(), "hbs/test", &map);
assert_eq!(template, Some(EXPECTED.into()));
}
#[test]
fn test_template_metadata_with_handlebars() {
let client = Client::debug(rocket()).unwrap();
let response = client.get("/hbs/test").dispatch();
assert_eq!(response.status(), Status::Ok);
let response = client.get("/hbs/not_existing").dispatch();
assert_eq!(response.status(), Status::NotFound);
let response = client.get("/tera/test").dispatch();
assert_eq!(response.status(), Status::NotFound);
}
#[test]
#[cfg(debug_assertions)]
fn test_template_reload() {
use std::fs::File;
use std::io::Write;
use std::time::Duration;
use rocket::local::blocking::Client;
const RELOAD_TEMPLATE: &str = "hbs/reload";
const INITIAL_TEXT: &str = "initial";
const NEW_TEXT: &str = "reload";
fn write_file(path: &Path, text: &str) {
let mut file = File::create(path).expect("open file");
file.write_all(text.as_bytes()).expect("write file");
file.sync_all().expect("sync file");
}
// set up the template before initializing the Rocket instance so
// that it will be picked up in the initial loading of templates.
let reload_path = template_root().join("hbs").join("reload.txt.hbs");
write_file(&reload_path, INITIAL_TEXT);
// set up the client. if we can't reload templates, then just quit
let client = Client::debug(rocket()).unwrap();
let res = client.get("/is_reloading").dispatch();
if res.status() != Status::Ok {
return;
}
// verify that the initial content is correct
let initial_rendered = Template::show(client.rocket(), RELOAD_TEMPLATE, ());
assert_eq!(initial_rendered, Some(INITIAL_TEXT.into()));
// write a change to the file
write_file(&reload_path, NEW_TEXT);
for _ in 0..6 {
// dispatch any request to trigger a template reload
client.get("/").dispatch();
// if the new content is correct, we are done
let new_rendered = Template::show(client.rocket(), RELOAD_TEMPLATE, ());
if new_rendered == Some(NEW_TEXT.into()) {
write_file(&reload_path, INITIAL_TEXT);
return;
}
// otherwise, retry a few times, waiting 250ms in between
std::thread::sleep(Duration::from_millis(250));
}
panic!("failed to reload modified template in 1.5s");
}
}
}

View File

@ -0,0 +1,58 @@
# `sync_db_pools` [![ci.svg]][ci] [![crates.io]][crate] [![docs.svg]][crate docs]
[crates.io]: https://img.shields.io/crates/v/rocket_sync_db_pools.svg
[crate]: https://crates.io/crates/rocket_sync_db_pools
[docs.svg]: https://img.shields.io/badge/web-master-red.svg?style=flat&label=docs&colorB=d33847
[crate docs]: https://api.rocket.rs/master/rocket_sync_db_pools
[ci.svg]: https://github.com/SergioBenitez/Rocket/workflows/CI/badge.svg
[ci]: https://github.com/SergioBenitez/Rocket/actions
This crate provides traits, utilities, and a procedural macro for configuring
and accessing database connection pools in Rocket. This implementation is backed
by [`r2d2`] and exposes connections through request guards.
[`r2d2`]: https://docs.rs/r2d2
## Usage
First, enable the feature corresponding to your database type:
```toml
[dependencies.rocket_sync_db_pools]
version = "0.1.0-dev"
default-features = false
features = ["diesel_sqlite_pool"]
```
A full list of supported databases and their associated feature names is
available in the [crate docs]. In whichever configuration source you choose,
configure a `databases` dictionary with a key for each database, here
`sqlite_logs` in a TOML source:
```toml
[default.databases]
sqlite_logs = { url = "/path/to/database.sqlite" }
```
In your application's source code:
```rust
#[macro_use] extern crate rocket;
use rocket_sync_db_pools::{database, diesel};
#[database("sqlite_logs")]
struct LogsDbConn(diesel::SqliteConnection);
#[get("/logs/<id>")]
async fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
conn.run(|c| Logs::by_id(c, id)).await
}
#[launch]
fn rocket() -> _ {
rocket::build().attach(LogsDbConn::fairing())
}
```
See the [crate docs] for full details.

View File

@ -0,0 +1,22 @@
[package]
name = "rocket_sync_db_pools_codegen"
version = "0.1.0-dev"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Procedural macros for rocket_sync_db_pools."
repository = "https://github.com/SergioBenitez/Rocket/contrib/sync_db_pools"
readme = "../README.md"
keywords = ["rocket", "framework", "database", "pools"]
license = "MIT OR Apache-2.0"
edition = "2018"
[lib]
proc-macro = true
[dependencies]
quote = "1.0"
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "df00b5" }
[dev-dependencies]
version_check = "0.9"
trybuild = "1.0"
rocket_sync_db_pools = { path = "../lib", features = ["diesel_sqlite_pool"] }

View File

@ -68,15 +68,15 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
let span = conn_type.span().into(); let span = conn_type.span().into();
// A few useful paths. // A few useful paths.
let databases = quote_spanned!(span => ::rocket_contrib::databases); let root = quote_spanned!(span => ::rocket_sync_db_pools);
let request = quote!(::rocket::request); let rocket = quote!(#root::rocket);
let request_guard_type = quote_spanned! { span => let request_guard_type = quote_spanned! { span =>
#vis struct #guard_type(#databases::Connection<Self, #conn_type>); #vis struct #guard_type(#root::Connection<Self, #conn_type>);
}; };
let pool = quote_spanned!(span => #databases::ConnectionPool<Self, #conn_type>); let pool = quote_spanned!(span => #root::ConnectionPool<Self, #conn_type>);
let conn = quote_spanned!(span => #databases::Connection<Self, #conn_type>); let conn = quote_spanned!(span => #root::Connection<Self, #conn_type>);
Ok(quote! { Ok(quote! {
#request_guard_type #request_guard_type
@ -84,15 +84,15 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
impl #guard_type { impl #guard_type {
/// Returns a fairing that initializes the associated database /// Returns a fairing that initializes the associated database
/// connection pool. /// connection pool.
pub fn fairing() -> impl ::rocket::fairing::Fairing { pub fn fairing() -> impl #rocket::fairing::Fairing {
<#pool>::fairing(#fairing_name, #name) <#pool>::fairing(#fairing_name, #name)
} }
/// Retrieves a connection of type `Self` from the `rocket` /// Retrieves a connection of type `Self` from the `rocket`
/// instance. Returns `Some` as long as `Self::fairing()` has been /// instance. Returns `Some` as long as `Self::fairing()` has been
/// attached. /// attached.
pub async fn get_one<P>(__rocket: &::rocket::Rocket<P>) -> Option<Self> pub async fn get_one<P>(__rocket: &#rocket::Rocket<P>) -> Option<Self>
where P: ::rocket::Phase, where P: #rocket::Phase,
{ {
<#pool>::get_one(&__rocket).await.map(Self) <#pool>::get_one(&__rocket).await.map(Self)
} }
@ -110,17 +110,19 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
} }
} }
#[::rocket::async_trait] #[#rocket::async_trait]
impl<'r> #request::FromRequest<'r> for #guard_type { impl<'r> #rocket::request::FromRequest<'r> for #guard_type {
type Error = (); type Error = ();
async fn from_request(__r: &'r #request::Request<'_>) -> #request::Outcome<Self, ()> { async fn from_request(
__r: &'r #rocket::request::Request<'_>
) -> #rocket::request::Outcome<Self, ()> {
<#conn>::from_request(__r).await.map(Self) <#conn>::from_request(__r).await.map(Self)
} }
} }
impl ::rocket::Sentinel for #guard_type { impl #rocket::Sentinel for #guard_type {
fn abort(__r: &::rocket::Rocket<::rocket::Ignite>) -> bool { fn abort(__r: &#rocket::Rocket<#rocket::Ignite>) -> bool {
<#conn>::abort(__r) <#conn>::abort(__r)
} }
} }

View File

@ -0,0 +1,50 @@
//! Code generation for rocket-sync-db-pools.
#![recursion_limit="256"]
#![warn(rust_2018_idioms)]
#[macro_use]
extern crate quote;
mod database;
use devise::{syn, proc_macro2};
use proc_macro::TokenStream;
/// Generates a request guard and fairing for retrieving a database connection.
///
/// The syntax for the `databases` macro is:
///
/// <pre>
/// macro := 'database' '( DATABASE_NAME ')'
///
/// DATABASE_NAME := string literal
/// </pre>
///
/// The attribute accepts a single string parameter that indicates the name of
/// the database. This corresponds to the database name set as the database's
/// configuration key:
///
/// The macro generates a [`FromRequest`] implementation for the decorated type,
/// allowing the type to be used as a request guard. This implementation
/// retrieves a connection from the database pool or fails with a
/// `Status::ServiceUnavailable` if connecting to the database times out.
///
/// The macro also generates two inherent methods on the decorated type:
///
/// * `fn fairing() -> impl Fairing`
///
/// Returns a fairing that initializes the associated database connection
/// pool.
///
/// * `async fn get_one<P: Phase>(&Rocket<P>) -> Option<Self>`
///
/// Retrieves a connection wrapper from the configured pool. Returns `Some`
/// as long as `Self::fairing()` has been attached.
///
/// [`FromRequest`]: rocket::request::FromRequest
#[proc_macro_attribute]
pub fn database(attr: TokenStream, input: TokenStream) -> TokenStream {
crate::database::database_attr(attr, input)
.unwrap_or_else(|diag| diag.emit_as_item_tokens().into())
}

View File

@ -1,21 +1,21 @@
error[E0277]: the trait bound `Unknown: Poolable` is not satisfied error[E0277]: the trait bound `Unknown: Poolable` is not satisfied
--> $DIR/database-types.rs:7:10 --> $DIR/database-types.rs:6:10
| |
7 | struct A(Unknown); 6 | struct A(Unknown);
| ^^^^^^^ the trait `Poolable` is not implemented for `Unknown` | ^^^^^^^ the trait `Poolable` is not implemented for `Unknown`
| |
::: $WORKSPACE/contrib/lib/src/databases/connection.rs ::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
| |
| pub struct Connection<K, C: Poolable> { | pub struct Connection<K, C: Poolable> {
| -------- required by this bound in `rocket_contrib::databases::Connection` | -------- required by this bound in `rocket_sync_db_pools::Connection`
error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied
--> $DIR/database-types.rs:10:10 --> $DIR/database-types.rs:9:10
| |
10 | struct B(Vec<i32>); 9 | struct B(Vec<i32>);
| ^^^^^^^^ the trait `Poolable` is not implemented for `Vec<i32>` | ^^^^^^^^ the trait `Poolable` is not implemented for `Vec<i32>`
| |
::: $WORKSPACE/contrib/lib/src/databases/connection.rs ::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
| |
| pub struct Connection<K, C: Poolable> { | pub struct Connection<K, C: Poolable> {
| -------- required by this bound in `rocket_contrib::databases::Connection` | -------- required by this bound in `rocket_sync_db_pools::Connection`

View File

@ -1,21 +1,21 @@
error[E0277]: the trait bound `Unknown: Poolable` is not satisfied error[E0277]: the trait bound `Unknown: Poolable` is not satisfied
--> $DIR/database-types.rs:7:10 --> $DIR/database-types.rs:6:10
| |
7 | struct A(Unknown); 6 | struct A(Unknown);
| ^^^^^^^ the trait `Poolable` is not implemented for `Unknown` | ^^^^^^^ the trait `Poolable` is not implemented for `Unknown`
| |
::: $WORKSPACE/contrib/lib/src/databases/connection.rs ::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
| |
| pub struct Connection<K, C: Poolable> { | pub struct Connection<K, C: Poolable> {
| -------- required by this bound in `rocket_contrib::databases::Connection` | -------- required by this bound in `rocket_sync_db_pools::Connection`
error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied
--> $DIR/database-types.rs:10:10 --> $DIR/database-types.rs:9:10
| |
10 | struct B(Vec<i32>); 9 | struct B(Vec<i32>);
| ^^^ the trait `Poolable` is not implemented for `Vec<i32>` | ^^^ the trait `Poolable` is not implemented for `Vec<i32>`
| |
::: $WORKSPACE/contrib/lib/src/databases/connection.rs ::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
| |
| pub struct Connection<K, C: Poolable> { | pub struct Connection<K, C: Poolable> {
| -------- required by this bound in `rocket_contrib::databases::Connection` | -------- required by this bound in `rocket_sync_db_pools::Connection`

View File

@ -1,7 +1,7 @@
#[macro_use] extern crate rocket_contrib; use rocket_sync_db_pools::database;
#[allow(unused_imports)] #[allow(unused_imports)]
use rocket_contrib::databases::diesel; use rocket_sync_db_pools::diesel;
#[database] #[database]
struct A(diesel::SqliteConnection); struct A(diesel::SqliteConnection);

View File

@ -1,5 +1,4 @@
extern crate rocket; #[macro_use] extern crate rocket_sync_db_pools;
#[macro_use] extern crate rocket_contrib;
struct Unknown; struct Unknown;

View File

@ -0,0 +1,44 @@
[package]
name = "rocket_sync_db_pools"
version = "0.1.0-dev"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Rocket async database pooling support for sync database drivers."
repository = "https://github.com/SergioBenitez/Rocket/contrib/sync_db_pools"
readme = "../README.md"
keywords = ["rocket", "framework", "database", "pools"]
license = "MIT OR Apache-2.0"
edition = "2018"
[features]
diesel_sqlite_pool = ["diesel/sqlite", "diesel/r2d2"]
diesel_postgres_pool = ["diesel/postgres", "diesel/r2d2"]
diesel_mysql_pool = ["diesel/mysql", "diesel/r2d2"]
sqlite_pool = ["rusqlite", "r2d2_sqlite"]
postgres_pool = ["postgres", "r2d2_postgres"]
memcache_pool = ["memcache", "r2d2-memcache"]
[dependencies]
r2d2 = "0.8"
tokio = { version = "1.4", features = ["rt", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"] }
diesel = { version = "1.0", default-features = false, optional = true }
postgres = { version = "0.19", optional = true }
r2d2_postgres = { version = "0.18", optional = true }
rusqlite = { version = "0.24", optional = true }
r2d2_sqlite = { version = "0.17", optional = true }
memcache = { version = "0.15", optional = true }
r2d2-memcache = { version = "0.6", optional = true }
[dependencies.rocket_sync_db_pools_codegen]
path = "../codegen"
[dependencies.rocket]
path = "../../../core/lib"
default-features = false
[package.metadata.docs.rs]
all-features = true

View File

@ -17,7 +17,7 @@ use serde::{Serialize, Deserialize};
/// ...`Config::from("my_database", rocket)` would return the following struct: /// ...`Config::from("my_database", rocket)` would return the following struct:
/// ///
/// ```rust /// ```rust
/// # use rocket_contrib::databases::Config; /// # use rocket_sync_db_pools::Config;
/// Config { /// Config {
/// url: "postgres://root:root@localhost/my_database".into(), /// url: "postgres://root:root@localhost/my_database".into(),
/// pool_size: 10, /// pool_size: 10,
@ -60,7 +60,7 @@ impl Config {
/// # "#).nested(); /// # "#).nested();
/// ///
/// use rocket::{Rocket, Build}; /// use rocket::{Rocket, Build};
/// use rocket_contrib::databases::Config; /// use rocket_sync_db_pools::Config;
/// ///
/// fn pool(rocket: &Rocket<Build>) { /// fn pool(rocket: &Rocket<Build>) {
/// let config = Config::from("my_db", rocket).unwrap(); /// let config = Config::from("my_db", rocket).unwrap();
@ -93,7 +93,7 @@ impl Config {
/// ///
/// ```rust /// ```rust
/// use rocket::{Rocket, Build}; /// use rocket::{Rocket, Build};
/// use rocket_contrib::databases::Config; /// use rocket_sync_db_pools::Config;
/// ///
/// fn pool(rocket: &Rocket<Build>) { /// fn pool(rocket: &Rocket<Build>) {
/// let my_db_figment = Config::figment("my_db", rocket); /// let my_db_figment = Config::figment("my_db", rocket);

View File

@ -10,7 +10,7 @@ use rocket::http::Status;
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex}; use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
use rocket::tokio::time::timeout; use rocket::tokio::time::timeout;
use crate::databases::{Config, Poolable, Error}; use crate::{Config, Poolable, Error};
/// Unstable internal details of generated code for the #[database] attribute. /// Unstable internal details of generated code for the #[database] attribute.
/// ///
@ -225,7 +225,6 @@ impl<K: 'static, C: Poolable> Sentinel for Connection<K, C> {
let fairing = Paint::default(format!("{}::fairing()", conn)).wrap().bold(); let fairing = Paint::default(format!("{}::fairing()", conn)).wrap().bold();
error!("requesting `{}` DB connection without attaching `{}`.", conn, fairing); error!("requesting `{}` DB connection without attaching `{}`.", conn, fairing);
info_!("Attach `{}` to use database connection pooling.", fairing); info_!("Attach `{}` to use database connection pooling.", fairing);
info_!("See the `contrib::database` documentation for more information.");
return true; return true;
} }

View File

@ -5,7 +5,7 @@ use rocket::figment;
/// This type is only relevant to implementors of the [`Poolable`] trait. See /// This type is only relevant to implementors of the [`Poolable`] trait. See
/// the [`Poolable`] documentation for more information on how to use this type. /// the [`Poolable`] documentation for more information on how to use this type.
/// ///
/// [`Poolable`]: crate::databases::Poolable /// [`Poolable`]: crate::Poolable
#[derive(Debug)] #[derive(Debug)]
pub enum Error<T> { pub enum Error<T> {
/// A custom error of type `T`. /// A custom error of type `T`.

View File

@ -2,17 +2,15 @@
//! //!
//! # Overview //! # Overview
//! //!
//! This module provides traits, utilities, and a procedural macro that allows //! This crate provides traits, utilities, and a procedural macro for
//! you to easily connect your Rocket application to databases through //! configuring and accessing database connection pools in Rocket. A _database
//! connection pools. A _database connection pool_ is a data structure that //! connection pool_ is a data structure that maintains active database
//! maintains active database connections for later use in the application. //! connections for later use in the application. This implementation is backed
//! This implementation of connection pooling support is based on //! by [`r2d2`] and exposes connections through request guards.
//! [`r2d2`] and exposes connections through [request guards]. Databases are
//! individually configured through Rocket's regular configuration mechanisms: a
//! `Rocket.toml` file, environment variables, or procedurally.
//! //!
//! Connecting your Rocket application to a database using this library occurs //! Databases are individually configured through Rocket's regular configuration
//! in three simple steps: //! mechanisms. Connecting a Rocket application to a database using this library
//! occurs in three simple steps:
//! //!
//! 1. Configure your databases in `Rocket.toml`. //! 1. Configure your databases in `Rocket.toml`.
//! (see [Configuration](#configuration)) //! (see [Configuration](#configuration))
@ -28,11 +26,11 @@
//! ## Example //! ## Example
//! //!
//! Before using this library, the feature corresponding to your database type //! Before using this library, the feature corresponding to your database type
//! in `rocket_contrib` must be enabled: //! in `rocket_sync_db_pools` must be enabled:
//! //!
//! ```toml //! ```toml
//! [dependencies.rocket_contrib] //! [dependencies.rocket_sync_db_pools]
//! version = "0.5.0-dev" //! version = "0.1.0-dev"
//! default-features = false //! default-features = false
//! features = ["diesel_sqlite_pool"] //! features = ["diesel_sqlite_pool"]
//! ``` //! ```
@ -45,19 +43,17 @@
//! in a TOML source: //! in a TOML source:
//! //!
//! ```toml //! ```toml
//! [global.databases] //! [default.databases]
//! sqlite_logs = { url = "/path/to/database.sqlite" } //! sqlite_logs = { url = "/path/to/database.sqlite" }
//! ``` //! ```
//! //!
//! In your application's source code, one-time: //! In your application's source code, one-time:
//! //!
//! ```rust //! ```rust
//! #[macro_use] extern crate rocket; //! # #[macro_use] extern crate rocket;
//! #[macro_use] extern crate rocket_contrib;
//!
//! # #[cfg(feature = "diesel_sqlite_pool")] //! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test { //! # mod test {
//! use rocket_contrib::databases::diesel; //! use rocket_sync_db_pools::{database, diesel};
//! //!
//! #[database("sqlite_logs")] //! #[database("sqlite_logs")]
//! struct LogsDbConn(diesel::SqliteConnection); //! struct LogsDbConn(diesel::SqliteConnection);
@ -73,11 +69,11 @@
//! //!
//! ```rust //! ```rust
//! # #[macro_use] extern crate rocket; //! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib; //! # #[macro_use] extern crate rocket_sync_db_pools;
//! # //! #
//! # #[cfg(feature = "diesel_sqlite_pool")] //! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test { //! # mod test {
//! # use rocket_contrib::databases::diesel; //! # use rocket_sync_db_pools::diesel;
//! # //! #
//! # #[database("sqlite_logs")] //! # #[database("sqlite_logs")]
//! # struct LogsDbConn(diesel::SqliteConnection); //! # struct LogsDbConn(diesel::SqliteConnection);
@ -142,9 +138,10 @@
//! //!
//! ```rust //! ```rust
//! # #[cfg(feature = "diesel_sqlite_pool")] { //! # #[cfg(feature = "diesel_sqlite_pool")] {
//! # use rocket::launch;
//! use rocket::figment::{value::{Map, Value}, util::map}; //! use rocket::figment::{value::{Map, Value}, util::map};
//! //!
//! #[rocket::launch] //! #[launch]
//! fn rocket() -> _ { //! fn rocket() -> _ {
//! let db: Map<_, Value> = map! { //! let db: Map<_, Value> = map! {
//! "url" => "db.sqlite".into(), //! "url" => "db.sqlite".into(),
@ -182,7 +179,7 @@
//! //!
//! Once a database has been configured, the `#[database]` attribute can be used //! Once a database has been configured, the `#[database]` attribute can be used
//! to tie a type in your application to a configured database. The database //! to tie a type in your application to a configured database. The database
//! attributes accepts a single string parameter that indicates the name of the //! attribute accepts a single string parameter that indicates the name of the
//! database. This corresponds to the database name set as the database's //! database. This corresponds to the database name set as the database's
//! configuration key. //! configuration key.
//! //!
@ -191,7 +188,7 @@
//! retrieves a connection from the database pool or fails with a //! retrieves a connection from the database pool or fails with a
//! `Status::ServiceUnavailable` if connecting to the database times out. //! `Status::ServiceUnavailable` if connecting to the database times out.
//! //!
//! The macro will also generate two inherent methods on the decorated type: //! The macro also generates two inherent methods on the decorated type:
//! //!
//! * `fn fairing() -> impl Fairing` //! * `fn fairing() -> impl Fairing`
//! //!
@ -207,11 +204,10 @@
//! internal type of the structure must implement [`Poolable`]. //! internal type of the structure must implement [`Poolable`].
//! //!
//! ```rust //! ```rust
//! # extern crate rocket; //! # #[macro_use] extern crate rocket_sync_db_pools;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[cfg(feature = "diesel_sqlite_pool")] //! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test { //! # mod test {
//! use rocket_contrib::databases::diesel; //! use rocket_sync_db_pools::diesel;
//! //!
//! #[database("my_db")] //! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection); //! struct MyDatabase(diesel::SqliteConnection);
@ -222,11 +218,10 @@
//! type: //! type:
//! //!
//! ```rust //! ```rust
//! # extern crate rocket; //! # #[macro_use] extern crate rocket_sync_db_pools;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[cfg(feature = "postgres_pool")] //! # #[cfg(feature = "postgres_pool")]
//! # mod test { //! # mod test {
//! use rocket_contrib::databases::postgres; //! use rocket_sync_db_pools::postgres;
//! //!
//! #[database("my_pg_db")] //! #[database("my_pg_db")]
//! struct MyPgDatabase(postgres::Client); //! struct MyPgDatabase(postgres::Client);
@ -239,11 +234,11 @@
//! //!
//! ```rust //! ```rust
//! # #[macro_use] extern crate rocket; //! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib; //! # #[macro_use] extern crate rocket_sync_db_pools;
//! # //! #
//! # #[cfg(feature = "diesel_sqlite_pool")] { //! # #[cfg(feature = "diesel_sqlite_pool")] {
//! # use rocket::figment::{value::{Map, Value}, util::map}; //! # use rocket::figment::{value::{Map, Value}, util::map};
//! use rocket_contrib::databases::diesel; //! use rocket_sync_db_pools::diesel;
//! //!
//! #[database("my_db")] //! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection); //! struct MyDatabase(diesel::SqliteConnection);
@ -266,11 +261,11 @@
//! //!
//! ```rust //! ```rust
//! # #[macro_use] extern crate rocket; //! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib; //! # #[macro_use] extern crate rocket_sync_db_pools;
//! # //! #
//! # #[cfg(feature = "diesel_sqlite_pool")] //! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test { //! # mod test {
//! # use rocket_contrib::databases::diesel; //! # use rocket_sync_db_pools::diesel;
//! #[database("my_db")] //! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection); //! struct MyDatabase(diesel::SqliteConnection);
//! //!
@ -285,11 +280,11 @@
//! //!
//! ```rust //! ```rust
//! # #[macro_use] extern crate rocket; //! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib; //! # #[macro_use] extern crate rocket_sync_db_pools;
//! # //! #
//! # #[cfg(feature = "diesel_sqlite_pool")] //! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test { //! # mod test {
//! # use rocket_contrib::databases::diesel; //! # use rocket_sync_db_pools::diesel;
//! # type Data = (); //! # type Data = ();
//! #[database("my_db")] //! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection); //! struct MyDatabase(diesel::SqliteConnection);
@ -353,25 +348,33 @@
//! //!
//! [`FromRequest`]: rocket::request::FromRequest //! [`FromRequest`]: rocket::request::FromRequest
//! [request guards]: rocket::request::FromRequest //! [request guards]: rocket::request::FromRequest
//! [`Poolable`]: crate::databases::Poolable //! [`Poolable`]: crate::Poolable
pub extern crate r2d2; #![doc(html_root_url = "https://api.rocket.rs/master/rocket_sync_db_pools")]
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
#[doc(hidden)]
#[macro_use]
pub extern crate rocket;
#[cfg(any( #[cfg(any(
feature = "diesel_sqlite_pool", feature = "diesel_sqlite_pool",
feature = "diesel_postgres_pool", feature = "diesel_postgres_pool",
feature = "diesel_mysql_pool" feature = "diesel_mysql_pool"
))] ))]
pub extern crate diesel; pub use diesel;
#[cfg(feature = "postgres_pool")] pub extern crate postgres; #[cfg(feature = "postgres_pool")] pub use postgres;
#[cfg(feature = "postgres_pool")] pub extern crate r2d2_postgres; #[cfg(feature = "postgres_pool")] pub use r2d2_postgres;
#[cfg(feature = "sqlite_pool")] pub extern crate rusqlite; #[cfg(feature = "sqlite_pool")] pub use rusqlite;
#[cfg(feature = "sqlite_pool")] pub extern crate r2d2_sqlite; #[cfg(feature = "sqlite_pool")] pub use r2d2_sqlite;
#[cfg(feature = "memcache_pool")] pub extern crate memcache; #[cfg(feature = "memcache_pool")] pub use memcache;
#[cfg(feature = "memcache_pool")] pub extern crate r2d2_memcache; #[cfg(feature = "memcache_pool")] pub use r2d2_memcache;
pub use r2d2;
mod poolable; mod poolable;
mod config; mod config;
@ -382,8 +385,5 @@ pub use self::poolable::{Poolable, PoolResult};
pub use self::config::Config; pub use self::config::Config;
pub use self::error::Error; pub use self::error::Error;
#[doc(hidden)] pub use rocket_sync_db_pools_codegen::*;
pub use rocket_contrib_codegen::*;
#[doc(hidden)]
pub use self::connection::*; pub use self::connection::*;

View File

@ -1,7 +1,8 @@
use r2d2::ManageConnection; use r2d2::ManageConnection;
use rocket::{Rocket, Build}; use rocket::{Rocket, Build};
use crate::databases::{Config, Error};
#[allow(unused_imports)]
use crate::{Config, Error};
/// Trait implemented by `r2d2`-based database adapters. /// Trait implemented by `r2d2`-based database adapters.
/// ///
@ -38,7 +39,7 @@ use crate::databases::{Config, Error};
/// ```rust /// ```rust
/// # mod foo { /// # mod foo {
/// # use std::fmt; /// # use std::fmt;
/// # use rocket_contrib::databases::r2d2; /// # use rocket_sync_db_pools::r2d2;
/// # #[derive(Debug)] pub struct Error; /// # #[derive(Debug)] pub struct Error;
/// # impl std::error::Error for Error { } /// # impl std::error::Error for Error { }
/// # impl fmt::Display for Error { /// # impl fmt::Display for Error {
@ -63,7 +64,7 @@ use crate::databases::{Config, Error};
/// # } /// # }
/// # } /// # }
/// use rocket::{Rocket, Build}; /// use rocket::{Rocket, Build};
/// use rocket_contrib::databases::{r2d2, Error, Config, Poolable, PoolResult}; /// use rocket_sync_db_pools::{r2d2, Error, Config, Poolable, PoolResult};
/// ///
/// impl Poolable for foo::Connection { /// impl Poolable for foo::Connection {
/// type Manager = foo::ConnectionManager; /// type Manager = foo::ConnectionManager;

View File

@ -1,6 +1,6 @@
#[cfg(all(feature = "diesel_sqlite_pool", feature = "diesel_postgres_pool"))] #[cfg(all(feature = "diesel_sqlite_pool", feature = "diesel_postgres_pool"))]
mod databases_tests { mod databases_tests {
use rocket_contrib::databases::{database, diesel}; use rocket_sync_db_pools::{database, diesel};
#[database("foo")] #[database("foo")]
struct TempStorage(diesel::SqliteConnection); struct TempStorage(diesel::SqliteConnection);
@ -12,8 +12,7 @@ mod databases_tests {
#[cfg(all(feature = "databases", feature = "sqlite_pool"))] #[cfg(all(feature = "databases", feature = "sqlite_pool"))]
#[cfg(test)] #[cfg(test)]
mod rusqlite_integration_test { mod rusqlite_integration_test {
use rocket_contrib::database; use rocket_sync_db_pools::{rusqlite, database};
use rocket_contrib::databases::rusqlite;
use rusqlite::types::ToSql; use rusqlite::types::ToSql;
@ -61,7 +60,7 @@ mod rusqlite_integration_test {
mod sentinel_and_runtime_test { mod sentinel_and_runtime_test {
use rocket::{Rocket, Build}; use rocket::{Rocket, Build};
use r2d2::{ManageConnection, Pool}; use r2d2::{ManageConnection, Pool};
use rocket_contrib::databases::{database, Poolable, PoolResult}; use rocket_sync_db_pools::{database, Poolable, PoolResult};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
struct ContainsRuntime(Runtime); struct ContainsRuntime(Runtime);

View File

@ -51,10 +51,7 @@ use crate::http::uncased::Uncased;
/// ///
/// # Built-in Limits /// # Built-in Limits
/// ///
/// The following table details recognized built-in limits used by Rocket. Note /// The following table details recognized built-in limits used by Rocket.
/// that this table _does not_ include limits for types outside of Rocket's
/// core. In particular, this does not include limits applicable to contrib
/// types like `json` and `msgpack`.
/// ///
/// | Limit Name | Default | Type | Description | /// | Limit Name | Default | Type | Description |
/// |-------------------|---------|--------------|---------------------------------------| /// |-------------------|---------|--------------|---------------------------------------|

View File

@ -95,7 +95,7 @@ macro_rules! ctrs {
} }
ctrs! { ctrs! {
// FIXME: Add a note that this is _not_ the `contrib` `Json`. // FIXME: Add a note that this is _not_ `serde::Json`.
Json: JSON, "JSON", "application/json", Json: JSON, "JSON", "application/json",
Xml: XML, "XML", "text/xml", Xml: XML, "XML", "text/xml",
MsgPack: MsgPack, "MessagePack", "application/msgpack", MsgPack: MsgPack, "MessagePack", "application/msgpack",

View File

@ -13,15 +13,6 @@
//! result, you'll often have types of the form `A<B<C>>` consisting of three //! result, you'll often have types of the form `A<B<C>>` consisting of three
//! `Responder`s `A`, `B`, and `C`. This is normal and encouraged as the type //! `Responder`s `A`, `B`, and `C`. This is normal and encouraged as the type
//! names typically illustrate the intended response. //! names typically illustrate the intended response.
//!
//! # Contrib
//!
//! The [`contrib` crate] contains several useful `Responder`s including
//! [`Template`] and [`Json`].
//!
//! [`contrib` crate]: ../../rocket_contrib
//! [`Template`]: ../../rocket_contrib/templates/struct.Template.html
//! [`Json`]: ../../rocket_contrib/json/struct.Json.html
mod responder; mod responder;
mod redirect; mod redirect;

View File

@ -8,7 +8,6 @@ publish = false
[dependencies] [dependencies]
rocket = { path = "../../core/lib", features = ["secrets"] } rocket = { path = "../../core/lib", features = ["secrets"] }
[dependencies.rocket_contrib] [dependencies.rocket_dyn_templates]
path = "../../contrib/lib" path = "../../contrib/dyn_templates"
default-features = false features = ["handlebars"]
features = ["handlebars_templates"]

View File

@ -6,7 +6,7 @@ mod session;
mod message; mod message;
use rocket::response::content::Html; use rocket::response::content::Html;
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
#[get("/")] #[get("/")]
fn index() -> Html<&'static str> { fn index() -> Html<&'static str> {

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use rocket::form::Form; use rocket::form::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::http::{Cookie, CookieJar}; use rocket::http::{Cookie, CookieJar};
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
#[macro_export] #[macro_export]
macro_rules! message_uri { macro_rules! message_uri {

View File

@ -6,7 +6,7 @@ use rocket::response::{Redirect, Flash};
use rocket::http::{Cookie, CookieJar}; use rocket::http::{Cookie, CookieJar};
use rocket::form::Form; use rocket::form::Form;
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
#[derive(FromForm)] #[derive(FromForm)]
struct Login<'r> { struct Login<'r> {

View File

@ -15,7 +15,6 @@ version = "0.5.1"
default-features = false default-features = false
features = ["runtime-tokio-rustls", "sqlite", "macros", "offline", "migrate"] features = ["runtime-tokio-rustls", "sqlite", "macros", "offline", "migrate"]
[dependencies.rocket_contrib] [dependencies.rocket_sync_db_pools]
path = "../../contrib/lib" path = "../../contrib/sync_db_pools/lib/"
default-features = false
features = ["diesel_sqlite_pool", "sqlite_pool"] features = ["diesel_sqlite_pool", "sqlite_pool"]

View File

@ -3,7 +3,7 @@ use rocket::fairing::AdHoc;
use rocket::response::{Debug, status::Created}; use rocket::response::{Debug, status::Created};
use rocket::serde::{Serialize, Deserialize, json::Json}; use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket_contrib::databases::diesel; use rocket_sync_db_pools::diesel;
use self::diesel::prelude::*; use self::diesel::prelude::*;

View File

@ -1,5 +1,5 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_contrib; #[macro_use] extern crate rocket_sync_db_pools;
#[macro_use] extern crate diesel_migrations; #[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate diesel; #[macro_use] extern crate diesel;

View File

@ -3,7 +3,7 @@ use rocket::fairing::AdHoc;
use rocket::serde::{Serialize, Deserialize, json::Json}; use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket::response::{Debug, status::Created}; use rocket::response::{Debug, status::Created};
use rocket_contrib::databases::rusqlite; use rocket_sync_db_pools::rusqlite;
use self::rusqlite::params; use self::rusqlite::params;

View File

@ -67,7 +67,7 @@ async fn destroy(db: &State<Db>) -> Result<()> {
} }
async fn init_db(rocket: Rocket<Build>) -> fairing::Result { async fn init_db(rocket: Rocket<Build>) -> fairing::Result {
use rocket_contrib::databases::Config; use rocket_sync_db_pools::Config;
let config = match Config::from("sqlx", &rocket) { let config = match Config::from("sqlx", &rocket) {
Ok(config) => config, Ok(config) => config,

View File

@ -7,5 +7,8 @@ publish = false
[dependencies] [dependencies]
rocket = { path = "../../core/lib" } rocket = { path = "../../core/lib" }
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates"] }
time = "0.2" time = "0.2"
[dependencies.rocket_dyn_templates]
path = "../../contrib/dyn_templates"
features = ["tera"]

View File

@ -5,7 +5,7 @@ use rocket::form::{Form, Contextual, FromForm, FromFormField, Context};
use rocket::fs::TempFile; use rocket::fs::TempFile;
use rocket::fs::{FileServer, relative}; use rocket::fs::{FileServer, relative};
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
#[derive(Debug, FromForm)] #[derive(Debug, FromForm)]
struct Password<'v> { struct Password<'v> {

View File

@ -8,8 +8,7 @@ publish = false
[dependencies] [dependencies]
rocket = { path = "../../core/lib" } rocket = { path = "../../core/lib" }
[dependencies.rocket_contrib]
path = "../../contrib/lib"
default-features = false
# in your application, you should enable only the template engine(s) used # in your application, you should enable only the template engine(s) used
features = ["tera_templates", "handlebars_templates"] [dependencies.rocket_dyn_templates]
path = "../../contrib/dyn_templates"
features = ["tera", "handlebars"]

View File

@ -2,7 +2,7 @@ use rocket::Request;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::serde::Serialize; use rocket::serde::Serialize;
use rocket_contrib::templates::{Template, handlebars}; use rocket_dyn_templates::{Template, handlebars};
use self::handlebars::{Handlebars, JsonRender}; use self::handlebars::{Handlebars, JsonRender};

View File

@ -6,7 +6,7 @@ mod tera;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
use rocket::response::content::Html; use rocket::response::content::Html;
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
#[get("/")] #[get("/")]
fn index() -> Html<&'static str> { fn index() -> Html<&'static str> {

View File

@ -4,7 +4,7 @@ use rocket::Request;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::serde::Serialize; use rocket::serde::Serialize;
use rocket_contrib::templates::{Template, tera::Tera}; use rocket_dyn_templates::{Template, tera::Tera};
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]

View File

@ -2,7 +2,7 @@ use super::rocket;
use rocket::http::{RawStr, Status, Method::*}; use rocket::http::{RawStr, Status, Method::*};
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
fn test_root(kind: &str) { fn test_root(kind: &str) {
// Check that the redirect works. // Check that the redirect works.

View File

@ -14,7 +14,10 @@ diesel_migrations = "1.3"
parking_lot = "0.11" parking_lot = "0.11"
rand = "0.8" rand = "0.8"
[dependencies.rocket_contrib] [dependencies.rocket_sync_db_pools]
path = "../../contrib/lib" path = "../../contrib/sync_db_pools/lib/"
default_features = false features = ["diesel_sqlite_pool"]
features = ["tera_templates", "diesel_sqlite_pool"]
[dependencies.rocket_dyn_templates]
path = "../../contrib/dyn_templates"
features = ["tera"]

View File

@ -1,7 +1,7 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
#[macro_use] extern crate diesel; #[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_migrations; #[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate rocket_contrib; #[macro_use] extern crate rocket_sync_db_pools;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -15,7 +15,7 @@ use rocket::serde::Serialize;
use rocket::form::Form; use rocket::form::Form;
use rocket::fs::{FileServer, relative}; use rocket::fs::{FileServer, relative};
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
use crate::task::{Task, Todo}; use crate::task::{Task, Todo};

View File

@ -46,8 +46,6 @@ BENCHMARKS_ROOT=$(relative "benchmarks") || exit $?
CORE_LIB_ROOT=$(relative "core/lib") || exit $? CORE_LIB_ROOT=$(relative "core/lib") || exit $?
CORE_CODEGEN_ROOT=$(relative "core/codegen") || exit $? CORE_CODEGEN_ROOT=$(relative "core/codegen") || exit $?
CORE_HTTP_ROOT=$(relative "core/http") || exit $? CORE_HTTP_ROOT=$(relative "core/http") || exit $?
CONTRIB_LIB_ROOT=$(relative "contrib/lib") || exit $?
CONTRIB_CODEGEN_ROOT=$(relative "contrib/codegen") || exit $?
GUIDE_TESTS_ROOT=$(relative "site/tests") || exit $? GUIDE_TESTS_ROOT=$(relative "site/tests") || exit $?
# Root of infrastructure directories. # Root of infrastructure directories.
@ -78,8 +76,11 @@ ALL_PROJECT_DIRS=(
"${CORE_HTTP_ROOT}" "${CORE_HTTP_ROOT}"
"${CORE_CODEGEN_ROOT}" "${CORE_CODEGEN_ROOT}"
"${CORE_LIB_ROOT}" "${CORE_LIB_ROOT}"
"${CONTRIB_CODEGEN_ROOT}" )
"${CONTRIB_LIB_ROOT}"
CONTRIB_LIB_DIRS=(
"${CONTRIB_ROOT}/sync_db_pools"
"${CONTRIB_ROOT}/dyn_templates"
) )
function print_environment() { function print_environment() {
@ -98,12 +99,11 @@ function print_environment() {
echo " CORE_LIB_ROOT: ${CORE_LIB_ROOT}" echo " CORE_LIB_ROOT: ${CORE_LIB_ROOT}"
echo " CORE_CODEGEN_ROOT: ${CORE_CODEGEN_ROOT}" echo " CORE_CODEGEN_ROOT: ${CORE_CODEGEN_ROOT}"
echo " CORE_HTTP_ROOT: ${CORE_HTTP_ROOT}" echo " CORE_HTTP_ROOT: ${CORE_HTTP_ROOT}"
echo " CONTRIB_LIB_ROOT: ${CONTRIB_LIB_ROOT}"
echo " CONTRIB_CODEGEN_ROOT: ${CONTRIB_CODEGEN_ROOT}"
echo " GUIDE_TESTS_ROOT: ${GUIDE_TESTS_ROOT}" echo " GUIDE_TESTS_ROOT: ${GUIDE_TESTS_ROOT}"
echo " EXAMPLES_DIR: ${EXAMPLES_DIR}" echo " EXAMPLES_DIR: ${EXAMPLES_DIR}"
echo " DOC_DIR: ${DOC_DIR}" echo " DOC_DIR: ${DOC_DIR}"
echo " ALL_PROJECT_DIRS: ${ALL_PROJECT_DIRS[*]}" echo " ALL_PROJECT_DIRS: ${ALL_PROJECT_DIRS[*]}"
echo " CONTRIB_LIB_DIRS: ${CONTRIB_LIB_DIRS[*]}"
echo " date(): $(future_date)" echo " date(): $(future_date)"
} }

View File

@ -19,8 +19,10 @@ fi
# Generate the rustdocs for all of the crates. # Generate the rustdocs for all of the crates.
echo ":::: Generating the docs..." echo ":::: Generating the docs..."
pushd "${PROJECT_ROOT}" > /dev/null 2>&1 pushd "${PROJECT_ROOT}" > /dev/null 2>&1
RUSTDOCFLAGS="-Z unstable-options --crate-version ${DOC_VERSION}" \ # Set the crate version and fill in missing doc URLs with docs.rs links.
cargo doc -Zrustdoc-map -p rocket -p rocket_contrib --no-deps --all-features RUSTDOCFLAGS="-Zunstable-options --crate-version ${DOC_VERSION}" \
cargo doc -p rocket -p rocket_sync_db_pools -p rocket_dyn_templates \
-Zrustdoc-map --no-deps --all-features
popd > /dev/null 2>&1 popd > /dev/null 2>&1
# Blank index, for redirection. # Blank index, for redirection.

View File

@ -59,9 +59,7 @@ function check_style() {
} }
function test_contrib() { function test_contrib() {
FEATURES=( SYNC_DB_POOLS_FEATURES=(
tera_templates
handlebars_templates
diesel_postgres_pool diesel_postgres_pool
diesel_sqlite_pool diesel_sqlite_pool
diesel_mysql_pool diesel_mysql_pool
@ -70,16 +68,20 @@ function test_contrib() {
memcache_pool memcache_pool
) )
echo ":: Building and testing contrib [default]..." DYN_TEMPLATES_FEATURES=(
tera
handlebars
)
pushd "${CONTRIB_LIB_ROOT}" > /dev/null 2>&1 for feature in "${SYNC_DB_POOLS_FEATURES[@]}"; do
$CARGO test $@ echo ":: Building and testing sync_db_pools [$feature]..."
$CARGO test -p rocket_sync_db_pools --no-default-features --features $feature $@
done
for feature in "${FEATURES[@]}"; do for feature in "${DYN_TEMPLATES_FEATURES[@]}"; do
echo ":: Building and testing contrib [${feature}]..." echo ":: Building and testing dyn_templates [$feature]..."
$CARGO test --no-default-features --features "${feature}" $@ $CARGO test -p rocket_dyn_templates --no-default-features --features $feature $@
done done
popd > /dev/null 2>&1
} }
function test_core() { function test_core() {
@ -108,7 +110,7 @@ function test_examples() {
pushd "${EXAMPLES_DIR}" > /dev/null 2>&1 pushd "${EXAMPLES_DIR}" > /dev/null 2>&1
# Rust compiles Rocket once with the `secrets` feature enabled, so when run # Rust compiles Rocket once with the `secrets` feature enabled, so when run
# in production, we need a secret key or tests will fail needlessly. We # in production, we need a secret key or tests will fail needlessly. We
# ensure in core that secret key failing/not failing works as expected. # test in core that secret key failing/not failing works as expected.
ROCKET_SECRET_KEY="itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" \ ROCKET_SECRET_KEY="itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" \
$CARGO test --all $@ $CARGO test --all $@
popd > /dev/null 2>&1 popd > /dev/null 2>&1
@ -173,7 +175,7 @@ case $TEST_KIND in
test_contrib $@ & contrib=$! test_contrib $@ & contrib=$!
failures=() failures=()
if ! wait $default ; then failures+=("ROOT WORKSPACE"); fi if ! wait $default ; then failures+=("DEFAULT"); fi
if ! wait $examples ; then failures+=("EXAMPLES"); fi if ! wait $examples ; then failures+=("EXAMPLES"); fi
if ! wait $core ; then failures+=("CORE"); fi if ! wait $core ; then failures+=("CORE"); fi
if ! wait $contrib ; then failures+=("CONTRIB"); fi if ! wait $contrib ; then failures+=("CONTRIB"); fi

View File

@ -158,7 +158,6 @@ async fn files(file: PathBuf) -> Option<NamedFile> {
`rocket.mount("/public", FileServer::from("static/"))` `rocket.mount("/public", FileServer::from("static/"))`
[`rocket_contrib`]: @api/rocket_contrib/
[`FileServer`]: @api/rocket/fs/struct.FileServer.html [`FileServer`]: @api/rocket/fs/struct.FileServer.html
[`FromSegments`]: @api/rocket/request/trait.FromSegments.html [`FromSegments`]: @api/rocket/request/trait.FromSegments.html

View File

@ -264,9 +264,8 @@ async fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
## Rocket Responders ## Rocket Responders
Some of Rocket's best features are implemented through responders. You can find Some of Rocket's best features are implemented through responders. Among these
many of these responders in the [`response`] module and [`rocket_contrib`] are:
library. Among these are:
* [`Content`] - Used to override the Content-Type of a response. * [`Content`] - Used to override the Content-Type of a response.
* [`NamedFile`] - Streams a file to the client; automatically sets the * [`NamedFile`] - Streams a file to the client; automatically sets the
@ -286,8 +285,8 @@ library. Among these are:
[`Redirect`]: @api/rocket/response/struct.Redirect.html [`Redirect`]: @api/rocket/response/struct.Redirect.html
[`Stream`]: @api/rocket/response/struct.Stream.html [`Stream`]: @api/rocket/response/struct.Stream.html
[`Flash`]: @api/rocket/response/struct.Flash.html [`Flash`]: @api/rocket/response/struct.Flash.html
[`MsgPack`]: @api/rocket_contrib/msgpack/struct.MsgPack.html [`MsgPack`]: @api/rocket/serde/msgpack/struct.MsgPack.html
[`rocket_contrib`]: @api/rocket_contrib/ [`Template`]: @api/rocket_dyn_templates/struct.Template.html
### Async Streams ### Async Streams
@ -381,16 +380,16 @@ The [serialization example] provides further illustration.
## Templates ## Templates
Rocket includes built-in templating support that works largely through a Rocket has first-class templating support that works largely through a
[`Template`] responder in `rocket_contrib`. To render a template named "index", [`Template`] responder in the `rocket_dyn_templates` contrib library. To render
for instance, you might return a value of type `Template` as follows: a template named "index", for instance, you might return a value of type
`Template` as follows:
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {} # fn main() {}
use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
#[get("/")] #[get("/")]
fn index() -> Template { fn index() -> Template {
@ -415,7 +414,7 @@ fairings. To attach the template fairing, simply call
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
# use rocket_contrib::templates::Template; use rocket_dyn_templates::Template;
#[launch] #[launch]
fn rocket() -> _ { fn rocket() -> _ {
@ -450,7 +449,6 @@ including how to customize a template engine to add custom helpers and filters.
The [templating example](@example/templating) uses both Tera and Handlebars The [templating example](@example/templating) uses both Tera and Handlebars
templating to implement the same application. templating to implement the same application.
[`Template`]: @api/rocket_contrib/templates/struct.Template.html
[configurable]: ../configuration/#extras [configurable]: ../configuration/#extras
## Typed URIs ## Typed URIs

View File

@ -234,7 +234,7 @@ three simple steps:
Presently, Rocket provides built-in support for the following databases: Presently, Rocket provides built-in support for the following databases:
<!-- Note: Keep this table in sync with contrib/lib/src/databases.rs --> <!-- Note: Keep this table in sync with contrib/sync_db_pools/src/lib.rs -->
| Kind | Driver | Version | `Poolable` Type | Feature | | Kind | Driver | Version | `Poolable` Type | Feature |
|----------|-----------------------|-----------|--------------------------------|------------------------| |----------|-----------------------|-----------|--------------------------------|------------------------|
| MySQL | [Diesel] | `1` | [`diesel::MysqlConnection`] | `diesel_mysql_pool` | | MySQL | [Diesel] | `1` | [`diesel::MysqlConnection`] | `diesel_mysql_pool` |
@ -266,8 +266,8 @@ identified in the "Feature" column. For instance, for Diesel-based SQLite
databases, you'd write in `Cargo.toml`: databases, you'd write in `Cargo.toml`:
```toml ```toml
[dependencies.rocket_contrib] [dependencies.rocket_sync_db_pools]
version = "0.5.0-dev" version = "0.1.0-dev"
default-features = false default-features = false
features = ["diesel_sqlite_pool"] features = ["diesel_sqlite_pool"]
``` ```
@ -283,18 +283,17 @@ sqlite_logs = { url = "/path/to/database.sqlite" }
In your application's source code, create a unit-like struct with one internal In your application's source code, create a unit-like struct with one internal
type. This type should be the type listed in the "`Poolable` Type" column. Then type. This type should be the type listed in the "`Poolable` Type" column. Then
decorate the type with the `#[database]` attribute, providing the name of the decorate the type with the `#[database]` attribute, providing the name of the
database that you configured in the previous step as the only parameter. database that you configured in the previous step as the only parameter. You
You will need to either add `#[macro_use] extern crate rocket_contrib` to the will need to either add `#[macro_use] extern crate rocket_sync_db_pools` to the
crate root or have a `use rocket_contrib::database` in scope, otherwise the crate root or have a `use rocket_sync_db_pools::database` in scope, otherwise
`database` attribute will not be available. the `database` attribute will not be available. Finally, attach the fairing
Finally, attach the fairing returned by `YourType::fairing()`, which was returned by `YourType::fairing()`, which was generated by the `#[database]`
generated by the `#[database]` attribute: attribute:
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_contrib;
use rocket_contrib::databases::diesel; use rocket_sync_db_pools::{diesel, database};
#[database("sqlite_logs")] #[database("sqlite_logs")]
struct LogsDbConn(diesel::SqliteConnection); struct LogsDbConn(diesel::SqliteConnection);
@ -310,10 +309,9 @@ request guard. The database can be accessed by calling the `run` method:
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {} # fn main() {}
# use rocket_contrib::databases::diesel; # use rocket_sync_db_pools::{diesel, database};
# #[database("sqlite_logs")] # #[database("sqlite_logs")]
# struct LogsDbConn(diesel::SqliteConnection); # struct LogsDbConn(diesel::SqliteConnection);
@ -354,11 +352,11 @@ features by adding them in `Cargo.toml` like so:
postgres = { version = "0.15", features = ["with-chrono"] } postgres = { version = "0.15", features = ["with-chrono"] }
``` ```
For more on Rocket's built-in database support, see the For more on Rocket's sanctioned database support, see the
[`rocket_contrib::databases`] module documentation. For examples of CRUD-like [`rocket_sync_db_pools`] library documentation. For examples of CRUD-like "blog"
"blog" JSON APIs backed by a SQLite database driven by each of `sqlx`, `diesel`, JSON APIs backed by a SQLite database driven by each of `sqlx`, `diesel`, and
and `rusqlite` with migrations run automatically for the former two drivers and `rusqlite` with migrations run automatically for the former two drivers and
`contrib` database support use for the latter two drivers, see the [databases Rocket's database support use for the latter two drivers, see the [databases
example](@example/databases). example](@example/databases).
[`rocket_contrib::databases`]: @api/rocket_contrib/databases/index.html [`rocket_sync_db_pools`]: @api/rocket_sync_db_pools/index.html

View File

@ -85,9 +85,8 @@ bytes Rocket should accept for that type. Rocket can parse both integers
By default, Rocket specifies a `32 KiB` limit for incoming forms. Since Rocket By default, Rocket specifies a `32 KiB` limit for incoming forms. Since Rocket
requires specifying a read limit whenever data is read, external data guards may requires specifying a read limit whenever data is read, external data guards may
also choose to have a configure limit via the `limits` parameter. The also choose to have a configure limit via the `limits` parameter. The
[`rocket_contrib::Json`] type, for instance, uses the `limits.json` parameter. [`Json`](@api/rocket/serde/json/struct.Json.html) type, for instance, uses the
`limits.json` parameter.
[`rocket_contrib::Json`]: @api/rocket_contrib/json/struct.Json.html
### TLS ### TLS

View File

@ -73,7 +73,7 @@ is a built-in Rocket type that knows how to parse web forms into structures.
Rocket will automatically attempt to parse the request body into the `Form` and Rocket will automatically attempt to parse the request body into the `Form` and
call the `login` handler if parsing succeeds. Other built-in `FromData` types call the `login` handler if parsing succeeds. Other built-in `FromData` types
include [`Data`](@api/rocket/struct.Data.html), include [`Data`](@api/rocket/struct.Data.html),
[`Json`](@api/rocket_contrib/json/struct.Json.html), and [`Json`](@api/rocket/serde/json/struct.Json.html), and
[`Flash`](@api/rocket/response/struct.Flash.html). [`Flash`](@api/rocket/response/struct.Flash.html).
''' '''
@ -119,7 +119,7 @@ the standard library types including `&str`, `String`, `File`, `Option`, and
`Result`. Rocket also implements custom responders such as `Result`. Rocket also implements custom responders such as
[Redirect](@api/rocket/response/struct.Redirect.html), [Redirect](@api/rocket/response/struct.Redirect.html),
[Flash](@api/rocket/response/struct.Flash.html), and [Flash](@api/rocket/response/struct.Flash.html), and
[Template](@api/rocket_contrib/templates/struct.Template.html). [Template](@api/rocket_dyn_templates/struct.Template.html).
The task of a `Responder` is to generate a The task of a `Responder` is to generate a
[`Response`](@api/rocket/response/struct.Response.html), if possible. [`Response`](@api/rocket/response/struct.Response.html), if possible.

View File

@ -10,8 +10,15 @@ rocket = { path = "../../core/lib", features = ["secrets"] }
[dev-dependencies] [dev-dependencies]
rocket = { path = "../../core/lib", features = ["secrets", "json"] } rocket = { path = "../../core/lib", features = ["secrets", "json"] }
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates", "diesel_sqlite_pool"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
rand = "0.8" rand = "0.8"
figment = { version = "0.10", features = ["toml", "env"] } figment = { version = "0.10", features = ["toml", "env"] }
time = "0.2" time = "0.2"
[dev-dependencies.rocket_dyn_templates]
path = "../../contrib/dyn_templates"
features = ["tera"]
[dev-dependencies.rocket_sync_db_pools]
path = "../../contrib/sync_db_pools/lib"
features = ["diesel_sqlite_pool"]