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/lib/",
|
||||||
"core/codegen/",
|
"core/codegen/",
|
||||||
"core/http/",
|
"core/http/",
|
||||||
"contrib/lib",
|
"contrib/sync_db_pools/codegen/",
|
||||||
"contrib/codegen",
|
"contrib/sync_db_pools/lib/",
|
||||||
|
"contrib/dyn_templates/",
|
||||||
"site/tests",
|
"site/tests",
|
||||||
]
|
]
|
||||||
|
|
35
README.md
35
README.md
|
@ -69,29 +69,25 @@ You should see `Hello, world!` by visiting `http://localhost:8000`.
|
||||||
|
|
||||||
## Building and Testing
|
## Building and Testing
|
||||||
|
|
||||||
### Core and Contrib
|
|
||||||
|
|
||||||
The `core` directory contains the three core libraries: `lib`, `codegen`, and
|
The `core` directory contains the three core libraries: `lib`, `codegen`, and
|
||||||
`http`. The `contrib` directory contains officially supported community
|
`http` published as `rocket`, `rocket_codegen` and `rocket_http`, respectively.
|
||||||
contributions and similarly consists of `lib` and `codegen`.
|
The latter two are implementations details and are reexported from `rocket`.
|
||||||
|
|
||||||
Public APIs are exposed via `lib` packages: `core/lib` is distributed as the
|
### Testing
|
||||||
`rocket` crate while `contrib/lib` is distributed as the `rocket_contrib` crate.
|
|
||||||
The remaining crates are implementation details.
|
|
||||||
|
|
||||||
### Library Testing
|
|
||||||
|
|
||||||
Rocket's complete test suite can be run with `./scripts/test.sh` from the root
|
Rocket's complete test suite can be run with `./scripts/test.sh` from the root
|
||||||
of the source tree. The script builds and tests all libraries and examples. It
|
of the source tree. The script builds and tests all libraries and examples in
|
||||||
accepts the following flags:
|
all configurations. It accepts the following flags:
|
||||||
|
|
||||||
* `--contrib`: tests each `contrib` feature individually
|
* `--examples`: tests all examples in `examples/`
|
||||||
* `--core`: tests each `core` feature individually
|
* `--contrib`: tests each `contrib` library and feature individually
|
||||||
* `--release`: runs the testing suite in `release` mode
|
* `--core`: tests each `core/lib` feature individually
|
||||||
|
* `--benchmarks`: runs all benchmarks
|
||||||
|
* `--all`: runs all tests in all configurations
|
||||||
|
|
||||||
Additionally, a `+${toolchain}` flag, where `${toolchain}` is a valid `rustup`
|
Additionally, a `+${toolchain}` flag, where `${toolchain}` is a valid `rustup`
|
||||||
toolchain string, can be passed as the first parameter. The flag is forwarded to
|
toolchain string, can be passed as the first parameter. The flag is forwarded to
|
||||||
`cargo` commands.
|
`cargo` commands. Any other extra parameters are passed directly to `cargo`.
|
||||||
|
|
||||||
To test crates individually, simply run `cargo test --all-features` in the
|
To test crates individually, simply run `cargo test --all-features` in the
|
||||||
crate's directory.
|
crate's directory.
|
||||||
|
@ -99,10 +95,11 @@ crate's directory.
|
||||||
### Codegen Testing
|
### Codegen Testing
|
||||||
|
|
||||||
Code generation diagnostics are tested using [`trybuild`]; tests can be found in
|
Code generation diagnostics are tested using [`trybuild`]; tests can be found in
|
||||||
the `codegen/tests/ui-fail` directory of both `core` and `contrib`. Each test is
|
the `codegen/tests/ui-fail` directories of respective `codegen` crates. Each
|
||||||
symlinked into sibling `ui-fail-stable` and `ui-fail-nightly` directories which
|
test is symlinked into sibling `ui-fail-stable` and `ui-fail-nightly`
|
||||||
contain the expected error output for stable and nightly compilers,
|
directories which contain the expected error output for stable and nightly
|
||||||
respectively.
|
compilers, respectively. To update codegen test UI output, run a codegen test
|
||||||
|
suite with `TRYBUILD=overwrite` and inspect the `diff` of `.std*` files.
|
||||||
|
|
||||||
[`trybuild`]: https://docs.rs/trybuild/1
|
[`trybuild`]: https://docs.rs/trybuild/1
|
||||||
|
|
||||||
|
|
|
@ -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::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use crate::templates::{Engines, TemplateInfo};
|
use crate::{Engines, TemplateInfo};
|
||||||
|
|
||||||
use rocket::http::ContentType;
|
use rocket::http::ContentType;
|
||||||
use normpath::PathExt;
|
use normpath::PathExt;
|
||||||
|
@ -88,7 +88,7 @@ impl Context {
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
mod manager {
|
mod manager {
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use crate::templates::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure
|
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure
|
||||||
/// additionally provides a method to reload the context at runtime.
|
/// additionally provides a method to reload the context at runtime.
|
|
@ -3,10 +3,10 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::templates::TemplateInfo;
|
use crate::TemplateInfo;
|
||||||
|
|
||||||
#[cfg(feature = "tera_templates")] use crate::templates::tera::Tera;
|
#[cfg(feature = "tera")] use crate::tera::Tera;
|
||||||
#[cfg(feature = "handlebars_templates")] use crate::templates::handlebars::Handlebars;
|
#[cfg(feature = "handlebars")] use crate::handlebars::Handlebars;
|
||||||
|
|
||||||
pub(crate) trait Engine: Send + Sync + Sized + 'static {
|
pub(crate) trait Engine: Send + Sync + Sized + 'static {
|
||||||
const EXT: &'static str;
|
const EXT: &'static str;
|
||||||
|
@ -19,17 +19,17 @@ pub(crate) trait Engine: Send + Sync + Sized + 'static {
|
||||||
///
|
///
|
||||||
/// Calling methods on the exposed template engine types may require importing
|
/// Calling methods on the exposed template engine types may require importing
|
||||||
/// types from the respective templating engine library. These types should be
|
/// types from the respective templating engine library. These types should be
|
||||||
/// imported from the reexported crate at the root of `rocket_contrib` to avoid
|
/// imported from the reexported crate at the root of `rocket_dyn_templates` to
|
||||||
/// version mismatches. For instance, when registering a Tera filter, the
|
/// avoid version mismatches. For instance, when registering a Tera filter, the
|
||||||
/// [`tera::Value`] and [`tera::Result`] types are required. Import them from
|
/// [`tera::Value`] and [`tera::Result`] types are required. Import them from
|
||||||
/// `rocket_contrib::templates::tera`. The example below illustrates this:
|
/// `rocket_dyn_templates::tera`. The example below illustrates this:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[cfg(feature = "tera_templates")] {
|
/// # #[cfg(feature = "tera")] {
|
||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
///
|
///
|
||||||
/// use rocket_contrib::templates::{Template, Engines};
|
/// use rocket_dyn_templates::{Template, Engines};
|
||||||
/// use rocket_contrib::templates::tera::{self, Value};
|
/// use rocket_dyn_templates::tera::{self, Value};
|
||||||
///
|
///
|
||||||
/// fn my_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
|
/// fn my_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
|
||||||
/// # /*
|
/// # /*
|
||||||
|
@ -49,27 +49,27 @@ pub(crate) trait Engine: Send + Sync + Sized + 'static {
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`tera::Value`]: crate::templates::tera::Value
|
/// [`tera::Value`]: crate::tera::Value
|
||||||
/// [`tera::Result`]: crate::templates::tera::Result
|
/// [`tera::Result`]: crate::tera::Result
|
||||||
pub struct Engines {
|
pub struct Engines {
|
||||||
/// A `Tera` templating engine. This field is only available when the
|
/// A `Tera` templating engine. This field is only available when the
|
||||||
/// `tera_templates` feature is enabled. When calling methods on the `Tera`
|
/// `tera_templates` feature is enabled. When calling methods on the `Tera`
|
||||||
/// instance, ensure you use types imported from
|
/// instance, ensure you use types imported from
|
||||||
/// `rocket_contrib::templates::tera` to avoid version mismatches.
|
/// `rocket_dyn_templates::tera` to avoid version mismatches.
|
||||||
#[cfg(feature = "tera_templates")]
|
#[cfg(feature = "tera")]
|
||||||
pub tera: Tera,
|
pub tera: Tera,
|
||||||
/// The Handlebars templating engine. This field is only available when the
|
/// The Handlebars templating engine. This field is only available when the
|
||||||
/// `handlebars_templates` feature is enabled. When calling methods on the
|
/// `handlebars_templates` feature is enabled. When calling methods on the
|
||||||
/// `Tera` instance, ensure you use types imported from
|
/// `Tera` instance, ensure you use types imported from
|
||||||
/// `rocket_contrib::templates::handlebars` to avoid version mismatches.
|
/// `rocket_dyn_templates::handlebars` to avoid version mismatches.
|
||||||
#[cfg(feature = "handlebars_templates")]
|
#[cfg(feature = "handlebars")]
|
||||||
pub handlebars: Handlebars<'static>,
|
pub handlebars: Handlebars<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engines {
|
impl Engines {
|
||||||
pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
|
pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
|
||||||
#[cfg(feature = "tera_templates")] Tera::EXT,
|
#[cfg(feature = "tera")] Tera::EXT,
|
||||||
#[cfg(feature = "handlebars_templates")] Handlebars::EXT,
|
#[cfg(feature = "handlebars")] Handlebars::EXT,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
|
pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
|
||||||
|
@ -83,12 +83,12 @@ impl Engines {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Engines {
|
Some(Engines {
|
||||||
#[cfg(feature = "tera_templates")]
|
#[cfg(feature = "tera")]
|
||||||
tera: match inner::<Tera>(templates) {
|
tera: match inner::<Tera>(templates) {
|
||||||
Some(tera) => tera,
|
Some(tera) => tera,
|
||||||
None => return None
|
None => return None
|
||||||
},
|
},
|
||||||
#[cfg(feature = "handlebars_templates")]
|
#[cfg(feature = "handlebars")]
|
||||||
handlebars: match inner::<Handlebars<'static>>(templates) {
|
handlebars: match inner::<Handlebars<'static>>(templates) {
|
||||||
Some(hb) => hb,
|
Some(hb) => hb,
|
||||||
None => return None
|
None => return None
|
||||||
|
@ -102,13 +102,13 @@ impl Engines {
|
||||||
info: &TemplateInfo,
|
info: &TemplateInfo,
|
||||||
context: C
|
context: C
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
#[cfg(feature = "tera_templates")] {
|
#[cfg(feature = "tera")] {
|
||||||
if info.engine_ext == Tera::EXT {
|
if info.engine_ext == Tera::EXT {
|
||||||
return Engine::render(&self.tera, name, context);
|
return Engine::render(&self.tera, name, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "handlebars_templates")] {
|
#[cfg(feature = "handlebars")] {
|
||||||
if info.engine_ext == Handlebars::EXT {
|
if info.engine_ext == Handlebars::EXT {
|
||||||
return Engine::render(&self.handlebars, name, context);
|
return Engine::render(&self.handlebars, name, context);
|
||||||
}
|
}
|
||||||
|
@ -119,20 +119,24 @@ impl Engines {
|
||||||
|
|
||||||
/// Returns iterator over template (name, engine_extension).
|
/// Returns iterator over template (name, engine_extension).
|
||||||
pub(crate) fn templates(&self) -> impl Iterator<Item = (&str, &'static str)> {
|
pub(crate) fn templates(&self) -> impl Iterator<Item = (&str, &'static str)> {
|
||||||
#[cfg(all(feature = "tera_templates", feature = "handlebars_templates"))] {
|
#[cfg(all(feature = "tera", feature = "handlebars"))] {
|
||||||
self.tera.get_template_names()
|
self.tera.get_template_names()
|
||||||
.map(|name| (name, Tera::EXT))
|
.map(|name| (name, Tera::EXT))
|
||||||
.chain(self.handlebars.get_templates().keys()
|
.chain(self.handlebars.get_templates().keys()
|
||||||
.map(|name| (name.as_str(), Handlebars::EXT)))
|
.map(|name| (name.as_str(), Handlebars::EXT)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "tera_templates", not(feature = "handlebars_templates")))] {
|
#[cfg(all(feature = "tera", not(feature = "handlebars")))] {
|
||||||
self.tera.get_template_names().map(|name| (name, Tera::EXT))
|
self.tera.get_template_names().map(|name| (name, Tera::EXT))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "handlebars_templates", not(feature = "tera_templates")))] {
|
#[cfg(all(feature = "handlebars", not(feature = "tera")))] {
|
||||||
self.handlebars.get_templates().keys()
|
self.handlebars.get_templates().keys()
|
||||||
.map(|name| (name.as_str(), Handlebars::EXT))
|
.map(|name| (name.as_str(), Handlebars::EXT))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "tera", feature = "handlebars")))] {
|
||||||
|
None.into_iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::templates::{DEFAULT_TEMPLATE_DIR, Context, Engines};
|
use crate::{DEFAULT_TEMPLATE_DIR, Context, Engines};
|
||||||
use crate::templates::context::{Callback, ContextManager};
|
use crate::context::{Callback, ContextManager};
|
||||||
|
|
||||||
use rocket::{Rocket, Build, Orbit};
|
use rocket::{Rocket, Build, Orbit};
|
||||||
use rocket::fairing::{self, Fairing, Info, Kind};
|
use rocket::fairing::{self, Fairing, Info, Kind};
|
|
@ -1,9 +1,9 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use crate::templates::Engine;
|
|
||||||
|
|
||||||
pub use crate::templates::handlebars::Handlebars;
|
use crate::engine::Engine;
|
||||||
|
pub use crate::handlebars::Handlebars;
|
||||||
|
|
||||||
impl Engine for Handlebars<'static> {
|
impl Engine for Handlebars<'static> {
|
||||||
const EXT: &'static str = "hbs";
|
const EXT: &'static str = "hbs";
|
|
@ -1,50 +1,42 @@
|
||||||
//! Dynamic template engine support for handlebars and tera.
|
//! Dynamic templating engine support for Rocket.
|
||||||
//!
|
//!
|
||||||
//! # Overview
|
//! This crate adds support for dynamic template rendering to Rocket. It
|
||||||
|
//! automatically discovers templates, provides a `Responder` to render
|
||||||
|
//! templates, and automatically reloads templates when compiled in debug mode.
|
||||||
|
//! At present, it supports [Handlebars] and [Tera].
|
||||||
//!
|
//!
|
||||||
//! The general outline for using templates in Rocket is:
|
//! # Usage
|
||||||
//!
|
//!
|
||||||
//! 0. Enable the `rocket_contrib` feature corresponding to your templating
|
//! 1. Enable the `rocket_dyn_templates` feature corresponding to your
|
||||||
//! engine(s) of choice:
|
//! templating engine(s) of choice:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies.rocket_contrib]
|
//! [dependencies.rocket_dyn_templates]
|
||||||
//! version = "0.5.0-dev"
|
//! version = "0.1.0-dev"
|
||||||
//! default-features = false
|
//! default-features = false
|
||||||
//! features = ["handlebars_templates", "tera_templates"]
|
//! features = ["handlebars", "tera"]
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! 1. Write your template files in Handlebars (extension: `.hbs`) and/or tera
|
//! 1. Write your template files in Handlebars (`.hbs`) and/or Tera (`.tera`)
|
||||||
//! (extensions: `.tera`) in the templates directory (default:
|
//! in the configurable `template_dir` directory (default:
|
||||||
//! `{rocket_root}/templates`).
|
//! `{rocket_root}/templates`).
|
||||||
//!
|
//!
|
||||||
//! 2. Attach the template fairing, [`Template::fairing()`]:
|
//! 2. Attach `Template::fairing()` return a `Template` using
|
||||||
//!
|
//! `Template::render()`, supplying the name of the template file **minus
|
||||||
//! ```rust
|
//! the last two extensions**:
|
||||||
//! # extern crate rocket;
|
|
||||||
//! # extern crate rocket_contrib;
|
|
||||||
//! use rocket_contrib::templates::Template;
|
|
||||||
//!
|
|
||||||
//! fn main() {
|
|
||||||
//! rocket::build()
|
|
||||||
//! .attach(Template::fairing())
|
|
||||||
//! // ...
|
|
||||||
//! # ;
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! 3. Return a [`Template`] using [`Template::render()`], supplying the name
|
|
||||||
//! of the template file **minus the last two extensions**, from a handler.
|
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[macro_use] extern crate rocket;
|
//! # #[macro_use] extern crate rocket;
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
//! use rocket_dyn_templates::Template;
|
||||||
//! # fn context() { }
|
//!
|
||||||
//! use rocket_contrib::templates::Template;
|
//! #[launch]
|
||||||
|
//! fn rocket() -> _ {
|
||||||
|
//! rocket::build().attach(Template::fairing())
|
||||||
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[get("/")]
|
//! #[get("/")]
|
||||||
//! fn index() -> Template {
|
//! fn index() -> Template {
|
||||||
//! let context = context();
|
//! # let context = ();
|
||||||
//! Template::render("template-name", &context)
|
//! Template::render("template-name", &context)
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -57,7 +49,8 @@
|
||||||
//! [Discovery](#discovery) for more.
|
//! [Discovery](#discovery) for more.
|
||||||
//!
|
//!
|
||||||
//! Templates that are _not_ discovered by Rocket, such as those registered
|
//! Templates that are _not_ discovered by Rocket, such as those registered
|
||||||
//! directly via [`Template::custom()`], are _not_ renamed.
|
//! directly via [`Template::custom()`], are _not_ renamed. Use the name with
|
||||||
|
//! which the template was orginally registered.
|
||||||
//!
|
//!
|
||||||
//! ## Content Type
|
//! ## Content Type
|
||||||
//!
|
//!
|
||||||
|
@ -66,7 +59,7 @@
|
||||||
//! extension or the extension is unknown. For example, for a discovered
|
//! extension or the extension is unknown. For example, for a discovered
|
||||||
//! template with file name `foo.html.hbs` or a manually registered template
|
//! template with file name `foo.html.hbs` or a manually registered template
|
||||||
//! with name ending in `foo.html`, the `Content-Type` is automatically set to
|
//! with name ending in `foo.html`, the `Content-Type` is automatically set to
|
||||||
//! [`ContentType::HTML`].
|
//! `ContentType::HTML`.
|
||||||
//!
|
//!
|
||||||
//! ## Discovery
|
//! ## Discovery
|
||||||
//!
|
//!
|
||||||
|
@ -75,8 +68,8 @@
|
||||||
//! template directory is configured via the `template_dir` configuration
|
//! template directory is configured via the `template_dir` configuration
|
||||||
//! parameter and defaults to `templates/`. The path set in `template_dir` is
|
//! parameter and defaults to `templates/`. The path set in `template_dir` is
|
||||||
//! relative to the Rocket configuration file. See the [configuration
|
//! relative to the Rocket configuration file. See the [configuration
|
||||||
//! chapter](https://rocket.rs/master/guide/configuration/#extras) of the guide
|
//! chapter](https://rocket.rs/master/guide/configuration) of the guide for more
|
||||||
//! for more information on configuration.
|
//! information on configuration.
|
||||||
//!
|
//!
|
||||||
//! The corresponding templating engine used for a given template is based on a
|
//! The corresponding templating engine used for a given template is based on a
|
||||||
//! template's extension. At present, this library supports the following
|
//! template's extension. At present, this library supports the following
|
||||||
|
@ -93,9 +86,10 @@
|
||||||
//! Any file that ends with one of these extension will be discovered and
|
//! Any file that ends with one of these extension will be discovered and
|
||||||
//! rendered with the corresponding templating engine. The _name_ of the
|
//! rendered with the corresponding templating engine. The _name_ of the
|
||||||
//! template will be the path to the template file relative to `template_dir`
|
//! template will be the path to the template file relative to `template_dir`
|
||||||
//! minus at most two extensions. The following table illustrates this mapping:
|
//! minus at most two extensions. The following table contains examples of this
|
||||||
|
//! mapping:
|
||||||
//!
|
//!
|
||||||
//! | path | name |
|
//! | example template path | template name |
|
||||||
//! |-----------------------------------------------|-----------------------|
|
//! |-----------------------------------------------|-----------------------|
|
||||||
//! | {template_dir}/index.html.hbs | index |
|
//! | {template_dir}/index.html.hbs | index |
|
||||||
//! | {template_dir}/index.tera | index |
|
//! | {template_dir}/index.tera | index |
|
||||||
|
@ -109,32 +103,58 @@
|
||||||
//! type, and one for the template extension. This means that template
|
//! type, and one for the template extension. This means that template
|
||||||
//! extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc.
|
//! extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc.
|
||||||
//!
|
//!
|
||||||
//! ## Template Fairing
|
//! ## Template Fairing and Customization
|
||||||
//!
|
//!
|
||||||
//! Template discovery is actualized by the template fairing, which itself is
|
//! Template discovery is actualized by the template fairing, which itself is
|
||||||
//! created via [`Template::fairing()`], [`Template::custom()`], or
|
//! created via [`Template::fairing()`], [`Template::custom()`], or
|
||||||
//! [`Template::try_custom()`], the latter two allowing customizations to
|
//! [`Template::try_custom()`], the latter two allowing customizations to
|
||||||
//! enabled templating engines. In order for _any_ templates to be rendered, the
|
//! templating engines such as registering template helpers and register
|
||||||
//! template fairing _must_ be [attached](rocket::Rocket::attach()) to the
|
//! templates from strings.
|
||||||
//! running Rocket instance. Failure to do so will result in a run-time error.
|
//!
|
||||||
|
//! In order for _any_ templates to be rendered, the template fairing _must_ be
|
||||||
|
//! [attached](rocket::Rocket::attach()) to the running Rocket instance. Failure
|
||||||
|
//! to do so will result in an ignite-time error.
|
||||||
|
//!
|
||||||
|
//! ## Rendering
|
||||||
//!
|
//!
|
||||||
//! Templates are rendered with the `render` method. The method takes in the
|
//! Templates are rendered with the `render` method. The method takes in the
|
||||||
//! name of a template and a context to render the template with. The context
|
//! name of a template and a context to render the template with. The context
|
||||||
//! can be any type that implements [`Serialize`] from [`serde`] and would
|
//! can be any type that implements [`Serialize`] from [`serde`] and would
|
||||||
//! serialize to an `Object` value.
|
//! serialize to an `Object` value.
|
||||||
//!
|
//!
|
||||||
//! In debug mode (without the `--release` flag passed to `rocket`), templates
|
//! ## Automatic Reloading
|
||||||
|
//!
|
||||||
|
//! In debug mode (without the `--release` flag passed to `cargo`), templates
|
||||||
//! will be automatically reloaded from disk if any changes have been made to
|
//! will be automatically reloaded from disk if any changes have been made to
|
||||||
//! the templates directory since the previous request. In release builds,
|
//! the templates directory since the previous request. In release builds,
|
||||||
//! template reloading is disabled to improve performance and cannot be enabled.
|
//! template reloading is disabled to improve performance and cannot be enabled.
|
||||||
//!
|
//!
|
||||||
//! [`Serialize`]: serde::Serialize
|
//! [`Serialize`]: serde::Serialize
|
||||||
|
|
||||||
#[cfg(feature = "tera_templates")] pub extern crate tera;
|
#![doc(html_root_url = "https://api.rocket.rs/master/rocket_dyn_templates")]
|
||||||
#[cfg(feature = "tera_templates")] mod tera_templates;
|
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
|
||||||
|
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
|
||||||
|
|
||||||
#[cfg(feature = "handlebars_templates")] pub extern crate handlebars;
|
#[macro_use] extern crate rocket;
|
||||||
#[cfg(feature = "handlebars_templates")] mod handlebars_templates;
|
|
||||||
|
#[cfg(not(any(feature = "tera", feature = "handlebars")))]
|
||||||
|
compile_error!("at least one of \"tera\" or \"handlebars\" features must be enabled");
|
||||||
|
|
||||||
|
/// The tera templating engine library, reexported.
|
||||||
|
#[doc(inline)]
|
||||||
|
#[cfg(feature = "tera")]
|
||||||
|
pub use _tera as tera;
|
||||||
|
|
||||||
|
#[cfg(feature = "tera")]
|
||||||
|
mod tera_templates;
|
||||||
|
|
||||||
|
/// The handlebars templating engine library, reexported.
|
||||||
|
#[doc(inline)]
|
||||||
|
#[cfg(feature = "handlebars")]
|
||||||
|
pub use _handlebars as handlebars;
|
||||||
|
|
||||||
|
#[cfg(feature = "handlebars")]
|
||||||
|
mod handlebars_templates;
|
||||||
|
|
||||||
mod engine;
|
mod engine;
|
||||||
mod fairing;
|
mod fairing;
|
||||||
|
@ -144,7 +164,6 @@ mod metadata;
|
||||||
pub use self::engine::Engines;
|
pub use self::engine::Engines;
|
||||||
pub use self::metadata::Metadata;
|
pub use self::metadata::Metadata;
|
||||||
|
|
||||||
use self::engine::Engine;
|
|
||||||
use self::fairing::TemplateFairing;
|
use self::fairing::TemplateFairing;
|
||||||
use self::context::{Context, ContextManager};
|
use self::context::{Context, ContextManager};
|
||||||
|
|
||||||
|
@ -169,56 +188,7 @@ const DEFAULT_TEMPLATE_DIR: &str = "templates";
|
||||||
/// contain the rendered template itself. The template is lazily rendered, at
|
/// contain the rendered template itself. The template is lazily rendered, at
|
||||||
/// response time. To render a template greedily, use [`Template::show()`].
|
/// response time. To render a template greedily, use [`Template::show()`].
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// See the [crate root](crate) for usage details.
|
||||||
///
|
|
||||||
/// To use, add the `handlebars_templates` feature, the `tera_templates`
|
|
||||||
/// feature, or both, to the `rocket_contrib` dependencies section of your
|
|
||||||
/// `Cargo.toml`:
|
|
||||||
///
|
|
||||||
/// ```toml
|
|
||||||
/// [dependencies.rocket_contrib]
|
|
||||||
/// version = "0.5.0-dev"
|
|
||||||
/// default-features = false
|
|
||||||
/// features = ["handlebars_templates", "tera_templates"]
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Then, ensure that the template [`Fairing`] is attached to your Rocket
|
|
||||||
/// application:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// # extern crate rocket_contrib;
|
|
||||||
/// use rocket_contrib::templates::Template;
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// rocket::build()
|
|
||||||
/// .attach(Template::fairing())
|
|
||||||
/// // ...
|
|
||||||
/// # ;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// The `Template` type implements Rocket's [`Responder`] trait, so it can be
|
|
||||||
/// returned from a request handler directly:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
|
||||||
/// # fn context() { }
|
|
||||||
/// use rocket_contrib::templates::Template;
|
|
||||||
///
|
|
||||||
/// #[get("/")]
|
|
||||||
/// fn index() -> Template {
|
|
||||||
/// let context = context();
|
|
||||||
/// Template::render("index", &context)
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Helpers, Filters, and Customization
|
|
||||||
///
|
|
||||||
/// You may use the [`Template::custom()`] method to construct a fairing with
|
|
||||||
/// customized templating engines. Among other things, this method allows you to
|
|
||||||
/// register template helpers and register templates from strings.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Template {
|
pub struct Template {
|
||||||
name: Cow<'static, str>,
|
name: Cow<'static, str>,
|
||||||
|
@ -254,9 +224,9 @@ impl Template {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// extern crate rocket;
|
/// extern crate rocket;
|
||||||
/// extern crate rocket_contrib;
|
/// extern crate rocket_dyn_templates;
|
||||||
///
|
///
|
||||||
/// use rocket_contrib::templates::Template;
|
/// use rocket_dyn_templates::Template;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// rocket::build()
|
/// rocket::build()
|
||||||
|
@ -283,9 +253,9 @@ impl Template {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// extern crate rocket;
|
/// extern crate rocket;
|
||||||
/// extern crate rocket_contrib;
|
/// extern crate rocket_dyn_templates;
|
||||||
///
|
///
|
||||||
/// use rocket_contrib::templates::Template;
|
/// use rocket_dyn_templates::Template;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// rocket::build()
|
/// rocket::build()
|
||||||
|
@ -314,9 +284,9 @@ impl Template {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// extern crate rocket;
|
/// extern crate rocket;
|
||||||
/// extern crate rocket_contrib;
|
/// extern crate rocket_dyn_templates;
|
||||||
///
|
///
|
||||||
/// use rocket_contrib::templates::Template;
|
/// use rocket_dyn_templates::Template;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// rocket::build()
|
/// rocket::build()
|
||||||
|
@ -343,7 +313,7 @@ impl Template {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
/// use rocket_contrib::templates::Template;
|
/// use rocket_dyn_templates::Template;
|
||||||
///
|
///
|
||||||
/// // Create a `context`. Here, just an empty `HashMap`.
|
/// // Create a `context`. Here, just an empty `HashMap`.
|
||||||
/// let mut context = HashMap::new();
|
/// let mut context = HashMap::new();
|
||||||
|
@ -376,10 +346,10 @@ impl Template {
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// # extern crate rocket_contrib;
|
/// # extern crate rocket_dyn_templates;
|
||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
///
|
///
|
||||||
/// use rocket_contrib::templates::Template;
|
/// use rocket_dyn_templates::Template;
|
||||||
/// use rocket::local::blocking::Client;
|
/// use rocket::local::blocking::Client;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
|
@ -2,7 +2,7 @@ use rocket::{Request, Rocket, Ignite, Sentinel};
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::request::{self, FromRequest};
|
use rocket::request::{self, FromRequest};
|
||||||
|
|
||||||
use crate::templates::context::ContextManager;
|
use crate::context::ContextManager;
|
||||||
|
|
||||||
/// Request guard for dynamically querying template metadata.
|
/// Request guard for dynamically querying template metadata.
|
||||||
///
|
///
|
||||||
|
@ -13,8 +13,8 @@ use crate::templates::context::ContextManager;
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # #[macro_use] extern crate rocket_contrib;
|
/// # #[macro_use] extern crate rocket_dyn_templates;
|
||||||
/// use rocket_contrib::templates::{Template, Metadata};
|
/// use rocket_dyn_templates::{Template, Metadata};
|
||||||
///
|
///
|
||||||
/// #[get("/")]
|
/// #[get("/")]
|
||||||
/// fn homepage(metadata: Metadata) -> Template {
|
/// fn homepage(metadata: Metadata) -> Template {
|
||||||
|
@ -45,9 +45,9 @@ impl Metadata<'_> {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket_contrib;
|
/// # extern crate rocket_dyn_templates;
|
||||||
/// #
|
/// #
|
||||||
/// use rocket_contrib::templates::Metadata;
|
/// use rocket_dyn_templates::Metadata;
|
||||||
///
|
///
|
||||||
/// #[get("/")]
|
/// #[get("/")]
|
||||||
/// fn handler(metadata: Metadata) {
|
/// fn handler(metadata: Metadata) {
|
||||||
|
@ -65,9 +65,9 @@ impl Metadata<'_> {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket_contrib;
|
/// # extern crate rocket_dyn_templates;
|
||||||
/// #
|
/// #
|
||||||
/// use rocket_contrib::templates::Metadata;
|
/// use rocket_dyn_templates::Metadata;
|
||||||
///
|
///
|
||||||
/// #[get("/")]
|
/// #[get("/")]
|
||||||
/// fn handler(metadata: Metadata) {
|
/// fn handler(metadata: Metadata) {
|
|
@ -2,9 +2,10 @@ use std::path::Path;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use crate::templates::Engine;
|
|
||||||
|
|
||||||
pub use crate::templates::tera::{Context, Tera};
|
use crate::engine::Engine;
|
||||||
|
|
||||||
|
pub use crate::tera::{Context, Tera};
|
||||||
|
|
||||||
impl Engine for Tera {
|
impl Engine for Tera {
|
||||||
const EXT: &'static str = "tera";
|
const EXT: &'static str = "tera";
|
|
@ -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();
|
let span = conn_type.span().into();
|
||||||
|
|
||||||
// A few useful paths.
|
// A few useful paths.
|
||||||
let databases = quote_spanned!(span => ::rocket_contrib::databases);
|
let root = quote_spanned!(span => ::rocket_sync_db_pools);
|
||||||
let request = quote!(::rocket::request);
|
let rocket = quote!(#root::rocket);
|
||||||
|
|
||||||
let request_guard_type = quote_spanned! { span =>
|
let request_guard_type = quote_spanned! { span =>
|
||||||
#vis struct #guard_type(#databases::Connection<Self, #conn_type>);
|
#vis struct #guard_type(#root::Connection<Self, #conn_type>);
|
||||||
};
|
};
|
||||||
|
|
||||||
let pool = quote_spanned!(span => #databases::ConnectionPool<Self, #conn_type>);
|
let pool = quote_spanned!(span => #root::ConnectionPool<Self, #conn_type>);
|
||||||
let conn = quote_spanned!(span => #databases::Connection<Self, #conn_type>);
|
let conn = quote_spanned!(span => #root::Connection<Self, #conn_type>);
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#request_guard_type
|
#request_guard_type
|
||||||
|
@ -84,15 +84,15 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
||||||
impl #guard_type {
|
impl #guard_type {
|
||||||
/// Returns a fairing that initializes the associated database
|
/// Returns a fairing that initializes the associated database
|
||||||
/// connection pool.
|
/// connection pool.
|
||||||
pub fn fairing() -> impl ::rocket::fairing::Fairing {
|
pub fn fairing() -> impl #rocket::fairing::Fairing {
|
||||||
<#pool>::fairing(#fairing_name, #name)
|
<#pool>::fairing(#fairing_name, #name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a connection of type `Self` from the `rocket`
|
/// Retrieves a connection of type `Self` from the `rocket`
|
||||||
/// instance. Returns `Some` as long as `Self::fairing()` has been
|
/// instance. Returns `Some` as long as `Self::fairing()` has been
|
||||||
/// attached.
|
/// attached.
|
||||||
pub async fn get_one<P>(__rocket: &::rocket::Rocket<P>) -> Option<Self>
|
pub async fn get_one<P>(__rocket: &#rocket::Rocket<P>) -> Option<Self>
|
||||||
where P: ::rocket::Phase,
|
where P: #rocket::Phase,
|
||||||
{
|
{
|
||||||
<#pool>::get_one(&__rocket).await.map(Self)
|
<#pool>::get_one(&__rocket).await.map(Self)
|
||||||
}
|
}
|
||||||
|
@ -110,17 +110,19 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[::rocket::async_trait]
|
#[#rocket::async_trait]
|
||||||
impl<'r> #request::FromRequest<'r> for #guard_type {
|
impl<'r> #rocket::request::FromRequest<'r> for #guard_type {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
async fn from_request(__r: &'r #request::Request<'_>) -> #request::Outcome<Self, ()> {
|
async fn from_request(
|
||||||
|
__r: &'r #rocket::request::Request<'_>
|
||||||
|
) -> #rocket::request::Outcome<Self, ()> {
|
||||||
<#conn>::from_request(__r).await.map(Self)
|
<#conn>::from_request(__r).await.map(Self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::rocket::Sentinel for #guard_type {
|
impl #rocket::Sentinel for #guard_type {
|
||||||
fn abort(__r: &::rocket::Rocket<::rocket::Ignite>) -> bool {
|
fn abort(__r: &#rocket::Rocket<#rocket::Ignite>) -> bool {
|
||||||
<#conn>::abort(__r)
|
<#conn>::abort(__r)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
error[E0277]: the trait bound `Unknown: Poolable` is not satisfied
|
||||||
--> $DIR/database-types.rs:7:10
|
--> $DIR/database-types.rs:6:10
|
||||||
|
|
|
|
||||||
7 | struct A(Unknown);
|
6 | struct A(Unknown);
|
||||||
| ^^^^^^^ the trait `Poolable` is not implemented for `Unknown`
|
| ^^^^^^^ the trait `Poolable` is not implemented for `Unknown`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
|
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
||||||
|
|
|
|
||||||
| pub struct Connection<K, C: Poolable> {
|
| pub struct Connection<K, C: Poolable> {
|
||||||
| -------- required by this bound in `rocket_contrib::databases::Connection`
|
| -------- required by this bound in `rocket_sync_db_pools::Connection`
|
||||||
|
|
||||||
error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied
|
error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied
|
||||||
--> $DIR/database-types.rs:10:10
|
--> $DIR/database-types.rs:9:10
|
||||||
|
|
|
|
||||||
10 | struct B(Vec<i32>);
|
9 | struct B(Vec<i32>);
|
||||||
| ^^^^^^^^ the trait `Poolable` is not implemented for `Vec<i32>`
|
| ^^^^^^^^ the trait `Poolable` is not implemented for `Vec<i32>`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
|
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
||||||
|
|
|
|
||||||
| pub struct Connection<K, C: Poolable> {
|
| pub struct Connection<K, C: Poolable> {
|
||||||
| -------- required by this bound in `rocket_contrib::databases::Connection`
|
| -------- required by this bound in `rocket_sync_db_pools::Connection`
|
|
@ -1,21 +1,21 @@
|
||||||
error[E0277]: the trait bound `Unknown: Poolable` is not satisfied
|
error[E0277]: the trait bound `Unknown: Poolable` is not satisfied
|
||||||
--> $DIR/database-types.rs:7:10
|
--> $DIR/database-types.rs:6:10
|
||||||
|
|
|
|
||||||
7 | struct A(Unknown);
|
6 | struct A(Unknown);
|
||||||
| ^^^^^^^ the trait `Poolable` is not implemented for `Unknown`
|
| ^^^^^^^ the trait `Poolable` is not implemented for `Unknown`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
|
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
||||||
|
|
|
|
||||||
| pub struct Connection<K, C: Poolable> {
|
| pub struct Connection<K, C: Poolable> {
|
||||||
| -------- required by this bound in `rocket_contrib::databases::Connection`
|
| -------- required by this bound in `rocket_sync_db_pools::Connection`
|
||||||
|
|
||||||
error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied
|
error[E0277]: the trait bound `Vec<i32>: Poolable` is not satisfied
|
||||||
--> $DIR/database-types.rs:10:10
|
--> $DIR/database-types.rs:9:10
|
||||||
|
|
|
|
||||||
10 | struct B(Vec<i32>);
|
9 | struct B(Vec<i32>);
|
||||||
| ^^^ the trait `Poolable` is not implemented for `Vec<i32>`
|
| ^^^ the trait `Poolable` is not implemented for `Vec<i32>`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/contrib/lib/src/databases/connection.rs
|
::: $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs
|
||||||
|
|
|
|
||||||
| pub struct Connection<K, C: Poolable> {
|
| pub struct Connection<K, C: Poolable> {
|
||||||
| -------- required by this bound in `rocket_contrib::databases::Connection`
|
| -------- required by this bound in `rocket_sync_db_pools::Connection`
|
|
@ -1,7 +1,7 @@
|
||||||
#[macro_use] extern crate rocket_contrib;
|
use rocket_sync_db_pools::database;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use rocket_contrib::databases::diesel;
|
use rocket_sync_db_pools::diesel;
|
||||||
|
|
||||||
#[database]
|
#[database]
|
||||||
struct A(diesel::SqliteConnection);
|
struct A(diesel::SqliteConnection);
|
|
@ -1,5 +1,4 @@
|
||||||
extern crate rocket;
|
#[macro_use] extern crate rocket_sync_db_pools;
|
||||||
#[macro_use] extern crate rocket_contrib;
|
|
||||||
|
|
||||||
struct Unknown;
|
struct Unknown;
|
||||||
|
|
|
@ -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:
|
/// ...`Config::from("my_database", rocket)` would return the following struct:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rocket_contrib::databases::Config;
|
/// # use rocket_sync_db_pools::Config;
|
||||||
/// Config {
|
/// Config {
|
||||||
/// url: "postgres://root:root@localhost/my_database".into(),
|
/// url: "postgres://root:root@localhost/my_database".into(),
|
||||||
/// pool_size: 10,
|
/// pool_size: 10,
|
||||||
|
@ -60,7 +60,7 @@ impl Config {
|
||||||
/// # "#).nested();
|
/// # "#).nested();
|
||||||
///
|
///
|
||||||
/// use rocket::{Rocket, Build};
|
/// use rocket::{Rocket, Build};
|
||||||
/// use rocket_contrib::databases::Config;
|
/// use rocket_sync_db_pools::Config;
|
||||||
///
|
///
|
||||||
/// fn pool(rocket: &Rocket<Build>) {
|
/// fn pool(rocket: &Rocket<Build>) {
|
||||||
/// let config = Config::from("my_db", rocket).unwrap();
|
/// let config = Config::from("my_db", rocket).unwrap();
|
||||||
|
@ -93,7 +93,7 @@ impl Config {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Rocket, Build};
|
/// use rocket::{Rocket, Build};
|
||||||
/// use rocket_contrib::databases::Config;
|
/// use rocket_sync_db_pools::Config;
|
||||||
///
|
///
|
||||||
/// fn pool(rocket: &Rocket<Build>) {
|
/// fn pool(rocket: &Rocket<Build>) {
|
||||||
/// let my_db_figment = Config::figment("my_db", rocket);
|
/// let my_db_figment = Config::figment("my_db", rocket);
|
|
@ -10,7 +10,7 @@ use rocket::http::Status;
|
||||||
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
|
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
|
||||||
use rocket::tokio::time::timeout;
|
use rocket::tokio::time::timeout;
|
||||||
|
|
||||||
use crate::databases::{Config, Poolable, Error};
|
use crate::{Config, Poolable, Error};
|
||||||
|
|
||||||
/// Unstable internal details of generated code for the #[database] attribute.
|
/// Unstable internal details of generated code for the #[database] attribute.
|
||||||
///
|
///
|
||||||
|
@ -225,7 +225,6 @@ impl<K: 'static, C: Poolable> Sentinel for Connection<K, C> {
|
||||||
let fairing = Paint::default(format!("{}::fairing()", conn)).wrap().bold();
|
let fairing = Paint::default(format!("{}::fairing()", conn)).wrap().bold();
|
||||||
error!("requesting `{}` DB connection without attaching `{}`.", conn, fairing);
|
error!("requesting `{}` DB connection without attaching `{}`.", conn, fairing);
|
||||||
info_!("Attach `{}` to use database connection pooling.", fairing);
|
info_!("Attach `{}` to use database connection pooling.", fairing);
|
||||||
info_!("See the `contrib::database` documentation for more information.");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use rocket::figment;
|
||||||
/// This type is only relevant to implementors of the [`Poolable`] trait. See
|
/// This type is only relevant to implementors of the [`Poolable`] trait. See
|
||||||
/// the [`Poolable`] documentation for more information on how to use this type.
|
/// the [`Poolable`] documentation for more information on how to use this type.
|
||||||
///
|
///
|
||||||
/// [`Poolable`]: crate::databases::Poolable
|
/// [`Poolable`]: crate::Poolable
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error<T> {
|
pub enum Error<T> {
|
||||||
/// A custom error of type `T`.
|
/// A custom error of type `T`.
|
|
@ -2,17 +2,15 @@
|
||||||
//!
|
//!
|
||||||
//! # Overview
|
//! # Overview
|
||||||
//!
|
//!
|
||||||
//! This module provides traits, utilities, and a procedural macro that allows
|
//! This crate provides traits, utilities, and a procedural macro for
|
||||||
//! you to easily connect your Rocket application to databases through
|
//! configuring and accessing database connection pools in Rocket. A _database
|
||||||
//! connection pools. A _database connection pool_ is a data structure that
|
//! connection pool_ is a data structure that maintains active database
|
||||||
//! maintains active database connections for later use in the application.
|
//! connections for later use in the application. This implementation is backed
|
||||||
//! This implementation of connection pooling support is based on
|
//! by [`r2d2`] and exposes connections through request guards.
|
||||||
//! [`r2d2`] and exposes connections through [request guards]. Databases are
|
|
||||||
//! individually configured through Rocket's regular configuration mechanisms: a
|
|
||||||
//! `Rocket.toml` file, environment variables, or procedurally.
|
|
||||||
//!
|
//!
|
||||||
//! Connecting your Rocket application to a database using this library occurs
|
//! Databases are individually configured through Rocket's regular configuration
|
||||||
//! in three simple steps:
|
//! mechanisms. Connecting a Rocket application to a database using this library
|
||||||
|
//! occurs in three simple steps:
|
||||||
//!
|
//!
|
||||||
//! 1. Configure your databases in `Rocket.toml`.
|
//! 1. Configure your databases in `Rocket.toml`.
|
||||||
//! (see [Configuration](#configuration))
|
//! (see [Configuration](#configuration))
|
||||||
|
@ -28,11 +26,11 @@
|
||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! Before using this library, the feature corresponding to your database type
|
//! Before using this library, the feature corresponding to your database type
|
||||||
//! in `rocket_contrib` must be enabled:
|
//! in `rocket_sync_db_pools` must be enabled:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies.rocket_contrib]
|
//! [dependencies.rocket_sync_db_pools]
|
||||||
//! version = "0.5.0-dev"
|
//! version = "0.1.0-dev"
|
||||||
//! default-features = false
|
//! default-features = false
|
||||||
//! features = ["diesel_sqlite_pool"]
|
//! features = ["diesel_sqlite_pool"]
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -45,19 +43,17 @@
|
||||||
//! in a TOML source:
|
//! in a TOML source:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [global.databases]
|
//! [default.databases]
|
||||||
//! sqlite_logs = { url = "/path/to/database.sqlite" }
|
//! sqlite_logs = { url = "/path/to/database.sqlite" }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! In your application's source code, one-time:
|
//! In your application's source code, one-time:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! #[macro_use] extern crate rocket;
|
//! # #[macro_use] extern crate rocket;
|
||||||
//! #[macro_use] extern crate rocket_contrib;
|
|
||||||
//!
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
//! # mod test {
|
//! # mod test {
|
||||||
//! use rocket_contrib::databases::diesel;
|
//! use rocket_sync_db_pools::{database, diesel};
|
||||||
//!
|
//!
|
||||||
//! #[database("sqlite_logs")]
|
//! #[database("sqlite_logs")]
|
||||||
//! struct LogsDbConn(diesel::SqliteConnection);
|
//! struct LogsDbConn(diesel::SqliteConnection);
|
||||||
|
@ -73,11 +69,11 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[macro_use] extern crate rocket;
|
//! # #[macro_use] extern crate rocket;
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
//! # #[macro_use] extern crate rocket_sync_db_pools;
|
||||||
//! #
|
//! #
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
//! # mod test {
|
//! # mod test {
|
||||||
//! # use rocket_contrib::databases::diesel;
|
//! # use rocket_sync_db_pools::diesel;
|
||||||
//! #
|
//! #
|
||||||
//! # #[database("sqlite_logs")]
|
//! # #[database("sqlite_logs")]
|
||||||
//! # struct LogsDbConn(diesel::SqliteConnection);
|
//! # struct LogsDbConn(diesel::SqliteConnection);
|
||||||
|
@ -142,9 +138,10 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
||||||
|
//! # use rocket::launch;
|
||||||
//! use rocket::figment::{value::{Map, Value}, util::map};
|
//! use rocket::figment::{value::{Map, Value}, util::map};
|
||||||
//!
|
//!
|
||||||
//! #[rocket::launch]
|
//! #[launch]
|
||||||
//! fn rocket() -> _ {
|
//! fn rocket() -> _ {
|
||||||
//! let db: Map<_, Value> = map! {
|
//! let db: Map<_, Value> = map! {
|
||||||
//! "url" => "db.sqlite".into(),
|
//! "url" => "db.sqlite".into(),
|
||||||
|
@ -182,7 +179,7 @@
|
||||||
//!
|
//!
|
||||||
//! Once a database has been configured, the `#[database]` attribute can be used
|
//! Once a database has been configured, the `#[database]` attribute can be used
|
||||||
//! to tie a type in your application to a configured database. The database
|
//! to tie a type in your application to a configured database. The database
|
||||||
//! attributes accepts a single string parameter that indicates the name of the
|
//! attribute accepts a single string parameter that indicates the name of the
|
||||||
//! database. This corresponds to the database name set as the database's
|
//! database. This corresponds to the database name set as the database's
|
||||||
//! configuration key.
|
//! configuration key.
|
||||||
//!
|
//!
|
||||||
|
@ -191,7 +188,7 @@
|
||||||
//! retrieves a connection from the database pool or fails with a
|
//! retrieves a connection from the database pool or fails with a
|
||||||
//! `Status::ServiceUnavailable` if connecting to the database times out.
|
//! `Status::ServiceUnavailable` if connecting to the database times out.
|
||||||
//!
|
//!
|
||||||
//! The macro will also generate two inherent methods on the decorated type:
|
//! The macro also generates two inherent methods on the decorated type:
|
||||||
//!
|
//!
|
||||||
//! * `fn fairing() -> impl Fairing`
|
//! * `fn fairing() -> impl Fairing`
|
||||||
//!
|
//!
|
||||||
|
@ -207,11 +204,10 @@
|
||||||
//! internal type of the structure must implement [`Poolable`].
|
//! internal type of the structure must implement [`Poolable`].
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate rocket;
|
//! # #[macro_use] extern crate rocket_sync_db_pools;
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
//! # mod test {
|
//! # mod test {
|
||||||
//! use rocket_contrib::databases::diesel;
|
//! use rocket_sync_db_pools::diesel;
|
||||||
//!
|
//!
|
||||||
//! #[database("my_db")]
|
//! #[database("my_db")]
|
||||||
//! struct MyDatabase(diesel::SqliteConnection);
|
//! struct MyDatabase(diesel::SqliteConnection);
|
||||||
|
@ -222,11 +218,10 @@
|
||||||
//! type:
|
//! type:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate rocket;
|
//! # #[macro_use] extern crate rocket_sync_db_pools;
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! # #[cfg(feature = "postgres_pool")]
|
//! # #[cfg(feature = "postgres_pool")]
|
||||||
//! # mod test {
|
//! # mod test {
|
||||||
//! use rocket_contrib::databases::postgres;
|
//! use rocket_sync_db_pools::postgres;
|
||||||
//!
|
//!
|
||||||
//! #[database("my_pg_db")]
|
//! #[database("my_pg_db")]
|
||||||
//! struct MyPgDatabase(postgres::Client);
|
//! struct MyPgDatabase(postgres::Client);
|
||||||
|
@ -239,11 +234,11 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[macro_use] extern crate rocket;
|
//! # #[macro_use] extern crate rocket;
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
//! # #[macro_use] extern crate rocket_sync_db_pools;
|
||||||
//! #
|
//! #
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
||||||
//! # use rocket::figment::{value::{Map, Value}, util::map};
|
//! # use rocket::figment::{value::{Map, Value}, util::map};
|
||||||
//! use rocket_contrib::databases::diesel;
|
//! use rocket_sync_db_pools::diesel;
|
||||||
//!
|
//!
|
||||||
//! #[database("my_db")]
|
//! #[database("my_db")]
|
||||||
//! struct MyDatabase(diesel::SqliteConnection);
|
//! struct MyDatabase(diesel::SqliteConnection);
|
||||||
|
@ -266,11 +261,11 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[macro_use] extern crate rocket;
|
//! # #[macro_use] extern crate rocket;
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
//! # #[macro_use] extern crate rocket_sync_db_pools;
|
||||||
//! #
|
//! #
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
//! # mod test {
|
//! # mod test {
|
||||||
//! # use rocket_contrib::databases::diesel;
|
//! # use rocket_sync_db_pools::diesel;
|
||||||
//! #[database("my_db")]
|
//! #[database("my_db")]
|
||||||
//! struct MyDatabase(diesel::SqliteConnection);
|
//! struct MyDatabase(diesel::SqliteConnection);
|
||||||
//!
|
//!
|
||||||
|
@ -285,11 +280,11 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[macro_use] extern crate rocket;
|
//! # #[macro_use] extern crate rocket;
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
//! # #[macro_use] extern crate rocket_sync_db_pools;
|
||||||
//! #
|
//! #
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
//! # mod test {
|
//! # mod test {
|
||||||
//! # use rocket_contrib::databases::diesel;
|
//! # use rocket_sync_db_pools::diesel;
|
||||||
//! # type Data = ();
|
//! # type Data = ();
|
||||||
//! #[database("my_db")]
|
//! #[database("my_db")]
|
||||||
//! struct MyDatabase(diesel::SqliteConnection);
|
//! struct MyDatabase(diesel::SqliteConnection);
|
||||||
|
@ -353,25 +348,33 @@
|
||||||
//!
|
//!
|
||||||
//! [`FromRequest`]: rocket::request::FromRequest
|
//! [`FromRequest`]: rocket::request::FromRequest
|
||||||
//! [request guards]: rocket::request::FromRequest
|
//! [request guards]: rocket::request::FromRequest
|
||||||
//! [`Poolable`]: crate::databases::Poolable
|
//! [`Poolable`]: crate::Poolable
|
||||||
|
|
||||||
pub extern crate r2d2;
|
#![doc(html_root_url = "https://api.rocket.rs/master/rocket_sync_db_pools")]
|
||||||
|
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
|
||||||
|
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_use]
|
||||||
|
pub extern crate rocket;
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "diesel_sqlite_pool",
|
feature = "diesel_sqlite_pool",
|
||||||
feature = "diesel_postgres_pool",
|
feature = "diesel_postgres_pool",
|
||||||
feature = "diesel_mysql_pool"
|
feature = "diesel_mysql_pool"
|
||||||
))]
|
))]
|
||||||
pub extern crate diesel;
|
pub use diesel;
|
||||||
|
|
||||||
#[cfg(feature = "postgres_pool")] pub extern crate postgres;
|
#[cfg(feature = "postgres_pool")] pub use postgres;
|
||||||
#[cfg(feature = "postgres_pool")] pub extern crate r2d2_postgres;
|
#[cfg(feature = "postgres_pool")] pub use r2d2_postgres;
|
||||||
|
|
||||||
#[cfg(feature = "sqlite_pool")] pub extern crate rusqlite;
|
#[cfg(feature = "sqlite_pool")] pub use rusqlite;
|
||||||
#[cfg(feature = "sqlite_pool")] pub extern crate r2d2_sqlite;
|
#[cfg(feature = "sqlite_pool")] pub use r2d2_sqlite;
|
||||||
|
|
||||||
#[cfg(feature = "memcache_pool")] pub extern crate memcache;
|
#[cfg(feature = "memcache_pool")] pub use memcache;
|
||||||
#[cfg(feature = "memcache_pool")] pub extern crate r2d2_memcache;
|
#[cfg(feature = "memcache_pool")] pub use r2d2_memcache;
|
||||||
|
|
||||||
|
pub use r2d2;
|
||||||
|
|
||||||
mod poolable;
|
mod poolable;
|
||||||
mod config;
|
mod config;
|
||||||
|
@ -382,8 +385,5 @@ pub use self::poolable::{Poolable, PoolResult};
|
||||||
pub use self::config::Config;
|
pub use self::config::Config;
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
|
|
||||||
#[doc(hidden)]
|
pub use rocket_sync_db_pools_codegen::*;
|
||||||
pub use rocket_contrib_codegen::*;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use self::connection::*;
|
pub use self::connection::*;
|
|
@ -1,7 +1,8 @@
|
||||||
use r2d2::ManageConnection;
|
use r2d2::ManageConnection;
|
||||||
|
|
||||||
use rocket::{Rocket, Build};
|
use rocket::{Rocket, Build};
|
||||||
use crate::databases::{Config, Error};
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use crate::{Config, Error};
|
||||||
|
|
||||||
/// Trait implemented by `r2d2`-based database adapters.
|
/// Trait implemented by `r2d2`-based database adapters.
|
||||||
///
|
///
|
||||||
|
@ -38,7 +39,7 @@ use crate::databases::{Config, Error};
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # mod foo {
|
/// # mod foo {
|
||||||
/// # use std::fmt;
|
/// # use std::fmt;
|
||||||
/// # use rocket_contrib::databases::r2d2;
|
/// # use rocket_sync_db_pools::r2d2;
|
||||||
/// # #[derive(Debug)] pub struct Error;
|
/// # #[derive(Debug)] pub struct Error;
|
||||||
/// # impl std::error::Error for Error { }
|
/// # impl std::error::Error for Error { }
|
||||||
/// # impl fmt::Display for Error {
|
/// # impl fmt::Display for Error {
|
||||||
|
@ -63,7 +64,7 @@ use crate::databases::{Config, Error};
|
||||||
/// # }
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// use rocket::{Rocket, Build};
|
/// use rocket::{Rocket, Build};
|
||||||
/// use rocket_contrib::databases::{r2d2, Error, Config, Poolable, PoolResult};
|
/// use rocket_sync_db_pools::{r2d2, Error, Config, Poolable, PoolResult};
|
||||||
///
|
///
|
||||||
/// impl Poolable for foo::Connection {
|
/// impl Poolable for foo::Connection {
|
||||||
/// type Manager = foo::ConnectionManager;
|
/// type Manager = foo::ConnectionManager;
|
|
@ -1,6 +1,6 @@
|
||||||
#[cfg(all(feature = "diesel_sqlite_pool", feature = "diesel_postgres_pool"))]
|
#[cfg(all(feature = "diesel_sqlite_pool", feature = "diesel_postgres_pool"))]
|
||||||
mod databases_tests {
|
mod databases_tests {
|
||||||
use rocket_contrib::databases::{database, diesel};
|
use rocket_sync_db_pools::{database, diesel};
|
||||||
|
|
||||||
#[database("foo")]
|
#[database("foo")]
|
||||||
struct TempStorage(diesel::SqliteConnection);
|
struct TempStorage(diesel::SqliteConnection);
|
||||||
|
@ -12,8 +12,7 @@ mod databases_tests {
|
||||||
#[cfg(all(feature = "databases", feature = "sqlite_pool"))]
|
#[cfg(all(feature = "databases", feature = "sqlite_pool"))]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod rusqlite_integration_test {
|
mod rusqlite_integration_test {
|
||||||
use rocket_contrib::database;
|
use rocket_sync_db_pools::{rusqlite, database};
|
||||||
use rocket_contrib::databases::rusqlite;
|
|
||||||
|
|
||||||
use rusqlite::types::ToSql;
|
use rusqlite::types::ToSql;
|
||||||
|
|
||||||
|
@ -61,7 +60,7 @@ mod rusqlite_integration_test {
|
||||||
mod sentinel_and_runtime_test {
|
mod sentinel_and_runtime_test {
|
||||||
use rocket::{Rocket, Build};
|
use rocket::{Rocket, Build};
|
||||||
use r2d2::{ManageConnection, Pool};
|
use r2d2::{ManageConnection, Pool};
|
||||||
use rocket_contrib::databases::{database, Poolable, PoolResult};
|
use rocket_sync_db_pools::{database, Poolable, PoolResult};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
struct ContainsRuntime(Runtime);
|
struct ContainsRuntime(Runtime);
|
|
@ -51,10 +51,7 @@ use crate::http::uncased::Uncased;
|
||||||
///
|
///
|
||||||
/// # Built-in Limits
|
/// # Built-in Limits
|
||||||
///
|
///
|
||||||
/// The following table details recognized built-in limits used by Rocket. Note
|
/// The following table details recognized built-in limits used by Rocket.
|
||||||
/// that this table _does not_ include limits for types outside of Rocket's
|
|
||||||
/// core. In particular, this does not include limits applicable to contrib
|
|
||||||
/// types like `json` and `msgpack`.
|
|
||||||
///
|
///
|
||||||
/// | Limit Name | Default | Type | Description |
|
/// | Limit Name | Default | Type | Description |
|
||||||
/// |-------------------|---------|--------------|---------------------------------------|
|
/// |-------------------|---------|--------------|---------------------------------------|
|
||||||
|
|
|
@ -95,7 +95,7 @@ macro_rules! ctrs {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrs! {
|
ctrs! {
|
||||||
// FIXME: Add a note that this is _not_ the `contrib` `Json`.
|
// FIXME: Add a note that this is _not_ `serde::Json`.
|
||||||
Json: JSON, "JSON", "application/json",
|
Json: JSON, "JSON", "application/json",
|
||||||
Xml: XML, "XML", "text/xml",
|
Xml: XML, "XML", "text/xml",
|
||||||
MsgPack: MsgPack, "MessagePack", "application/msgpack",
|
MsgPack: MsgPack, "MessagePack", "application/msgpack",
|
||||||
|
|
|
@ -13,15 +13,6 @@
|
||||||
//! result, you'll often have types of the form `A<B<C>>` consisting of three
|
//! result, you'll often have types of the form `A<B<C>>` consisting of three
|
||||||
//! `Responder`s `A`, `B`, and `C`. This is normal and encouraged as the type
|
//! `Responder`s `A`, `B`, and `C`. This is normal and encouraged as the type
|
||||||
//! names typically illustrate the intended response.
|
//! names typically illustrate the intended response.
|
||||||
//!
|
|
||||||
//! # Contrib
|
|
||||||
//!
|
|
||||||
//! The [`contrib` crate] contains several useful `Responder`s including
|
|
||||||
//! [`Template`] and [`Json`].
|
|
||||||
//!
|
|
||||||
//! [`contrib` crate]: ../../rocket_contrib
|
|
||||||
//! [`Template`]: ../../rocket_contrib/templates/struct.Template.html
|
|
||||||
//! [`Json`]: ../../rocket_contrib/json/struct.Json.html
|
|
||||||
|
|
||||||
mod responder;
|
mod responder;
|
||||||
mod redirect;
|
mod redirect;
|
||||||
|
|
|
@ -8,7 +8,6 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_dyn_templates]
|
||||||
path = "../../contrib/lib"
|
path = "../../contrib/dyn_templates"
|
||||||
default-features = false
|
features = ["handlebars"]
|
||||||
features = ["handlebars_templates"]
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod session;
|
||||||
mod message;
|
mod message;
|
||||||
|
|
||||||
use rocket::response::content::Html;
|
use rocket::response::content::Html;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index() -> Html<&'static str> {
|
fn index() -> Html<&'static str> {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::http::{Cookie, CookieJar};
|
use rocket::http::{Cookie, CookieJar};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! message_uri {
|
macro_rules! message_uri {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use rocket::response::{Redirect, Flash};
|
||||||
use rocket::http::{Cookie, CookieJar};
|
use rocket::http::{Cookie, CookieJar};
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
|
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
struct Login<'r> {
|
struct Login<'r> {
|
||||||
|
|
|
@ -15,7 +15,6 @@ version = "0.5.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "offline", "migrate"]
|
features = ["runtime-tokio-rustls", "sqlite", "macros", "offline", "migrate"]
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_sync_db_pools]
|
||||||
path = "../../contrib/lib"
|
path = "../../contrib/sync_db_pools/lib/"
|
||||||
default-features = false
|
|
||||||
features = ["diesel_sqlite_pool", "sqlite_pool"]
|
features = ["diesel_sqlite_pool", "sqlite_pool"]
|
||||||
|
|
|
@ -3,7 +3,7 @@ use rocket::fairing::AdHoc;
|
||||||
use rocket::response::{Debug, status::Created};
|
use rocket::response::{Debug, status::Created};
|
||||||
use rocket::serde::{Serialize, Deserialize, json::Json};
|
use rocket::serde::{Serialize, Deserialize, json::Json};
|
||||||
|
|
||||||
use rocket_contrib::databases::diesel;
|
use rocket_sync_db_pools::diesel;
|
||||||
|
|
||||||
use self::diesel::prelude::*;
|
use self::diesel::prelude::*;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
#[macro_use] extern crate rocket_contrib;
|
#[macro_use] extern crate rocket_sync_db_pools;
|
||||||
#[macro_use] extern crate diesel_migrations;
|
#[macro_use] extern crate diesel_migrations;
|
||||||
#[macro_use] extern crate diesel;
|
#[macro_use] extern crate diesel;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use rocket::fairing::AdHoc;
|
||||||
use rocket::serde::{Serialize, Deserialize, json::Json};
|
use rocket::serde::{Serialize, Deserialize, json::Json};
|
||||||
use rocket::response::{Debug, status::Created};
|
use rocket::response::{Debug, status::Created};
|
||||||
|
|
||||||
use rocket_contrib::databases::rusqlite;
|
use rocket_sync_db_pools::rusqlite;
|
||||||
|
|
||||||
use self::rusqlite::params;
|
use self::rusqlite::params;
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ async fn destroy(db: &State<Db>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_db(rocket: Rocket<Build>) -> fairing::Result {
|
async fn init_db(rocket: Rocket<Build>) -> fairing::Result {
|
||||||
use rocket_contrib::databases::Config;
|
use rocket_sync_db_pools::Config;
|
||||||
|
|
||||||
let config = match Config::from("sqlx", &rocket) {
|
let config = match Config::from("sqlx", &rocket) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
|
|
|
@ -7,5 +7,8 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib" }
|
rocket = { path = "../../core/lib" }
|
||||||
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates"] }
|
|
||||||
time = "0.2"
|
time = "0.2"
|
||||||
|
|
||||||
|
[dependencies.rocket_dyn_templates]
|
||||||
|
path = "../../contrib/dyn_templates"
|
||||||
|
features = ["tera"]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use rocket::form::{Form, Contextual, FromForm, FromFormField, Context};
|
||||||
use rocket::fs::TempFile;
|
use rocket::fs::TempFile;
|
||||||
use rocket::fs::{FileServer, relative};
|
use rocket::fs::{FileServer, relative};
|
||||||
|
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[derive(Debug, FromForm)]
|
#[derive(Debug, FromForm)]
|
||||||
struct Password<'v> {
|
struct Password<'v> {
|
||||||
|
|
|
@ -8,8 +8,7 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib" }
|
rocket = { path = "../../core/lib" }
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
|
||||||
path = "../../contrib/lib"
|
|
||||||
default-features = false
|
|
||||||
# in your application, you should enable only the template engine(s) used
|
# in your application, you should enable only the template engine(s) used
|
||||||
features = ["tera_templates", "handlebars_templates"]
|
[dependencies.rocket_dyn_templates]
|
||||||
|
path = "../../contrib/dyn_templates"
|
||||||
|
features = ["tera", "handlebars"]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use rocket::Request;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::serde::Serialize;
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
use rocket_contrib::templates::{Template, handlebars};
|
use rocket_dyn_templates::{Template, handlebars};
|
||||||
|
|
||||||
use self::handlebars::{Handlebars, JsonRender};
|
use self::handlebars::{Handlebars, JsonRender};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod tera;
|
||||||
#[cfg(test)] mod tests;
|
#[cfg(test)] mod tests;
|
||||||
|
|
||||||
use rocket::response::content::Html;
|
use rocket::response::content::Html;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index() -> Html<&'static str> {
|
fn index() -> Html<&'static str> {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use rocket::Request;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::serde::Serialize;
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
use rocket_contrib::templates::{Template, tera::Tera};
|
use rocket_dyn_templates::{Template, tera::Tera};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::rocket;
|
||||||
|
|
||||||
use rocket::http::{RawStr, Status, Method::*};
|
use rocket::http::{RawStr, Status, Method::*};
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
fn test_root(kind: &str) {
|
fn test_root(kind: &str) {
|
||||||
// Check that the redirect works.
|
// Check that the redirect works.
|
||||||
|
|
|
@ -14,7 +14,10 @@ diesel_migrations = "1.3"
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.11"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_sync_db_pools]
|
||||||
path = "../../contrib/lib"
|
path = "../../contrib/sync_db_pools/lib/"
|
||||||
default_features = false
|
features = ["diesel_sqlite_pool"]
|
||||||
features = ["tera_templates", "diesel_sqlite_pool"]
|
|
||||||
|
[dependencies.rocket_dyn_templates]
|
||||||
|
path = "../../contrib/dyn_templates"
|
||||||
|
features = ["tera"]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
#[macro_use] extern crate diesel;
|
#[macro_use] extern crate diesel;
|
||||||
#[macro_use] extern crate diesel_migrations;
|
#[macro_use] extern crate diesel_migrations;
|
||||||
#[macro_use] extern crate rocket_contrib;
|
#[macro_use] extern crate rocket_sync_db_pools;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -15,7 +15,7 @@ use rocket::serde::Serialize;
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use rocket::fs::{FileServer, relative};
|
use rocket::fs::{FileServer, relative};
|
||||||
|
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
use crate::task::{Task, Todo};
|
use crate::task::{Task, Todo};
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,6 @@ BENCHMARKS_ROOT=$(relative "benchmarks") || exit $?
|
||||||
CORE_LIB_ROOT=$(relative "core/lib") || exit $?
|
CORE_LIB_ROOT=$(relative "core/lib") || exit $?
|
||||||
CORE_CODEGEN_ROOT=$(relative "core/codegen") || exit $?
|
CORE_CODEGEN_ROOT=$(relative "core/codegen") || exit $?
|
||||||
CORE_HTTP_ROOT=$(relative "core/http") || exit $?
|
CORE_HTTP_ROOT=$(relative "core/http") || exit $?
|
||||||
CONTRIB_LIB_ROOT=$(relative "contrib/lib") || exit $?
|
|
||||||
CONTRIB_CODEGEN_ROOT=$(relative "contrib/codegen") || exit $?
|
|
||||||
GUIDE_TESTS_ROOT=$(relative "site/tests") || exit $?
|
GUIDE_TESTS_ROOT=$(relative "site/tests") || exit $?
|
||||||
|
|
||||||
# Root of infrastructure directories.
|
# Root of infrastructure directories.
|
||||||
|
@ -78,8 +76,11 @@ ALL_PROJECT_DIRS=(
|
||||||
"${CORE_HTTP_ROOT}"
|
"${CORE_HTTP_ROOT}"
|
||||||
"${CORE_CODEGEN_ROOT}"
|
"${CORE_CODEGEN_ROOT}"
|
||||||
"${CORE_LIB_ROOT}"
|
"${CORE_LIB_ROOT}"
|
||||||
"${CONTRIB_CODEGEN_ROOT}"
|
)
|
||||||
"${CONTRIB_LIB_ROOT}"
|
|
||||||
|
CONTRIB_LIB_DIRS=(
|
||||||
|
"${CONTRIB_ROOT}/sync_db_pools"
|
||||||
|
"${CONTRIB_ROOT}/dyn_templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
function print_environment() {
|
function print_environment() {
|
||||||
|
@ -98,12 +99,11 @@ function print_environment() {
|
||||||
echo " CORE_LIB_ROOT: ${CORE_LIB_ROOT}"
|
echo " CORE_LIB_ROOT: ${CORE_LIB_ROOT}"
|
||||||
echo " CORE_CODEGEN_ROOT: ${CORE_CODEGEN_ROOT}"
|
echo " CORE_CODEGEN_ROOT: ${CORE_CODEGEN_ROOT}"
|
||||||
echo " CORE_HTTP_ROOT: ${CORE_HTTP_ROOT}"
|
echo " CORE_HTTP_ROOT: ${CORE_HTTP_ROOT}"
|
||||||
echo " CONTRIB_LIB_ROOT: ${CONTRIB_LIB_ROOT}"
|
|
||||||
echo " CONTRIB_CODEGEN_ROOT: ${CONTRIB_CODEGEN_ROOT}"
|
|
||||||
echo " GUIDE_TESTS_ROOT: ${GUIDE_TESTS_ROOT}"
|
echo " GUIDE_TESTS_ROOT: ${GUIDE_TESTS_ROOT}"
|
||||||
echo " EXAMPLES_DIR: ${EXAMPLES_DIR}"
|
echo " EXAMPLES_DIR: ${EXAMPLES_DIR}"
|
||||||
echo " DOC_DIR: ${DOC_DIR}"
|
echo " DOC_DIR: ${DOC_DIR}"
|
||||||
echo " ALL_PROJECT_DIRS: ${ALL_PROJECT_DIRS[*]}"
|
echo " ALL_PROJECT_DIRS: ${ALL_PROJECT_DIRS[*]}"
|
||||||
|
echo " CONTRIB_LIB_DIRS: ${CONTRIB_LIB_DIRS[*]}"
|
||||||
echo " date(): $(future_date)"
|
echo " date(): $(future_date)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ fi
|
||||||
# Generate the rustdocs for all of the crates.
|
# Generate the rustdocs for all of the crates.
|
||||||
echo ":::: Generating the docs..."
|
echo ":::: Generating the docs..."
|
||||||
pushd "${PROJECT_ROOT}" > /dev/null 2>&1
|
pushd "${PROJECT_ROOT}" > /dev/null 2>&1
|
||||||
RUSTDOCFLAGS="-Z unstable-options --crate-version ${DOC_VERSION}" \
|
# Set the crate version and fill in missing doc URLs with docs.rs links.
|
||||||
cargo doc -Zrustdoc-map -p rocket -p rocket_contrib --no-deps --all-features
|
RUSTDOCFLAGS="-Zunstable-options --crate-version ${DOC_VERSION}" \
|
||||||
|
cargo doc -p rocket -p rocket_sync_db_pools -p rocket_dyn_templates \
|
||||||
|
-Zrustdoc-map --no-deps --all-features
|
||||||
popd > /dev/null 2>&1
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
# Blank index, for redirection.
|
# Blank index, for redirection.
|
||||||
|
|
|
@ -59,9 +59,7 @@ function check_style() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_contrib() {
|
function test_contrib() {
|
||||||
FEATURES=(
|
SYNC_DB_POOLS_FEATURES=(
|
||||||
tera_templates
|
|
||||||
handlebars_templates
|
|
||||||
diesel_postgres_pool
|
diesel_postgres_pool
|
||||||
diesel_sqlite_pool
|
diesel_sqlite_pool
|
||||||
diesel_mysql_pool
|
diesel_mysql_pool
|
||||||
|
@ -70,16 +68,20 @@ function test_contrib() {
|
||||||
memcache_pool
|
memcache_pool
|
||||||
)
|
)
|
||||||
|
|
||||||
echo ":: Building and testing contrib [default]..."
|
DYN_TEMPLATES_FEATURES=(
|
||||||
|
tera
|
||||||
|
handlebars
|
||||||
|
)
|
||||||
|
|
||||||
pushd "${CONTRIB_LIB_ROOT}" > /dev/null 2>&1
|
for feature in "${SYNC_DB_POOLS_FEATURES[@]}"; do
|
||||||
$CARGO test $@
|
echo ":: Building and testing sync_db_pools [$feature]..."
|
||||||
|
$CARGO test -p rocket_sync_db_pools --no-default-features --features $feature $@
|
||||||
|
done
|
||||||
|
|
||||||
for feature in "${FEATURES[@]}"; do
|
for feature in "${DYN_TEMPLATES_FEATURES[@]}"; do
|
||||||
echo ":: Building and testing contrib [${feature}]..."
|
echo ":: Building and testing dyn_templates [$feature]..."
|
||||||
$CARGO test --no-default-features --features "${feature}" $@
|
$CARGO test -p rocket_dyn_templates --no-default-features --features $feature $@
|
||||||
done
|
done
|
||||||
popd > /dev/null 2>&1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_core() {
|
function test_core() {
|
||||||
|
@ -108,7 +110,7 @@ function test_examples() {
|
||||||
pushd "${EXAMPLES_DIR}" > /dev/null 2>&1
|
pushd "${EXAMPLES_DIR}" > /dev/null 2>&1
|
||||||
# Rust compiles Rocket once with the `secrets` feature enabled, so when run
|
# Rust compiles Rocket once with the `secrets` feature enabled, so when run
|
||||||
# in production, we need a secret key or tests will fail needlessly. We
|
# in production, we need a secret key or tests will fail needlessly. We
|
||||||
# ensure in core that secret key failing/not failing works as expected.
|
# test in core that secret key failing/not failing works as expected.
|
||||||
ROCKET_SECRET_KEY="itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" \
|
ROCKET_SECRET_KEY="itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" \
|
||||||
$CARGO test --all $@
|
$CARGO test --all $@
|
||||||
popd > /dev/null 2>&1
|
popd > /dev/null 2>&1
|
||||||
|
@ -173,7 +175,7 @@ case $TEST_KIND in
|
||||||
test_contrib $@ & contrib=$!
|
test_contrib $@ & contrib=$!
|
||||||
|
|
||||||
failures=()
|
failures=()
|
||||||
if ! wait $default ; then failures+=("ROOT WORKSPACE"); fi
|
if ! wait $default ; then failures+=("DEFAULT"); fi
|
||||||
if ! wait $examples ; then failures+=("EXAMPLES"); fi
|
if ! wait $examples ; then failures+=("EXAMPLES"); fi
|
||||||
if ! wait $core ; then failures+=("CORE"); fi
|
if ! wait $core ; then failures+=("CORE"); fi
|
||||||
if ! wait $contrib ; then failures+=("CONTRIB"); fi
|
if ! wait $contrib ; then failures+=("CONTRIB"); fi
|
||||||
|
|
|
@ -158,7 +158,6 @@ async fn files(file: PathBuf) -> Option<NamedFile> {
|
||||||
|
|
||||||
`rocket.mount("/public", FileServer::from("static/"))`
|
`rocket.mount("/public", FileServer::from("static/"))`
|
||||||
|
|
||||||
[`rocket_contrib`]: @api/rocket_contrib/
|
|
||||||
[`FileServer`]: @api/rocket/fs/struct.FileServer.html
|
[`FileServer`]: @api/rocket/fs/struct.FileServer.html
|
||||||
[`FromSegments`]: @api/rocket/request/trait.FromSegments.html
|
[`FromSegments`]: @api/rocket/request/trait.FromSegments.html
|
||||||
|
|
||||||
|
|
|
@ -264,9 +264,8 @@ async fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
|
||||||
|
|
||||||
## Rocket Responders
|
## Rocket Responders
|
||||||
|
|
||||||
Some of Rocket's best features are implemented through responders. You can find
|
Some of Rocket's best features are implemented through responders. Among these
|
||||||
many of these responders in the [`response`] module and [`rocket_contrib`]
|
are:
|
||||||
library. Among these are:
|
|
||||||
|
|
||||||
* [`Content`] - Used to override the Content-Type of a response.
|
* [`Content`] - Used to override the Content-Type of a response.
|
||||||
* [`NamedFile`] - Streams a file to the client; automatically sets the
|
* [`NamedFile`] - Streams a file to the client; automatically sets the
|
||||||
|
@ -286,8 +285,8 @@ library. Among these are:
|
||||||
[`Redirect`]: @api/rocket/response/struct.Redirect.html
|
[`Redirect`]: @api/rocket/response/struct.Redirect.html
|
||||||
[`Stream`]: @api/rocket/response/struct.Stream.html
|
[`Stream`]: @api/rocket/response/struct.Stream.html
|
||||||
[`Flash`]: @api/rocket/response/struct.Flash.html
|
[`Flash`]: @api/rocket/response/struct.Flash.html
|
||||||
[`MsgPack`]: @api/rocket_contrib/msgpack/struct.MsgPack.html
|
[`MsgPack`]: @api/rocket/serde/msgpack/struct.MsgPack.html
|
||||||
[`rocket_contrib`]: @api/rocket_contrib/
|
[`Template`]: @api/rocket_dyn_templates/struct.Template.html
|
||||||
|
|
||||||
### Async Streams
|
### Async Streams
|
||||||
|
|
||||||
|
@ -381,16 +380,16 @@ The [serialization example] provides further illustration.
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|
||||||
Rocket includes built-in templating support that works largely through a
|
Rocket has first-class templating support that works largely through a
|
||||||
[`Template`] responder in `rocket_contrib`. To render a template named "index",
|
[`Template`] responder in the `rocket_dyn_templates` contrib library. To render
|
||||||
for instance, you might return a value of type `Template` as follows:
|
a template named "index", for instance, you might return a value of type
|
||||||
|
`Template` as follows:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
# #[macro_use] extern crate rocket_contrib;
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
|
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index() -> Template {
|
fn index() -> Template {
|
||||||
|
@ -415,7 +414,7 @@ fairings. To attach the template fairing, simply call
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
|
|
||||||
# use rocket_contrib::templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
|
|
||||||
#[launch]
|
#[launch]
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
|
@ -450,7 +449,6 @@ including how to customize a template engine to add custom helpers and filters.
|
||||||
The [templating example](@example/templating) uses both Tera and Handlebars
|
The [templating example](@example/templating) uses both Tera and Handlebars
|
||||||
templating to implement the same application.
|
templating to implement the same application.
|
||||||
|
|
||||||
[`Template`]: @api/rocket_contrib/templates/struct.Template.html
|
|
||||||
[configurable]: ../configuration/#extras
|
[configurable]: ../configuration/#extras
|
||||||
|
|
||||||
## Typed URIs
|
## Typed URIs
|
||||||
|
|
|
@ -234,7 +234,7 @@ three simple steps:
|
||||||
|
|
||||||
Presently, Rocket provides built-in support for the following databases:
|
Presently, Rocket provides built-in support for the following databases:
|
||||||
|
|
||||||
<!-- Note: Keep this table in sync with contrib/lib/src/databases.rs -->
|
<!-- Note: Keep this table in sync with contrib/sync_db_pools/src/lib.rs -->
|
||||||
| Kind | Driver | Version | `Poolable` Type | Feature |
|
| Kind | Driver | Version | `Poolable` Type | Feature |
|
||||||
|----------|-----------------------|-----------|--------------------------------|------------------------|
|
|----------|-----------------------|-----------|--------------------------------|------------------------|
|
||||||
| MySQL | [Diesel] | `1` | [`diesel::MysqlConnection`] | `diesel_mysql_pool` |
|
| MySQL | [Diesel] | `1` | [`diesel::MysqlConnection`] | `diesel_mysql_pool` |
|
||||||
|
@ -266,8 +266,8 @@ identified in the "Feature" column. For instance, for Diesel-based SQLite
|
||||||
databases, you'd write in `Cargo.toml`:
|
databases, you'd write in `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_sync_db_pools]
|
||||||
version = "0.5.0-dev"
|
version = "0.1.0-dev"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["diesel_sqlite_pool"]
|
features = ["diesel_sqlite_pool"]
|
||||||
```
|
```
|
||||||
|
@ -283,18 +283,17 @@ sqlite_logs = { url = "/path/to/database.sqlite" }
|
||||||
In your application's source code, create a unit-like struct with one internal
|
In your application's source code, create a unit-like struct with one internal
|
||||||
type. This type should be the type listed in the "`Poolable` Type" column. Then
|
type. This type should be the type listed in the "`Poolable` Type" column. Then
|
||||||
decorate the type with the `#[database]` attribute, providing the name of the
|
decorate the type with the `#[database]` attribute, providing the name of the
|
||||||
database that you configured in the previous step as the only parameter.
|
database that you configured in the previous step as the only parameter. You
|
||||||
You will need to either add `#[macro_use] extern crate rocket_contrib` to the
|
will need to either add `#[macro_use] extern crate rocket_sync_db_pools` to the
|
||||||
crate root or have a `use rocket_contrib::database` in scope, otherwise the
|
crate root or have a `use rocket_sync_db_pools::database` in scope, otherwise
|
||||||
`database` attribute will not be available.
|
the `database` attribute will not be available. Finally, attach the fairing
|
||||||
Finally, attach the fairing returned by `YourType::fairing()`, which was
|
returned by `YourType::fairing()`, which was generated by the `#[database]`
|
||||||
generated by the `#[database]` attribute:
|
attribute:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
#[macro_use] extern crate rocket_contrib;
|
|
||||||
|
|
||||||
use rocket_contrib::databases::diesel;
|
use rocket_sync_db_pools::{diesel, database};
|
||||||
|
|
||||||
#[database("sqlite_logs")]
|
#[database("sqlite_logs")]
|
||||||
struct LogsDbConn(diesel::SqliteConnection);
|
struct LogsDbConn(diesel::SqliteConnection);
|
||||||
|
@ -310,10 +309,9 @@ request guard. The database can be accessed by calling the `run` method:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
# #[macro_use] extern crate rocket_contrib;
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
|
|
||||||
# use rocket_contrib::databases::diesel;
|
# use rocket_sync_db_pools::{diesel, database};
|
||||||
|
|
||||||
# #[database("sqlite_logs")]
|
# #[database("sqlite_logs")]
|
||||||
# struct LogsDbConn(diesel::SqliteConnection);
|
# struct LogsDbConn(diesel::SqliteConnection);
|
||||||
|
@ -354,11 +352,11 @@ features by adding them in `Cargo.toml` like so:
|
||||||
postgres = { version = "0.15", features = ["with-chrono"] }
|
postgres = { version = "0.15", features = ["with-chrono"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
For more on Rocket's built-in database support, see the
|
For more on Rocket's sanctioned database support, see the
|
||||||
[`rocket_contrib::databases`] module documentation. For examples of CRUD-like
|
[`rocket_sync_db_pools`] library documentation. For examples of CRUD-like "blog"
|
||||||
"blog" JSON APIs backed by a SQLite database driven by each of `sqlx`, `diesel`,
|
JSON APIs backed by a SQLite database driven by each of `sqlx`, `diesel`, and
|
||||||
and `rusqlite` with migrations run automatically for the former two drivers and
|
`rusqlite` with migrations run automatically for the former two drivers and
|
||||||
`contrib` database support use for the latter two drivers, see the [databases
|
Rocket's database support use for the latter two drivers, see the [databases
|
||||||
example](@example/databases).
|
example](@example/databases).
|
||||||
|
|
||||||
[`rocket_contrib::databases`]: @api/rocket_contrib/databases/index.html
|
[`rocket_sync_db_pools`]: @api/rocket_sync_db_pools/index.html
|
||||||
|
|
|
@ -85,9 +85,8 @@ bytes Rocket should accept for that type. Rocket can parse both integers
|
||||||
By default, Rocket specifies a `32 KiB` limit for incoming forms. Since Rocket
|
By default, Rocket specifies a `32 KiB` limit for incoming forms. Since Rocket
|
||||||
requires specifying a read limit whenever data is read, external data guards may
|
requires specifying a read limit whenever data is read, external data guards may
|
||||||
also choose to have a configure limit via the `limits` parameter. The
|
also choose to have a configure limit via the `limits` parameter. The
|
||||||
[`rocket_contrib::Json`] type, for instance, uses the `limits.json` parameter.
|
[`Json`](@api/rocket/serde/json/struct.Json.html) type, for instance, uses the
|
||||||
|
`limits.json` parameter.
|
||||||
[`rocket_contrib::Json`]: @api/rocket_contrib/json/struct.Json.html
|
|
||||||
|
|
||||||
### TLS
|
### TLS
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ is a built-in Rocket type that knows how to parse web forms into structures.
|
||||||
Rocket will automatically attempt to parse the request body into the `Form` and
|
Rocket will automatically attempt to parse the request body into the `Form` and
|
||||||
call the `login` handler if parsing succeeds. Other built-in `FromData` types
|
call the `login` handler if parsing succeeds. Other built-in `FromData` types
|
||||||
include [`Data`](@api/rocket/struct.Data.html),
|
include [`Data`](@api/rocket/struct.Data.html),
|
||||||
[`Json`](@api/rocket_contrib/json/struct.Json.html), and
|
[`Json`](@api/rocket/serde/json/struct.Json.html), and
|
||||||
[`Flash`](@api/rocket/response/struct.Flash.html).
|
[`Flash`](@api/rocket/response/struct.Flash.html).
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ the standard library types including `&str`, `String`, `File`, `Option`, and
|
||||||
`Result`. Rocket also implements custom responders such as
|
`Result`. Rocket also implements custom responders such as
|
||||||
[Redirect](@api/rocket/response/struct.Redirect.html),
|
[Redirect](@api/rocket/response/struct.Redirect.html),
|
||||||
[Flash](@api/rocket/response/struct.Flash.html), and
|
[Flash](@api/rocket/response/struct.Flash.html), and
|
||||||
[Template](@api/rocket_contrib/templates/struct.Template.html).
|
[Template](@api/rocket_dyn_templates/struct.Template.html).
|
||||||
|
|
||||||
The task of a `Responder` is to generate a
|
The task of a `Responder` is to generate a
|
||||||
[`Response`](@api/rocket/response/struct.Response.html), if possible.
|
[`Response`](@api/rocket/response/struct.Response.html), if possible.
|
||||||
|
|
|
@ -10,8 +10,15 @@ rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rocket = { path = "../../core/lib", features = ["secrets", "json"] }
|
rocket = { path = "../../core/lib", features = ["secrets", "json"] }
|
||||||
rocket_contrib = { path = "../../contrib/lib", features = ["tera_templates", "diesel_sqlite_pool"] }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
figment = { version = "0.10", features = ["toml", "env"] }
|
figment = { version = "0.10", features = ["toml", "env"] }
|
||||||
time = "0.2"
|
time = "0.2"
|
||||||
|
|
||||||
|
[dev-dependencies.rocket_dyn_templates]
|
||||||
|
path = "../../contrib/dyn_templates"
|
||||||
|
features = ["tera"]
|
||||||
|
|
||||||
|
[dev-dependencies.rocket_sync_db_pools]
|
||||||
|
path = "../../contrib/sync_db_pools/lib"
|
||||||
|
features = ["diesel_sqlite_pool"]
|
||||||
|
|
Loading…
Reference in New Issue