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/codegen/",
"core/http/",
"contrib/lib",
"contrib/codegen",
"contrib/sync_db_pools/codegen/",
"contrib/sync_db_pools/lib/",
"contrib/dyn_templates/",
"site/tests",
]

View File

@ -69,29 +69,25 @@ You should see `Hello, world!` by visiting `http://localhost:8000`.
## Building and Testing
### Core and Contrib
The `core` directory contains the three core libraries: `lib`, `codegen`, and
`http`. The `contrib` directory contains officially supported community
contributions and similarly consists of `lib` and `codegen`.
`http` published as `rocket`, `rocket_codegen` and `rocket_http`, respectively.
The latter two are implementations details and are reexported from `rocket`.
Public APIs are exposed via `lib` packages: `core/lib` is distributed as the
`rocket` crate while `contrib/lib` is distributed as the `rocket_contrib` crate.
The remaining crates are implementation details.
### Library Testing
### Testing
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
accepts the following flags:
of the source tree. The script builds and tests all libraries and examples in
all configurations. It accepts the following flags:
* `--contrib`: tests each `contrib` feature individually
* `--core`: tests each `core` feature individually
* `--release`: runs the testing suite in `release` mode
* `--examples`: tests all examples in `examples/`
* `--contrib`: tests each `contrib` library and feature individually
* `--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`
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
crate's directory.
@ -99,10 +95,11 @@ crate's directory.
### Codegen Testing
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
symlinked into sibling `ui-fail-stable` and `ui-fail-nightly` directories which
contain the expected error output for stable and nightly compilers,
respectively.
the `codegen/tests/ui-fail` directories of respective `codegen` crates. Each
test is symlinked into sibling `ui-fail-stable` and `ui-fail-nightly`
directories which contain the expected error output for stable and nightly
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

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

View File

