mirror of https://github.com/rwf2/Rocket.git
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:
parent
b2519208a7
commit
5a4e66ec43
|
@ -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",
|
||||
]
|
||||
|
|
35
README.md
35
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
|
@ -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())
|
||||
}
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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};
|
|
@ -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";
|
|
@ -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() {
|
|
@ -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) {
|
|
@ -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";
|
|
@ -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<script />\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> <script /> 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");
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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, ¶ms);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<script />\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> <script /> 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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"] }
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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`
|
|
@ -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`
|
|
@ -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);
|
|
@ -1,5 +1,4 @@
|
|||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket_contrib;
|
||||
#[macro_use] extern crate rocket_sync_db_pools;
|
||||
|
||||
struct Unknown;
|
||||
|
|
@ -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
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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`.
|
|
@ -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::*;
|
|
@ -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;
|
|
@ -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);
|
|
@ -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 |
|
||||
/// |-------------------|---------|--------------|---------------------------------------|
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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)"
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ fi
|
|||
# Generate the rustdocs for all of the crates.
|
||||
echo ":::: Generating the docs..."
|
||||
pushd "${PROJECT_ROOT}" > /dev/null 2>&1
|
||||
RUSTDOCFLAGS="-Z unstable-options --crate-version ${DOC_VERSION}" \
|
||||
cargo doc -Zrustdoc-map -p rocket -p rocket_contrib --no-deps --all-features
|
||||
# Set the crate version and fill in missing doc URLs with docs.rs links.
|
||||
RUSTDOCFLAGS="-Zunstable-options --crate-version ${DOC_VERSION}" \
|
||||
cargo doc -p rocket -p rocket_sync_db_pools -p rocket_dyn_templates \
|
||||
-Zrustdoc-map --no-deps --all-features
|
||||
popd > /dev/null 2>&1
|
||||
|
||||
# Blank index, for redirection.
|
||||
|
|
|
@ -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 "${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 "${FEATURES[@]}"; do
|
||||
echo ":: Building and testing contrib [${feature}]..."
|
||||
$CARGO test --no-default-features --features "${feature}" $@
|
||||
done
|
||||
popd > /dev/null 2>&1
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Reference in New Issue