@ -3,10 +3,10 @@ use std::collections::HashMap;
use serde::Serialize;
use crate::templates::TemplateInfo;
use crate::TemplateInfo;
#[cfg(feature = "tera_templates")] use crate::templates::tera::Tera;
#[cfg(feature = "handlebars_templates")] use crate::templates::handlebars::Handlebars;
#[cfg(feature = "tera")] use crate::tera::Tera;
#[cfg(feature = "handlebars")] use crate::handlebars::Handlebars;
pub(crate) trait Engine: Send + Sync + Sized + 'static {
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
/// types from the respective templating engine library. These types should be
/// imported from the reexported crate at the root of `rocket_contrib` to avoid
/// version mismatches. For instance, when registering a Tera filter, the
/// imported from the reexported crate at the root of `rocket_dyn_templates` to
/// avoid version mismatches. For instance, when registering a Tera filter, the
/// [`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
/// # #[cfg(feature = "tera_templates")] {
/// # #[cfg(feature = "tera")] {
/// use std::collections::HashMap;
///
/// use rocket_contrib::templates::{Template, Engines};
/// use rocket_contrib::templates::tera::{self, Value};
/// use rocket_dyn_templates::{Template, Engines};
/// use rocket_dyn_templates::tera::{self, 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::Result`]: crate::templates::tera::Result
/// [`tera::Value`]: crate::tera::Value
/// [`tera::Result`]: crate::tera::Result
pub struct Engines {
/// A `Tera` templating engine. This field is only available when the
/// `tera_templates` feature is enabled. When calling methods on the `Tera`
/// instance, ensure you use types imported from
/// `rocket_contrib::templates::tera` to avoid version mismatches.
#[cfg(feature = "tera_templates")]
/// `rocket_dyn_templates::tera` to avoid version mismatches.
#[cfg(feature = "tera")]
pub tera: Tera,
/// The Handlebars templating engine. This field is only available when the
/// `handlebars_templates` feature is enabled. When calling methods on the
/// `Tera` instance, ensure you use types imported from
/// `rocket_contrib::templates::handlebars` to avoid version mismatches.
#[cfg(feature = "handlebars_templates")]
/// `rocket_dyn_templates::handlebars` to avoid version mismatches.
#[cfg(feature = "handlebars")]
pub handlebars: Handlebars<'static>,
}
impl Engines {
pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
#[cfg(feature = "tera_templates")] Tera::EXT,
#[cfg(feature = "handlebars_templates")] Handlebars::EXT,
#[cfg(feature = "tera")] Tera::EXT,
#[cfg(feature = "handlebars")] Handlebars::EXT,
];
pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
@ -83,12 +83,12 @@ impl Engines {
}
Some(Engines {
#[cfg(feature = "tera_templates")]
#[cfg(feature = "tera")]
tera: match inner::<Tera>(templates) {
Some(tera) => tera,
None => return None
},
#[cfg(feature = "handlebars_templates")]
#[cfg(feature = "handlebars")]
handlebars: match inner::<Handlebars<'static>>(templates) {
Some(hb) => hb,
None => return None
@ -102,13 +102,13 @@ impl Engines {
info: &TemplateInfo,
context: C
) -> Option<String> {
#[cfg(feature = "tera_templates")] {
#[cfg(feature = "tera")] {
if info.engine_ext == Tera::EXT {
return Engine::render(&self.tera, name, context);
}
}
#[cfg(feature = "handlebars_templates")] {
#[cfg(feature = "handlebars")] {
if info.engine_ext == Handlebars::EXT {
return Engine::render(&self.handlebars, name, context);
}
@ -119,20 +119,24 @@ impl Engines {
/// Returns iterator over template (name, engine_extension).
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()
.map(|name| (name, Tera::EXT))
.chain(self.handlebars.get_templates().keys()
.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))
}
#[cfg(all(feature = "handlebars_templates", not(feature = "tera_templates")))] {
#[cfg(all(feature = "handlebars", not(feature = "tera")))] {
self.handlebars.get_templates().keys()
.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::templates::context::{Callback, ContextManager};
use crate::{DEFAULT_TEMPLATE_DIR, Context, Engines};
use crate::context::{Callback, ContextManager};
use rocket::{Rocket, Build, Orbit};
use rocket::fairing::{self, Fairing, Info, Kind};

View File

@ -1,9 +1,9 @@
use std::path::Path;
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> {
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
//! engine(s) of choice:
//! 1. Enable the `rocket_dyn_templates` feature corresponding to your
//! templating engine(s) of choice:
//!
//! ```toml
//! [dependencies.rocket_contrib]
//! version = "0.5.0-dev"
//! [dependencies.rocket_dyn_templates]
//! version = "0.1.0-dev"
//! default-features = false
//! features = ["handlebars_templates", "tera_templates"]
//! features = ["handlebars", "tera"]
//! ```
//!
//! 1. Write your template files in Handlebars (extension: `.hbs`) and/or tera
//! (extensions: `.tera`) in the templates directory (default:
//! 1. Write your template files in Handlebars (`.hbs`) and/or Tera (`.tera`)
//! in the configurable `template_dir` directory (default:
//! `{rocket_root}/templates`).
//!
//! 2. Attach the template fairing, [`Template::fairing()`]:
//!
//! ```rust
//! # 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.
//! 2. Attach `Template::fairing()` return a `Template` using
//! `Template::render()`, supplying the name of the template file **minus
//! the last two extensions**:
//!
//! ```rust
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # fn context() { }
//! use rocket_contrib::templates::Template;
//! use rocket_dyn_templates::Template;
//!
//! #[launch]
//! fn rocket() -> _ {
//! rocket::build().attach(Template::fairing())
//! }
//!
//! #[get("/")]
//! fn index() -> Template {
//! let context = context();
//! # let context = ();
//! Template::render("template-name", &context)
//! }
//! ```
@ -57,7 +49,8 @@
//! [Discovery](#discovery) for more.
//!
//! 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
//!
@ -66,7 +59,7 @@
//! extension or the extension is unknown. For example, for a discovered
//! 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
//! [`ContentType::HTML`].
//! `ContentType::HTML`.
//!
//! ## Discovery
//!
@ -75,8 +68,8 @@
//! template directory is configured via the `template_dir` configuration
//! parameter and defaults to `templates/`. The path set in `template_dir` is
//! relative to the Rocket configuration file. See the [configuration
//! chapter](https://rocket.rs/master/guide/configuration/#extras) of the guide
//! for more information on configuration.
//! chapter](https://rocket.rs/master/guide/configuration) of the guide for more
//! information on configuration.
//!
//! The corresponding templating engine used for a given template is based on a
//! 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
//! rendered with the corresponding templating engine. The _name_ of the
//! 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.tera | index |
@ -109,32 +103,58 @@
//! type, and one for the template extension. This means that template
//! 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
//! created via [`Template::fairing()`], [`Template::custom()`], or
//! [`Template::try_custom()`], the latter two allowing customizations to
//! enabled templating engines. 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 a run-time error.
//! templating engines such as registering template helpers and register
//! templates from strings.
//!
//! 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
//! 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
//! 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
//! the templates directory since the previous request. In release builds,
//! template reloading is disabled to improve performance and cannot be enabled.
//!
//! [`Serialize`]: serde::Serialize
#[cfg(feature = "tera_templates")] pub extern crate tera;
#[cfg(feature = "tera_templates")] mod tera_templates;
#![doc(html_root_url = "https://api.rocket.rs/master/rocket_dyn_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;
#[cfg(feature = "handlebars_templates")] mod handlebars_templates;
#[macro_use] extern crate rocket;
#[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 fairing;
@ -144,7 +164,6 @@ mod metadata;
pub use self::engine::Engines;
pub use self::metadata::Metadata;
use self::engine::Engine;
use self::fairing::TemplateFairing;
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
/// response time. To render a template greedily, use [`Template::show()`].
///
/// # Usage
///
/// 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.
/// See the [crate root](crate) for usage details.
#[derive(Debug)]
pub struct Template {
name: Cow<'static, str>,
@ -254,9 +224,9 @@ impl Template {
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
/// extern crate rocket_dyn_templates;
///
/// use rocket_contrib::templates::Template;
/// use rocket_dyn_templates::Template;
///
/// fn main() {
/// rocket::build()
@ -283,9 +253,9 @@ impl Template {
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
/// extern crate rocket_dyn_templates;
///
/// use rocket_contrib::templates::Template;
/// use rocket_dyn_templates::Template;
///
/// fn main() {
/// rocket::build()
@ -314,9 +284,9 @@ impl Template {
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
/// extern crate rocket_dyn_templates;
///
/// use rocket_contrib::templates::Template;
/// use rocket_dyn_templates::Template;
///
/// fn main() {
/// rocket::build()
@ -343,7 +313,7 @@ impl Template {
///
/// ```rust
/// use std::collections::HashMap;
/// use rocket_contrib::templates::Template;
/// use rocket_dyn_templates::Template;
///
/// // Create a `context`. Here, just an empty `HashMap`.
/// let mut context = HashMap::new();
@ -376,10 +346,10 @@ impl Template {
///
/// ```rust,no_run
/// # extern crate rocket;
/// # extern crate rocket_contrib;
/// # extern crate rocket_dyn_templates;
/// use std::collections::HashMap;
///
/// use rocket_contrib::templates::Template;
/// use rocket_dyn_templates::Template;
/// use rocket::local::blocking::Client;
///
/// fn main() {

View File

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

View File

@ -2,9 +2,10 @@ use std::path::Path;
use std::error::Error;
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 {
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();
// A few useful paths.
let databases = quote_spanned!(span => ::rocket_contrib::databases);
let request = quote!(::rocket::request);
let root = quote_spanned!(span => ::rocket_sync_db_pools);
let rocket = quote!(#root::rocket);
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 conn = quote_spanned!(span => #databases::Connection<Self, #conn_type>);
let pool = quote_spanned!(span => #root::ConnectionPool<Self, #conn_type>);
let conn = quote_spanned!(span => #root::Connection<Self, #conn_type>);
Ok(quote! {
#request_guard_type
@ -84,15 +84,15 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
impl #guard_type {
/// Returns a fairing that initializes the associated database
/// connection pool.
pub fn fairing() -> impl ::rocket::fairing::Fairing {
pub fn fairing() -> impl #rocket::fairing::Fairing {
<#pool>::fairing(#fairing_name, #name)
}
/// Retrieves a connection of type `Self` from the `rocket`
/// instance. Returns `Some` as long as `Self::fairing()` has been
/// attached.
pub async fn get_one<P>(__rocket: &::rocket::Rocket<P>) -> Option<Self>
where P: ::rocket::Phase,
pub async fn get_one<P>(__rocket: &#rocket::Rocket<P>) -> Option<Self>
where P: #rocket::Phase,
{
<#pool>::get_one(&__rocket).await.map(Self)
}
@ -110,17 +110,19 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
}
}
#[::rocket::async_trait]
impl<'r> #request::FromRequest<'r> for #guard_type {
#[#rocket::async_trait]
impl<'r> #rocket::request::FromRequest<'r> for #guard_type {
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)
}
}
impl ::rocket::Sentinel for #guard_type {
fn abort(__r: &::rocket::Rocket<::rocket::Ignite>) -> bool {
impl #rocket::Sentinel for #guard_type {
fn abort(__r: &#rocket::Rocket<#rocket::Ignite>) -> bool {
<#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
--> $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`
|
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
| 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
--> $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>`
|
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
| 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
--> $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`
|
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
| 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
--> $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>`
|
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
| 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)]
use rocket_contrib::databases::diesel;
use rocket_sync_db_pools::diesel;
#[database]
struct A(diesel::SqliteConnection);

View File

@ -1,5 +1,4 @@
extern crate rocket;
#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate rocket_sync_db_pools;
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:
///
/// ```rust
/// # use rocket_contrib::databases::Config;
/// # use rocket_sync_db_pools::Config;
/// Config {
/// url: "postgres://root:root@localhost/my_database".into(),
/// pool_size: 10,
@ -60,7 +60,7 @@ impl Config {
/// # "#).nested();
///
/// use rocket::{Rocket, Build};
/// use rocket_contrib::databases::Config;
/// use rocket_sync_db_pools::Config;
///
/// fn pool(rocket: &Rocket<Build>) {
/// let config = Config::from("my_db", rocket).unwrap();
@ -93,7 +93,7 @@ impl Config {
///
/// ```rust
/// use rocket::{Rocket, Build};
/// use rocket_contrib::databases::Config;
/// use rocket_sync_db_pools::Config;
///
/// fn pool(rocket: &Rocket<Build>) {
/// 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::time::timeout;
use crate::databases::{Config, Poolable, Error};
use crate::{Config, Poolable, Error};
/// 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();
error!("requesting `{}` DB connection without attaching `{}`.", conn, fairing);
info_!("Attach `{}` to use database connection pooling.", fairing);
info_!("See the `contrib::database` documentation for more information.");
return true;
}

View File

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

View File

@ -2,17 +2,15 @@
//!
//! # Overview
//!
//! This module provides traits, utilities, and a procedural macro that allows
//! you to easily connect your Rocket application to databases through
//! connection pools. A _database connection pool_ is a data structure that
//! maintains active database connections for later use in the application.
//! This implementation of connection pooling support is based on
//! [`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.
//! This crate provides traits, utilities, and a procedural macro for
//! configuring and accessing database connection pools in Rocket. A _database
//! connection pool_ is a data structure that maintains active database
//! connections for later use in the application. This implementation is backed
//! by [`r2d2`] and exposes connections through request guards.
//!
//! Connecting your Rocket application to a database using this library occurs
//! in three simple steps:
//! Databases are individually configured through Rocket's regular configuration
//! mechanisms. Connecting a Rocket application to a database using this library
//! occurs in three simple steps:
//!
//! 1. Configure your databases in `Rocket.toml`.
//! (see [Configuration](#configuration))
@ -28,11 +26,11 @@
//! ## Example
//!
//! 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
//! [dependencies.rocket_contrib]
//! version = "0.5.0-dev"
//! [dependencies.rocket_sync_db_pools]
//! version = "0.1.0-dev"
//! default-features = false
//! features = ["diesel_sqlite_pool"]
//! ```
@ -45,19 +43,17 @@
//! in a TOML source:
//!
//! ```toml
//! [global.databases]
//! [default.databases]
//! sqlite_logs = { url = "/path/to/database.sqlite" }
//! ```
//!
//! In your application's source code, one-time:
//!
//! ```rust
//! #[macro_use] extern crate rocket;
//! #[macro_use] extern crate rocket_contrib;
//!
//! # #[macro_use] extern crate rocket;
//! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test {
//! use rocket_contrib::databases::diesel;
//! use rocket_sync_db_pools::{database, diesel};
//!
//! #[database("sqlite_logs")]
//! struct LogsDbConn(diesel::SqliteConnection);
@ -73,11 +69,11 @@
//!
//! ```rust
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[macro_use] extern crate rocket_sync_db_pools;
//! #
//! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test {
//! # use rocket_contrib::databases::diesel;
//! # use rocket_sync_db_pools::diesel;
//! #
//! # #[database("sqlite_logs")]
//! # struct LogsDbConn(diesel::SqliteConnection);
@ -142,9 +138,10 @@
//!
//! ```rust
//! # #[cfg(feature = "diesel_sqlite_pool")] {
//! # use rocket::launch;
//! use rocket::figment::{value::{Map, Value}, util::map};
//!
//! #[rocket::launch]
//! #[launch]
//! fn rocket() -> _ {
//! let db: Map<_, Value> = map! {
//! "url" => "db.sqlite".into(),
@ -182,7 +179,7 @@
//!
//! 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
//! 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
//! configuration key.
//!
@ -191,7 +188,7 @@
//! retrieves a connection from the database pool or fails with a
//! `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`
//!
@ -207,11 +204,10 @@
//! internal type of the structure must implement [`Poolable`].
//!
//! ```rust
//! # extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[macro_use] extern crate rocket_sync_db_pools;
//! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test {
//! use rocket_contrib::databases::diesel;
//! use rocket_sync_db_pools::diesel;
//!
//! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection);
@ -222,11 +218,10 @@
//! type:
//!
//! ```rust
//! # extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[macro_use] extern crate rocket_sync_db_pools;
//! # #[cfg(feature = "postgres_pool")]
//! # mod test {
//! use rocket_contrib::databases::postgres;
//! use rocket_sync_db_pools::postgres;
//!
//! #[database("my_pg_db")]
//! struct MyPgDatabase(postgres::Client);
@ -239,11 +234,11 @@
//!
//! ```rust
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[macro_use] extern crate rocket_sync_db_pools;
//! #
//! # #[cfg(feature = "diesel_sqlite_pool")] {
//! # use rocket::figment::{value::{Map, Value}, util::map};
//! use rocket_contrib::databases::diesel;
//! use rocket_sync_db_pools::diesel;
//!
//! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection);
@ -266,11 +261,11 @@
//!
//! ```rust
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[macro_use] extern crate rocket_sync_db_pools;
//! #
//! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test {
//! # use rocket_contrib::databases::diesel;
//! # use rocket_sync_db_pools::diesel;
//! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection);
//!
@ -285,11 +280,11 @@
//!
//! ```rust
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # #[macro_use] extern crate rocket_sync_db_pools;
//! #
//! # #[cfg(feature = "diesel_sqlite_pool")]
//! # mod test {
//! # use rocket_contrib::databases::diesel;
//! # use rocket_sync_db_pools::diesel;
//! # type Data = ();
//! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection);
@ -353,25 +348,33 @@
//!
//! [`FromRequest`]: 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(
feature = "diesel_sqlite_pool",
feature = "diesel_postgres_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 extern crate r2d2_postgres;
#[cfg(feature = "postgres_pool")] pub use postgres;
#[cfg(feature = "postgres_pool")] pub use r2d2_postgres;
#[cfg(feature = "sqlite_pool")] pub extern crate rusqlite;
#[cfg(feature = "sqlite_pool")] pub extern crate r2d2_sqlite;
#[cfg(feature = "sqlite_pool")] pub use rusqlite;
#[cfg(feature = "sqlite_pool")] pub use r2d2_sqlite;
#[cfg(feature = "memcache_pool")] pub extern crate memcache;
#[cfg(feature = "memcache_pool")] pub extern crate r2d2_memcache;
#[cfg(feature = "memcache_pool")] pub use memcache;
#[cfg(feature = "memcache_pool")] pub use r2d2_memcache;
pub use r2d2;
mod poolable;
mod config;
@ -382,8 +385,5 @@ pub use self::poolable::{Poolable, PoolResult};
pub use self::config::Config;
pub use self::error::Error;
#[doc(hidden)]
pub use rocket_contrib_codegen::*;
#[doc(hidden)]
pub use rocket_sync_db_pools_codegen::*;
pub use self::connection::*;

View File

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

View File

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

View File

@ -51,10 +51,7 @@ use crate::http::uncased::Uncased;
///
/// # Built-in Limits
///
/// The following table details recognized built-in limits used by Rocket. Note
/// 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`.
/// The following table details recognized built-in limits used by Rocket.
///
/// | Limit Name | Default | Type | Description |
/// |-------------------|---------|--------------|---------------------------------------|

View File

@ -95,7 +95,7 @@ macro_rules! 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",
Xml: XML, "XML", "text/xml",
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
//! `Responder`s `A`, `B`, and `C`. This is normal and encouraged as the type
//! 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 redirect;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
#[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;

View File

@ -3,7 +3,7 @@ use rocket::fairing::AdHoc;
use rocket::serde::{Serialize, Deserialize, json::Json};
use rocket::response::{Debug, status::Created};
use rocket_contrib::databases::rusqlite;
use rocket_sync_db_pools::rusqlite;
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 {
use rocket_contrib::databases::Config;
use rocket_sync_db_pools::Config;
let config = match Config::from("sqlx", &rocket) {
Ok(config) => config,

View File

@ -7,5 +7,8 @@ publish = false
[dependencies]
rocket = { path = "../../core/lib" }
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates"] }
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::{FileServer, relative};
use rocket_contrib::templates::Template;
use rocket_dyn_templates::Template;
#[derive(Debug, FromForm)]
struct Password<'v> {

View File

@ -8,8 +8,7 @@ publish = false
[dependencies]
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
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::serde::Serialize;
use rocket_contrib::templates::{Template, handlebars};
use rocket_dyn_templates::{Template, handlebars};
use self::handlebars::{Handlebars, JsonRender};

View File

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

View File

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

View File

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

View File

@ -14,7 +14,10 @@ diesel_migrations = "1.3"
parking_lot = "0.11"
rand = "0.8"
[dependencies.rocket_contrib]
path = "../../contrib/lib"
default_features = false
features = ["tera_templates", "diesel_sqlite_pool"]
[dependencies.rocket_sync_db_pools]
path = "../../contrib/sync_db_pools/lib/"
features = ["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 diesel;
#[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate rocket_sync_db_pools;
#[cfg(test)]
mod tests;
@ -15,7 +15,7 @@ use rocket::serde::Serialize;
use rocket::form::Form;
use rocket::fs::{FileServer, relative};
use rocket_contrib::templates::Template;
use rocket_dyn_templates::Template;
use crate::task::{Task, Todo};

View File

@ -46,8 +46,6 @@ BENCHMARKS_ROOT=$(relative "benchmarks") || exit $?
CORE_LIB_ROOT=$(relative "core/lib") || exit $?
CORE_CODEGEN_ROOT=$(relative "core/codegen") || 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 $?
# Root of infrastructure directories.
@ -78,8 +76,11 @@ ALL_PROJECT_DIRS=(
"${CORE_HTTP_ROOT}"
"${CORE_CODEGEN_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() {
@ -98,12 +99,11 @@ function print_environment() {
echo " CORE_LIB_ROOT: ${CORE_LIB_ROOT}"
echo " CORE_CODEGEN_ROOT: ${CORE_CODEGEN_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 " EXAMPLES_DIR: ${EXAMPLES_DIR}"
echo " DOC_DIR: ${DOC_DIR}"
echo " ALL_PROJECT_DIRS: ${ALL_PROJECT_DIRS[*]}"
echo " CONTRIB_LIB_DIRS: ${CONTRIB_LIB_DIRS[*]}"
echo " date(): $(future_date)"
}

View File

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

View File

@ -59,9 +59,7 @@ function check_style() {
}
function test_contrib() {
FEATURES=(
tera_templates
handlebars_templates
SYNC_DB_POOLS_FEATURES=(
diesel_postgres_pool
diesel_sqlite_pool
diesel_mysql_pool
@ -70,16 +68,20 @@ function test_contrib() {
memcache_pool
)
echo ":: Building and testing contrib [default]..."
DYN_TEMPLATES_FEATURES=(
tera
handlebars
)
pushd "${CONTRIB_LIB_ROOT}" > /dev/null 2>&1
$CARGO test $@
for feature in "${FEATURES[@]}"; do
echo ":: Building and testing contrib [${feature}]..."
$CARGO test --no-default-features --features "${feature}" $@
for feature in "${SYNC_DB_POOLS_FEATURES[@]}"; do
echo ":: Building and testing sync_db_pools [$feature]..."
$CARGO test -p rocket_sync_db_pools --no-default-features --features $feature $@
done
for feature in "${DYN_TEMPLATES_FEATURES[@]}"; do
echo ":: Building and testing dyn_templates [$feature]..."
$CARGO test -p rocket_dyn_templates --no-default-features --features $feature $@
done
popd > /dev/null 2>&1
}
function test_core() {
@ -108,7 +110,7 @@ function test_examples() {
pushd "${EXAMPLES_DIR}" > /dev/null 2>&1
# 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
# 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=" \
$CARGO test --all $@
popd > /dev/null 2>&1
@ -173,7 +175,7 @@ case $TEST_KIND in
test_contrib $@ & contrib=$!
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 $core ; then failures+=("CORE"); 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_contrib`]: @api/rocket_contrib/
[`FileServer`]: @api/rocket/fs/struct.FileServer.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
Some of Rocket's best features are implemented through responders. You can find
many of these responders in the [`response`] module and [`rocket_contrib`]
library. Among these are:
Some of Rocket's best features are implemented through responders. Among these
are:
* [`Content`] - Used to override the Content-Type of a response.
* [`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
[`Stream`]: @api/rocket/response/struct.Stream.html
[`Flash`]: @api/rocket/response/struct.Flash.html
[`MsgPack`]: @api/rocket_contrib/msgpack/struct.MsgPack.html
[`rocket_contrib`]: @api/rocket_contrib/
[`MsgPack`]: @api/rocket/serde/msgpack/struct.MsgPack.html
[`Template`]: @api/rocket_dyn_templates/struct.Template.html
### Async Streams
@ -381,16 +380,16 @@ The [serialization example] provides further illustration.
## Templates
Rocket includes built-in templating support that works largely through a
[`Template`] responder in `rocket_contrib`. To render a template named "index",
for instance, you might return a value of type `Template` as follows:
Rocket has first-class templating support that works largely through a
[`Template`] responder in the `rocket_dyn_templates` contrib library. To render
a template named "index", for instance, you might return a value of type
`Template` as follows:
```rust
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
use rocket_contrib::templates::Template;
use rocket_dyn_templates::Template;
#[get("/")]
fn index() -> Template {
@ -415,7 +414,7 @@ fairings. To attach the template fairing, simply call
```rust
# #[macro_use] extern crate rocket;
# use rocket_contrib::templates::Template;
use rocket_dyn_templates::Template;
#[launch]
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
templating to implement the same application.
[`Template`]: @api/rocket_contrib/templates/struct.Template.html
[configurable]: ../configuration/#extras
## Typed URIs

View File

@ -234,7 +234,7 @@ three simple steps:
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 |
|----------|-----------------------|-----------|--------------------------------|------------------------|
| 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`:
```toml
[dependencies.rocket_contrib]
version = "0.5.0-dev"
[dependencies.rocket_sync_db_pools]
version = "0.1.0-dev"
default-features = false
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
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
database that you configured in the previous step as the only parameter.
You will need to either add `#[macro_use] extern crate rocket_contrib` to the
crate root or have a `use rocket_contrib::database` in scope, otherwise the
`database` attribute will not be available.
Finally, attach the fairing returned by `YourType::fairing()`, which was
generated by the `#[database]` attribute:
database that you configured in the previous step as the only parameter. You
will need to either add `#[macro_use] extern crate rocket_sync_db_pools` to the
crate root or have a `use rocket_sync_db_pools::database` in scope, otherwise
the `database` attribute will not be available. Finally, attach the fairing
returned by `YourType::fairing()`, which was generated by the `#[database]`
attribute:
```rust
# #[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")]
struct LogsDbConn(diesel::SqliteConnection);
@ -310,10 +309,9 @@ request guard. The database can be accessed by calling the `run` method:
```rust
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
# use rocket_contrib::databases::diesel;
# use rocket_sync_db_pools::{diesel, database};
# #[database("sqlite_logs")]
# struct LogsDbConn(diesel::SqliteConnection);
@ -354,11 +352,11 @@ features by adding them in `Cargo.toml` like so:
postgres = { version = "0.15", features = ["with-chrono"] }
```
For more on Rocket's built-in database support, see the
[`rocket_contrib::databases`] module documentation. For examples of CRUD-like
"blog" JSON APIs backed by a SQLite database driven by each of `sqlx`, `diesel`,
and `rusqlite` with migrations run automatically for the former two drivers and
`contrib` database support use for the latter two drivers, see the [databases
For more on Rocket's sanctioned database support, see the
[`rocket_sync_db_pools`] library documentation. For examples of CRUD-like "blog"
JSON APIs backed by a SQLite database driven by each of `sqlx`, `diesel`, and
`rusqlite` with migrations run automatically for the former two drivers and
Rocket's database support use for the latter two drivers, see the [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
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
[`rocket_contrib::Json`] type, for instance, uses the `limits.json` parameter.
[`rocket_contrib::Json`]: @api/rocket_contrib/json/struct.Json.html
[`Json`](@api/rocket/serde/json/struct.Json.html) type, for instance, uses the
`limits.json` parameter.
### 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
call the `login` handler if parsing succeeds. Other built-in `FromData` types
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).
'''
@ -119,7 +119,7 @@ the standard library types including `&str`, `String`, `File`, `Option`, and
`Result`. Rocket also implements custom responders such as
[Redirect](@api/rocket/response/struct.Redirect.html),
[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
[`Response`](@api/rocket/response/struct.Response.html), if possible.

View File

@ -10,8 +10,15 @@ rocket = { path = "../../core/lib", features = ["secrets"] }
[dev-dependencies]
rocket = { path = "../../core/lib", features = ["secrets", "json"] }
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates", "diesel_sqlite_pool"] }
serde = { version = "1.0", features = ["derive"] }
rand = "0.8"
figment = { version = "0.10", features = ["toml", "env"] }
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"]