Repository: rwf2/Rocket Branch: master Commit: 3a54d079aef0 Files: 686 Total size: 3.3 MB Directory structure: gitextract_jjluluj_/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ ├── doc-problem.yml │ │ ├── feature-request.yml │ │ └── suggestion.yml │ └── workflows/ │ ├── ci.yml │ └── trigger.yaml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benchmarks/ │ ├── Cargo.toml │ ├── src/ │ │ ├── bench.rs │ │ └── routing.rs │ └── static/ │ ├── bitwarden_rs.routes │ └── rust-lang.routes ├── contrib/ │ ├── db_pools/ │ │ ├── README.md │ │ ├── codegen/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── database.rs │ │ │ │ └── lib.rs │ │ │ └── tests/ │ │ │ ├── ui-fail/ │ │ │ │ ├── database-syntax.rs │ │ │ │ └── database-types.rs │ │ │ ├── ui-fail-nightly/ │ │ │ │ ├── database-syntax.stderr │ │ │ │ └── database-types.stderr │ │ │ ├── ui-fail-stable/ │ │ │ │ ├── database-syntax.stderr │ │ │ │ └── database-types.stderr │ │ │ └── ui-fail.rs │ │ └── lib/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── config.rs │ │ │ ├── database.rs │ │ │ ├── diesel.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ └── pool.rs │ │ └── tests/ │ │ └── databases.rs │ ├── dyn_templates/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── context.rs │ │ │ ├── engine/ │ │ │ │ ├── handlebars.rs │ │ │ │ ├── minijinja.rs │ │ │ │ ├── mod.rs │ │ │ │ └── tera.rs │ │ │ ├── fairing.rs │ │ │ ├── lib.rs │ │ │ ├── metadata.rs │ │ │ └── template.rs │ │ └── tests/ │ │ ├── templates/ │ │ │ ├── hbs/ │ │ │ │ ├── common/ │ │ │ │ │ ├── footer.html.hbs │ │ │ │ │ └── header.html.hbs │ │ │ │ ├── reload.txt.hbs │ │ │ │ └── test.html.hbs │ │ │ ├── j2/ │ │ │ │ ├── [test]/ │ │ │ │ │ └── html_test.html.j2 │ │ │ │ ├── base.txt.j2 │ │ │ │ ├── html_test.html.j2 │ │ │ │ └── txt_test.txt.j2 │ │ │ └── tera/ │ │ │ ├── [test]/ │ │ │ │ └── html_test.html.tera │ │ │ ├── base.txt.tera │ │ │ ├── html_test.html.tera │ │ │ └── txt_test.txt.tera │ │ └── templates.rs │ ├── sync_db_pools/ │ │ ├── README.md │ │ ├── codegen/ │ │ │ ├── Cargo.toml │ │ │ ├── src/ │ │ │ │ ├── database.rs │ │ │ │ └── lib.rs │ │ │ └── tests/ │ │ │ ├── ui-fail/ │ │ │ │ ├── database-syntax.rs │ │ │ │ └── database-types.rs │ │ │ ├── ui-fail-nightly/ │ │ │ │ ├── database-syntax.stderr │ │ │ │ └── database-types.stderr │ │ │ ├── ui-fail-stable/ │ │ │ │ ├── database-syntax.stderr │ │ │ │ └── database-types.stderr │ │ │ └── ui-fail.rs │ │ └── lib/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── src/ │ │ │ ├── config.rs │ │ │ ├── connection.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ └── poolable.rs │ │ └── tests/ │ │ ├── databases.rs │ │ ├── drop-with-connection.rs │ │ └── shutdown.rs │ └── ws/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── duplex.rs │ ├── lib.rs │ └── websocket.rs ├── core/ │ ├── codegen/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── attribute/ │ │ │ │ ├── async_bound/ │ │ │ │ │ └── mod.rs │ │ │ │ ├── catch/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── parse.rs │ │ │ │ ├── entry/ │ │ │ │ │ ├── launch.rs │ │ │ │ │ ├── main.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── test.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── param/ │ │ │ │ │ ├── guard.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── parse.rs │ │ │ │ ├── route/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── parse.rs │ │ │ │ └── suppress/ │ │ │ │ ├── lint.rs │ │ │ │ └── mod.rs │ │ │ ├── bang/ │ │ │ │ ├── export.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── test_guide.rs │ │ │ │ ├── typed_stream.rs │ │ │ │ ├── uri.rs │ │ │ │ └── uri_parsing.rs │ │ │ ├── derive/ │ │ │ │ ├── form_field.rs │ │ │ │ ├── from_form.rs │ │ │ │ ├── from_form_field.rs │ │ │ │ ├── from_param.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── responder.rs │ │ │ │ └── uri_display.rs │ │ │ ├── exports.rs │ │ │ ├── http_codegen.rs │ │ │ ├── lib.rs │ │ │ ├── name.rs │ │ │ ├── proc_macro_ext.rs │ │ │ └── syn_ext.rs │ │ └── tests/ │ │ ├── async-entry.rs │ │ ├── async-routes.rs │ │ ├── catcher.rs │ │ ├── expansion.rs │ │ ├── from_form.rs │ │ ├── from_form_field.rs │ │ ├── from_param.rs │ │ ├── responder.rs │ │ ├── route-data.rs │ │ ├── route-format.rs │ │ ├── route-ranking.rs │ │ ├── route-raw.rs │ │ ├── route-uniqueness.rs │ │ ├── route.rs │ │ ├── segment-ignore.rs │ │ ├── typed-uris.rs │ │ ├── ui-fail/ │ │ │ ├── async-entry.rs │ │ │ ├── bad-ignored-segments.rs │ │ │ ├── catch.rs │ │ │ ├── catch_type_errors.rs │ │ │ ├── catchers.rs │ │ │ ├── from_form.rs │ │ │ ├── from_form_field.rs │ │ │ ├── from_form_type_errors.rs │ │ │ ├── from_param.rs │ │ │ ├── responder-types.rs │ │ │ ├── responder.rs │ │ │ ├── route-attribute-general-syntax.rs │ │ │ ├── route-path-bad-syntax.rs │ │ │ ├── route-type-errors.rs │ │ │ ├── route-warnings.rs │ │ │ ├── routes.rs │ │ │ ├── synchronize.sh │ │ │ ├── typed-uri-bad-type.rs │ │ │ ├── typed-uris-bad-params.rs │ │ │ ├── typed-uris-invalid-syntax.rs │ │ │ ├── uri_display.rs │ │ │ └── uri_display_type_errors.rs │ │ ├── ui-fail-nightly/ │ │ │ ├── async-entry.stderr │ │ │ ├── bad-ignored-segments.stderr │ │ │ ├── catch.stderr │ │ │ ├── catch_type_errors.stderr │ │ │ ├── catchers.stderr │ │ │ ├── from_form.stderr │ │ │ ├── from_form_field.stderr │ │ │ ├── from_form_type_errors.stderr │ │ │ ├── from_param.stderr │ │ │ ├── responder-types.stderr │ │ │ ├── responder.stderr │ │ │ ├── route-attribute-general-syntax.stderr │ │ │ ├── route-path-bad-syntax.stderr │ │ │ ├── route-type-errors.stderr │ │ │ ├── route-warnings.stderr │ │ │ ├── routes.stderr │ │ │ ├── typed-uri-bad-type.stderr │ │ │ ├── typed-uris-bad-params.stderr │ │ │ ├── typed-uris-invalid-syntax.stderr │ │ │ ├── uri_display.stderr │ │ │ └── uri_display_type_errors.stderr │ │ ├── ui-fail-stable/ │ │ │ ├── async-entry.stderr │ │ │ ├── bad-ignored-segments.stderr │ │ │ ├── catch.stderr │ │ │ ├── catch_type_errors.stderr │ │ │ ├── catchers.stderr │ │ │ ├── from_form.stderr │ │ │ ├── from_form_field.stderr │ │ │ ├── from_form_type_errors.stderr │ │ │ ├── from_param.stderr │ │ │ ├── responder-types.stderr │ │ │ ├── responder.stderr │ │ │ ├── route-attribute-general-syntax.stderr │ │ │ ├── route-path-bad-syntax.stderr │ │ │ ├── route-type-errors.stderr │ │ │ ├── route-warnings.stderr │ │ │ ├── routes.stderr │ │ │ ├── typed-uri-bad-type.stderr │ │ │ ├── typed-uris-bad-params.stderr │ │ │ ├── typed-uris-invalid-syntax.stderr │ │ │ ├── uri_display.stderr │ │ │ └── uri_display_type_errors.stderr │ │ ├── ui-fail.rs │ │ └── uri_display.rs │ ├── http/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── ext.rs │ │ ├── header/ │ │ │ ├── accept.rs │ │ │ ├── content_type.rs │ │ │ ├── header.rs │ │ │ ├── known_media_types.rs │ │ │ ├── media_type.rs │ │ │ ├── mod.rs │ │ │ └── proxy_proto.rs │ │ ├── lib.rs │ │ ├── method.rs │ │ ├── parse/ │ │ │ ├── accept.rs │ │ │ ├── checkers.rs │ │ │ ├── indexed.rs │ │ │ ├── media_type.rs │ │ │ ├── mod.rs │ │ │ └── uri/ │ │ │ ├── error.rs │ │ │ ├── mod.rs │ │ │ ├── parser.rs │ │ │ ├── spec.txt │ │ │ ├── tables.rs │ │ │ └── tests.rs │ │ ├── raw_str.rs │ │ ├── status.rs │ │ └── uri/ │ │ ├── absolute.rs │ │ ├── asterisk.rs │ │ ├── authority.rs │ │ ├── error.rs │ │ ├── fmt/ │ │ │ ├── encoding.rs │ │ │ ├── formatter.rs │ │ │ ├── from_uri_param.rs │ │ │ ├── mod.rs │ │ │ ├── part.rs │ │ │ └── uri_display.rs │ │ ├── host.rs │ │ ├── mod.rs │ │ ├── origin.rs │ │ ├── path_query.rs │ │ ├── reference.rs │ │ ├── segments.rs │ │ └── uri.rs │ └── lib/ │ ├── Cargo.toml │ ├── build.rs │ ├── fuzz/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── corpus/ │ │ │ ├── collision-matching/ │ │ │ │ ├── another.seed │ │ │ │ ├── base.seed │ │ │ │ ├── complex.seed │ │ │ │ └── large.seed │ │ │ └── uri-parsing/ │ │ │ ├── absolute.seed │ │ │ ├── asterisk.seed │ │ │ ├── authority.seed │ │ │ ├── origin.seed │ │ │ └── reference.seed │ │ └── targets/ │ │ ├── collision-matching.rs │ │ ├── uri-normalization.rs │ │ ├── uri-parsing.rs │ │ └── uri-roundtrip.rs │ ├── src/ │ │ ├── catcher/ │ │ │ ├── catcher.rs │ │ │ ├── handler.rs │ │ │ └── mod.rs │ │ ├── config/ │ │ │ ├── cli_colors.rs │ │ │ ├── config.rs │ │ │ ├── http_header.rs │ │ │ ├── ident.rs │ │ │ ├── mod.rs │ │ │ ├── secret_key.rs │ │ │ └── tests.rs │ │ ├── data/ │ │ │ ├── capped.rs │ │ │ ├── data.rs │ │ │ ├── data_stream.rs │ │ │ ├── from_data.rs │ │ │ ├── io_stream.rs │ │ │ ├── limits.rs │ │ │ ├── mod.rs │ │ │ ├── peekable.rs │ │ │ └── transform.rs │ │ ├── erased.rs │ │ ├── error.rs │ │ ├── fairing/ │ │ │ ├── ad_hoc.rs │ │ │ ├── fairings.rs │ │ │ ├── info_kind.rs │ │ │ └── mod.rs │ │ ├── form/ │ │ │ ├── buffer.rs │ │ │ ├── context.rs │ │ │ ├── error.rs │ │ │ ├── field.rs │ │ │ ├── form.rs │ │ │ ├── from_form.rs │ │ │ ├── from_form_field.rs │ │ │ ├── lenient.rs │ │ │ ├── mod.rs │ │ │ ├── name/ │ │ │ │ ├── buf.rs │ │ │ │ ├── key.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── name.rs │ │ │ │ └── view.rs │ │ │ ├── options.rs │ │ │ ├── parser.rs │ │ │ ├── strict.rs │ │ │ ├── tests.rs │ │ │ └── validate.rs │ │ ├── fs/ │ │ │ ├── file_name.rs │ │ │ ├── mod.rs │ │ │ ├── named_file.rs │ │ │ ├── rewrite.rs │ │ │ ├── server.rs │ │ │ └── temp_file.rs │ │ ├── http/ │ │ │ ├── cookies.rs │ │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── lifecycle.rs │ │ ├── listener/ │ │ │ ├── bind.rs │ │ │ ├── bounced.rs │ │ │ ├── cancellable.rs │ │ │ ├── connection.rs │ │ │ ├── default.rs │ │ │ ├── endpoint.rs │ │ │ ├── listener.rs │ │ │ ├── mod.rs │ │ │ ├── quic.rs │ │ │ ├── tcp.rs │ │ │ └── unix.rs │ │ ├── local/ │ │ │ ├── asynchronous/ │ │ │ │ ├── client.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── request.rs │ │ │ │ └── response.rs │ │ │ ├── blocking/ │ │ │ │ ├── client.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── request.rs │ │ │ │ └── response.rs │ │ │ ├── client.rs │ │ │ ├── mod.rs │ │ │ ├── request.rs │ │ │ └── response.rs │ │ ├── mtls/ │ │ │ ├── certificate.rs │ │ │ ├── config.rs │ │ │ ├── error.rs │ │ │ ├── mod.rs │ │ │ └── name.rs │ │ ├── outcome.rs │ │ ├── phase.rs │ │ ├── request/ │ │ │ ├── atomic_method.rs │ │ │ ├── from_param.rs │ │ │ ├── from_request.rs │ │ │ ├── mod.rs │ │ │ ├── request.rs │ │ │ └── tests.rs │ │ ├── response/ │ │ │ ├── body.rs │ │ │ ├── content.rs │ │ │ ├── debug.rs │ │ │ ├── flash.rs │ │ │ ├── mod.rs │ │ │ ├── redirect.rs │ │ │ ├── responder.rs │ │ │ ├── response.rs │ │ │ ├── status.rs │ │ │ └── stream/ │ │ │ ├── bytes.rs │ │ │ ├── mod.rs │ │ │ ├── one.rs │ │ │ ├── raw_sse.rs │ │ │ ├── reader.rs │ │ │ ├── sse.rs │ │ │ └── text.rs │ │ ├── rocket.rs │ │ ├── route/ │ │ │ ├── handler.rs │ │ │ ├── mod.rs │ │ │ ├── route.rs │ │ │ ├── segment.rs │ │ │ └── uri.rs │ │ ├── router/ │ │ │ ├── collider.rs │ │ │ ├── matcher.rs │ │ │ ├── mod.rs │ │ │ └── router.rs │ │ ├── sentinel.rs │ │ ├── serde/ │ │ │ ├── json.rs │ │ │ ├── mod.rs │ │ │ ├── msgpack.rs │ │ │ └── uuid.rs │ │ ├── server.rs │ │ ├── shield/ │ │ │ ├── mod.rs │ │ │ ├── policy.rs │ │ │ └── shield.rs │ │ ├── shutdown/ │ │ │ ├── config.rs │ │ │ ├── handle.rs │ │ │ ├── mod.rs │ │ │ ├── sig.rs │ │ │ └── tripwire.rs │ │ ├── state.rs │ │ ├── tls/ │ │ │ ├── config.rs │ │ │ ├── error.rs │ │ │ ├── listener.rs │ │ │ ├── mod.rs │ │ │ └── resolver.rs │ │ ├── trace/ │ │ │ ├── level.rs │ │ │ ├── macros.rs │ │ │ ├── mod.rs │ │ │ ├── subscriber/ │ │ │ │ ├── common.rs │ │ │ │ ├── compact.rs │ │ │ │ ├── dynamic.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── pretty.rs │ │ │ │ ├── request_id.rs │ │ │ │ └── visit.rs │ │ │ └── traceable.rs │ │ └── util/ │ │ ├── chain.rs │ │ ├── join.rs │ │ ├── mod.rs │ │ ├── reader_stream.rs │ │ └── unix.rs │ └── tests/ │ ├── absolute-uris-okay-issue-443.rs │ ├── adhoc-uri-normalizer.rs │ ├── byte-slices-form-field-issue-2148.rs │ ├── can-correct-bad-local-uri.rs │ ├── can-launch-tls.rs │ ├── catcher-cookies-1213.rs │ ├── conditionally-set-server-header-996.rs │ ├── config-proxy-proto-header.rs │ ├── config-real-ip-header.rs │ ├── config-secret-key-1500.rs │ ├── content-length.rs │ ├── cookies-private.rs │ ├── derive-reexports.rs │ ├── deserialize-limits-issue-2268.rs │ ├── encoded-uris.rs │ ├── fairing_before_head_strip-issue-546.rs │ ├── file_server.rs │ ├── flash-lazy-removes-issue-466.rs │ ├── form-validation-names.rs │ ├── form_method-issue-45.rs │ ├── form_value_decoding-issue-82.rs │ ├── form_value_from_encoded_str-issue-1425.rs │ ├── forward-includes-status-1560.rs │ ├── head_handling.rs │ ├── http_serde.rs │ ├── launch-inspect.rs │ ├── limits.rs │ ├── local-client-access-runtime-in-drop.rs │ ├── local-client-json.rs │ ├── local-request-content-type-issue-505.rs │ ├── local_request_private_cookie-issue-368.rs │ ├── many-cookie-jars-at-once.rs │ ├── mapped-base-issue-1262.rs │ ├── mount_point.rs │ ├── msgpack_encoding.rs │ ├── multipart-limit.rs │ ├── nested-fairing-attaches.rs │ ├── on_launch_fairing_can_inspect_port.rs │ ├── panic-handling.rs │ ├── precise-content-type-matching.rs │ ├── raw-strings-multipart-files-1987.rs │ ├── recursive-singleton-fairing.rs │ ├── redirect_from_catcher-issue-113.rs │ ├── replace-content-type-518.rs │ ├── responder_lifetime-issue-345.rs │ ├── route_guard.rs │ ├── scoped-uri.rs │ ├── segments-issues-41-86.rs │ ├── sentinel.rs │ ├── session-cookies-issue-1506.rs │ ├── shield.rs │ ├── shutdown-fairings.rs │ ├── static/ │ │ ├── .hidden │ │ ├── index.html │ │ ├── inner/ │ │ │ ├── .hideme │ │ │ ├── goodbye │ │ │ └── index.html │ │ └── other/ │ │ ├── hello.txt │ │ └── index.htm │ ├── strict_and_lenient_forms.rs │ ├── timer-on-attach.rs │ ├── tls-config-from-source-1503.rs │ ├── twice_managed_state.rs │ ├── typed-uri-docs-redef-issue-1373.rs │ ├── unsound-local-request-1312.rs │ ├── untracked-vs-tracked.rs │ └── uri-percent-encoding-issue-808.rs ├── docs/ │ ├── LICENSE │ ├── guide/ │ │ ├── 00-introduction.md │ │ ├── 01-upgrading.md │ │ ├── 02-quickstart.md │ │ ├── 03-getting-started.md │ │ ├── 04-overview.md │ │ ├── 05-requests.md │ │ ├── 06-responses.md │ │ ├── 07-state.md │ │ ├── 08-fairings.md │ │ ├── 09-testing.md │ │ ├── 10-configuration.md │ │ ├── 11-deploying.md │ │ ├── 12-pastebin.md │ │ ├── 13-conclusion.md │ │ ├── 14-faq.md │ │ └── index.md │ └── tests/ │ ├── Cargo.toml │ └── src/ │ ├── guide.rs │ ├── lib.rs │ └── readme.rs ├── examples/ │ ├── Cargo.toml │ ├── README.md │ ├── chat/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── main.rs │ │ │ └── tests.rs │ │ └── static/ │ │ ├── index.html │ │ ├── reset.css │ │ ├── script.js │ │ └── style.css │ ├── config/ │ │ ├── Cargo.toml │ │ ├── Rocket.toml │ │ └── src/ │ │ ├── main.rs │ │ └── tests.rs │ ├── cookies/ │ │ ├── Cargo.toml │ │ ├── Rocket.toml │ │ ├── src/ │ │ │ ├── main.rs │ │ │ ├── message.rs │ │ │ ├── session.rs │ │ │ └── tests.rs │ │ └── templates/ │ │ ├── login.html.hbs │ │ ├── message.html.hbs │ │ └── session.html.hbs │ ├── databases/ │ │ ├── .sqlx/ │ │ │ ├── query-11e3096becb72f427c8d3911ef4327afd9516143806981e11f8e34d069c14472.json │ │ │ ├── query-4415c35941e52a981b10707fe2e1ceb0bad0e473701e51ef21ecb2973c76b4df.json │ │ │ ├── query-668690acaca0a0c0b4ac306b14d82aa1bee940f0776fae3f9962639b78328858.json │ │ │ ├── query-79301b44b77802e0096efd73b1e9adac27b27a3cf7bf853af3a9f130b1684d91.json │ │ │ └── query-bea4ef6e25064f6b383e854f8bc2770d89cfaf9859d0bfca78b2ca24627675b7.json │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── Rocket.toml │ │ ├── db/ │ │ │ ├── diesel/ │ │ │ │ ├── migrations/ │ │ │ │ │ ├── .gitkeep │ │ │ │ │ └── 20210329150332_create_posts_table/ │ │ │ │ │ ├── down.sql │ │ │ │ │ └── up.sql │ │ │ │ └── mysql-migrations/ │ │ │ │ └── 20210329150332_create_posts_table/ │ │ │ │ ├── down.sql │ │ │ │ └── up.sql │ │ │ └── sqlx/ │ │ │ └── migrations/ │ │ │ └── 20210331024424_create-posts-table.sql │ │ └── src/ │ │ ├── diesel_mysql.rs │ │ ├── diesel_sqlite.rs │ │ ├── main.rs │ │ ├── rusqlite.rs │ │ ├── sqlx.rs │ │ └── tests.rs │ ├── error-handling/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── tests.rs │ ├── fairings/ │ │ ├── Cargo.toml │ │ ├── Rocket.toml │ │ └── src/ │ │ ├── main.rs │ │ └── tests.rs │ ├── forms/ │ │ ├── Cargo.toml │ │ ├── Rocket.toml │ │ ├── src/ │ │ │ ├── main.rs │ │ │ └── tests.rs │ │ └── templates/ │ │ ├── index.html.tera │ │ ├── macros.html.tera │ │ └── success.html.tera │ ├── hello/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── tests.rs │ ├── manual-routing/ │ │ ├── Cargo.toml │ │ ├── Rocket.toml │ │ └── src/ │ │ ├── main.rs │ │ └── tests.rs │ ├── pastebin/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ ├── paste_id.rs │ │ └── tests.rs │ ├── responders/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ └── tests.rs │ ├── serialization/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── json.rs │ │ ├── main.rs │ │ ├── msgpack.rs │ │ ├── tests.rs │ │ └── uuid.rs │ ├── state/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── main.rs │ │ ├── managed_hit_count.rs │ │ ├── managed_queue.rs │ │ ├── request_local.rs │ │ └── tests.rs │ ├── static-files/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── main.rs │ │ │ └── tests.rs │ │ └── static/ │ │ ├── hidden/ │ │ │ ├── hi.txt │ │ │ └── index.html │ │ └── index.html │ ├── templating/ │ │ ├── Cargo.toml │ │ ├── Rocket.toml │ │ ├── src/ │ │ │ ├── hbs.rs │ │ │ ├── main.rs │ │ │ ├── minijinja.rs │ │ │ ├── tera.rs │ │ │ └── tests.rs │ │ └── templates/ │ │ ├── hbs/ │ │ │ ├── error/ │ │ │ │ └── 404.html.hbs │ │ │ ├── footer.html.hbs │ │ │ ├── index.html.hbs │ │ │ ├── layout.html.hbs │ │ │ └── nav.html.hbs │ │ ├── minijinja/ │ │ │ ├── error/ │ │ │ │ └── 404.html.j2 │ │ │ ├── footer.html.j2 │ │ │ ├── index.html.j2 │ │ │ ├── layout.html.j2 │ │ │ └── nav.html.j2 │ │ └── tera/ │ │ ├── base.html.tera │ │ ├── error/ │ │ │ └── 404.html.tera │ │ ├── index.html.tera │ │ └── nav.html.tera │ ├── testing/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── async_required.rs │ │ └── main.rs │ ├── tls/ │ │ ├── Cargo.toml │ │ ├── Rocket.toml │ │ ├── private/ │ │ │ ├── ca_cert.pem │ │ │ ├── ca_key.pem │ │ │ ├── client.pem │ │ │ ├── ecdsa_nistp256_sha256.p12 │ │ │ ├── ecdsa_nistp256_sha256_cert.pem │ │ │ ├── ecdsa_nistp256_sha256_key_pkcs8.pem │ │ │ ├── ecdsa_nistp256_sha256_key_sec1.pem │ │ │ ├── ecdsa_nistp384_sha384.p12 │ │ │ ├── ecdsa_nistp384_sha384_cert.pem │ │ │ ├── ecdsa_nistp384_sha384_key_pkcs8.pem │ │ │ ├── ecdsa_nistp384_sha384_key_sec1.pem │ │ │ ├── ecdsa_nistp521_sha512.p12 │ │ │ ├── ecdsa_nistp521_sha512_cert.pem │ │ │ ├── ecdsa_nistp521_sha512_key_pkcs8.pem │ │ │ ├── ed25519.p12 │ │ │ ├── ed25519_cert.pem │ │ │ ├── ed25519_key.pem │ │ │ ├── gen_certs.sh │ │ │ ├── rsa_sha256.p12 │ │ │ ├── rsa_sha256_cert.pem │ │ │ └── rsa_sha256_key.pem │ │ └── src/ │ │ ├── main.rs │ │ ├── redirector.rs │ │ └── tests.rs │ ├── todo/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── Rocket.toml │ │ ├── db/ │ │ │ └── DB_LIVES_HERE │ │ ├── migrations/ │ │ │ ├── .gitkeep │ │ │ └── 20160720150332_create_tasks_table/ │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── src/ │ │ │ ├── main.rs │ │ │ ├── task.rs │ │ │ └── tests.rs │ │ └── static/ │ │ ├── css/ │ │ │ ├── normalize.css │ │ │ ├── skeleton.css │ │ │ └── style.css │ │ └── index.html.tera │ └── upgrade/ │ ├── Cargo.toml │ ├── src/ │ │ └── main.rs │ └── static/ │ └── index.html ├── scripts/ │ ├── config.sh │ ├── mk-docs.sh │ ├── publish.sh │ └── test.sh └── testbench/ ├── Cargo.toml └── src/ ├── client.rs ├── config.rs ├── lib.rs ├── main.rs ├── runner.rs ├── server.rs └── servers/ ├── bind.rs ├── http_extensions.rs ├── ignite_failure.rs ├── infinite_stream.rs ├── mod.rs ├── mtls.rs ├── no_content.rs ├── sni_resolver.rs ├── tls.rs ├── tls_resolver.rs └── tracing.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*.rs] insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 4 tab_width = 4 ================================================ FILE: .gitattributes ================================================ * text eol=lf # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary *.gif binary *.svg binary ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Rocket Community Code of Conduct Like the technical community as a whole, the Rocket project, its teams, and its community are made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission. Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to everyone in our community: maintainers, contributors, team leads, mentors, those seeking help and guidance, and so on. This isn’t an exhaustive list of things that you can’t do. Rather, take it in the spirit in which it’s intended - a guide to make it easier to enrich all of us and the technical communities in which we participate. _If you believe someone is violating the code of conduct, we ask that you report it by emailing community@rwf2.org._ - **Be friendly. Be Kind. Be patient.** - **Be welcoming.** We strive to be a community that welcomes and supports everyone, of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. - **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a global community, so you might not be communicating in someone else's primary language. - **Be respectful.** We won't all agree all the time, but disagreement isn't an excuse for poor behavior or poor manners. While frustrations may occasionally arise, we cannot allow those frustrations to turn into personal attacks. Remember that a community where people feel uncomfortable or threatened is not a productive one. Be respectful, not just to fellow community members, but to individuals outside the community as well. - **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to: - Violent threats or language directed against another person. - Discriminatory jokes and language. - Posting sexually explicit or violent material. - Posting (or threatening to post) other people's personally identifying information ("doxing"). - Personal insults, especially those using racist or sexist terms. - Unwelcome sexual attention. - Advocating for, or encouraging, any of the above behavior. - Repeated harassment of others. In general, if someone asks you to stop, then stop. - **Be understanding.** Disagreements, both social and technical, happen all the time. It is important that we resolve differing views constructively. When we disagree, try to understand why. Our community consists of people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err; blame accomplishes nothing. Instead, focus on helping to resolve issues and learning from mistakes. _This code of conduct applies to all spaces managed by Rocket or the Rocket Web Framework Foundation including issues and discussions on GitHub, Matrix, and any other forums which the community uses for communication._ _This Code of Conduct is adapted from the [Django Code of Conduct]. Original text from the [Speak Up! project]._ [Django Code of Conduct]: https://www.djangoproject.com/conduct/ [Speak Up! project]: http://web.archive.org/web/20141109123859/http://speakup.io/coc.html ================================================ FILE: .github/FUNDING.yml ================================================ github: rwf2 open_collective: rwf2 ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug Report description: Report a functionality issue that deviates from the documentation. labels: ["triage"] body: - type: markdown attributes: value: > **Thanks for taking the time to fill out this bug report!** Your report helps make Rocket better. Please only report issues related to _functionality_ that deviates from published specification or reasonable expectation. Do not report issues with documentation, infrastructure, or anything unrelated to functional correctness here. - type: input attributes: label: Rocket Version description: > Enter the exact version of Rocket (x.y.z) or git shorthash (8d9dfce) you're using. Please ensure you're using the latest release before reporting a bug. placeholder: "ex: 0.5.0" validations: required: true - type: input attributes: label: Operating System description: Which operating system and version are you running? placeholder: "examples: macOS 13.6.2, Arch Linux 4.16.13" validations: required: true - type: input attributes: label: Rust Toolchain Version description: Which version of `rustc` are you using? (`rustc --version`) placeholder: "ex: rustc 1.74.0 (79e9716c9 2023-11-13)" validations: required: true - type: textarea attributes: label: What happened? description: Provide a brief overview of what went wrong. validations: required: true - type: textarea attributes: label: Test Case description: > Provide a Rocket application that elicits the bug. Ideally the program contains a `#[test]` case using Rocket's [`local`](https://api.rocket.rs/v0.5/rocket/local/index.html) testing module. placeholder: > #[macro_use] extern crate rocket; #[launch] fn rocket() -> _ { rocket::build() } #[test] fn failing_test() { use rocket::local::blocking::Client; let client = Client::tracked(rocket()).unwrap(); let response = client.get("/").dispatch(); assert!(response.status().class().is_success()); } render: rust validations: required: true - type: textarea attributes: label: Log Output description: > Please provide the complete log output captured with `ROCKET_LOG_LEVEL=debug` when the test case is run. placeholder: > ❯ ROCKET_LOG_LEVEL=debug cargo test running 1 test test failing_test ... FAILED failures: ---- failing_test stdout ---- -- configuration trace information -- >> "address" parameter source: rocket::Config::default() >> "port" parameter source: rocket::Config::default() >> "workers" parameter source: rocket::Config::default() >> "max_blocking" parameter source: rocket::Config::default() >> "keep_alive" parameter source: rocket::Config::default() >> "ident" parameter source: rocket::Config::default() >> "ip_header" parameter source: rocket::Config::default() >> "limits" parameter source: rocket::Config::default() >> "temp_dir" parameter source: rocket::Config::default() >> "log_level" parameter source: `ROCKET_` environment variable(s) >> "shutdown" parameter source: rocket::Config::default() >> "cli_colors" parameter source: rocket::Config::default() 🔧 Configured for debug. >> address: 127.0.0.1 >> port: 8000 [...] render: shell validations: required: true - type: textarea attributes: label: Additional Context description: > Feel free to provide any additional context for your bug report. - type: checkboxes attributes: label: System Checks description: "Please confirm all of the following:" options: - label: My bug report relates to functionality. required: true - label: I have tested against the latest Rocket release or a recent git commit. required: true - label: I have tested against the latest stable `rustc` toolchain. required: true - label: I was unable to find this issue previously reported. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: FAQ url: https://rocket.rs/guide/faq/ about: Please see our FAQ for answers to common questions. - name: Questions url: https://github.com/rwf2/Rocket/discussions/new?category=questions about: For other questions or help, please use GitHub discussions. - name: Feedback url: https://github.com/rwf2/Rocket/discussions/new/choose about: For general chat or feedback, please use GitHub discussions. - name: Chat url: https://chat.mozilla.org/#/room/#rocket:mozilla.org about: Chat with us live on rocket:mozilla.org on Matrix. ================================================ FILE: .github/ISSUE_TEMPLATE/doc-problem.yml ================================================ name: Documentation Problem description: Report an issue with or suggest documentation. labels: ["docs"] body: - type: markdown attributes: value: > **Thanks for taking the time to report a documentation issue!** Documentation problems include everything from typos to missing or incorrect technical details in any of the following: - [Rocket's Website](https://rocket.rs/) - [The Rocket Programming Guide](https://rocket.rs/guide/) - [API Docs](https://api.rocket.rs) - [Content on GitHub](https://github.com/rwf2/Rocket) If we've written it, we want to know how it can be improved. - type: dropdown validations: required: true attributes: label: What kind of documentation problem are you reporting? multiple: true options: - Typo (PRs welcome!) - Unclear Docs - Undocumented Feature - Broken Links - Rendering Issue - Grammar Issue - Technical Problem - Other - type: input validations: required: true attributes: label: Where is the issue found? description: Please provide a direct link to the documentation. placeholder: "ex: https://rocket.rs/v0.5/guide/requests/#multiple-segments" - type: textarea validations: required: true attributes: label: What's wrong? description: > Please describe what's wrong with the documentation. - type: checkboxes attributes: label: System Checks description: "Please confirm all of the following:" options: - label: I confirmed that the issue still exists on `master` on GitHub. required: true - label: I was unable to find a previous report of this problem. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ name: Feature Request description: Propose a change that introduces new functionality. labels: ["request"] body: - type: markdown attributes: value: > **Thanks for taking the time to request a feature!** Your request helps make Rocket better. Please note that Rocket is designed to have a small but pluggable core. Feature requests that can be implemented outside of Rocket _are typically declined._ In your request, please make a strong case for why this feature can't or shouldn't be implemented outside of Rocket. - type: textarea attributes: label: What's missing? description: > Provide a brief overview of the functionality that's missing in Rocket today, which problem(s) that functionality would solve for you, and what the functionality would enable you to do. placeholder: > example: I frequently want to do X, but Rocket makes it hard. It would be nice if Rocket... example: I want to do X but Rocket makes it impossible because... example: Feature Z exists, but it has these N drawbacks: [..]. What if... validations: required: true - type: textarea attributes: label: Ideal Solution description: > If you already have an idea of how this feature can be implemented, please describe it here. Hypothetical code examples are particularly useful. - type: textarea attributes: label: Why can't this be implemented outside of Rocket? description: > Please make a strong case for why this feature can't or shouldn't be implemented outside of Rocket. We are likely to decline feature requests that can exist outside of Rocket without compromise. validations: required: true - type: textarea attributes: label: Are there workarounds usable today? description: > If the functionality being requested can be achieved today, please detail how here. - type: textarea attributes: label: Alternative Solutions description: > If you have other ideas about how this feature can be implemented, let us know. - type: textarea attributes: label: Additional Context description: > Feel free to provide any additional context for your request. - type: checkboxes attributes: label: System Checks description: "Please confirm all of the following:" options: - label: I do not believe that this feature can or should be implemented outside of Rocket. required: true - label: I was unable to find a previous request for this feature. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/suggestion.yml ================================================ name: Suggestion description: Suggest a change or improvement to existing functionality. labels: ["suggestion"] body: - type: markdown attributes: value: > **Thanks for taking the time to make a suggestion!** - type: input validations: required: true attributes: label: API Docs to Existing Functionality description: Please provide a direct link to the API docs for the functionality you'd like to change. placeholder: "ex: https://api.rocket.rs/v0.5/rocket/trait.Sentinel.html" - type: textarea validations: required: true attributes: label: Problems with Existing Functionality description: Please let us know what you think is wrong with the existing functionality. placeholder: > example: Sentinels don't allow me to access `Foo`, but I'd like to because... example: Feature Z exists, but it has these drawbacks. What if... - type: textarea validations: required: true attributes: label: Suggested Changes description: > How do you propose the existing functionality be changed? Code examples are particular useful. - type: textarea validations: required: true attributes: label: Alternatives Considered description: > Instead of making a change to Rocket, please describe alternative solutions using existing features or new features you've considered. - type: textarea attributes: label: Additional Context description: Feel free to provide any additional context for your suggestion. - type: checkboxes attributes: label: System Checks description: "Please confirm all of the following:" options: - label: > I do not believe that this suggestion can or should be implemented outside of Rocket. required: true - label: I was unable to find a previous suggestion for this change. required: true ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push, pull_request] env: CARGO_TERM_COLOR: always jobs: test: name: "${{ matrix.platform.name }} ${{ matrix.test.name }} (${{ matrix.platform.toolchain }})" runs-on: ${{ matrix.platform.distro }} strategy: fail-fast: false matrix: platform: - { name: Linux, distro: ubuntu-latest, toolchain: stable } - { name: Windows, distro: windows-latest, toolchain: stable } - { name: macOS, distro: macOS-latest, toolchain: stable } - { name: Linux, distro: ubuntu-latest, toolchain: nightly } test: - { name: Debug } - { name: Examples, flag: "--examples" } - { name: Contrib, flag: "--contrib" } include: # Additional tests on Linux/stable. - platform: { name: Linux, distro: ubuntu-latest, toolchain: stable } test: { name: Core, flag: "--core" } - platform: { name: Linux, distro: ubuntu-latest, toolchain: stable } test: { name: Release, flag: "--release" } - platform: { name: Linux, distro: ubuntu-latest, toolchain: stable } test: { name: Testbench, flag: "--testbench" } - platform: { name: Linux, distro: ubuntu-latest, toolchain: stable } test: { name: UI, flag: "--ui" } fallible: true # Allow tests on nightly to fail. - platform: { toolchain: nightly } fallible: true # Use the bigger 'C:/' from the "Switch Disk" step - platform: { name: Windows } working-directory: "C:\\a\\${{ github.event.repository.name }}\\${{ github.event.repository.name }}" steps: - name: Checkout Sources uses: actions/checkout@v4 - name: Free Disk Space (Linux) if: matrix.platform.name == 'Linux' run: | echo "Freeing up disk space on Linux CI" df -h sudo rm -rf /usr/share/dotnet/ sudo rm -rf /opt/ghc sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/local/graalvm/ sudo rm -rf /usr/local/.ghcup/ sudo rm -rf /usr/local/share/powershell sudo rm -rf /usr/local/share/chromium sudo rm -rf /usr/local/lib/android sudo rm -rf /usr/local/lib/node_modules sudo rm -rf "$AGENT_TOOLSDIRECTORY" sudo docker image prune --all --force df -h - name: Install Native Dependencies (macOS) if: matrix.platform.name == 'macOS' run: | brew install mysql-client@8.4 libpq sqlite coreutils brew link --force --overwrite mysql-client@8.4 brew link --force --overwrite libpq echo "/usr/local/opt/mysql-client/bin" >> "$GITHUB_PATH" # vcpkg --triplet x64-windows install libmysql libpq sqlite3 openssl # + vcpkg/installed/vcpkg (in particular, the status file) - name: Install Native Dependencies (Windows) if: matrix.platform.name == 'Windows' run: | curl -fsLS -o vcpkg.7z https://blob.rocket.rs/vcpkg-2024-08-16.7z 7z x vcpkg.7z -y -bb0 xcopy .\vcpkg $env:VCPKG_INSTALLATION_ROOT /s /e /h /y /q vcpkg integrate install echo "VCPKGRS_DYNAMIC=1" >> "$env:GITHUB_ENV" echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" >> "$env:GITHUB_ENV" echo "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib" >> "$env:GITHUB_PATH" echo "MYSQLCLIENT_VERSION=8.0.39" >> "$env:GITHUB_ENV" - name: Install NASM (Windows) if: matrix.platform.name == 'Windows' uses: ilammy/setup-nasm@v1 - name: Install Native Dependencies (Linux) if: matrix.platform.name == 'Linux' run: | sudo apt-get update sudo apt-get install -y libmysqlclient-dev libpq-dev libsqlite3-dev - name: Install Rust uses: dtolnay/rust-toolchain@master id: toolchain with: toolchain: ${{ matrix.platform.toolchain }} components: rust-src - name: Cache Example Workspace if: matrix.test.name == 'Examples' uses: Swatinem/rust-cache@v2 with: workspaces: examples key: ${{ matrix.test.name }}-${{ steps.toolchain.outputs.cachekey }} - name: Cache Root Workspace if: matrix.test.name != 'Examples' uses: Swatinem/rust-cache@v2 with: key: ${{ matrix.test.name }}-${{ steps.toolchain.outputs.cachekey }} # Don't run out of disk space on Windows. C: has much much space than D:. - name: Switch Disk (Windows) if: matrix.platform.name == 'Windows' run: | Get-PSDrive cp D:\a C:\ -Recurse Get-PSDrive - name: Run Tests continue-on-error: ${{ matrix.fallible || false }} working-directory: ${{ matrix.working-directory || github.workspace }} run: ./scripts/test.sh ${{ matrix.test.flag || '' }} -q shell: bash ================================================ FILE: .github/workflows/trigger.yaml ================================================ name: Trigger on: [push] jobs: trigger: name: api.rocket.rs runs-on: ubuntu-latest if: github.repository == 'rwf2/Rocket' steps: - uses: actions/github-script@v7 with: github-token: ${{ secrets.API_DOCS_DEPLOY_TOKEN }} script: | github.rest.actions.createWorkflowDispatch({ owner: 'rwf2', repo: 'api.rocket.rs', workflow_id: 'deploy.yaml', ref: 'master' }) ================================================ FILE: .gitignore ================================================ # Compiled files *.o *.so *.rlib *.dll # Executables *.exe # Generated by Cargo target # Generated databases db.sqlite db.sqlite-shm db.sqlite-wal # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # Cargo config directory .cargo/ # The upload script, for now. scripts/upload-docs.sh scripts/redirect.html # Backup files. *.bak # Uploads in pastebin example. examples/pastebin/upload/* ================================================ FILE: .rustfmt.toml ================================================ disable_all_formatting = true ================================================ FILE: CHANGELOG.md ================================================ # Version 0.5.1 (May 22, 2024) This release contains the following crate updates: - `rocket` `0.5.1` - `rocket_db_pools` `0.2.0` - `rocket_dyn_templates` `0.2.0` - `rocket_ws` `0.1.1` ## [`rocket`](https://api.rocket.rs/v0.5/rocket/) `0.5.1` * The following `char` and `std::ops::Range` types now implement `FromForm`: - `char` - `Range` with `start` and `end` fields - `RangeFrom` with `start` field - `RangeTo` with `end` field - `RangeToInclusive` with `end` field * `[T; N]`, `Vec`, and `[u8]` can now be passed to `uri!`. * The guide now includes a [deploying section]. * The `FromForm` derive now properly records errors involving entire forms. * `FromForm` derive can now be used in code emitted by `macro_rules!` macros. * **(fix [#2668] via [52de9a])** [`TempFile`] now ensures it flushes before being persisted. ## [`rocket_db_pools`](https://api.rocket.rs/v0.5/rocket_db_pools/) `0.2.0` * SQLite extensions are now supported in `sqlx_sqlite`. Use a database configuration option of `extensions` to specify extensions: ```toml [default.databases.db_name] url = "db.sqlite" # This option is only supported by the `sqlx_sqlite` driver. extensions = ["memvfs", "rot13"] ``` * (**breaking**) `deadpool` was updated to `0.13`. * (**breaking**) The [`Config`](https://api.rocket.rs/v0.5/rocket_db_pools/struct.Config) structure has a new `extensions` field. ## [`rocket_dyn_templates`](https://api.rocket.rs/v0.5/rocket_dyn_templates/) `0.2.0` * Support for `minijinja` `2.0` templates was introduced. Templates with an extension of `.j2` are recognized and rendered with Minijinja. * **(breaking)** `handlebars` was updated to `5.1`. ## [`rocket_ws`](https://api.rocket.rs/v0.5/rocket_ws/) `0.1.1` * Introduced [`WebSocket::accept_key()`] method. * `tungstenite` was updated to `0.21`. ## General Changes * The `rust-version` for all crates was updated to `1.64`. This reflects the correct MSRV required to build `rocket` `0.5.0`. * License files are now present in all published crates. [52de9a]: https://github.com/rwf2/Rocket/commit/52de9a [#2668]: https://github.com/rwf2/Rocket/pull/2668 [deploying section]: https://rocket.rs/guide/v0.5/deploying/ [`WebSocket::accept_key()`]: https://api.rocket.rs/v0.5/rocket_ws/struct.WebSocket#method.accept_key # Version 0.5.0 (Nov 17, 2023) ## Major Features and Improvements This release introduces the following major features and improvements: * Support for [compilation on Rust's stable] release channel. * A rewritten, fully asynchronous core with support for [`async`/`await`]. * WebSocket support via [`rocket_ws`]. * [Feature-complete forms support] including multipart, collections, [ad-hoc validation], and [context](https://rocket.rs/v0.5/guide/requests/#context). * [Sentinels]: automatic verification of application state at start-up to prevent runtime errors. * [Graceful shutdown] with configurable signaling, grace periods, [notification], and [shutdown fairings]. * An entirely new, flexible and robust [configuration system] based on [Figment]. * Typed [asynchronous streams] and [Server-Sent Events] with generator syntax. * Asynchronous database pooling support via [`rocket_db_pools`]. * Support for [mutual TLS] and client [`Certificate`]s. * Automatic support for HTTP/2 including `h2` ALPN. * Graduation of `json`, `msgpack`, and `uuid` `rocket_contrib` [features into core]. * An automatically enabled [`Shield`]: security and privacy headers for all responses. * Type-system enforced [incoming data limits] to mitigate memory-based DoS attacks. * Compile-time URI literals via a fully revamped [`uri!`] macro. * [Request connection upgrade APIs] with support for raw I/O with the client. * Full support for [UTF-8 characters] in routes and catchers. * Precise detection of missing managed state, databases, and templating with [sentinels]. * Typed [build phases] with strict application-level guarantees. * [Ignorable segments]: wildcard route matching with no typing restrictions. * First-class [support for `serde`] for built-in guards and types. * New application launch attributes: [`#[launch]`](https://api.rocket.rs/v0.5/rocket/attr.launch.html) and [`#[rocket::main]`](https://api.rocket.rs/v0.5/rocket/attr.main.html). * [Default catchers] via `#[catch(default)]`, which handle _any_ status code. * [Catcher scoping] to narrow the scope of a catcher to a URI prefix. * Built-in libraries and support for [asynchronous testing]. * A [`TempFile`] data and form guard for automatic uploading to a temporary file. * A [`Capped`] data and form guard which enables detecting truncation due to data limits. * Support for dynamic and static prefixing and suffixing of route URIs in [`uri!`]. * Support for custom config profiles and [automatic typed config extraction]. * Rewritten, zero-copy, RFC compliant URI parsers with support for URI-[`Reference`]s. * Multi-segment parameters (``) which match _zero_ segments. * A [`local_cache!`] macro for request-local storage of non-uniquely typed values. * A [`CookieJar`] without "one-at-a-time" limitations. * [Singleton fairings] with replacement and guaranteed uniqueness. * [Data limit declaration in SI units]: "2 MiB", `2.mebibytes()`. * Optimistic responding even when data is left unread or limits are exceeded. * Fully decoded borrowed strings as dynamic parameters, form and data guards. * Borrowed byte slices as data and form guards. * Fail-fast behavior for [misconfigured secrets], file serving paths. * Support for generics and custom generic bounds in [`#[derive(Responder)]`](https://api.rocket.rs/v0.5/rocket/derive.Responder.html). * [Default ranking colors], which prevent more routing collisions automatically. * Improved error logging with suggestions when common errors are detected. * Completely rewritten examples including a new real-time [`chat`] application. ## Support for Rust Stable As a result of support for Rust stable (Rust 2021 Edition and beyond), `#![feature(..)]` crate attributes are no longer required to use Rocket. The complete canonical example with a single `hello` route becomes: ```rust #[macro_use] extern crate rocket; #[get("//")] fn hello(name: &str, age: u8) -> String { format!("Hello, {} year old named {}!", age, name) } #[launch] fn rocket() -> _ { rocket::build().mount("/hello", routes![hello]) } ```
See a diff of the changes from v0.4. ```diff - #![feature(proc_macro_hygiene, decl_macro)] - #[macro_use] extern crate rocket; #[get("//")] - fn hello(name: String, age: u8) -> String { + fn hello(name: &str, age: u8) -> String { format!("Hello, {} year old named {}!", age, name) } - fn main() { - rocket::ignite().mount("/hello", routes![hello]).launch(); - } + #[launch] + fn rocket() -> _ { + rocket::build().mount("/hello", routes![hello]) + } ```
## Breaking Changes This release includes many breaking changes. For a walkthrough guide on handling these changes, see the [v0.4 to v0.5 migration guide]. The most significant changes are listed below. ### Silent Changes These changes are invisible to the compiler and will _not_ yield errors or warnings at compile-time. We **strongly** advise all application authors to review this list carefully. * Blocking I/O (long running compute, synchronous `sleep()`, `Mutex`, `RwLock`, etc.) may prevent the server from making progress and should be avoided, replaced with an `async` variant, or performed in a worker thread. This is a consequence of Rust's cooperative `async` multitasking. For details, see the new [multitasking] section of the guide. * `ROCKET_ENV` is now `ROCKET_PROFILE`. A warning is emitted a launch time if the former is set. * The default profile for debug builds is now `debug`, not `dev`. * The default profile for release builds is now `release`, not `prod`. * `ROCKET_LOG` is now `ROCKET_LOG_LEVEL`. A warning is emitted a launch time if the former is set. * `ROCKET_ADDRESS` accepts only IP addresses, no longer resolves hostnames like `localhost`. * `ROCKET_CLI_COLORS` accepts booleans `true`, `false` in place of strings `"on"`, `"off"`. * It is a launch-time error if `secrets` is enabled in non-`debug` profiles without a configured `secret_key`. * A misconfigured `template_dir` is reported as an error at launch time. * [`FileServer::new()`] fails immediately if the provided directory does not exist. * Catcher collisions result in a launch failure as opposed to a warning. * Default ranks now range from `-12` to `-1`. There is no breaking change if only code generated routes are used. Manually configured routes with negative ranks may collide or be considered in a different order than before. * The order of execution of path and query guards relative to each other is now unspecified. * URIs beginning with `:` are properly recognized as invalid and rejected. * URI normalization now normalizes the query part as well. * The `Segments` iterator now returns percent-decoded `&str`s. * Forms are now parsed leniently by the [`Form` guard]. Use [`Strict`] for the previous behavior. * The `Option` form guard defaults to `None` instead of the default value for `T`. * When data limits are exceeded, a `413 Payload Too Large` status is returned to the client. * The default catcher now returns JSON when the client indicates preference via the `Accept` header. * Empty boolean form values parse as `true`: the query string `?f` is the same as `?f=true`. * [`Created`] does not automatically send an `ETag` header if `R: Hash`. Use [`Created::tagged_body`] instead. * `FileServer` now forwards when a file is not found instead of failing with `404 Not Found`. * [`Shield`] is enabled by default. You may need to disable or change policies if your application depends on typically insecure browser features or if you wish to opt-in to different policies than the defaults. * [`CookieJar`] `get()`s do not return cookies added during request handling. See [`CookieJar`#pending]. * `Hash` `impl`s for `MediaType` and `ContentType` no longer consider media type parameters. * When requested, the `FromForm` implementations of `Vec` and `Map`s are now properly lenient. * To agree with browsers, the `[` and `]` characters are now accepted in URI paths. * The `[` and `]` characters are no longer encoded by [`uri!`]. * The `Secure` cookie flag is set by default for all cookies when serving over TLS. * Removal cookies have `SameSite` set to `Lax` by default. * [`MediaType::JavaScript`] is now `text/javascript`. ### Contrib Graduation The `rocket_contrib` crate is deprecated and the functionality moved to other `rocket` crates. The [contrib deprecation upgrade guide] provides a walkthrough on migrating. The relevant changes are: * Several features previously in `rocket_contrib` were merged into `rocket` itself: - `json`, `msgpack`, and `uuid` are now [features of `rocket`]. - Moved `rocket_contrib::json` to [`rocket::serde::json`]. - Moved `rocket_contrib::msgpack` to [`rocket::serde::msgpack`]. - Moved `rocket_contrib::uuid` to [`rocket::serde::uuid`]. - Moved `rocket_contrib::helmet` to [`rocket::shield`]. [`Shield`] is enabled by default. - Moved `rocket_contrib::serve` to [`rocket::fs`], `StaticFiles` to [`rocket::fs::FileServer`]. - Removed the now unnecessary `Uuid` and `JsonValue` wrapper types. - Removed headers in `Shield` that are no longer respected by browsers. * The remaining features from `rocket_contrib` are now provided by separate crates: - Replaced `rocket_contrib::templates` with [`rocket_dyn_templates`]. - Replaced `rocket_contrib::databases` with [`rocket_sync_db_pools`] and [`rocket_db_pools`]. - These crates are versioned and released independently of `rocket`. - `rocket_contrib::databases::DbError` is now `rocket_sync_db_pools::Error`. - Removed `redis`, `mongodb`, and `mysql` integrations which have upstream `async` drivers. - The [`#[database]`](https://api.rocket.rs/v0.5/rocket_sync_db_pools/attr.database.html) attribute generates an [`async run()`] method instead of `Deref` implementations. ### General The following breaking changes apply broadly and are likely to cause compile-time errors. * [`Rocket`] is now generic over a [phase] marker: - APIs operate on `Rocket`, `Rocket`, `Rocket`, or `Rocket` as needed. - The phase marker statically enforces state transitions in `Build`, `Ignite`, `Orbit` order. - `rocket::ignite()` is now [`rocket::build()`] and returns a `Rocket`. - [`Rocket::ignite()`] transitions to the `Ignite` phase. This is run automatically on launch as needed. - Ignition finalizes configuration, runs `ignite` fairings, and verifies [sentinels]. - [`Rocket::launch()`] transitions into the `Orbit` phase and starts the server. - Methods like [`Request::rocket()`] that refer to a live Rocket instance return an `&Rocket`. * [Fairings] have been reorganized and restructured for `async`: - Replaced `attach` fairings with `ignite` fairings. Unlike `attach` fairings, which ran immediately at the time of attachment, `ignite` fairings are run when transitioning into the `Ignite` phase. - Replaced `launch` fairings with `liftoff` fairings. `liftoff` fairings are always run, even in local clients, after the server begins listening and the concrete port is known. * Introduced a new [configuration system] based on [Figment]: - The concept of "environments" is replaced with "profiles". - `ROCKET_ENV` is superseded by `ROCKET_PROFILE`. - `ROCKET_LOG` is superseded by `ROCKET_LOG_LEVEL`. - Profile names can now be arbitrarily chosen. The `dev`, `stage`, and `prod` profiles carry no special meaning. - The `debug` and `release` profiles are the default profiles for the debug and release compilation profiles. - A new specially recognized `default` profile specifies defaults for all profiles. - The `global` profile has highest precedence, followed by the selected profile, followed by `default`. - Added support for limits specified in SI units: "1 MiB". - Renamed `LoggingLevel` to [`LogLevel`]. - Inlined error variants into the [`Error`] structure. - Changed the type of `workers` to `usize` from `u16`. - Changed accepted values for `keep_alive`: it is disabled with `0`, not `false` or `off`. - Disabled the `secrets` feature (for private cookies) by default. - Removed APIs related to "extras". Typed values can be extracted from the configured `Figment`. - Removed `ConfigBuilder`: all fields of [`Config`] are public with constructors for each field type. * Many functions, traits, and trait bounds have been modified for `async`: - [`FromRequest`], [`Fairing`], [`catcher::Handler`], [`route::Handler`], and [`FromData`] use `#[async_trait]`. - [`NamedFile::open`] is now an `async` function. - Added [`Request::local_cache_async()`] for use in async request guards. - Unsized `Response` bodies must be [`AsyncRead`] instead of `Read`. - Automatically sized `Response` bodies must be [`AsyncSeek`] instead of `Seek`. - The `local` module is split into two: [`rocket::local::asynchronous`] and [`rocket::local::blocking`]. * Functionality and features requiring Rust nightly were removed: - Removed the `Try` implementation on [`Outcome`] which allowed using `?` with `Outcome`s. The recommended replacement is the [`rocket::outcome::try_outcome!`] macro or the various combinator functions on `Outcome`. - [`Result` implements `Responder`] only when both `T` and `E` implement `Responder`. The new [`Debug`] wrapping responder replaces `Result`. - APIs which used the `!` type to now use [`std::convert::Infallible`]. * [`IntoOutcome`] was overhauled to supplant methods now removed in `Outcome`. - `IntoOutcome::into_outcome()` is now `or_error()`. - `IntoOutcome` is implemented for all `Outcome` type aliases. - `Outcome::forward()` requires specifying a status code. - `Outcome::from()` and `Outcome::from_or_forward()` were removed. * [`Rocket::register()`] now takes a base path to scope catchers under as its first argument. * `ErrorKind::Collision` has been renamed to [`ErrorKind::Collisions`]. * TLS config values are only available when the `tls` feature is enabled. * [`MediaType::with_params()`] and [`ContentType::with_params()`] are now builder methods. * Content-Type [`content`] responder type names are now prefixed with `Raw`. * The `content::Plain` responder is now called `content::RawText`. * The `content::Custom` responder was removed in favor of [`(ContentType, T)`]. * Removed `CookieJar::get_private_pending()` in favor of [`CookieJar::get_pending()`]. * The [`local_cache!`] macro accepts fewer types. Use [`local_cache_once!`] as appropriate. * [`Rocket::launch()`] allows `Rocket` recovery by returning the instance after shutdown. * `ErrorKind::Runtime` was removed; [`ErrorKind::Shutdown`] was added. * `Outcome::Failure` was renamed to [`Outcome::Error`]. ### Routing and URIs * In `#[route(GET, path = "...")]`, `path` is now `uri`: `#[route(GET, uri = "...")]`. * Multi-segment paths (`/`) now match _zero_ or more segments. * Codegen improvements preclude identically named routes and modules in the same namespace. * A route URI like (`//`) now collides with (`/`), requires a `rank` to resolve. * All catcher related types and traits moved to [`rocket::catcher`]. * All route related types and traits moved to [`rocket::route`]. * URI formatting types and traits moved to [`rocket::http::uri::fmt`]. * `T` no longer converts to `Option` or `Result` for [`uri!`] query parameters. * For optional query parameters, [`uri!`] requires using a wrapped value or `_`. * `&RawStr` no longer implements `FromParam`: use `&str` instead. * Percent-decoding is performed before calling `FromParam` implementations. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` allocate as necessary, return `Cow`. * `RawStr::from_str()` was replaced with `RawStr::new()`. * `Origin::segments()` was replaced with `Origin.path().segments()`. * `Origin::path()` and `Origin::query()` return `&RawStr` instead of `&str`. * The type of `Route::name` is now `Option>`. * `Route::set_uri` was replaced with [`Route::map_base()`]. * The `Route::uri` field is now of type [`RouteUri`]. * `Route::base` was removed in favor of `Route.uri().base()`. * [Route `Forward` outcomes] are now associated with a `Status`. * The status codes used when built-in guards forward were changed: - Route parameter `FromParam` errors now forward as 422. - Query parameter errors now forward as 422. - Incorrect form content-type errors forwards as 413. - `&Host`, `&Accept`, `&ContentType`, `IpAddr`, and `SocketAddr` all forward with a 500. ### Data and Forms * `Data` now has a lifetime generic: `Data<'r>`. * [`Data::open()`] indelibly requires a data limit. * Removed `FromDataSimple`. Use [`FromData`] and [`local_cache!`] or [`local_cache_once!`]. * All [`DataStream`] APIs require limits and return [`Capped`] types. * Form types and traits were moved from `rocket::request` to [`rocket::form`]. * Removed `FromQuery`. Dynamic query parameters (`#[get("/?")]`) use [`FromForm`] instead. * Replaced `FromFormValue` with [`FromFormField`]. All `T: FromFormField` implement `FromForm`. * Form field values are percent-decoded before calling [`FromFormField`] implementations. * Renamed the `#[form(field = ...)]` attribute to `#[field(name = ...)]`. * [Custom form errors] must now specify an associated `Status`. ### Request Guards * Renamed `Cookies` to [`CookieJar`]. Its methods take `&self`. * Renamed `Flash.name` to `Flash.kind`, `Flash.msg` to `Flash.message`. * Replaced `Request::get_param()` with `Request::param()`. * Replaced `Request::get_segments()` to `Request::segments()`. * Replaced `Request::get_query_value()` with `Request::query_value()`. * Replaced `Segments::into_path_buf()` with `Segments::to_path_buf()`. * Replaced `Segments` and `QuerySegments` with [`Segments` and `Segments`]. * [`Flash`] constructors now take `Into` instead of `AsRef`. * The `State<'_, T>` request guard is now `&State`. * Removed a lifetime from [`FromRequest`]: `FromRequest<'r>`. * Removed a lifetime from [`FlashMessage`]: `FlashMessage<'_>`. * Removed all `State` reexports except [`rocket::State`]. ### Responders * Moved `NamedFile` to `rocket::fs::NamedFile` * Replaced `Content` with `content::Custom`. * `Response::body` and `Response::body_mut` are now infallible methods. * Renamed `ResponseBuilder` to `Builder`. * Removed direct `Response` body reading methods. Use methods on `r.body_mut()` instead. * Removed inaccurate "chunked body" types and variants. * Removed `Responder` `impl` for `Response`. Prefer custom responders with `#[derive(Responder)]`. * Removed the unused reason phrase from `Status`. * The types of responders in [`response::status`] were unified to all be of the form `Status(R)`. ## General Improvements In addition to new features and changes, Rocket saw the following improvements: ### General * Added support for [raw identifiers] in the `FromForm` derive, `#[route]` macros, and `uri!`. * Added support for uncased derived form fields: `#[field(name = uncased(...))]`. * Added support for [default form field values]: `#[field(default = expr())]`. * Added support for multiple `#[field]` attributes on struct fields. * Added support for base16-encoded (a.k.a. hex-encoded) secret keys. * Added [`Config::ident`] for configuring or removing the global `Server` header. * Added [`Rocket::figment()`] and [`Rocket::catchers()`]. * Added [`LocalRequest::json()`] and [`LocalResponse::json()`]. * Added [`LocalRequest::msgpack()`] and [`LocalResponse::msgpack()`]. * Added support for `use m::route; routes![route]` instead of needing `routes![m::route]`. * Added support for [hierarchical data limits]: a limit of `a/b/c` falls back to `a/b` then `a`. * Added [`LocalRequest::inner_mut()`]. `LocalRequest` implements `DerefMut` to `Request`. * Added support for ECDSA and EdDSA TLS keys. * Added associated constants in `Config` for all config parameter names. * Added `ErrorKind::Config` to represent errors in configuration at runtime. * Added `rocket::fairing::Result` type alias, returned by `Fairing::on_ignite()`. * All guard failures are logged at runtime. * `Rocket::mount()` now accepts a base value of any type that implements `TryInto>`. * The default error catcher's HTML has been compacted. * The default error catcher returns JSON if requested by the client. * Panics in routes or catchers are caught and forwarded to `500` error catcher. * A detailed warning is emitted if a route or catcher panics. * Emoji characters are no longer output on Windows. * Fixed [`Error`] to not panic if a panic is already in progress. * Introduced [`Reference`] and [`Asterisk`] URI types. * Added support to [`UriDisplayQuery`] for C-like enums. * The [`UriDisplayQuery`] derive now recognizes the `#[field]` attribute for field renaming. * `Client` method builders accept `TryInto` allowing a `uri!()` to be used directly. * [`Rocket`] is now `#[must_use]`. * Support for HTTP/2 can be disabled by disabling the default `http2` crate feature. * Added [`rocket::execute()`] for executing Rocket's `launch()` future. * Added the [`context!`] macro to [`rocket_dyn_templates`] for ad-hoc template contexts. * The `time` crate is re-exported from the crate root. * The `FromForm`, `Responder`, and `UriDisplay` derives now fully support generics. * Added helper functions to `serde` submodules. * The [`Shield`] HSTS preload header now includes `includeSubdomains`. * Logging ignores `write!` errors if `stdout` disappears, preventing panics. * Added [`Client::terminate()`] to run graceful shutdown in testing. * Shutdown now terminates the `async` runtime, never the process. * Added a [`local_cache_once!`] macro for request-local storage. * Final launch messages are now _always_ logged, irrespective of profile. * Only functions that return `Rocket` are now `#[must_use]`, not all `Rocket

`. * Fixed mismatched form field names in errors under certain conditions in [`FromForm`] derive. * The [`FromForm`] derive now collects _all_ errors that occur. * Data pools are now gracefully shutdown in [`rocket_sync_db_pools`]. * Added [`Metadata::render()`] in [`rocket_dyn_templates`] for direct template rendering. * Rocket salvages more information from malformed requests for error catchers. * The `cookie` `secure` feature is now properly conditionally enabled. * Data before encapsulation boundaries in TLS keys is allowed and ignored. * Support for TLS keys in SEC1 format was added. * Rocket now warns when a known secret key is configured. * A panic that could occur on shutdown in `rocket_sync_db_pools` was fixed. * Added a [`max_blocking`] configuration parameter to control number of blocking threads. * Added an [`ip_header`] "real IP" header configuration parameter. * A [`pool()`] method is emitted by [`rocket_sync_db_pools`] for code-generated pools. * Data guards are now eligible [sentinels]. * Raw binary form field data can be retrieved using the `&[u8]` form guard. * Added [`TempFile::open()`] to stream `TempFile` data. * mTLS certificates can be set on local requests with [`LocalRequest::identity()`]. * Added [`Error::pretty_print()`] for pretty-printing errors like Rocket. * Warnings are logged when data limits are reached. * A warning is emitted when `String` is used as a route parameter. * Configuration provenance information is logged under the `debug` log level. * Logging of `Outcome`s now includes the relevant status code. * `Span::mixed_site()` is used in codegen to reduce errant `clippy` warnings. ### HTTP * Added support for HTTP/2, enabled by default via the `http2` crate feature. * Added a `const` constructor for `MediaType`. * Introduced [`RawStrBuf`], an owned `RawStr`. * Added many new "pattern" methods to [`RawStr`]. * Added [`RawStr::percent_encode()`] and [`RawStr::strip()`]. * Added support for unencoded query characters in URIs that are frequently sent by browsers. * Introduced [`Host`] and [`&Host`] request guards. * Added [`RawStr::percent_encode_bytes()`]. * `NODELAY` is now enabled on all connections by default. * The TLS implementation handles handshakes off the main task, improving DoS resistance. ### Known Media Types * Added AVIF: `image/avif`. * Added `EventStream`: `text/event-stream`. * Added `Markdown`: `text/markdown`. * Added `MP3`: `audio/mpeg`. * Added `CBZ`: `application/vnd.comicbook+zip`, extension `.cbz`. * Added `CBR`: `application/vnd.comicbook-rar`, extension `.cbr`. * Added `RAR`: `application/vnd.rar`, extension `.rar`. * Added `EPUB`: `application/epub+zip`, extension `.epub`. * Added `OPF`: `application/oebps-package+xml`, extension `.opf`. * Added `XHTML`: `application/xhtml+xml`, extension `.xhtml`. * Added `Text` as an alias for the `Plain` media type. * Added `Bytes` as an alias for the `Binary` media type. * Added `.mjs` as known JavaScript extension. * Added '.exe', '.iso', '.dmg' as known extensions. ### Request * Added support for all UTF-8 characters in route paths. * Added support for percent-encoded `:` in socket or IP address values in [`FromFormValue`]. * Added [`Request::rocket()`] to access the active `Rocket` instance. * `Request::uri()` now returns an `&Origin<'r>` instead of `&Origin<'_>`. * `Request::accept()`, `Request::content_type()` reflect changes to `Accept`, `Content-Type`. * `Json`, `MsgPack` accept `T: Deserialize`, not only `T: DeserializeOwned`. * Diesel SQLite connections in `rocket_sync_db_pools` use better defaults. * The default number of workers for synchronous database pools is now `workers * 4`. * Added [`Request::host()`] to retrieve the client-requested host. ### Response * Added [`Template::try_custom()`] for fallible template engine customization. * Manually registered templates can now be rendered with `Template::render()`. * Added support for the `X-DNS-Prefetch-Control` header to `Shield`. * Added support for manually-set `expires` values for private cookies. * Added support for type generics and custom generic bounds to [`#[derive(Responder)]`](https://api.rocket.rs/v0.5/rocket/derive.Responder.html). * The `Server` header is only set if one isn't already set. * Accurate `Content-Length` headers are sent even for partially read `Body`s. * [`Redirect`] now accepts a `TryFrom`, allowing fragment parts. ### Trait Implementations * Implemented `Clone` for `State`. * Implemented `Copy` and `Clone` for `fairing::Info`. * Implemented `Debug` for `Rocket` and `Client`. * Implemented `Default` for `Status` (returns `Status::Ok`). * Implemented `PartialEq`, `Eq`, `Hash`, `PartialOrd`, and `Ord` for `Status`. * Implemented `Eq`, `Hash`, and `PartialEq<&str>` for `Origin`. * Implemented `PartialEq>>` for `RawStr`. * Implemented `std::error::Error` for `Error`. * Implemented `Deref` and `DerefMut` for `LocalRequest` (to `Request`). * Implemented `DerefMut` for `Form`, `LenientForm`. * Implemented `From` for `Json`, `MsgPack`. * Implemented `TryFrom` and `TryFrom<&str>` for `Origin`. * Implemented `TryFrom` for each of the specific URI variants. * Implemented `FromRequest` for `&Config`. * Implemented `FromRequest` for `IpAddr`. * Implemented `FromParam` for `PathBuf` * Implemented `FromParam`, `FromData`, and `FromForm` for `&str`. * Implemented `FromForm` for `Json`, `MsgPack`. * Implemented `FromFormField` for `Cow` and `Capped>` * Implemented `Responder` for `tokio::fs::File`. * Implemented `Responder` for `(ContentType, R) where R: Responder`. * Implemented `Responder` for `(Status, R) where R: Responder` which overrides `R`'s status. * Implemented `Responder` for `std::io::Error` (behaves as `Debug`). * Implemented `Responder` for `Either`, equivalently to `Result`. * Implemented `Serialize` for `Flash`. * Implemented `Serialize`, `Deserialize`, `UriDisplay` and `FromUriParam` for `uuid::Uuid` * Implemented `Serialize`, `Deserialize` for `RawStr`. * Implemented `Serialize`, `Deserialize` for all URI types. * Implemented `Responder` for `Arc`, `Box` where `T: Responder`. * Implemented `Serialize` and `Deserialize` for [`Method`]. * Implemented `Eq` for [`MediaType`] and [`ContentType`]. * Implemented `Responder` for `Box`. * Implemented `FromForm` for `Arc`. * Implemented `Fairing` for `Arc`. * Implemented `Serialize` and `Deserialize` for `Status`. ### Dependency Changes * `serde` was introduced (`1.0`). * `futures` was introduced (`0.3`). * `binascii` was introduced (`0.1`). * `ref-cast` was introduced (`1.0`). * `atomic` was introduced (`0.5`). * `parking_lot` was introduced (`0.11`). * `ubtye` was introduced (`0.10`). * `figment` was introduced (`0.10`). * `rand` was introduced (`0.8`). * `either` was introduced (`1.0`). * `pin-project-lite` was introduced (`0.2`). * `indexmap` was introduced (`2.0`). * `tempfile` was introduced (`3.0`). * `async-trait` was introduced (`0.1`). * `async-stream` was introduced (`0.3`). * `multer` was introduced (`2.0`). * `tokio` was introduced (`1.6.1`). * `tokio-util` was introduced (`0.6`). * `tokio-stream` was introduced (`0.1.6`). * `bytes` was introduced (`1.0`). * `normpath` was introduced (`1`). * `state` was updated to `0.6`. * `rmp-serde` was updated to `0.15`. * `uuid` was updated to `0.8`. * `tera` was updated to `1.10`. * `postgres` was updated to `0.19`. * `rusqlite` was updated to `0.25`. * `r2d2_sqlite` was updated to `0.18`. * `time` was updated to `0.3`. * `handlebars` was updated to `4.0`. * `memcache` was updated to `0.16`. * `rustls` was updated to `0.21`. * `tokio-rustls` was updated to `0.24`. * `syn` was updated to `2`. * `diesel` was updated to `2.0`. * `sqlx` was updated to `0.7`. * `notify` was updated to `6`. * `criterion` was updated to `0.4`. * `cookie` was updated to `0.18`. * `yansi` was updated to `1.0`. * `atty` was removed. ## Infrastructure The following changes were made to the project's infrastructure: * Rocket now uses the 2021 edition of Rust. * Added a [v0.4 to v0.5 migration guide] and [FAQ] to Rocket's website. * Added visible `use` statements to examples in the guide. * Split examples into a separate workspace for easier testing. * Updated documentation for all changes. * Fixed many typos, errors, and broken links throughout documentation and examples. * Improved the general robustness of macros, and the quality and frequency of error messages. * Benchmarks now use `criterion` and datasets extracted from real-world projects. * Fixed the SPDX license expressions in `Cargo.toml` files. * Added support to `test.sh` for a `+` flag (e.g. `+stable`) to pass to `cargo`. * Added support to `test.sh` for extra flags to be passed on to `cargo`. * UI tests are now allowed to fail by the CI to avoid false negatives. * The GitHub CI workflow was updated to use maintained actions. * The CI now frees disk space before proceeding to avoid out-of-disk errors. * All workspaces now use `resolver = 2`. [phase]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#phases [`async`/`await`]: https://rocket.rs/v0.5/guide/overview/#async-routes [compilation on Rust's stable]: https://rocket.rs/v0.5/guide/getting-started/#installing-rust [Feature-complete forms support]: https://rocket.rs/v0.5/guide/requests/#forms [configuration system]: https://rocket.rs/v0.5/guide/configuration/#configuration [graceful shutdown]: https://api.rocket.rs/v0.5/rocket/config/struct.Shutdown.html#summary [asynchronous testing]: https://rocket.rs/v0.5/guide/testing/#asynchronous-testing [UTF-8 characters]: https://rocket.rs/v0.5/guide/requests/#static-parameters [ignorable segments]: https://rocket.rs/v0.5/guide/requests/#ignored-segments [Catcher scoping]: https://rocket.rs/v0.5/guide/requests/#scoping [ad-hoc validation]: https://rocket.rs/v0.5/guide/requests#ad-hoc-validation [incoming data limits]: https://rocket.rs/v0.5/guide/requests/#streaming [build phases]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#phases [Singleton fairings]: https://api.rocket.rs/v0.5/rocket/fairing/trait.Fairing.html#singletons [features into core]: https://api.rocket.rs/v0.5/rocket/index.html#features [features of `rocket`]: https://api.rocket.rs/v0.5/rocket/index.html#features [Data limit declaration in SI units]: https://api.rocket.rs/v0.5/rocket/data/struct.ByteUnit.html [support for `serde`]: https://api.rocket.rs/v0.5/rocket/serde/index.html [automatic typed config extraction]: https://api.rocket.rs/v0.5/rocket/fairing/struct.AdHoc.html#method.config [misconfigured secrets]: https://api.rocket.rs/v0.5/rocket/config/struct.SecretKey.html [default ranking colors]: https://rocket.rs/v0.5/guide/requests/#default-ranking [`chat`]: https://github.com/rwf2/Rocket/tree/v0.5/examples/chat [`Form` guard]: https://api.rocket.rs/v0.5/rocket/form/struct.Form.html [`Strict`]: https://api.rocket.rs/v0.5/rocket/form/struct.Strict.html [`CookieJar`#pending]: https://api.rocket.rs/v0.5/rocket/http/struct.CookieJar.html#pending [`rocket::serde::json`]: https://api.rocket.rs/v0.5/rocket/serde/json/index.html [`rocket::serde::msgpack`]: https://api.rocket.rs/v0.5/rocket/serde/msgpack/index.html [`rocket::serde::uuid`]: https://api.rocket.rs/v0.5/rocket/serde/uuid/index.html [`rocket::shield`]: https://api.rocket.rs/v0.5/rocket/shield/index.html [`rocket::fs`]: https://api.rocket.rs/v0.5/rocket/fs/index.html [`async run()`]: https://api.rocket.rs/v0.5/rocket_sync_db_pools/index.html#handlers [`LocalRequest::json()`]: https://api.rocket.rs/v0.5/rocket/local/blocking/struct.LocalRequest.html#method.json [`LocalRequest::msgpack()`]: https://api.rocket.rs/v0.5/rocket/local/blocking/struct.LocalRequest.html#method.msgpack [`LocalResponse::json()`]: https://api.rocket.rs/v0.5/rocket/local/blocking/struct.LocalResponse.html#method.json [`LocalResponse::msgpack()`]: https://api.rocket.rs/v0.5/rocket/local/blocking/struct.LocalResponse.html#method.msgpack [hierarchical data limits]: https://api.rocket.rs/v0.5/rocket/data/struct.Limits.html#hierarchy [default form field values]: https://rocket.rs/v0.5/guide/requests/#defaults [`Config::ident`]: https://api.rocket.rs/rocket/struct.Config.html#structfield.ident [`tokio`]: https://tokio.rs/ [Figment]: https://docs.rs/figment/0.10/figment/ [`TempFile`]: https://api.rocket.rs/v0.5/rocket/fs/enum.TempFile.html [`Contextual`]: https://rocket.rs/v0.5/guide/requests/#context [`Capped`]: https://api.rocket.rs/v0.5/rocket/data/struct.Capped.html [default catchers]: https://rocket.rs/v0.5/guide/requests/#default-catchers [URI types]: https://api.rocket.rs/v0.5/rocket/http/uri/index.html [`uri!`]: https://api.rocket.rs/v0.5/rocket/macro.uri.html [`Reference`]: https://api.rocket.rs/v0.5/rocket/http/uri/struct.Reference.html [`Asterisk`]: https://api.rocket.rs/v0.5/rocket/http/uri/struct.Asterisk.html [`Redirect`]: https://api.rocket.rs/v0.5/rocket/response/struct.Redirect.html [`UriDisplayQuery`]: https://api.rocket.rs/v0.5/rocket/derive.UriDisplayQuery.html [`Shield`]: https://api.rocket.rs/v0.5/rocket/shield/struct.Shield.html [Sentinels]: https://api.rocket.rs/v0.5/rocket/trait.Sentinel.html [`local_cache!`]: https://api.rocket.rs/v0.5/rocket/request/macro.local_cache.html [`local_cache_once!`]: https://api.rocket.rs/v0.5/rocket/request/macro.local_cache_once.html [`CookieJar`]: https://api.rocket.rs/v0.5/rocket/http/struct.CookieJar.html [asynchronous streams]: https://rocket.rs/v0.5/guide/responses/#async-streams [Server-Sent Events]: https://api.rocket.rs/v0.5/rocket/response/stream/struct.EventStream.html [`fs::relative!`]: https://api.rocket.rs/v0.5/rocket/fs/macro.relative.html [notification]: https://api.rocket.rs/v0.5/rocket/struct.Shutdown.html [`Rocket`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html [`rocket::build()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.build [`Rocket::ignite()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.ignite [`Rocket::launch()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.launch [`Request::rocket()`]: https://api.rocket.rs/v0.5/rocket/request/struct.Request.html#method.rocket [Fairings]: https://rocket.rs/v0.5/guide/fairings/ [configuration system]: https://rocket.rs/v0.5/guide/configuration/ [`Poolable`]: https://api.rocket.rs/v0.5/rocket_sync_db_pools/trait.Poolable.html [`Config`]: https://api.rocket.rs/v0.5/rocket/struct.Config.html [`Error`]: https://api.rocket.rs/v0.5/rocket/struct.Error.html [`LogLevel`]: https://api.rocket.rs/v0.5/rocket/config/enum.LogLevel.html [`Rocket::register()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.register [`NamedFile::open`]: https://api.rocket.rs/v0.5/rocket/fs/struct.NamedFile.html#method.open [`Request::local_cache_async()`]: https://api.rocket.rs/v0.5/rocket/request/struct.Request.html#method.local_cache_async [`FromRequest`]: https://api.rocket.rs/v0.5/rocket/request/trait.FromRequest.html [`Fairing`]: https://api.rocket.rs/v0.5/rocket/fairing/trait.Fairing.html [`catcher::Handler`]: https://api.rocket.rs/v0.5/rocket/catcher/trait.Handler.html [`route::Handler`]: https://api.rocket.rs/v0.5/rocket/route/trait.Handler.html [`FromData`]: https://api.rocket.rs/v0.5/rocket/data/trait.FromData.html [`AsyncRead`]: https://docs.rs/tokio/1/tokio/io/trait.AsyncRead.html [`AsyncSeek`]: https://docs.rs/tokio/1/tokio/io/trait.AsyncSeek.html [`rocket::local::asynchronous`]: https://api.rocket.rs/v0.5/rocket/local/asynchronous/index.html [`rocket::local::blocking`]: https://api.rocket.rs/v0.5/rocket/local/blocking/index.html [`Outcome`]: https://api.rocket.rs/v0.5/rocket/outcome/enum.Outcome.html [`rocket::outcome::try_outcome!`]: https://api.rocket.rs/v0.5/rocket/outcome/macro.try_outcome.html [`Result` implements `Responder`]: https://api.rocket.rs/v0.5/rocket/response/trait.Responder.html#provided-implementations [`Debug`]: https://api.rocket.rs/v0.5/rocket/response/struct.Debug.html [`std::convert::Infallible`]: https://doc.rust-lang.org/stable/std/convert/enum.Infallible.html [`ErrorKind::Collisions`]: https://api.rocket.rs/v0.5/rocket/error/enum.ErrorKind.html#variant.Collisions [`rocket::http::uri::fmt`]: https://api.rocket.rs/v0.5/rocket/http/uri/fmt/index.html [`Data::open()`]: https://api.rocket.rs/v0.5/rocket/data/struct.Data.html#method.open [`DataStream`]: https://api.rocket.rs/v0.5/rocket/data/struct.DataStream.html [`rocket::form`]: https://api.rocket.rs/v0.5/rocket/form/index.html [`FromFormField`]: https://api.rocket.rs/v0.5/rocket/form/trait.FromFormField.html [`FromForm`]: https://api.rocket.rs/v0.5/rocket/form/trait.FromForm.html [`FlashMessage`]: https://api.rocket.rs/v0.5/rocket/request/type.FlashMessage.html [`Flash`]: https://api.rocket.rs/v0.5/rocket/response/struct.Flash.html [`rocket::State`]: https://api.rocket.rs/v0.5/rocket/struct.State.html [`Segments` and `Segments`]: https://api.rocket.rs/v0.5/rocket/http/uri/struct.Segments.html [`Route::map_base()`]: https://api.rocket.rs/v0.5/rocket/route/struct.Route.html#method.map_base [`uuid` support]: https://api.rocket.rs/v0.5/rocket/serde/uuid/index.html [`json`]: https://api.rocket.rs/v0.5/rocket/serde/json/index.html [`msgpack`]: https://api.rocket.rs/v0.5/rocket/serde/msgpack/index.html [`rocket::serde::json::json!`]: https://api.rocket.rs/v0.5/rocket/serde/json/macro.json.html [`rocket::shield::Shield`]: https://api.rocket.rs/v0.5/rocket/shield/struct.Shield.html [`rocket::fs::FileServer`]: https://api.rocket.rs/v0.5/rocket/fs/struct.FileServer.html [`rocket_dyn_templates`]: https://api.rocket.rs/v0.5/rocket_dyn_templates/index.html [`rocket_sync_db_pools`]: https://api.rocket.rs/v0.5/rocket_sync_db_pools/index.html [multitasking]: https://rocket.rs/v0.5/guide/overview/#multitasking [`Created`]: https://api.rocket.rs/v0.5/rocket/response/status/struct.Created.html [`Created::tagged_body`]: https://api.rocket.rs/v0.5/rocket/response/status/struct.Created.html#method.tagged_body [raw identifiers]: https://doc.rust-lang.org/1.51.0/book/appendix-01-keywords.html#raw-identifiers [`Rocket::config()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.config [`Rocket::figment()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.figment [`Rocket::state()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.state [`Rocket::catchers()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.catchers [`LocalRequest::inner_mut()`]: https://api.rocket.rs/v0.5/rocket/local/blocking/struct.LocalRequest.html#method.inner_mut [`RawStrBuf`]: https://api.rocket.rs/v0.5/rocket/http/struct.RawStrBuf.html [`RawStr`]: https://api.rocket.rs/v0.5/rocket/http/struct.RawStr.html [`RawStr::percent_encode()`]: https://api.rocket.rs/v0.5/rocket/http/struct.RawStr.html#method.percent_encode [`RawStr::percent_encode_bytes()`]: https://api.rocket.rs/v0.5/rocket/http/struct.RawStr.html#method.percent_encode_bytes [`RawStr::strip()`]: https://api.rocket.rs/v0.5/rocket/http/struct.RawStr.html#method.strip_prefix [`rocket::catcher`]: https://api.rocket.rs/v0.5/rocket/catcher/index.html [`rocket::route`]: https://api.rocket.rs/v0.5/rocket/route/index.html [`Segments::prefix_of()`]: https://api.rocket.rs/v0.5/rocket/http/uri/struct.Segments.html#method.prefix_of [`Template::try_custom()`]: https://api.rocket.rs/v0.5/rocket_dyn_templates/struct.Template.html#method.try_custom [`Template::custom`]: https://api.rocket.rs/v0.5/rocket_dyn_templates/struct.Template.html#method.custom [`FileServer::new()`]: https://api.rocket.rs/v0.5/rocket/fs/struct.FileServer.html#method.new [`content`]: https://api.rocket.rs/v0.5/rocket/response/content/index.html [`rocket_db_pools`]: https://api.rocket.rs/v0.5/rocket_db_pools/index.html [mutual TLS]: https://rocket.rs/v0.5/guide/configuration/#mutual-tls [`Certificate`]: https://api.rocket.rs/v0.5/rocket/mtls/struct.Certificate.html [`MediaType::with_params()`]: https://api.rocket.rs/v0.5/rocket/http/struct.MediaType.html#method.with_params [`ContentType::with_params()`]: https://api.rocket.rs/v0.5/rocket/http/struct.ContentType.html#method.with_params [`Host`]: https://api.rocket.rs/v0.5/rocket/http/uri/struct.Host.html [`&Host`]: https://api.rocket.rs/v0.5/rocket/http/uri/struct.Host.html [`Request::host()`]: https://api.rocket.rs/v0.5/rocket/request/struct.Request.html#method.host [`context!`]: https://api.rocket.rs/v0.5/rocket_dyn_templates/macro.context.html [`MediaType`]: https://api.rocket.rs/v0.5/rocket/http/struct.MediaType.html [`ContentType`]: https://api.rocket.rs/v0.5/rocket/http/struct.ContentType.html [`Method`]: https://api.rocket.rs/v0.5/rocket/http/enum.Method.html [`(ContentType, T)`]: https://api.rocket.rs/v0.5/rocket/response/content/index.html#usage [v0.4 to v0.5 migration guide]: https://rocket.rs/v0.5/guide/upgrading/ [contrib deprecation upgrade guide]: https://rocket.rs/v0.5/guide/upgrading/#contrib-deprecation [FAQ]: https://rocket.rs/v0.5/guide/faq/ [`Rocket::launch()`]: https://api.rocket.rs/v0.5/rocket/struct.Rocket.html#method.launch [`ErrorKind::Shutdown`]: https://api.rocket.rs/v0.5/rocket/error/enum.ErrorKind.html#variant.Shutdown [shutdown fairings]: https://api.rocket.rs/v0.5/rocket/fairing/trait.Fairing.html#shutdown [`Client::terminate()`]: https://api.rocket.rs/v0.5/rocket/local/blocking/struct.Client.html#method.terminate [`rocket::execute()`]: https://api.rocket.rs/v0.5/rocket/fn.execute.html [`CookieJar::get_pending()`]: https://api.rocket.rs/v0.5/rocket/http/struct.CookieJar.html#method.get_pending [`Metadata::render()`]: https://api.rocket.rs/v0.5/rocket_dyn_templates/struct.Metadata.html#method.render [`pool()`]: https://api.rocket.rs/v0.5/rocket_sync_db_pools/example/struct.ExampleDb.html#method.pool [`Request::client_ip()`]: https://api.rocket.rs/v0.5/rocket/request/struct.Request.html#method.client_ip [`max_blocking`]: https://api.rocket.rs/v0.5/rocket/struct.Config.html#structfield.max_blocking [`ip_header`]: https://api.rocket.rs/v0.5/rocket/struct.Config.html#structfield.ip_header [`LocalRequest::identity()`]: https://api.rocket.rs/v0.5/rocket/local/blocking/struct.LocalRequest.html#method.identity [`TempFile::open()`]: https://api.rocket.rs/v0.5/rocket/fs/enum.TempFile.html#method.open [`Error::pretty_print()`]: https://api.rocket.rs/v0.5/rocket/struct.Error.html#method.pretty_print [Request connection upgrade APIs]: https://api.rocket.rs/v0.5/rocket/struct.Response.html#upgrading [`rocket_ws`]: https://api.rocket.rs/v0.5/rocket_ws/ [registering]: https://api.rocket.rs/v0.5/rocket/response/struct.Response.html#method.add_upgrade [`IoHandler`]: https://api.rocket.rs/v0.5/rocket/data/trait.IoHandler.html [`response::status`]: https://api.rocket.rs/v0.5/rocket/response/status/index.html [Custom form errors]: https://api.rocket.rs/v0.5/rocket/form/error/enum.ErrorKind.html#variant.Custom [`request::Outcome`]: https://api.rocket.rs/v0.5/rocket/request/type.Outcome.html#variant.Forward [Route `Forward` outcomes]: https://api.rocket.rs/v0.5/rocket/request/type.Outcome.html#variant.Forward [`Outcome::Error`]: https://api.rocket.rs/v0.5/rocket/outcome/enum.Outcome.html#variant.Error [`IntoOutcome`]: https://api.rocket.rs/v0.5/rocket/outcome/trait.IntoOutcome.html [`MediaType::JavaScript`]: https://api.rocket.rs/v0.5/rocket/http/struct.MediaType.html#associatedconstant.JavaScript [`TempFile::open()`]: https://api.rocket.rs/v0.5/rocket/fs/enum.TempFile.html#method.open [`Error::pretty_print()`]: https://api.rocket.rs/v0.5/rocket/struct.Error.html#method.pretty_print [`RouteUri`]: https://api.rocket.rs/v0.5/rocket/route/struct.RouteUri.html # Version 0.4.10 (May 21, 2021) ## Core * [[`3276b8`]] Removed `unsafe` in `Origin::parse_owned()`, fixing a soundness issue. [`3276b8`]: https://github.com/rwf2/Rocket/commit/3276b8 # Version 0.4.9 (May 19, 2021) ## Core * [[`#1645`], [`f2a56f`]] Fixed `Try` `impl FromResidual for Outcome`. [`#1645`]: https://github.com/rwf2/Rocket/issues/1645 [`f2a56f`]: https://github.com/rwf2/Rocket/commit/f2a56f # Version 0.4.8 (May 18, 2021) ## Core * [[`#1548`], [`93e88b0`]] Fixed an issue that prevented compilation under Windows Subsystem for Linux v1. * Updated `Outcome` `Try` implementation to v2 in latest nightly. * Minimum required `rustc` is `1.54.0-nightly (2021-05-18)`. ## Internal * Updated `base64` dependency to `0.13`. [`#1548`]: https://github.com/rwf2/Rocket/issues/1548 [`93e88b0`]: https://github.com/rwf2/Rocket/commit/93e88b0 # Version 0.4.7 (Feb 09, 2021) ## Core * [[#1534], [`2059a6`]] Fixed a low-severity, minimal impact soundness issue in `uri::Formatter`. [#1534]: https://github.com/rwf2/Rocket/issues/1534 [`2059a6`]: https://github.com/rwf2/Rocket/commit/2059a6 # Version 0.4.6 (Nov 09, 2020) ## Core * [[`86bd7c`]] Added default and configurable read/write timeouts: `read_timeout` and `write_timeout`. * [[`c24a96`]] Added the `sse` feature, which [enables flushing] by returning `io::ErrorKind::WouldBlock`. ## Docs * Fixed broken doc links in `contrib`. * Fixed database library versions in `contrib` docs. ## Internal * Updated source code for Rust 2018. * UI tests now use `trybuild` instead of `compiletest-rs`. [`86bd7c`]: https://github.com/rwf2/Rocket/commit/86bd7c [`c24a96`]: https://github.com/rwf2/Rocket/commit/c24a96 [enables flushing]: https://api.rocket.rs/v0.4/rocket/response/struct.Stream.html#buffering-and-blocking # Version 0.4.5 (May 30, 2020) ## Core * [[#1312], [`89150f`]] Fixed a low-severity, minimal impact soundness issue in `LocalRequest::clone()`. * [[#1263], [`376f74`]] Fixed a cookie serialization issue that led to incorrect cookie deserialization in certain cases. * Removed dependency on `ring` for private cookies and thus Rocket, by default. * Added [`Origin::map_path()`] for manipulating `Origin` paths. * Added [`handler::Outcome::from_or_forward()`]. * Added [`Options::NormalizeDirs`] option to `StaticFiles`. * Improved accessibility of default error HTML. ## Docs * Fixed various typos. [#1312]: https://github.com/rwf2/Rocket/issues/1312 [`89150f`]: https://github.com/rwf2/Rocket/commit/89150f [#1263]: https://github.com/rwf2/Rocket/issues/1263 [`376f74`]: https://github.com/rwf2/Rocket/commit/376f74 [`Origin::map_path()`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Origin.html#method.map_path [`handler::Outcome::from_or_forward()`]: https://api.rocket.rs/v0.4/rocket/handler/type.Outcome.html#method.from_or_forward [`Options::NormalizeDirs`]: https://api.rocket.rs/v0.4/rocket_contrib/serve/struct.Options.html#associatedconstant.NormalizeDirs # Version 0.4.4 (Mar 09, 2020) ## Core * Removed use of unsupported `cfg(debug_assertions)` in `Cargo.toml`, allowing for builds on latest nightlies. ## Docs * Fixed various broken links. # Version 0.4.3 (Feb 29, 2020) ## Core * Added a new [`Debug`] `500` `Responder` that `Debug`-prints its contents on response. * Specialization on `Result` was deprecated. [`Debug`] can be used in place of non-`Responder` errors. * Fixed an issue that resulted in cookies not being set on error responses. * Various `Debug` implementations on Rocket types now respect formatting options. * Added `Responder`s for various HTTP status codes: [`NoContent`], [`Unauthorized`], [`Forbidden`], and [`Conflict`]. * `FromParam` is implemented for `NonZero` core types. ## Codegen * Docs for Rocket-generated macros are now hidden. * Generated code now works even when prelude imports like `Some`, `Ok`, and `Err` are shadowed. * Error messages referring to responder types in routes now point to the type correctly. ## Docs * All code examples in the guide are now tested and guaranteed to compile. * All macros are documented in the `core` crate; `rocket_codegen` makes no appearances. ## Infrastructure * CI was moved from Travis to Azure Pipelines; Windows support is tested. * Rocket's chat moved to [Matrix] and [Freenode]. [`Debug`]: https://api.rocket.rs/v0.4/rocket/response/struct.Debug.html [`NoContent`]: https://api.rocket.rs/v0.4/rocket/response/status/struct.NoContent.html [`Unauthorized`]: https://api.rocket.rs/v0.4/rocket/response/status/struct.Unauthorized.html [`Forbidden`]: https://api.rocket.rs/v0.4/rocket/response/status/struct.Forbidden.html [`Conflict`]: https://api.rocket.rs/v0.4/rocket/response/status/struct.Conflict.html [Matrix]: https://chat.mozilla.org/#/room/#rocket:mozilla.org [Freenode]: https://kiwiirc.com/client/chat.freenode.net/#rocket # Version 0.4.2 (Jun 28, 2019) ## Core * Replaced use of `FnBox` with `Box`. * Removed the stable feature gates `try_from` and `transpose_result`. * Derive macros are reexported alongside their respective traits. * Minimum required `rustc` is `1.35.0-nightly (2019-04-05)`. ## Codegen * `JsonValue` now implements `FromIterator`. * `non_snake_case` errors are silenced in generated code. * Minimum required `rustc` is `1.33.0-nightly (2019-01-03)`. ## Contrib * Allow setting custom ranks on `StaticFiles` via [`StaticFiles::rank()`]. * `MsgPack` correctly sets a MessagePack Content-Type on responses. ## Docs * Fixed typos across rustdocs and guide. * Documented library versions in contrib database documentation. ## Infrastructure * Updated internal dependencies to their latest versions. [`StaticFiles::rank()`]: https://api.rocket.rs/v0.4/rocket_contrib/serve/struct.StaticFiles.html#method.rank # Version 0.4.1 (May 11, 2019) ## Core * Rocket's default `Server` HTTP header no longer overrides a user-set header. * Fixed encoding and decoding of certain URI characters. ## Codegen * Compiler diagnostic information is more reliably produced. ## Contrib * Database pool types now implement `DerefMut`. * Added support for memcache connection pools. * Stopped depending on default features from core. ## Docs * Fixed many typos across the rustdocs and guide. * Added guide documentation on mounting more than one route at once. ## Infrastructure * Testing no longer requires "bootstrapping". * Removed deprecated `isatty` dependency in favor of `atty`. # Version 0.4.0 (Dec 06, 2018) ## New Features This release includes the following new features: * Introduced [Typed URIs]. * Introduced [ORM agnostic database support]. * Introduced [Request-Local State]. * Introduced mountable static-file serving via [`StaticFiles`]. * Introduced automatic [live template reloading]. * Introduced custom stateful handlers via [`Handler`]. * Introduced [transforming] data guards via [`FromData::transform()`]. * Introduced revamped [query string handling]. * Introduced the [`SpaceHelmet`] security and privacy headers fairing. * Private cookies are gated behind a `private-cookies` default feature. * Added [derive for `FromFormValue`]. * Added [derive for `Responder`]. * Added [`Template::custom()`] for customizing templating engines including registering filters and helpers. * Cookies are automatically tracked and propagated by [`Client`]. * Private cookies can be added to local requests with [`LocalRequest::private_cookie()`]. * Release builds default to the `production` environment. * Keep-alive can be configured via the `keep_alive` configuration parameter. * Allow CLI colors and emoji to be disabled with `ROCKET_CLI_COLORS=off`. * Route `format` accepts [shorthands] such as `json` and `html`. * Implemented [`Responder` for `Status`]. * Added [`Response::cookies()`] for retrieving response cookies. * All logging is disabled when `log` is set to `off`. * Added [`Metadata`] guard for retrieving templating information. * The [`Uri`] type parses URIs according to RFC 7230 into one of [`Origin`], [`Absolute`], or [`Authority`]. * Added [`Outcome::and_then()`], [`Outcome::failure_then()`], and [`Outcome::forward_then()`]. * Implemented `Responder` for `&[u8]`. * Any `T: Into>` can be [`mount()`]ed. * [Default rankings] range from -6 to -1, differentiating on static query strings. * Added [`Request::get_query_value()`] for retrieving a query value by key. * Applications can launch without a working directory. * Added [`State::from()`] for constructing `State` values. [`SpaceHelmet`]: https://api.rocket.rs/v0.4/rocket_contrib/helmet/index.html [`State::from()`]: https://api.rocket.rs/v0.4/rocket/struct.State.html#method.from [Typed URIs]: https://rocket.rs/v0.4/guide/responses/#typed-uris [ORM agnostic database support]: https://rocket.rs/v0.4/guide/state/#databases [`Template::custom()`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Template.html#method.custom [`LocalRequest::private_cookie()`]: https://api.rocket.rs/v0.4/rocket/local/struct.LocalRequest.html#method.private_cookie [`LocalRequest`]: https://api.rocket.rs/v0.4/rocket/local/struct.LocalRequest.html [shorthands]: https://api.rocket.rs/v0.4/rocket/http/struct.ContentType.html#method.parse_flexible [derive for `FromFormValue`]: https://api.rocket.rs/v0.4/rocket_codegen/derive.FromFormValue.html [derive for `Responder`]: https://api.rocket.rs/v0.4/rocket_codegen/derive.Responder.html [`Response::cookies()`]: https://api.rocket.rs/v0.4/rocket/struct.Response.html#method.cookies [`Client`]: https://api.rocket.rs/v0.4/rocket/local/struct.Client.html [Request-Local State]: https://rocket.rs/v0.4/guide/state/#request-local-state [`Metadata`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Metadata.html [`Uri`]: https://api.rocket.rs/v0.4/rocket/http/uri/enum.Uri.html [`Origin`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Origin.html [`Absolute`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Absolute.html [`Authority`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Authority.html [`Outcome::and_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.and_then [`Outcome::forward_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.forward_then [`Outcome::failure_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.failure_then [`StaticFiles`]: https://api.rocket.rs/v0.4/rocket_contrib/serve/struct.StaticFiles.html [live template reloading]: https://rocket.rs/v0.4/guide/responses/#live-reloading [`Handler`]: https://api.rocket.rs/v0.4/rocket/trait.Handler.html [`mount()`]: https://api.rocket.rs/v0.4/rocket/struct.Rocket.html#method.mount [`FromData::transform()`]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html#tymethod.transform [transforming]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html#transforming [query string handling]: https://rocket.rs/v0.4/guide/requests/#query-strings [Default rankings]: https://rocket.rs/v0.4/guide/requests/#default-ranking [`Request::get_query_value()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.get_query_value [`Responder` for `Status`]: https://rocket.rs/v0.4/guide/responses/#status ## Codegen Rewrite The [`rocket_codegen`] crate has been entirely rewritten using to-be-stable procedural macro APIs. We expect nightly breakages to drop dramatically, likely to zero, as a result. The new prelude import for Rocket applications is: ```diff - #![feature(plugin)] - #![plugin(rocket_codegen)] + #![feature(proc_macro_hygiene, decl_macro)] - extern crate rocket; + #[macro_use] extern crate rocket; ``` The [`rocket_codegen`] crate should **_not_** be a direct dependency. Remove it from your `Cargo.toml`: ```diff [dependencies] - rocket = "0.3" + rocket = "0.4" - rocket_codegen = "0.3" ``` [`rocket_codegen`]: https://api.rocket.rs/v0.4/rocket_codegen/index.html ## Breaking Changes This release includes many breaking changes. These changes are listed below along with a short note about how to handle the breaking change in existing applications when applicable. * **Route and catcher attributes respect function privacy.** To mount a route or register a catcher outside of the module it is declared, ensure that the handler function is marked `pub` or `crate`. * **Query handling syntax has been completely revamped.** A query parameter of `` is now ``. Consider whether your application benefits from the revamped [query string handling]. * **The `#[error]` attribute and `errors!` macro were removed.** Use `#[catch]` and `catchers!` instead. * **`Rocket::catch()` was renamed to [`Rocket::register()`].** Change calls of the form `.catch(errors![..])` to `.register(catchers![..])`. * **The `#[catch]` attribute only accepts functions with 0 or 1 argument.** Ensure the argument to the catcher, if any, is of type `&Request`. * **[`json!`] returns a [`JsonValue`], no longer needs wrapping.** Change instances of `Json(json!(..))` to `json!` and change the corresponding type to `JsonValue`. * **All environments default to port 8000.** Manually configure a port of `80` for the `stage` and `production` environments for the previous behavior. * **Release builds default to the production environment.** Manually set the environment to `debug` with `ROCKET_ENV=debug` for the previous behavior. * **[`Form`] and [`LenientForm`] lost a lifetime parameter, `get()` method.** Change a type of `Form<'a, T<'a>>` to `Form` or `Form>`. `Form` and `LenientForm` now implement `Deref`, allowing for calls to `.get()` to be removed. * **[`ring`] was updated to 0.13.** Ensure all transitive dependencies to `ring` refer to version `0.13`. * **`Uri` was largely replaced by [`Origin`].** In general, replace the type `Uri` with `Origin`. The `base` and `uri` fields of [`Route`] are now of type [`Origin`]. The `&Uri` guard is now `&Origin`. [`Request::uri()`] now returns an [`Origin`]. * **All items in [`rocket_contrib`] are namespaced behind modules.** * `Json` is now `json::Json` * `MsgPack` is now `msgpack::MsgPack` * `MsgPackError` is now `msgpack::Error` * `Template` is now `templates::Template` * `UUID` is now `uuid::Uuid` * `Value` is replaced by `json::JsonValue` * **TLS certificates require the `subjectAltName` extension.** Ensure that your TLS certificates contain the `subjectAltName` extension with a value set to your domain. * **Route paths, mount points, and [`LocalRequest`] URIs are strictly checked.** Ensure your mount points are absolute paths with no parameters, ensure your route paths are absolute paths with proper parameter syntax, and ensure that paths passed to `LocalRequest` are valid. * **[`Template::show()`] takes an `&Rocket`, doesn't accept a `root`.** Use [`client.rocket()`] to get a reference to an instance of `Rocket` when testing. Use [`Template::render()`] in routes. * **[`Request::remote()`] returns the _actual_ remote IP, doesn't rewrite.** Use [`Request::real_ip()`] or [`Request::client_ip()`] to retrieve the IP address from the "X-Real-IP" header if it is present. * **[`Bind`] variant was added to [`LaunchErrorKind`].** Ensure matches on `LaunchErrorKind` include or ignore the `Bind` variant. * **Cookies are automatically tracked and propagated by [`Client`].** For the previous behavior, construct a `Client` with [`Client::untracked()`]. * **`UUID` was renamed to [`Uuid`].** Use `Uuid` instead of `UUID`. * **`LocalRequest::cloned_dispatch()` was removed.** Chain calls to `.clone().dispatch()` for the previous behavior. * **[`Redirect`] constructors take a generic type of `T: TryInto>`.** A call to a `Redirect` constructor with a non-`'static` `&str` of the form `Redirect::to(string)` should become `Redirect::to(string.to_string())`, heap-allocating the string before being passed to the constructor. * **The [`FromData`] impl for [`Form`] and [`LenientForm`] now return an error of type [`FormDataError`].** On non-I/O errors, the form string is stored in the variant as an `&'f str`. * **[`Missing`] variant was added to [`ConfigError`].** Ensure matches on `ConfigError` include or ignore the `Missing` variant. * **The [`FromData`] impl for [`Json`] now returns an error of type [`JsonError`].** The previous `SerdeError` is now the `.1` member of the `JsonError` `enum`. Match and destruct the variant for the previous behavior. * **[`FromData`] is now emulated by [`FromDataSimple`].** Change _implementations_, not uses, of `FromData` to `FromDataSimple`. Consider whether your implementation could benefit from [transformations]. * **[`FormItems`] iterates over values of type [`FormItem`].** Map using `.map(|item| item.key_value())` for the previous behavior. * **[`LaunchErrorKind::Collision`] contains a vector of the colliding routes.** Destruct using `LaunchErrorKind::Collision(..)` to ignore the vector. * **[`Request::get_param()`] and [`Request::get_segments()`] are indexed by _segment_, not dynamic parameter.** Modify the `n` argument in calls to these functions appropriately. * **Method-based route attributes no longer accept a keyed `path` parameter.** Change an attribute of the form `#[get(path = "..")]` to `#[get("..")]`. * **[`Json`] and [`MsgPack`] data guards no longer reject requests with an unexpected Content-Type** To approximate the previous behavior, add a `format = "json"` route parameter when using `Json` or `format = "msgpack"` when using `MsgPack`. * **Implemented [`Responder` for `Status`]. Removed `Failure`, `status::NoContent`, and `status::Reset` responders.** Replace uses of `Failure(status)` with `status` directly. Replace `status::NoContent` with `Status::NoContent`. Replace `status::Reset` with `Status::ResetContent`. * **[`Config::root()`] returns an `Option<&Path>` instead of an `&Path`.** For the previous behavior, use `config.root().unwrap()`. * **[`Status::new()`] is no longer `const`.** Construct a `Status` directly. * **[`Config`] constructors return a `Config` instead of a `Result`.** * **`ConfigError::BadCWD`, `Config.config_path` were removed.** * **[`Json`] no longer has a default value for its type parameter.** * **Using `data` on a non-payload method route is a warning instead of error.** * **The `raw_form_string` method of [`Form`] and [`LenientForm`] was removed.** * **Various impossible `Error` associated types are now set to `!`.** * **All [`AdHoc`] constructors require a name as the first parameter.** * **The top-level `Error` type was removed.** [`LaunchErrorKind::Collision`]: https://api.rocket.rs/v0.4/rocket/error/enum.LaunchErrorKind.html#variant.Collision [`json!`]: https://api.rocket.rs/v0.4/rocket_contrib/macro.json.html [`JsonValue`]: https://api.rocket.rs/v0.4/rocket_contrib/json/struct.JsonValue.html [`Json`]: https://api.rocket.rs/v0.4/rocket_contrib/json/struct.Json.html [`ring`]: https://crates.io/crates/ring [`Template::show()`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Template.html#method.show [`Template::render()`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Template.html#method.render [`client.rocket()`]: https://api.rocket.rs/v0.4/rocket/local/struct.Client.html#method.rocket [`Request::remote()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.remote [`Request::real_ip()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.real_ip [`Request::client_ip()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.client_ip [`Bind`]: https://api.rocket.rs/v0.4/rocket/error/enum.LaunchErrorKind.html#variant.Bind [`LaunchErrorKind`]: https://api.rocket.rs/v0.4/rocket/error/enum.LaunchErrorKind.html [`Client::untracked()`]: https://api.rocket.rs/v0.4/rocket/local/struct.Client.html#method.untracked [`Uuid`]: https://api.rocket.rs/v0.4/rocket_contrib/uuid/struct.Uuid.html [`Route`]: https://api.rocket.rs/v0.4/rocket/struct.Route.html [`Redirect`]: https://api.rocket.rs/v0.4/rocket/response/struct.Redirect.html [`Request::uri()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.uri [`FormDataError`]: https://api.rocket.rs/v0.4/rocket/request/enum.FormDataError.html [`FromData`]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html [`Form`]: https://api.rocket.rs/v0.4/rocket/request/struct.Form.html [`LenientForm`]: https://api.rocket.rs/v0.4/rocket/request/struct.LenientForm.html [`AdHoc`]: https://api.rocket.rs/v0.4/rocket/fairing/struct.AdHoc.html [`Missing`]: https://api.rocket.rs/v0.4/rocket/config/enum.ConfigError.html#variant.Missing [`ConfigError`]: https://api.rocket.rs/v0.4/rocket/config/enum.ConfigError.html [`Rocket::register()`]: https://api.rocket.rs/v0.4/rocket/struct.Rocket.html#method.register [`JsonError`]: https://api.rocket.rs/v0.4/rocket_contrib/json/enum.JsonError.html [transformations]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html#transforming [`FromDataSimple`]: https://api.rocket.rs/v0.4/rocket/data/trait.FromDataSimple.html [`Request::get_param()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.get_param [`Request::get_segments()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.get_segments [`FormItem`]: https://api.rocket.rs/v0.4/rocket/request/struct.FormItem.html [`rocket_contrib`]: https://api.rocket.rs/v0.4/rocket_contrib/index.html [`MsgPack`]: https://api.rocket.rs/v0.4/rocket_contrib/msgpack/struct.MsgPack.html [`Status::new()`]: https://api.rocket.rs/v0.4/rocket/http/struct.Status.html#method.new [`Config`]: https://api.rocket.rs/v0.4/rocket/struct.Config.html [`Config::root()`]: https://api.rocket.rs/v0.4/rocket/struct.Config.html#method.root ## General Improvements In addition to new features, Rocket saw the following improvements: * Log messages now refer to routes by name. * Collision errors on launch name the colliding routes. * Launch fairing failures refer to the failing fairing by name. * The default `403` catcher now references authorization, not authentication. * Private cookies are set to `HttpOnly` and are given an expiration date of 1 week by default. * A [Tera templates example] was added. * All macros, derives, and attributes are individually documented in [`rocket_codegen`]. * Invalid client requests receive a response of `400` instead of `500`. * Response bodies are reliably stripped on `HEAD` requests. * Added a default catcher for `504: Gateway Timeout`. * Configuration information is logged in all environments. * Use of `unsafe` was reduced from 9 to 2 in core library. * [`FormItems`] now parses empty keys and values as well as keys without values. * Added [`Config::active()`] as a shorthand for `Config::new(Environment::active()?)`. * Address/port binding errors at launch are detected and explicitly emitted. * [`Flash`] cookies are cleared only after they are inspected. * `Sync` bound on [`AdHoc::on_attach()`], [`AdHoc::on_launch()`] was removed. * [`AdHoc::on_attach()`], [`AdHoc::on_launch()`] accept an `FnOnce`. * Added [`Config::root_relative()`] for retrieving paths relative to the configuration file. * Added [`Config::tls_enabled()`] for determining whether TLS is actively enabled. * ASCII color codes are not emitted on versions of Windows that do not support them. * Added FLAC (`audio/flac`), Icon (`image/x-icon`), WEBA (`audio/webm`), TIFF (`image/tiff`), AAC (`audio/aac`), Calendar (`text/calendar`), MPEG (`video/mpeg`), TAR (`application/x-tar`), GZIP (`application/gzip`), MOV (`video/quicktime`), MP4 (`video/mp4`), ZIP (`application/zip`) as known media types. * Added `.weba` (`WEBA`), `.ogv` (`OGG`), `.mp4` (`MP4`), `.mpeg4` (`MP4`), `.aac` (`AAC`), `.ics` (`Calendar`), `.bin` (`Binary`), `.mpg` (`MPEG`), `.mpeg` (`MPEG`), `.tar` (`TAR`), `.gz` (`GZIP`), `.tif` (`TIFF`), `.tiff` (`TIFF`), `.mov` (`MOV`) as known extensions. * Interaction between route attributes and declarative macros has been improved. * Generated code now logs through logging infrastructures as opposed to using `println!`. * Routing has been optimized by caching routing metadata. * [`Form`] and [`LenientForm`] can be publicly constructed. * Console coloring uses default terminal colors instead of white. * Console coloring is consistent across all messages. * `i128` and `u128` now implement [`FromParam`], [`FromFormValue`]. * The `base64` dependency was updated to `0.10`. * The `log` dependency was updated to `0.4`. * The `handlebars` dependency was updated to `1.0`. * The `tera` dependency was updated to `0.11`. * The `uuid` dependency was updated to `0.7`. * The `rustls` dependency was updated to `0.14`. * The `cookie` dependency was updated to `0.11`. [Tera templates example]: https://github.com/rwf2/Rocket/tree/v0.4/examples/tera_templates [`FormItems`]: https://api.rocket.rs/v0.4/rocket/request/enum.FormItems.html [`Config::active()`]: https://api.rocket.rs/v0.4/rocket/config/struct.Config.html#method.active [`Flash`]: https://api.rocket.rs/v0.4/rocket/response/struct.Flash.html [`AdHoc::on_attach()`]: https://api.rocket.rs/v0.4/rocket/fairing/struct.AdHoc.html#method.on_attach [`AdHoc::on_launch()`]: https://api.rocket.rs/v0.4/rocket/fairing/struct.AdHoc.html#method.on_launch [`Config::root_relative()`]: https://api.rocket.rs/v0.4/rocket/struct.Config.html#method.root_relative [`Config::tls_enabled()`]: https://api.rocket.rs/v0.4/rocket/struct.Config.html#method.tls_enabled [`rocket_codegen`]: https://api.rocket.rs/v0.4/rocket_codegen/index.html [`FromParam`]: https://api.rocket.rs/v0.4/rocket/request/trait.FromParam.html [`FromFormValue`]: https://api.rocket.rs/v0.4/rocket/request/trait.FromFormValue.html [`Data`]: https://api.rocket.rs/v0.4/rocket/struct.Data.html ## Infrastructure * All documentation is versioned. * Previous, current, and development versions of all documentation are hosted. * The repository was reorganized with top-level directories of `core` and `contrib`. * The `http` module was split into its own `rocket_http` crate. This is an internal change only. * All uses of `unsafe` are documented with informal proofs of correctness. # Version 0.3.16 (Aug 24, 2018) ## Codegen * Codegen was updated for `2018-08-23` nightly. * Minimum required `rustc` is `1.30.0-nightly 2018-08-23`. ## Core * Force close only the read end of connections. This allows responses to be sent even when the client transmits more data than expected. ## Docs * Add details on retrieving configuration extras to guide. # Version 0.3.15 (Jul 16, 2018) ## Codegen * The `#[catch]` decorator and `catchers!` macro were introduced, replacing `#[error]` and `errors!`. * The `#[error]` decorator and `errors!` macro were deprecated. * Codegen was updated for `2018-07-15` nightly. * Minimum required `rustc` is `1.29.0-nightly 2018-07-15`. # Version 0.3.14 (Jun 22, 2018) ## Codegen * Codegen was updated for `2018-06-22` nightly. * Minimum required `rustc` is `1.28.0-nightly 2018-06-22`. # Version 0.3.13 (Jun 16, 2018) ## Codegen * Codegen was updated for `2018-06-12` nightly. * Minimum required `rustc` is `1.28.0-nightly 2018-06-12`. # Version 0.3.12 (May 31, 2018) ## Codegen * Codegen was updated for `2018-05-30` nightly. * Minimum required `rustc` is `1.28.0-nightly 2018-05-30`. # Version 0.3.11 (May 19, 2018) ## Core * Core was updated for `2018-05-18` nightly. ## Infrastructure * Fixed injection of dependencies for codegen compile-fail tests. # Version 0.3.10 (May 05, 2018) ## Core * Fixed parsing of nested TOML structures in config environment variables. ## Codegen * Codegen was updated for `2018-05-03` nightly. * Minimum required `rustc` is `1.27.0-nightly 2018-05-04`. ## Contrib * Contrib was updated for `2018-05-03` nightly. ## Docs * Fixed database pool type in state guide. # Version 0.3.9 (Apr 26, 2018) ## Core * Core was updated for `2018-04-26` nightly. * Minimum required `rustc` is `1.27.0-nightly 2018-04-26`. * Managed state retrieval cost was reduced to an unsynchronized `HashMap` lookup. ## Codegen * Codegen was updated for `2018-04-26` nightly. * Minimum required `rustc` is `1.27.0-nightly 2018-04-26`. ## Contrib * A 512-byte buffer is preallocated when deserializing JSON, improving performance. ## Docs * Fixed various typos in rustdocs and guide. # Version 0.3.8 (Apr 07, 2018) ## Codegen * Codegen was updated for `2018-04-06` nightly. * Minimum required `rustc` is `1.27.0-nightly 2018-04-06`. # Version 0.3.7 (Apr 03, 2018) ## Core * Fixed a bug where incoming request URIs would match routes with the same path prefix and suffix and ignore the rest. * Added known media types for WASM, WEBM, OGG, and WAV. * Fixed fragment URI parsing. ## Codegen * Codegen was updated for `2018-04-03` nightly. * Minimum required `rustc` is `1.27.0-nightly 2018-04-03`. ## Contrib * JSON data is read eagerly, improving deserialization performance. ## Docs * Database example and docs were updated for Diesel 1.1. * Removed outdated README performance section. * Fixed various typos in rustdocs and guide. ## Infrastructure * Removed gates for stabilized features: `iterator_for_each`, `i128_type`, `conservative_impl_trait`, `never_type`. * Travis now tests in both debug and release mode. # Version 0.3.6 (Jan 12, 2018) ## Core * `Rocket.state()` method was added to retrieve managed state from `Rocket` instances. * Nested calls to `Rocket.attach()` are now handled correctly. * JSON API (`application/vnd.api+json`) is now a known media type. * Uncached markers for `ContentType` and `Accept` headers are properly preserved on `Request.clone()`. * Minimum required `rustc` is `1.25.0-nightly 2018-01-12`. ## Codegen * Codegen was updated for `2017-12-22` nightly. * Minimum required `rustc` is `1.24.0-nightly 2017-12-22`. ## Docs * Fixed typo in state guide: ~~simple~~ simply. * Database example and docs were updated for Diesel 1.0. ## Infrastructure * Shell scripts now use `git grep` instead of `egrep` for faster searching. # Version 0.3.5 (Dec 18, 2017) ## Codegen * Codegen was updated for `2017-12-17` nightly. * Minimum required `rustc` is `1.24.0-nightly 2017-12-17`. # Version 0.3.4 (Dec 14, 2017) ## Core * `NamedFile`'s `Responder` implementation now uses a sized body when the file's length is known. * `#[repr(C)]` is used on `str` wrappers to guarantee correct structure layout across platforms. * A `status::BadRequest` `Responder` was added. ## Codegen * Codegen was updated for `2017-12-13` nightly. * Minimum required `rustc` is `1.24.0-nightly 2017-12-13`. ## Docs * The rustdoc `html_root_url` now points to the correct address. * Fixed typo in fairings guide: ~~event~~ events. * Fixed typo in `Outcome` docs: ~~users~~ Users. # Version 0.3.3 (Sep 25, 2017) ## Core * `Config`'s `Debug` implementation now respects formatting options. * `Cow` now implements `FromParam`. * `Vec` now implements `Responder`. * Added a `Binary` media type for `application/octet-stream`. * Empty fairing collections are no longer logged. * Emojis are no longer emitted to non-terminals. * Minimum required `rustc` is `1.22.0-nightly 2017-09-13`. ## Codegen * Improved "missing argument in handler" compile-time error message. * Codegen was updated for `2017-09-25` nightly. * Minimum required `rustc` is `1.22.0-nightly 2017-09-25`. ## Docs * Fixed typos in site overview: ~~by~~ be, ~~`Reponder`~~ `Responder`. * Markdown indenting was adjusted for CommonMark. ## Infrastructure * Shell scripts handle paths with spaces. # Version 0.3.2 (Aug 15, 2017) ## Core * Added conversion methods from and to `Box`. ## Codegen * Lints were removed due to compiler instability. Lints will likely return as a separate `rocket_lints` crate. # Version 0.3.1 (Aug 11, 2017) ## Core * Added support for ASCII colors on modern Windows consoles. * Form field renames can now include _any_ valid characters, not just idents. ## Codegen * Ignored named route parameters are now allowed (`_ident`). * Fixed issue where certain paths would cause a lint `assert!` to fail ([#367](https://github.com/rwf2/Rocket/issues/367)). * Lints were updated for `2017-08-10` nightly. * Minimum required `rustc` is `1.21.0-nightly (2017-08-10)`. ## Contrib * Tera errors that were previously skipped internally are now emitted. ## Documentation * Typos were fixed across the board. # Version 0.3.0 (Jul 14, 2017) ## New Features This release includes the following new features: * [Fairings], Rocket's structure middleware, were introduced. * [Native TLS support] was introduced. * [Private cookies] were introduced. * A [`MsgPack`] type has been added to [`contrib`] for simple consumption and returning of MessagePack data. * Launch failures ([`LaunchError`]) from [`Rocket::launch()`] are now returned for inspection without panicking. * Routes without query parameters now match requests with or without query parameters. * [Default rankings] range from -4 to -1, preferring static paths and routes with query string matches. * A native [`Accept`] header structure was added. * The [`Accept`] request header can be retrieved via [`Request::accept()`]. * Incoming form fields [can be renamed] via a new `#[form(field = "name")]` structure field attribute. * All active routes can be retrieved via [`Rocket::routes()`]. * [`Response::body_string()`] was added to retrieve the response body as a `String`. * [`Response::body_bytes()`] was added to retrieve the response body as a `Vec`. * [`Response::content_type()`] was added to easily retrieve the Content-Type header of a response. * Size limits on incoming data are [now configurable](https://rocket.rs/v0.3/guide/configuration/#data-limits). * [`Request::limits()`] was added to retrieve incoming data limits. * Responders may dynamically adjust their response based on the incoming request. * [`Request::guard()`] was added for simple retrieval of request guards. * [`Request::route()`] was added to retrieve the active route, if any. * `&Route` is now a request guard. * The base mount path of a [`Route`] can be retrieved via `Route::base` or `Route::base()`. * [`Cookies`] supports _private_ (authenticated encryption) cookies, encrypted with the `secret_key` config key. * `Config::{development, staging, production}` constructors were added for [`Config`]. * [`Config::get_datetime()`] was added to retrieve an extra as a `Datetime`. * Forms can be now parsed _leniently_ via the new [`LenientForm`] data guard. * The `?` operator can now be used with `Outcome`. * Quoted string, array, and table based [configuration parameters] can be set via environment variables. * Log coloring is disabled when `stdout` is not a TTY. * [`FromForm`] is implemented for `Option`, `Result`. * The [`NotFound`] responder was added for simple **404** response construction. [Fairings]: https://rocket.rs/v0.3/guide/fairings/ [Native TLS support]: https://rocket.rs/v0.3/guide/configuration/#configuring-tls [Private cookies]: https://rocket.rs/v0.3/guide/requests/#private-cookies [can be renamed]: https://rocket.rs/v0.3/guide/requests/#field-renaming [`MsgPack`]: https://api.rocket.rs/v0.3/rocket_contrib/struct.MsgPack.html [`Rocket::launch()`]: https://api.rocket.rs/v0.3/rocket/struct.Rocket.html#method.launch [`LaunchError`]: https://api.rocket.rs/v0.3/rocket/error/struct.LaunchError.html [Default rankings]: https://api.rocket.rs/v0.3/rocket/struct.Route.html [`Route`]: https://api.rocket.rs/v0.3/rocket/struct.Route.html [`Accept`]: https://api.rocket.rs/v0.3/rocket/http/struct.Accept.html [`Request::accept()`]: https://api.rocket.rs/v0.3/rocket/struct.Request.html#method.accept [`contrib`]: https://api.rocket.rs/v0.3/rocket_contrib/ [`Rocket::routes()`]: https://api.rocket.rs/v0.3/rocket/struct.Rocket.html#method.routes [`Response::body_string()`]: https://api.rocket.rs/v0.3/rocket/struct.Response.html#method.body_string [`Response::body_bytes()`]: https://api.rocket.rs/v0.3/rocket/struct.Response.html#method.body_bytes [`Response::content_type()`]: https://api.rocket.rs/v0.3/rocket/struct.Response.html#method.content_type [`Request::guard()`]: https://api.rocket.rs/v0.3/rocket/struct.Request.html#method.guard [`Request::limits()`]: https://api.rocket.rs/v0.3/rocket/struct.Request.html#method.limits [`Request::route()`]: https://api.rocket.rs/v0.3/rocket/struct.Request.html#method.route [`Config`]: https://api.rocket.rs/v0.3/rocket/struct.Config.html [`Cookies`]: https://api.rocket.rs/v0.3/rocket/http/enum.Cookies.html [`Config::get_datetime()`]: https://api.rocket.rs/v0.3/rocket/struct.Config.html#method.get_datetime [`LenientForm`]: https://api.rocket.rs/v0.3/rocket/request/struct.LenientForm.html [configuration parameters]: https://api.rocket.rs/v0.3/rocket/config/index.html#environment-variables [`NotFound`]: https://api.rocket.rs/v0.3/rocket/response/status/struct.NotFound.html ## Breaking Changes This release includes many breaking changes. These changes are listed below along with a short note about how to handle the breaking change in existing applications. * **`session_key` was renamed to `secret_key`, requires a 256-bit base64 key** It's unlikely that `session_key` was previously used. If it was, rename `session_key` to `secret_key`. Generate a random 256-bit base64 key using a tool like openssl: `openssl rand -base64 32`. * **The `&Cookies` request guard has been removed in favor of `Cookies`** Change `&Cookies` in a request guard position to `Cookies`. * **`Rocket::launch()` now returns a `LaunchError`, doesn't panic.** For the old behavior, suffix a call to `.launch()` with a semicolon: `.launch();`. * **Routes without query parameters match requests with or without query parameters.** There is no workaround, but this change may allow manual ranks from routes to be removed. * **The `format` route attribute on non-payload requests matches against the Accept header.** Excepting a custom request guard, there is no workaround. Previously, `format` always matched against the Content-Type header, regardless of whether the request method indicated a payload or not. * **A type of `&str` can no longer be used in form structures or parameters.** Use the new [`&RawStr`] type instead. * **`ContentType` is no longer a request guard.** Use `&ContentType` instead. * **`Request::content_type()` returns `&ContentType` instead of `ContentType`.** Use `.clone()` on `&ContentType` if a type of `ContentType` is required. * **`Response::header_values()` was removed. `Response::headers()` now returns an `&HeaderMap`.** A call to `Response::headers()` can be replaced with `Response::headers().iter()`. A call to `Response::header_values(name)` can be replaced with `Response::headers().get(name)`. * **Route collisions result in a hard error and panic.** There is no workaround. Previously, route collisions were a warning. * **The [`IntoOutcome`] trait has been expanded and made more flexible.** There is no workaround. `IntoOutcome::into_outcome()` now takes a `Failure` value to use. `IntoOutcome::or_forward()` was added to return a `Forward` outcome if `self` indicates an error. * **The 'testing' feature was removed.** Remove `features = ["testing"]` from `Cargo.toml`. Use the new [`local`] module for testing. * **`serde` was updated to 1.0.** There is no workaround. Ensure all dependencies rely on `serde` `1.0`. * **`config::active()` was removed.** Use [`Rocket::config()`] to retrieve the configuration before launch. If needed, use [managed state] to store config information for later use. * **The [`Responder`] trait has changed.** `Responder::respond(self)` was removed in favor of `Responder::respond_to(self, &Request)`. Responders may dynamically adjust their response based on the incoming request. * **`Outcome::of(Responder)` was removed while `Outcome::from(&Request, Responder)` was added.** Use `Outcome::from(..)` instead of `Outcome::of(..)`. * **Usage of templates requires `Template::fairing()` to be attached.** Call `.attach(Template::fairing())` on the application's Rocket instance before launching. * **The `Display` implementation of `Template` was removed.** Use [`Template::show()`] to render a template directly. * **`Request::new()` is no longer exported.** There is no workaround. * **The [`FromForm`] trait has changed.** `Responder::from_form_items(&mut FormItems)` was removed in favor of `Responder::from_form(&mut FormItems, bool)`. The second parameter indicates whether parsing should be strict (if `true`) or lenient (if `false`). * **`LoggingLevel` was removed as a root reexport.** It can now be imported from `rocket::config::LoggingLevel`. * **An `Io` variant was added to [`ConfigError`].** Ensure `match`es on `ConfigError` include an `Io` variant. * **[`ContentType::from_extension()`] returns an `Option`.** For the old behavior, use `.unwrap_or(ContentType::Any)`. * **The `IntoValue` config trait was removed in favor of `Into`.** There is no workaround. Use `Into` as necessary. * **The `rocket_contrib::JSON` type has been renamed to [`rocket_contrib::Json`].** Use `Json` instead of `JSON`. * **All structs in the [`content`] module use TitleCase names.** Use `Json`, `Xml`, `Html`, and `Css` instead of `JSON`, `XML`, `HTML`, and `CSS`, respectively. [`&RawStr`]: https://api.rocket.rs/v0.3/rocket/http/struct.RawStr.html [`IntoOutcome`]: https://api.rocket.rs/v0.3/rocket/outcome/trait.IntoOutcome.html [`local`]: https://api.rocket.rs/v0.3/rocket/local/index.html [`Rocket::config()`]: https://api.rocket.rs/v0.3/rocket/struct.Rocket.html#method.config [managed state]: https://rocket.rs/v0.3/guide/state/ [`Responder`]: https://api.rocket.rs/v0.3/rocket/response/trait.Responder.html [`Template::show()`]: https://api.rocket.rs/v0.3/rocket_contrib/struct.Template.html#method.show [`FromForm`]: https://api.rocket.rs/v0.3/rocket/request/trait.FromForm.html [`ConfigError`]: https://api.rocket.rs/v0.3/rocket/config/enum.ConfigError.html [`ContentType::from_extension()`]: https://api.rocket.rs/v0.3/rocket/http/struct.ContentType.html#method.from_extension [`rocket_contrib::Json`]: https://api.rocket.rs/v0.3/rocket_contrib/struct.Json.html [`content`]: https://api.rocket.rs/v0.3/rocket/response/content/index.html ## General Improvements In addition to new features, Rocket saw the following improvements: * "Rocket" is now capitalized in the `Server` HTTP header. * The generic parameter of `rocket_contrib::Json` defaults to `json::Value`. * The trailing '...' in the launch message was removed. * The launch message prints regardless of the config environment. * For debugging, `FromData` is implemented for `Vec` and `String`. * The port displayed on launch is the port resolved, not the one configured. * The `uuid` dependency was updated to `0.5`. * The `base64` dependency was updated to `0.6`. * The `toml` dependency was updated to `0.4`. * The `handlebars` dependency was updated to `0.27`. * The `tera` dependency was updated to `0.10`. * [`yansi`] is now used for all terminal coloring. * The `dev` `rustc` release channel is supported during builds. * [`Config`] is now exported from the root. * [`Request`] implements `Clone` and `Debug`. * The `workers` config parameter now defaults to `num_cpus * 2`. * Console logging for table-based config values is improved. * `PartialOrd`, `Ord`, and `Hash` are now implemented for [`State`]. * The format of a request is always logged when available. * Route matching on `format` now functions as documented. [`yansi`]: https://crates.io/crates/yansi [`Request`]: https://api.rocket.rs/v0.3/rocket/struct.Request.html [`State`]: https://api.rocket.rs/v0.3/rocket/struct.State.html ## Infrastructure * All examples include a test suite. * The `master` branch now uses a `-dev` version number. # Version 0.2.8 (Jun 01, 2017) ## Codegen * Lints were updated for `2017-06-01` nightly. * Minimum required `rustc` is `1.19.0-nightly (2017-06-01)`. # Version 0.2.7 (May 26, 2017) ## Codegen * Codegen was updated for `2017-05-26` nightly. # Version 0.2.6 (Apr 17, 2017) ## Codegen * Allow `k` and `v` to be used as fields in `FromForm` structures by avoiding identifier collisions ([#265]). [#265]: https://github.com/rwf2/Rocket/issues/265 # Version 0.2.5 (Apr 16, 2017) ## Codegen * Lints were updated for `2017-04-15` nightly. * Minimum required `rustc` is `1.18.0-nightly (2017-04-15)`. # Version 0.2.4 (Mar 30, 2017) ## Codegen * Codegen was updated for `2017-03-30` nightly. * Minimum required `rustc` is `1.18.0-nightly (2017-03-30)`. # Version 0.2.3 (Mar 22, 2017) ## Fixes * Multiple header values for the same header name are now properly preserved (#223). ## Core * The `get_slice` and `get_table` methods were added to `Config`. * The `pub_restricted` feature has been stabilized! ## Codegen * Lints were updated for `2017-03-20` nightly. * Minimum required `rustc` is `1.17.0-nightly (2017-03-22)`. ## Infrastructure * The test script now denies trailing whitespace. # Version 0.2.2 (Feb 26, 2017) ## Codegen * Lints were updated for `2017-02-25` and `2017-02-26` nightlies. * Minimum required `rustc` is `1.17.0-nightly (2017-02-26)`. # Version 0.2.1 (Feb 24, 2017) ## Core Fixes * `Flash` cookie deletion functions as expected regardless of the path. * `config` properly accepts IPv6 addresses. * Multiple `Set-Cookie` headers are properly set. ## Core Improvements * `Display` and `Error` were implemented for `ConfigError`. * `webp`, `ttf`, `otf`, `woff`, and `woff2` were added as known content types. * Routes are presorted for faster routing. * `into_bytes` and `into_inner` methods were added to `Body`. ## Codegen * Fixed `unmanaged_state` lint so that it works with prefilled type aliases. ## Contrib * Better errors are emitted on Tera template parse errors. ## Documentation * Fixed typos in `manage` and `JSON` docs. ## Infrastructure * Updated doctests for latest Cargo nightly. # Version 0.2.0 (Feb 06, 2017) Detailed release notes for v0.2 can also be found on [rocket.rs](https://rocket.rs/v0.3/news/2017-02-06-version-0.2/). ## New Features This release includes the following new features: * Introduced managed state. * Added lints that warn on unmanaged state and unmounted routes. * Added the ability to set configuration parameters via environment variables. * `Config` structures can be built via `ConfigBuilder`, which follows the builder pattern. * Logging can be enabled or disabled on custom configuration via a second parameter to the `Rocket::custom` method. * `name` and `value` methods were added to `Header` to retrieve the name and value of a header. * A new configuration parameter, `workers`, can be used to set the number of threads Rocket uses. * The address of the remote connection is available via `Request.remote()`. Request preprocessing overrides remote IP with value from the `X-Real-IP` header, if present. * During testing, the remote address can be set via `MockRequest.remote()`. * The `SocketAddr` request guard retrieves the remote address. * A `UUID` type has been added to `contrib`. * `rocket` and `rocket_codegen` will refuse to build with an incompatible nightly version and emit nice error messages. * Major performance and usability improvements were upstreamed to the `cookie` crate, including the addition of a `CookieBuilder`. * When a checkbox isn't present in a form, `bool` types in a `FromForm` structure will parse as `false`. * The `FormItems` iterator can be queried for a complete parse via `completed` and `exhausted`. * Routes for `OPTIONS` requests can be declared via the `options` decorator. * Strings can be percent-encoded via `URI::percent_encode()`. ## Breaking Changes This release includes several breaking changes. These changes are listed below along with a short note about how to handle the breaking change in existing applications. * **`Rocket::custom` takes two parameters, the first being `Config` by value.** A call in v0.1 of the form `Rocket::custom(&config)` is now `Rocket::custom(config, false)`. * **Tera templates are named without their extension.** A templated named `name.html.tera` is now simply `name`. * **`JSON` `unwrap` method has been renamed to `into_inner`.** A call to `.unwrap()` should be changed to `.into_inner()`. * **The `map!` macro was removed in favor of the `json!` macro.** A call of the form `map!{ "a" => b }` can be written as: `json!({ "a": b })`. * **The `hyper::SetCookie` header is no longer exported.** Use the `Cookie` type as an `Into

` type directly. * **The `Content-Type` for `String` is now `text/plain`.** Use `content::HTML` for HTML-based `String` responses. * **`Request.content_type()` returns an `Option`.** Use `.unwrap_or(ContentType::Any)` to get the old behavior. * **The `ContentType` request guard forwards when the request has no `Content-Type` header.** Use an `Option` and `.unwrap_or(ContentType::Any)` for the old behavior. * **A `Rocket` instance must be declared _before_ a `MockRequest`.** Change the order of the `rocket::ignite()` and `MockRequest::new()` calls. * **A route with `format` specified only matches requests with the same format.** Previously, a route with a `format` would match requests without a format specified. There is no workaround to this change; simply specify formats when required. * **`FormItems` can no longer be constructed directly.** Instead of constructing as `FormItems(string)`, construct as `FormItems::from(string)`. * **`from_from_string(&str)` in `FromForm` removed in favor of `from_form_items(&mut FormItems)`.** Most implementation should be using `FormItems` internally; simply use the passed in `FormItems`. In other cases, the form string can be retrieved via the `inner_str` method of `FormItems`. * **`Config::{set, default_for}` are deprecated.** Use the `set_{param}` methods instead of `set`, and `new` or `build` in place of `default_for`. * **Route paths must be absolute.** Prepend a `/` to convert a relative path into an absolute one. * **Route paths cannot contain empty segments.** Remove any empty segments, including trailing ones, from a route path. ## Bug Fixes A couple of bugs were fixed in this release: * Handlebars partials were not properly registered ([#122](https://github.com/rwf2/Rocket/issues/122)). * `Rocket::custom` did not set the custom configuration as the `active` configuration. * Route path segments containing more than one dynamic parameter were allowed. ## General Improvements In addition to new features, Rocket saw the following smaller improvements: * Rocket no longer overwrites a catcher's response status. * The `port` `Config` type is now a proper `u16`. * Clippy issues injected by codegen are resolved. * Handlebars was updated to `0.25`. * The `PartialEq` implementation of `Config` doesn't consider the path or secret key. * Hyper dependency updated to `0.10`. * The `Error` type for `JSON as FromData` has been exposed as `SerdeError`. * SVG was added as a known Content-Type. * Serde was updated to `0.9`. * Form parse failure now results in a **422** error code. * Tera has been updated to `0.7`. * `pub(crate)` is used throughout to enforce visibility rules. * Query parameters in routes (`/path?`) are now logged. * Routes with and without query parameters no longer _collide_. ## Infrastructure * Testing was parallelized, resulting in 3x faster Travis builds. # Version 0.1.6 (Jan 26, 2017) ## Infrastructure * Hyper version pinned to 0.9.14 due to upstream non-semver breaking change. # Version 0.1.5 (Jan 14, 2017) ## Core * Fixed security checks in `FromSegments` implementation for `PathBuf`. ## Infrastructure * `proc_macro` feature removed from examples due to stability. # Version 0.1.4 (Jan 4, 2017) ## Core * Header names are treated as case-preserving. ## Codegen * Minimum supported nightly is `2017-01-03`. # Version 0.1.3 (Dec 31, 2016) ## Core * Typo in `Outcome` formatting fixed (Succcess -> Success). * Added `ContentType::CSV`. * Dynamic segments parameters are properly resolved, even when mounted. * Request methods are only overridden via `_method` field on POST. * Form value `String`s are properly decoded. ## Codegen * The `_method` field is now properly ignored in `FromForm` derivation. * Unknown Content-Types in `format` no longer result in an error. * Deriving `FromForm` no longer results in a deprecation warning. * Codegen will refuse to build with incompatible rustc, presenting error message and suggestion. * Added `head` as a valid decorator for `HEAD` requests. * Added `route(OPTIONS)` as a valid decorator for `OPTIONS` requests. ## Contrib * Templates with the `.tera` extension are properly autoescaped. * Nested template names are properly resolved on Windows. * Template implements `Display`. * Tera dependency updated to version 0.6. ## Docs * Todo example requirements clarified in its `README`. ## Testing * Tests added for `config`, `optional_result`, `optional_redirect`, and `query_params` examples. * Testing script checks for and disallows tab characters. ## Infrastructure * New script (`bump_version.sh`) automates version bumps. * Config script emits error when readlink/readpath support is bad. * Travis badge points to public builds. # Version 0.1.2 (Dec 24, 2016) ## Codegen * Fix `get_raw_segments` index argument in route codegen ([#41](https://github.com/rwf2/Rocket/issues/41)). * Segments params (``) respect prefixes. ## Contrib * Fix nested template name resolution ([#42](https://github.com/rwf2/Rocket/issues/42)). ## Infrastructure * New script (`publish.sh`) automates publishing to crates.io. * New script (`bump_version.sh`) automates version bumps. # Version 0.1.1 (Dec 23, 2016) ## Core * `NamedFile` `Responder` lost its body in the shuffle; it's back! # Version 0.1.0 (Dec 23, 2016) This is the first public release of Rocket! ## Breaking All of the mentions to `hyper` types in core Rocket types are no more. Rocket now implements its own `Request` and `Response` types. * `ContentType` uses associated constants instead of static methods. * `StatusCode` removed in favor of new `Status` type. * `Response` type alias superseded by `Response` type. * `Responder::respond` no longer takes in hyper type. * `Responder::respond` returns `Response`, takes `self` by move. * `Handler` returns `Outcome` instead of `Response` type alias. * `ErrorHandler` returns `Result`. * All `Hyper*` types were moved to unprefixed versions in `hyper::`. * `MockRequest::dispatch` now returns a `Response` type. * `URIBuf` removed in favor of unified `URI`. * Rocket panics when an illegal, dynamic mount point is used. ## Core * Rocket handles `HEAD` requests automatically. * New `Response` and `ResponseBuilder` types. * New `Request`, `Header`, `Status`, and `ContentType` types. ## Testing * `MockRequest` allows any type of header. * `MockRequest` allows cookies. ## Codegen * Debug output disabled by default. * The `ROCKET_CODEGEN_DEBUG` environment variables enables codegen logging. # Version 0.0.11 (Dec 11, 2016) ## Streaming Requests All incoming request data is now streamed. This resulted in a major change to the Rocket APIs. They are summarized through the following API changes: * The `form` route parameter has been removed. * The `data` route parameter has been introduced. * Forms are now handled via the `data` parameter and `Form` type. * Removed the `data` parameter from `Request`. * Added `FromData` conversion trait and default implementation. * `FromData` is used to automatically derive the `data` parameter. * `Responder`s are now final: they cannot forward to other requests. * `Responder`s may only forward to catchers. ## Breaking * Request `uri` parameter is private. Use `uri()` method instead. * `form` module moved under `request` module. * `response::data` was renamed to `response::content`. * Introduced `Outcome` with `Success`, `Failure`, and `Forward` variants. * `outcome` module moved to top-level. * `Response` is now a type alias to `Outcome`. * `Empty` `Responder` was removed. * `StatusResponder` removed in favor of `response::status` module. ## Codegen * Error handlers can now take 0, 1, or 2 parameters. * `FromForm` derive now works on empty structs. * Lifetimes are now properly stripped in code generation. * Any valid ident is now allowed in single-parameter route parameters. ## Core * Route is now cloneable. * `Request` no longer has any lifetime parameters. * `Handler` type now includes a `Data` parameter. * `http` module is public. * `Responder` implemented for `()` type as an empty response. * Add `config::get()` for global config access. * Introduced `testing` module. * `Rocket.toml` allows global configuration via `[global]` table. ## Docs * Added a `raw_upload` example. * Added a `pastebin` example. * Documented all public APIs. ## Testing * Now building and running tests with `--all-features` flag. * Added appveyor config for Windows CI testing. # Version 0.0.10 (Oct 03, 2016) ## Breaking * Remove `Rocket::new` in favor of `ignite` method. * Remove `Rocket::mount_and_launch` in favor of chaining `mount(..).launch()`. * `mount` and `catch` take `Rocket` type by value. * All types related to HTTP have been moved into `http` module. * `Template::render` in `contrib` now takes context by reference. ## Core * Rocket now parses option `Rocket.toml` for configuration, defaulting to sane values. * `ROCKET_ENV` environment variable can be used to specify running environment. ## Docs * Document `ContentType`. * Document `Request`. * Add script that builds docs. ## Testing * Scripts can now be run from any directory. * Cache Cargo directories in Travis for faster testing. * Check that library version numbers match in testing script. # Version 0.0.9 (Sep 29, 2016) ## Breaking * Rename `response::data_type` to `response::data`. ## Core * Rocket interprets `_method` field in forms as the incoming request's method. * Add `Outcome::Bad` to signify responses that failed internally. * Add a `NamedFile` `Responder` type that uses a file's extension for the response's content type. * Add a `Stream` `Responder` for streaming responses. ## Contrib * Introduce the `contrib` crate. * Add JSON support via `JSON`, which implements `FromRequest` and `Responder`. * Add templating support via `Template` which implements `Responder`. ## Docs * Initial guide-like documentation. * Add documentation, testing, and contributing sections to README. ## Testing * Add a significant number of codegen tests. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Rocket **Please read this document before contributing!** Thank you for contributing! We welcome your contributions in whichever form they may come. This document provides guidelines and resources to help you successfully contribute to the project. Rocket is a tool designed to push the envelope of usability, security, _and_ performance in web frameworks, and accordingly, our quality standards are high. To make the best use of everyone's time and avoid wasted efforts, take a moment to understand our expectations and conventions outlined here. ## Submitting Pull Requests Before creating a new pull request: * Read and understand [Code Style Conventions], [Commit Message Guidelines], and [Testing]. * If you're resolving an open issue, follow [Resolving an Open Issue]. * If you're implementing new functionality, check whether the functionality you're implementing has been proposed before, either as an [issue] or [pull request]. Ensure your PR resolves any previously raised concerns. Then, follow [Implementing an Unproposed Feature]. * For everything else, see [Other Common Contributions]. We aim to keep Rocket's code quality at the highest level. This means that any code you contribute must be: * **Commented:** Complex or subtle functionality must be properly commented. * **Documented:** Public items must have doc comments with examples. * **Styled:** Your code must follow the [Code Style Conventions]. * **Simple:** Your code should accomplish its task as simply and idiomatically as possible. * **Tested:** You must write (and pass) convincing [tests](#testing) for all new or changed functionality. * **Focused:** Your code should do what it's supposed to and nothing more. ### Resolving an Open Issue [Resolving an Open Issue]: #resolving-an-open-issue If you spot an open issue that you'd like to resolve: 1. **First identify if there's a proposed solution to the problem.** If there is, proceed to step 2. If there isn't, your first course of action, before writing any code, is to propose a solution. To do so, leave a comment describing your solution in the relevant issue. It's especially useful to see test cases and hypothetical examples. This step is critical: it allows us to identify and resolve concerns with a proposed solution before anyone spends time writing code. It may also allow us to point you in more efficient implementation directions. 2. **Write a failing test case you expect to pass after resolving the issue.** If you can write proper tests cases that fail, do so (see [Testing]). If you cannot, for instance because you're introducing new APIs which can't be used until they exist, write a test case that mocks usage of those APIs. In either case, allow the tests and mock examples to guide your progress. 3. **Write basic functionality, pass tests, and submit a PR.** Think about edge cases to the problem and ensure you have tests for those edge cases. Once your implementation is functionally complete, submit a PR. Don't spend time writing or changing a bunch of documentation just yet. 4. **Wait for a review, iterate, and polish.** If a review doesn't come in a few days, feel free to ping a maintainer. Once someone reviews your PR, integrate their feedback. If the PR solves the issue (which it should because you have passing tests) and fits the project (which it should since you sought feedback _before_ submitting), it will be _conditionally_ approved pending final polish: documentation (rustdocs, guide docs), style improvements, and testing. Your PR will then be merged. ### Implementing an Unproposed Feature [Implementing an Unproposed Feature]: #implementing-an-unproposed-feature First and foremost, **please do not submit a PR that implements a new feature without first proposing a design and seeking feedback.** We take the addition of new features _very_ seriously because they directly impact usability. To propose a new feature, create a [new feature request issue] and follow the template. Note that certain classes of features require particularly compelling justification to be taken into consideration. These include features that: * Can be implemented outside of Rocket. * Introduce new dependencies, especially heavier ones. * Only exist to add support for an external crate. * Are too specific to one use-case. * Are overtly complex _and_ have "simple" workarounds. * Only partially solve a bigger deficiency. Once your feature request is accepted, follow [Resolving an Open Issue]. [new feature request issue]: https://github.com/rwf2/Rocket/issues/new?assignees=&labels=request&projects=&template=feature-request.yml ### Other Common Contributions [Other Common Contributions]: #other-common-contributions * **Doc fixes, typos, wording improvements.** We encourage any of these! Just a submit a PR with your changes. Please preserve the surrounding markdown formatting as much as possible. This typically means keeping lines under 80 characters, keeping table delimiters aligned, and preserving indentation accordingly. The guide's source files are at [docs/guide]. Note the following special syntax available in guide markdown: - **Cross-linking** pages is accomplished via relative links. Outside of the index, this is: `../{page}#anchor`. For instance, to link to **Quickstart > Running Examples**, use `../quickstart#running-examples`. - **Aliases** are shorthand URLs that start with `@` (e.g, `@api`). They are used throughout the guide to simplify creating versioned URLs. They are replaced at build time with the appropriate versioned instance. * **New examples or changes to existing ones.** Please follow the [Implementing an Unproposed Feature] process. * **Formatting or other purely cosmetic changes.** We generally do not accept purely cosmetic changes to the codebase such as style or formatting changes. All PRs must add something substantial to Rocket's functionality, coherence, testing, performance, usability, bug fixes, security, documentation, or overall maintainability. * **Advertisements of any nature.** We do not accept any contributions that resemble advertisements or promotional content. If you are interested in supporting Rocket, we encourage you to [sponsor the project]. ## Testing [Testing]: #testing All testing happens through [test.sh]. Before submitting a PR, run the script and fix any issues. The default mode (passing no arguments or `--default`) will usually suffice, but you may also wish to execute additional tests. In particular: * If you make changes to `contrib`: `test.sh --contrib` * If you make user-facing API changes or update deps: `test.sh --examples` * If you add or modify feature flags: `test.sh --core` * If you modify codegen: see [UI Tests]. Run `test.sh --help` to get an overview of how to execute the script: ```sh USAGE: ./scripts/test.sh [+] [--help|-h] [--] OPTIONS: + Forwarded to Cargo to select toolchain. --help, -h Print this help message and exit. -- Run the specified test suite. (Run without -- to run default tests.) AVAILABLE OPTIONS: default all core contrib examples benchmarks testbench ui EXAMPLES: ./scripts/test.sh # Run default tests on current toolchain. ./scripts/test.sh +stable --all # Run all tests on stable toolchain. ./scripts/test.sh --ui # Run UI tests on current toolchain. ``` ### Writing Tests Rocket is tested in a variety of ways. This includes via Rust's regular testing facilities such as doctests, unit tests, and integration tests, as well Rocket's examples, testbench, and [UI Tests]: - **Examples**: The [`examples`](examples/) directory contains applications that make use of many of Rocket's features. Each example is integration tested using Rocket's built-in [local testing]. This both ensures that typical Rocket code continues to work as expected and serves as a way to detect and resolve user-facing breaking changes. - **Testbench**: Rocket's [testbench](testbench/) tests end-to-end server or protocol properties by starting up full Rocket servers to which it dispatches real HTTP requests. Each server is independently written in [testbench/src/servers/](testbench/src/servers/). You're unlikely to need to write a testbench test unless you're modifying low-level details. - **UI Tests**: UI tests ensure Rocket's codegen produces meaningful compiler diagnostics. They compile Rocket applications and compare the compiler's output to expected results. If you're changing codegen, you'll need to update or create UI tests. See [UI Tests] for details. For any change that affects functionality, we ask that you write a test that verifies that functionality. Minimally, this means a unit test, doctest, integration test, or some combination of these. For small changes, unit tests will likely suffice. If the change affects the user in any way, then doctests should be added or modified. And if the change requires using unrelated APIs to test, then an integration test should be added. Additionally, the following scenarios require special attention: - **Improved Features** Modifying an existing example is a great place to write tests for improved features. If you do modify an example, make sure you modify the README in the example directory, too. - **New Features** For major features, introducing a new example that showcases idiomatic use of the feature can be useful. Make sure you modify the README in the `examples` directory if you do. In addition, all newly introduced public APIs should be fully documented and include doctests as well as unit and integration tests. - **Fixing a Bug** To avoid regressions, _always_ introduce or modify an integration or testbench test for a bugfix. Integration tests should live in the usual `tests/` directory and be named `short-issue-description-NNNN.rs`, where `NNNN` is the GitHub issue number for the bug. For example, `forward-includes-status-1560.rs`. [local testing]: https://api.rocket.rs/master/rocket/local/ ### UI Tests [UI Tests]: #ui-tests Changes to codegen (i.e, `rocket_codegen` and other `_codegen` crates) necessitate adding and running UI tests, which capture compiler output and compare it against some expected output. UI tests use [`trybuild`]. Tests can be found in the `codegen/tests/ui-fail` directories of respective `codegen` crates. Each test is symlinked into sibling `ui-fail-stable` and `ui-fail-nightly` directories, which also contain the expected error output for stable and nightly compilers, respectively. For example: ``` ./core/codegen/tests ├── ui-fail │   ├── async-entry.rs │   ├── ... │   └── uri_display_type_errors.rs ├── ui-fail-nightly │   ├── async-entry.rs -> ../ui-fail/async-entry.rs │   ├── async-entry.stderr │   ├── ... │   ├── uri_display_type_errors.rs -> ../ui-fail/uri_display_type_errors.rs │   └── uri_display_type_errors.stderr └── ui-fail-stable    ├── async-entry.rs -> ../ui-fail/async-entry.rs    ├── async-entry.stderr    ├── ...    ├── uri_display_type_errors.rs -> ../ui-fail/uri_display_type_errors.rs    └── uri_display_type_errors.stderr ``` If you make changes to codegen, run the UI tests for stable and nightly with `test.sh +stable --ui` and `test.sh +nightly --ui`. If there are failures, update the outputs with `TRYBUILD=overwrite test.sh +nightly --ui` and `TRYBUILD=overwrite test.sh +stable --ui`. Look at the diff to see what's changed. Ensure that error messages properly attribute (i.e., visually underline or point to) the source of the error. For example, if a type need to implement a trait, then that type should be underlined. We strive to emit the most helpful and descriptive error messages possible. ### API Docs If you make changes to documentation, you should build the API docs and verify that your changes look as you expect. API documentation is built with [mk-docs.sh] and output to the usual `target/docs` directory. By default, the script will `clean` any existing docs to avoid potential caching issues. To override this behavior, use `mk-docs.sh -d`. ## Code Style Conventions [Code Style Conventions]: #code-style-conventions We _do not_ use `rustfmt` or `cargo fmt` due to bugs and missing functionality. Instead, we ask that you follow the [Rust Style Guide] with the following changes: **Always separate items with one blank line.**
✅ Yes No 🚫
```rust fn foo() { // .. } fn bar() { // .. } ``` ```rust fn foo() { // .. } fn bar() { // .. } ```
**Prefer a where-clause over block-indented generics.**
✅ Yes No 🚫
```rust fn foo(x: Vec, y: Vec) where T: Display, U: Debug { // .. } ``` ```rust fn foo< T: Display, U: Debug, >(x: Vec, y: Vec) { // .. } ```
**For "short" where-clauses, follow Rust guidelines. For "long" where-clauses, block-indent `where`, place the first bound on the same line as `where`, and block-align the remaining bounds.**
✅ Yes No 🚫
```rust fn foo(v: Foo) -> G where T: for<'x> SomeTrait<'x> F: Fn(Item) -> G, Item: Display + Debug, G: Error, { // .. } ``` ```rust fn foo(v: Foo) -> G where T: for<'x> SomeTrait<'x> F: Fn(Item) -> G, Item: Display + Debug, G: Error, { // .. } ```
**Do not use multi-line imports. Use multiple lines grouped by import kind if possible.**
✅ Yes No 🚫
```rust use foo::{Long, List, Of, Type, Imports}; use foo::{some_macro, imports}; ``` ```rust use foo::{ Long, List, Of, Type, Imports, some_macro, imports, }; ```
**Order imports in order of decreasing "distance" to the current module: `std`, `core`, and `alloc`, external crates, then current crate. Prefer using `crate` relative imports to `super`. Separate each category with one blank line.**
✅ Yes No 🚫
```rust use std::{foo, bar}; use alloc::{bar, baz}; use either::Either; use futures::{SomeItem, OtherItem}; use crate::{item1, item2}; use crate::module::item3; use crate::module2::item4; ``` ```rust use crate::{item1, item2}; use std::{foo, bar}; use either::Either; use alloc::{bar, baz}; use futures::{SomeItem, OtherItem}; use super::{item3, item4}; use super::item4; ```
## Commit Message Guidelines [Commit Message Guidelines]: #commit-message-guidelines Git commit messages should start with a single-line _header_ of at most 50 characters followed by a body with any number of descriptive paragraphs, with lines not to exceed 72 characters, and a footer. The **header** must be an imperative statement that precisely describes the primary change made by the commit. The goal is to give the reader a good understanding of what the commit does via only the header. It should not require context to understand. It should not include references to git commits or issues. Avoid using Markdown in the header if possible. Typically, the first word in the header will be one of the following: * **Fix** - to fix a functional or doc bug - Example: `Fix 'TcpListener': allow 'udp://' prefix.` * **Improve** - for minor feature or doc improvements - Example: `Improve 'FromParam' derive error messages.` * **Introduce** - for major feature introductions - Example: `Introduce WebSocket support.` * **Add**, **Remove** - for changes - Example: `Add 'Foo::new()' constructor.` - Example: `Remove 'Foo::new()'; add 'Foo::build()'.` * **Update** - for crate updates - Example: `Update 'base64' to 0.12.` * **Impl** or **Implement** - for trait implementations - Example: `Implement 'FromForm' for 'ThisNewType'.` Note how generic words like "change" are avoided, and how the headers are specific about the changes they made. You need not limit yourself to this vocabulary. When in doubt, consult the `git log` for examples. | **✅ Yes** | **No 🚫** | |--------------------------------------------------|--------------------------------------------| | Fix 'FromForm' derive docs typo: 'yis' -> 'yes'. | ~~Change word in docs~~ | | Default 'MsgPack' to named variant. | ~~Change default to more likely variant.~~ | | Fix 'Compact' advice in 'MsgPack' docs. | ~~Update docs to make sense~~ | | Improve 'Sentinel' docs: explain 'Sentry'. | ~~Add missing doc details.~~ | | Fix CI: pin macOS CI 'mysql-client' to '8.4'. | ~~Fix CI~~ | | Fix link to 'rocket::build()' in config guide. | ~~Fix wrong URL in guide (configuration~~) | The **body** should describe what the commit does. For example, if the commit introduces a new feature it should describe what the feature enables and how it enables it. A body may be unnecessary if the header sufficiently describes the commit. Avoid referencing issues in the body as well: we'll do that in the footer. If you reference a commit, reference it by shorthash only. Feel free to use markdown including lists and code. Finally, the **footer** is where references to issues should be made. See the GitHub's [linked issues] documentation. [linked issues]: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue [Rust Style Guide]: https://doc.rust-lang.org/nightly/style-guide/ [issue]: https://github.com/rwf2/Rocket/issues [pull request]: https://github.com/rwf2/Rocket/pulls [test.sh]: scripts/test.sh [mk-docs.sh]: scripts/mk-docs.sh [`trybuild`]: https://docs.rs/trybuild [sponsor the project]: https://github.com/sponsors/rwf2 [docs/guide]: docs/guide ## Licensing Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Rocket by you shall be dual licensed under the MIT License and Apache License, Version 2.0, without any additional terms or conditions. The Rocket website docs are licensed under [separate terms](docs/LICENSE). Any contribution intentionally submitted for inclusion in the Rocket website docs by you shall be licensed under those terms. ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "core/lib/", "core/codegen/", "core/http/", "contrib/db_pools/codegen/", "contrib/db_pools/lib/", "contrib/sync_db_pools/codegen/", "contrib/sync_db_pools/lib/", "contrib/dyn_templates/", "contrib/ws/", "docs/tests", ] [workspace.lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(nightly)'] } rust_2018_idioms = "warn" async_fn_in_trait = "allow" refining_impl_trait = "allow" # unreachable_pub = "warn" # single_use_lifetimes = "warn" # missing_docs = "warn" [workspace.lints.clippy] type_complexity = "allow" module_inception = "allow" multiple_bound_locations = "allow" manual_range_contains = "allow" ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 Sergio Benitez Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ The MIT License (MIT) Copyright (c) 2016 Sergio Benitez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Rocket [![Build Status](https://github.com/rwf2/Rocket/workflows/CI/badge.svg)](https://github.com/rwf2/Rocket/actions) [![Rocket Homepage](https://img.shields.io/badge/web-rocket.rs-red.svg?style=flat&label=https&colorB=d33847)](https://rocket.rs) [![Current Crates.io Version](https://img.shields.io/crates/v/rocket.svg)](https://crates.io/crates/rocket) [![Matrix: #rocket:mozilla.org](https://img.shields.io/badge/style-%23rocket:mozilla.org-blue.svg?style=flat&label=[m])](https://chat.mozilla.org/#/room/#rocket:mozilla.org) Rocket is an async web framework for Rust with a focus on usability, security, extensibility, and speed. ```rust #[macro_use] extern crate rocket; #[get("//")] fn hello(name: &str, age: u8) -> String { format!("Hello, {} year old named {}!", age, name) } #[launch] fn rocket() -> _ { rocket::build().mount("/hello", routes![hello]) } ``` Visiting `localhost:8000/hello/John/58`, for example, will trigger the `hello` route resulting in the string `Hello, 58 year old named John!` being sent to the browser. If an `` string was passed in that can't be parsed as a `u8`, the route won't get called, resulting in a 404 error. ## Documentation Rocket is extensively documented: * [Overview]: A brief look at what makes Rocket special. * [Quickstart]: How to get started as quickly as possible. * [Getting Started]: How to start your first Rocket project. * [Guide]: A detailed guide and reference to Rocket. * [API Documentation]: The "rustdocs". [Quickstart]: https://rocket.rs/guide/quickstart [Getting Started]: https://rocket.rs/guide/getting-started [Overview]: https://rocket.rs/overview/ [Guide]: https://rocket.rs/guide/ [API Documentation]: https://api.rocket.rs Documentation for the `master` branch is available at https://rocket.rs/master and https://api.rocket.rs/master. Documentation for major release version `${x}` is available at `https://[api.]rocket.rs/v${x}`. For example, the v0.4 docs are available at https://rocket.rs/v0.4 and https://api.rocket.rs/v0.4. Finally, API docs for active git branches are available at `https://api.rocket.rs/${branch}`. For example, API docs for the `master` branch are available at https://api.rocket.rs/master. Branch rustdocs are built and deployed on every commit. ## Examples The [examples](examples#readme) directory contains complete crates that showcase Rocket's features and usage. Each example can be compiled and run with Cargo. For instance, the following sequence of commands builds and runs the `hello` example: ```sh cd examples/hello cargo run ``` ## Getting Help If you find yourself needing help outside of the documentation, you may: * Ask questions via [GitHub discussions questions]. * Chat with us at [`#rocket:mozilla.org`] on Matrix (join [via Element]). [`#rocket:mozilla.org`]: https://chat.mozilla.org/#/room/#rocket:mozilla.org [via Element]: https://chat.mozilla.org/#/room/#rocket:mozilla.org [GitHub discussions questions]: https://github.com/rwf2/Rocket/discussions/categories/questions ## Contributing Contributions are absolutely, positively welcomed and encouraged! If you're interested in contributing code, please first read [CONTRIBUTING] for complete guidelines. Additionally, you could: 1. Submit a feature request or bug report as an [issue]. 2. Ask for improved documentation as an [issue]. 3. Comment on [issues that require feedback]. 4. Answers questions in [GitHub discussions questions]. 5. Share a project in [GitHub discussions show & tell]. [issue]: https://github.com/rwf2/Rocket/issues [issues that require feedback]: https://github.com/rwf2/Rocket/issues?q=is%3Aissue+is%3Aopen+label%3A%22feedback+wanted%22 [pull requests]: https://github.com/rwf2/Rocket/pulls [CONTRIBUTING]: CONTRIBUTING.md [GitHub discussions show & tell]: https://github.com/rwf2/Rocket/discussions/categories/show-tell ## License Rocket is licensed under either of the following, at your option: * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) * MIT License ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Rocket by you shall be dual licensed under the MIT License and Apache License, Version 2.0, without any additional terms or conditions. The Rocket website docs are licensed under [separate terms](docs/LICENSE). Any contribution intentionally submitted for inclusion in the Rocket website docs by you shall be licensed under those terms. ================================================ FILE: benchmarks/Cargo.toml ================================================ [package] name = "rocket-benchmarks" version = "0.0.0" edition = "2021" publish = false [workspace] [[bench]] name = "main" path = "src/bench.rs" harness = false [dev-dependencies] rocket = { path = "../core/lib/" } criterion = "0.5.1" ================================================ FILE: benchmarks/src/bench.rs ================================================ mod routing; criterion::criterion_main!(routing::routing); ================================================ FILE: benchmarks/src/routing.rs ================================================ use criterion::{criterion_group, Criterion}; use rocket::{route, config, Request, Data, Route, Config}; use rocket::http::{Method, RawStr, ContentType, Accept, Status}; use rocket::local::blocking::{Client, LocalRequest}; fn dummy_handler<'r>(req: &'r Request, _: Data<'r>) -> route::BoxFuture<'r> { route::Outcome::from(req, ()).pin() } fn parse_routes_table(table: &str) -> Vec { let mut routes = vec![]; for line in table.split("\n").filter(|s| !s.is_empty()) { let mut components = line.split(" "); let method: Method = components.next().expect("c").parse().expect("method"); let uri: &str = components.next().unwrap(); let (mut rank, mut name, mut format) = (None, None, None); for component in components { match component { c if c.starts_with('[') => rank = c.trim_matches(&['[', ']'][..]).parse().ok(), c if c.starts_with('(') => name = Some(c.trim_matches(&['(', ')'][..])), c => format = c.parse().ok(), } } let mut route = Route::new(method, uri, dummy_handler); if let Some(rank) = rank { route.rank = rank; } route.format = format; route.name = name.map(|s| s.to_string().into()); routes.push(route); } routes } fn generate_matching_requests<'c>(client: &'c Client, routes: &[Route]) -> Vec> { fn staticify_segment(segment: &RawStr) -> &str { segment.as_str().trim_matches(&['<', '>', '.', '_'][..]) } fn request_for_route<'c>(client: &'c Client, route: &Route) -> LocalRequest<'c> { let path = route.uri.path() .raw_segments() .map(staticify_segment) .collect::>() .join("/"); let query = route.uri.query() .map(|q| q.raw_segments()) .into_iter() .flatten() .map(staticify_segment) .collect::>() .join("&"); let uri = format!("/{}?{}", path, query); let mut req = client.req(route.method.unwrap(), uri); if let Some(ref format) = route.format { if let Some(true) = route.method.and_then(|m| m.allows_request_body()) { req.add_header(ContentType::from(format.clone())); } else { req.add_header(Accept::from(format.clone())); } } req } routes.iter() .map(|route| request_for_route(client, route)) .collect() } fn client(routes: Vec) -> Client { let config = Config { profile: Config::RELEASE_PROFILE, log_level: None, cli_colors: config::CliColors::Never, shutdown: config::ShutdownConfig { ctrlc: false, #[cfg(unix)] signals: std::collections::hash_set::HashSet::new(), ..Default::default() }, ..Default::default() }; match Client::untracked(rocket::custom(config).mount("/", routes)) { Ok(client) => client, Err(e) => { drop(e); panic!("bad launch") } } } pub fn bench_rust_lang_routes(c: &mut Criterion) { let table = include_str!("../static/rust-lang.routes"); let routes = parse_routes_table(table); let client = client(routes.clone()); let requests = generate_matching_requests(&client, &routes); c.bench_function("rust-lang.routes", |b| b.iter(|| { for request in requests.clone() { let response = request.dispatch(); assert_eq!(response.status(), Status::Ok); } })); } pub fn bench_bitwarden_routes(c: &mut Criterion) { let table = include_str!("../static/bitwarden_rs.routes"); let routes = parse_routes_table(table); let client = client(routes.clone()); let requests = generate_matching_requests(&client, &routes); c.bench_function("bitwarden_rs.routes", |b| b.iter(|| { for request in requests.clone() { let response = request.dispatch(); assert_eq!(response.status(), Status::Ok); } })); } criterion_group!(routing, bench_rust_lang_routes, bench_bitwarden_routes); ================================================ FILE: benchmarks/static/bitwarden_rs.routes ================================================ GET /attachments// (attachments) GET /alive (alive) GET /bwrs_static/ (static_files) POST /api/accounts/register (register) GET /api/accounts/profile (profile) PUT /api/accounts/profile (put_profile) POST /api/accounts/profile (post_profile) GET /api/users//public-key (get_public_keys) POST /api/accounts/keys (post_keys) POST /api/accounts/password (post_password) POST /api/accounts/kdf (post_kdf) POST /api/accounts/key (post_rotatekey) POST /api/accounts/security-stamp (post_sstamp) POST /api/accounts/email-token (post_email_token) POST /api/accounts/email (post_email) POST /api/accounts/verify-email (post_verify_email) POST /api/accounts/verify-email-token (post_verify_email_token) POST /api/accounts/delete-recover (post_delete_recover) POST /api/accounts/delete-recover-token (post_delete_recover_token) DELETE /api/accounts (delete_account) POST /api/accounts/delete (post_delete_account) GET /api/accounts/revision-date (revision_date) POST /api/accounts/password-hint (password_hint) POST /api/accounts/prelogin (prelogin) POST /api/accounts/verify-password (verify_password) GET /api/sync? (sync) GET /api/ciphers (get_ciphers) GET /api/ciphers/ (get_cipher) GET /api/ciphers//admin (get_cipher_admin) GET /api/ciphers//details (get_cipher_details) POST /api/ciphers (post_ciphers) PUT /api/ciphers//admin (put_cipher_admin) POST /api/ciphers/admin (post_ciphers_admin) POST /api/ciphers/create (post_ciphers_create) POST /api/ciphers/import (post_ciphers_import) POST /api/ciphers//attachment multipart/form-data (post_attachment) POST /api/ciphers//attachment-admin multipart/form-data (post_attachment_admin) POST /api/ciphers//attachment//share multipart/form-data (post_attachment_share) POST /api/ciphers//attachment//delete (delete_attachment_post) POST /api/ciphers//attachment//delete-admin (delete_attachment_post_admin) DELETE /api/ciphers//attachment/ (delete_attachment) DELETE /api/ciphers//attachment//admin (delete_attachment_admin) POST /api/ciphers//admin (post_cipher_admin) POST /api/ciphers//share (post_cipher_share) PUT /api/ciphers//share (put_cipher_share) PUT /api/ciphers/share (put_cipher_share_selected) POST /api/ciphers/ (post_cipher) PUT /api/ciphers/ (put_cipher) POST /api/ciphers//delete (delete_cipher_post) POST /api/ciphers//delete-admin (delete_cipher_post_admin) PUT /api/ciphers//delete (delete_cipher_put) PUT /api/ciphers//delete-admin (delete_cipher_put_admin) DELETE /api/ciphers/ (delete_cipher) DELETE /api/ciphers//admin (delete_cipher_admin) DELETE /api/ciphers (delete_cipher_selected) POST /api/ciphers/delete (delete_cipher_selected_post) PUT /api/ciphers/delete (delete_cipher_selected_put) DELETE /api/ciphers/admin (delete_cipher_selected_admin) POST /api/ciphers/delete-admin (delete_cipher_selected_post_admin) PUT /api/ciphers/delete-admin (delete_cipher_selected_put_admin) PUT /api/ciphers//restore (restore_cipher_put) PUT /api/ciphers//restore-admin (restore_cipher_put_admin) PUT /api/ciphers/restore (restore_cipher_selected) POST /api/ciphers/purge? (delete_all) POST /api/ciphers/move (move_cipher_selected) PUT /api/ciphers/move (move_cipher_selected_put) PUT /api/ciphers//collections (put_collections_update) POST /api/ciphers//collections (post_collections_update) POST /api/ciphers//collections-admin (post_collections_admin) PUT /api/ciphers//collections-admin (put_collections_admin) GET /api/folders (get_folders) GET /api/folders/ (get_folder) POST /api/folders (post_folders) POST /api/folders/ (post_folder) PUT /api/folders/ (put_folder) POST /api/folders//delete (delete_folder_post) DELETE /api/folders/ (delete_folder) GET /api/organizations/ (get_organization) POST /api/organizations (create_organization) DELETE /api/organizations/ (delete_organization) POST /api/organizations//delete (post_delete_organization) POST /api/organizations//leave (leave_organization) GET /api/collections (get_user_collections) GET /api/organizations//collections (get_org_collections) GET /api/organizations//collections//details (get_org_collection_detail) GET /api/organizations//collections//users (get_collection_users) PUT /api/organizations//collections//users (put_collection_users) PUT /api/organizations/ (put_organization) POST /api/organizations/ (post_organization) POST /api/organizations//collections (post_organization_collections) DELETE /api/organizations//collections//user/ (delete_organization_collection_user) POST /api/organizations//collections//delete-user/ (post_organization_collection_delete_user) POST /api/organizations//collections/ (post_organization_collection_update) PUT /api/organizations//collections/ (put_organization_collection_update) DELETE /api/organizations//collections/ (delete_organization_collection) POST /api/organizations//collections//delete (post_organization_collection_delete) GET /api/ciphers/organization-details? (get_org_details) GET /api/organizations//users (get_org_users) POST /api/organizations//users/invite (send_invite) POST /api/organizations//users//reinvite (reinvite_user) POST /api/organizations//users//confirm (confirm_invite) POST /api/organizations/<_org_id>/users/<_org_user_id>/accept (accept_invite) GET /api/organizations//users/ (get_user) POST /api/organizations//users/ [1] (edit_user) PUT /api/organizations//users/ (put_organization_user) DELETE /api/organizations//users/ (delete_user) POST /api/organizations//users//delete (post_delete_user) POST /api/ciphers/import-organization? (post_org_import) GET /api/organizations//policies (list_policies) GET /api/organizations//policies/token? (list_policies_token) GET /api/organizations//policies/ (get_policy) PUT /api/organizations//policies/ (put_policy) GET /api/organizations//tax (get_organization_tax) GET /api/plans (get_plans) GET /api/plans/sales-tax-rates (get_plans_tax_rates) POST /api/organizations//import (import) GET /api/two-factor (get_twofactor) POST /api/two-factor/get-recover (get_recover) POST /api/two-factor/recover (recover) POST /api/two-factor/disable (disable_twofactor) PUT /api/two-factor/disable (disable_twofactor_put) POST /api/two-factor/get-authenticator (generate_authenticator) POST /api/two-factor/authenticator (activate_authenticator) PUT /api/two-factor/authenticator (activate_authenticator_put) POST /api/two-factor/get-duo (get_duo) POST /api/two-factor/duo (activate_duo) PUT /api/two-factor/duo (activate_duo_put) POST /api/two-factor/get-email (get_email) POST /api/two-factor/send-email-login (send_email_login) POST /api/two-factor/send-email (send_email) PUT /api/two-factor/email (email) POST /api/two-factor/get-u2f (generate_u2f) POST /api/two-factor/get-u2f-challenge (generate_u2f_challenge) POST /api/two-factor/u2f (activate_u2f) PUT /api/two-factor/u2f (activate_u2f_put) DELETE /api/two-factor/u2f (delete_u2f) POST /api/two-factor/get-yubikey (generate_yubikey) POST /api/two-factor/yubikey (activate_yubikey) PUT /api/two-factor/yubikey (activate_yubikey_put) POST /api/sends (post_send) POST /api/sends/file multipart/form-data (post_send_file) POST /api/sends/access/ (post_access) POST /api/sends//access/file/ (post_access_file) PUT /api/sends/ (put_send) DELETE /api/sends/ (delete_send) PUT /api/sends//remove-password (put_remove_password) PUT /api/devices/identifier//clear-token (clear_device_token) PUT /api/devices/identifier//token (put_device_token) GET /api/settings/domains (get_eq_domains) POST /api/settings/domains (post_eq_domains) PUT /api/settings/domains (put_eq_domains) GET /api/hibp/breach? (hibp_breach) GET /admin (admin_disabled) POST /identity/connect/token (login) GET /icons//icon.png (icon) POST /notifications/hub/negotiate (negotiate) GET /notifications/hub (websockets_err) ================================================ FILE: benchmarks/static/rust-lang.routes ================================================ GET / (index) GET / (category) GET /governance (governance) GET /governance/
/ [2] (team) GET /production/users (production) GET /sponsors (sponsors) GET // [4] (subject) GET /static/ (files) GET /robots.txt (robots_txt) GET /logos/ (logos) GET /components/<_file..> (components) GET / [3] (index_locale) GET // [11] (category_locale) GET //governance [10] (governance_locale) GET //governance/
/ [12] (team_locale) GET //production/users [10] (production_locale) GET //sponsors [10] (sponsors_locale) GET /// [14] (subject_locale) GET //components/<_file..> [12] (components_locale) GET / [19] (redirect) GET /pdfs/ (redirect_pdfs) GET /en-US (redirect_bare_en_us) GET /<_locale> [20] (redirect_bare_locale) GET /en-US/ (redirect_en_us) GET /<_locale>/ [20] (redirect_locale) ================================================ FILE: contrib/db_pools/README.md ================================================ # `db_pools` [![ci.svg]][ci] [![crates.io]][crate] [![docs.svg]][crate docs] [crates.io]: https://img.shields.io/crates/v/rocket_db_pools.svg [crate]: https://crates.io/crates/rocket_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_db_pools [ci.svg]: https://github.com/rwf2/Rocket/workflows/CI/badge.svg [ci]: https://github.com/rwf2/Rocket/actions Asynchronous database driver integration for Rocket. See the [crate docs] for full usage details. ## Usage 1. Add `rocket_db_pools` as a dependency with one or more [database driver features] enabled: ```toml [dependencies.rocket_db_pools] version = "0.1.0" features = ["sqlx_sqlite"] ``` 2. Choose a name for your database, here `sqlite_logs`. [Configure] _at least_ a URL for the database: ```toml [default.databases.sqlite_logs] url = "/path/to/database.sqlite" ``` 3. [Derive `Database`] for a unit type (`Logs` here) which wraps the selected driver's [`Pool`] type and is decorated with `#[database("name")]`. Attach `Type::init()` to your application's `Rocket` to initialize the database pool: ```rust use rocket_db_pools::{Database, Connection}; #[derive(Database)] #[database("sqlite_logs")] struct Logs(sqlx::SqlitePool); #[launch] fn rocket() -> _ { rocket::build().attach(Logs::init()) } ``` 4. Use [`Connection`] as a request guard to retrieve an active database connection: ```rust #[get("/")] async fn read(mut db: Connection, id: i64) -> Result { sqlx::query!("SELECT content FROM logs WHERE id = ?", id) .fetch_one(&mut *db) .map_ok(|r| Log(r.content)) .await } ``` [database driver features]: https://api.rocket.rs/master/rocket_db_pools/index.html#supported-drivers [`Pool`]: https://api.rocket.rs/master/rocket_db_pools/index.html#supported-drivers [Configure]: https://api.rocket.rs/master/rocket_db_pools/index.html#configuration [Derive `Database`]: https://api.rocket.rs/master/rocket_db_pools/derive.Database.html [`Connection`]: https://api.rocket.rs/master/rocket_db_pools/struct.Connection.html ================================================ FILE: contrib/db_pools/codegen/Cargo.toml ================================================ [package] name = "rocket_db_pools_codegen" version = "0.1.0" authors = ["Sergio Benitez ", "Jeb Rosen "] description = "Procedural macros for rocket_db_pools." repository = "https://github.com/rwf2/Rocket/tree/master/contrib/db_pools" readme = "../README.md" keywords = ["rocket", "framework", "database", "pools"] license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.75" [lib] proc-macro = true [lints] workspace = true [dependencies] devise = "0.4" quote = "1" [dev-dependencies] rocket = { path = "../../../core/lib", default-features = false } rocket_db_pools = { path = "../lib", features = ["deadpool_postgres"] } trybuild = "1.0" version_check = "0.9" ================================================ FILE: contrib/db_pools/codegen/src/database.rs ================================================ use proc_macro::TokenStream; use devise::{DeriveGenerator, FromMeta, MapperBuild, Support, ValidatorBuild}; use devise::proc_macro2_diagnostics::SpanDiagnosticExt; use devise::syn::{self, spanned::Spanned}; const ONE_DATABASE_ATTR: &str = "missing `#[database(\"name\")]` attribute"; const ONE_UNNAMED_FIELD: &str = "struct must have exactly one unnamed field"; #[derive(Debug, FromMeta)] struct DatabaseAttribute { #[meta(naked)] name: String, } pub fn derive_database(input: TokenStream) -> TokenStream { DeriveGenerator::build_for(input, quote!(impl rocket_db_pools::Database)) .support(Support::TupleStruct) .validator(ValidatorBuild::new() .struct_validate(|_, s| { if s.fields.len() == 1 { Ok(()) } else { Err(s.span().error(ONE_UNNAMED_FIELD)) } }) ) .outer_mapper(MapperBuild::new() .struct_map(|_, s| { let pool_type = match &s.fields { syn::Fields::Unnamed(f) => &f.unnamed[0].ty, _ => unreachable!("Support::TupleStruct"), }; let decorated_type = &s.ident; let db_ty = quote_spanned!(decorated_type.span() => <#decorated_type as rocket_db_pools::Database> ); quote_spanned! { decorated_type.span() => impl From<#pool_type> for #decorated_type { fn from(pool: #pool_type) -> Self { Self(pool) } } impl std::ops::Deref for #decorated_type { type Target = #pool_type; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for #decorated_type { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } #[rocket::async_trait] impl<'r> rocket::request::FromRequest<'r> for &'r #decorated_type { type Error = (); async fn from_request( req: &'r rocket::request::Request<'_> ) -> rocket::request::Outcome { match #db_ty::fetch(req.rocket()) { Some(db) => rocket::outcome::Outcome::Success(db), None => rocket::outcome::Outcome::Error(( rocket::http::Status::InternalServerError, ())) } } } impl rocket::Sentinel for &#decorated_type { fn abort(rocket: &rocket::Rocket) -> bool { #db_ty::fetch(rocket).is_none() } } } }) ) .outer_mapper(quote!(#[rocket::async_trait])) .inner_mapper(MapperBuild::new() .try_struct_map(|_, s| { let db_name = DatabaseAttribute::one_from_attrs("database", &s.attrs)? .map(|attr| attr.name) .ok_or_else(|| s.span().error(ONE_DATABASE_ATTR))?; let fairing_name = format!("'{}' Database Pool", db_name); let pool_type = match &s.fields { syn::Fields::Unnamed(f) => &f.unnamed[0].ty, _ => unreachable!("Support::TupleStruct"), }; Ok(quote_spanned! { pool_type.span() => type Pool = #pool_type; const NAME: &'static str = #db_name; fn init() -> rocket_db_pools::Initializer { rocket_db_pools::Initializer::with_name(#fairing_name) } }) }) ) .to_tokens() } ================================================ FILE: contrib/db_pools/codegen/src/lib.rs ================================================ #![recursion_limit="256"] #![warn(rust_2018_idioms)] //! # `rocket_db_pool` - Code Generation //! //! Implements the code generation portion of the `rocket_db_pool` crate. This //! is an implementation detail. This create should never be depended on //! directly. #[macro_use] extern crate quote; mod database; /// Automatic derive for the [`Database`] trait. /// /// ```rust /// use rocket_db_pools::Database; /// # type PoolType = rocket_db_pools::deadpool_postgres::Pool; /// /// #[derive(Database)] /// #[database("database_name")] /// struct Db(PoolType); /// ``` /// /// The derive generates an implementation of [`Database`] as follows: /// /// * [`Database::NAME`] is set to the value in the `#[database("name")]` /// attribute. /// /// This names the database, providing an anchor to configure the database via /// `Rocket.toml` or any other configuration source. Specifically, the /// configuration in `databases.name` is used to configure the driver. /// /// * [`Database::Pool`] is set to the wrapped type: `PoolType` above. The type /// must implement [`Pool`]. /// /// To meet the required [`Database`] supertrait bounds, this derive also /// generates implementations for: /// /// * `From` /// /// * `Deref` /// /// * `DerefMut` /// /// * `FromRequest<'_> for &Db` /// /// * `Sentinel for &Db` /// /// The `Deref` impls enable accessing the database pool directly from /// references `&Db` or `&mut Db`. To force a dereference to the underlying /// type, use `&db.0` or `&**db` or their `&mut` variants. /// /// [`Database`]: ../rocket_db_pools/trait.Database.html /// [`Database::NAME`]: ../rocket_db_pools/trait.Database.html#associatedconstant.NAME /// [`Database::Pool`]: ../rocket_db_pools/trait.Database.html#associatedtype.Pool /// [`Pool`]: ../rocket_db_pools/trait.Pool.html #[proc_macro_derive(Database, attributes(database))] pub fn derive_database(input: proc_macro::TokenStream) -> proc_macro::TokenStream { crate::database::derive_database(input) } ================================================ FILE: contrib/db_pools/codegen/tests/ui-fail/database-syntax.rs ================================================ use rocket_db_pools::{deadpool_postgres, Database}; #[derive(Database)] #[database(123)] struct A(deadpool_postgres::Pool); #[derive(Database)] #[database("some-name", "another")] struct B(deadpool_postgres::Pool); #[derive(Database)] #[database("some-name", name = "another")] struct C(deadpool_postgres::Pool); #[derive(Database)] #[database("foo")] enum D { } #[derive(Database)] struct E(deadpool_postgres::Pool); #[derive(Database)] #[database("foo")] struct F; #[derive(Database)] #[database("foo")] struct G(deadpool_postgres::Pool, deadpool_postgres::Pool); #[derive(Database)] #[database("foo")] struct H { foo: deadpool_postgres::Pool, } fn main() { } ================================================ FILE: contrib/db_pools/codegen/tests/ui-fail/database-types.rs ================================================ #[macro_use] extern crate rocket_db_pools; struct Unknown; #[derive(Database)] #[database("foo")] struct A(Unknown); #[derive(Database)] #[database("bar")] struct B(Vec); fn main() { } ================================================ FILE: contrib/db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr ================================================ error: invalid value: expected string literal --> tests/ui-fail-nightly/database-syntax.rs:4:12 | 4 | #[database(123)] | ^^^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:3:10 | 3 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` --> tests/ui-fail-nightly/database-syntax.rs:8:25 | 8 | #[database("some-name", "another")] | ^^^^^^^^^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:7:10 | 7 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `name` --> tests/ui-fail-nightly/database-syntax.rs:12:25 | 12 | #[database("some-name", name = "another")] | ^^^^^^^^^^^^^^^^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:11:10 | 11 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: enums are not supported --> tests/ui-fail-nightly/database-syntax.rs:16:1 | 16 | / #[database("foo")] 17 | | enum D { } | |___________^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:15:10 | 15 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: missing `#[database("name")]` attribute --> tests/ui-fail-nightly/database-syntax.rs:20:1 | 20 | struct E(deadpool_postgres::Pool); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:19:10 | 19 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field --> tests/ui-fail-nightly/database-syntax.rs:23:1 | 23 | / #[database("foo")] 24 | | struct F; | |_________^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:22:10 | 22 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field --> tests/ui-fail-nightly/database-syntax.rs:27:1 | 27 | / #[database("foo")] 28 | | struct G(deadpool_postgres::Pool, deadpool_postgres::Pool); | |___________________________________________________________^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:26:10 | 26 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported --> tests/ui-fail-nightly/database-syntax.rs:31:1 | 31 | / #[database("foo")] 32 | | struct H { 33 | | foo: deadpool_postgres::Pool, 34 | | } | |_^ | note: error occurred while deriving `Database` --> tests/ui-fail-nightly/database-syntax.rs:30:10 | 30 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: contrib/db_pools/codegen/tests/ui-fail-nightly/database-types.stderr ================================================ error[E0277]: the trait bound `Unknown: Pool` is not satisfied --> tests/ui-fail-nightly/database-types.rs:7:10 | 7 | struct A(Unknown); | ^^^^^^^ the trait `Pool` is not implemented for `Unknown` | = help: the trait `Pool` is implemented for `deadpool::managed::Pool` note: required by a bound in `rocket_db_pools::Database::Pool` --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | | type Pool: Pool; | ^^^^ required by this bound in `Database::Pool` error[E0277]: the trait bound `Vec: Pool` is not satisfied --> tests/ui-fail-nightly/database-types.rs:11:10 | 11 | struct B(Vec); | ^^^^^^^^ the trait `Pool` is not implemented for `Vec` | = help: the trait `Pool` is implemented for `deadpool::managed::Pool` note: required by a bound in `rocket_db_pools::Database::Pool` --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | | type Pool: Pool; | ^^^^ required by this bound in `Database::Pool` ================================================ FILE: contrib/db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr ================================================ error: invalid value: expected string literal --> tests/ui-fail-stable/database-syntax.rs:4:12 | 4 | #[database(123)] | ^^^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:3:10 | 3 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` --> tests/ui-fail-stable/database-syntax.rs:8:25 | 8 | #[database("some-name", "another")] | ^^^^^^^^^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:7:10 | 7 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `name` --> tests/ui-fail-stable/database-syntax.rs:12:25 | 12 | #[database("some-name", name = "another")] | ^^^^^^^^^^^^^^^^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:11:10 | 11 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: enums are not supported --> tests/ui-fail-stable/database-syntax.rs:16:1 | 16 | / #[database("foo")] 17 | | enum D { } | |___________^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:15:10 | 15 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: missing `#[database("name")]` attribute --> tests/ui-fail-stable/database-syntax.rs:20:1 | 20 | struct E(deadpool_postgres::Pool); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:19:10 | 19 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field --> tests/ui-fail-stable/database-syntax.rs:23:1 | 23 | / #[database("foo")] 24 | | struct F; | |_________^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:22:10 | 22 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field --> tests/ui-fail-stable/database-syntax.rs:27:1 | 27 | / #[database("foo")] 28 | | struct G(deadpool_postgres::Pool, deadpool_postgres::Pool); | |___________________________________________________________^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:26:10 | 26 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported --> tests/ui-fail-stable/database-syntax.rs:31:1 | 31 | / #[database("foo")] 32 | | struct H { 33 | | foo: deadpool_postgres::Pool, 34 | | } | |_^ error: [note] error occurred while deriving `Database` --> tests/ui-fail-stable/database-syntax.rs:30:10 | 30 | #[derive(Database)] | ^^^^^^^^ | = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: contrib/db_pools/codegen/tests/ui-fail-stable/database-types.stderr ================================================ error[E0277]: the trait bound `Unknown: Pool` is not satisfied --> tests/ui-fail-stable/database-types.rs:7:10 | 7 | struct A(Unknown); | ^^^^^^^ the trait `Pool` is not implemented for `Unknown` | = help: the trait `Pool` is implemented for `deadpool::managed::Pool` note: required by a bound in `rocket_db_pools::Database::Pool` --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | | type Pool: Pool; | ^^^^ required by this bound in `Database::Pool` error[E0277]: the trait bound `Vec: Pool` is not satisfied --> tests/ui-fail-stable/database-types.rs:11:10 | 11 | struct B(Vec); | ^^^^^^^^ the trait `Pool` is not implemented for `Vec` | = help: the trait `Pool` is implemented for `deadpool::managed::Pool` note: required by a bound in `rocket_db_pools::Database::Pool` --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | | type Pool: Pool; | ^^^^ required by this bound in `Database::Pool` ================================================ FILE: contrib/db_pools/codegen/tests/ui-fail.rs ================================================ #[test] #[ignore] fn ui() { let path = match version_check::is_feature_flaggable() { Some(true) => "ui-fail-nightly", _ => "ui-fail-stable" }; let t = trybuild::TestCases::new(); t.compile_fail(format!("tests/{}/*.rs", path)); } ================================================ FILE: contrib/db_pools/lib/Cargo.toml ================================================ [package] name = "rocket_db_pools" version = "0.1.0" authors = ["Sergio Benitez ", "Jeb Rosen "] description = "Rocket async database pooling support" repository = "https://github.com/rwf2/Rocket/tree/master/contrib/db_pools" readme = "../README.md" keywords = ["rocket", "framework", "database", "pools"] license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.75" [package.metadata.docs.rs] all-features = true [lints] workspace = true [features] # deadpool features deadpool_postgres = ["deadpool-postgres", "deadpool"] deadpool_redis = ["deadpool-redis", "deadpool"] # sqlx features sqlx_mysql = ["sqlx", "sqlx/mysql", "log"] sqlx_postgres = ["sqlx", "sqlx/postgres", "log"] sqlx_sqlite = ["sqlx", "sqlx/sqlite", "log"] sqlx_macros = ["sqlx/macros"] # diesel features diesel_postgres = ["diesel-async/postgres", "diesel-async/deadpool", "deadpool", "diesel"] diesel_mysql = ["diesel-async/mysql", "diesel-async/deadpool", "deadpool", "diesel"] # implicit features: mongodb [dependencies.rocket] path = "../../../core/lib" version = "0.6.0-dev" default-features = false [dependencies.rocket_db_pools_codegen] path = "../codegen" version = "0.1.0" [dependencies.deadpool] version = "0.12.1" default-features = false features = ["rt_tokio_1", "managed"] optional = true [dependencies.deadpool-postgres] version = "0.14" default-features = false features = ["rt_tokio_1"] optional = true [dependencies.deadpool-redis] version = "0.16" default-features = false features = ["rt_tokio_1"] optional = true [dependencies.mongodb] version = "3" default-features = false features = ["compat-3-0-0", "rustls-tls"] optional = true [dependencies.diesel-async] version = "0.6.0" default-features = false features = ["async-connection-wrapper"] optional = true [dependencies.diesel] version = "2.1" default-features = false optional = true [dependencies.sqlx] version = "0.8" default-features = false features = ["runtime-tokio-rustls"] optional = true [dependencies.log] version = "0.4" default-features = false optional = true [dev-dependencies.rocket] path = "../../../core/lib" default-features = false features = ["json"] [build-dependencies] version_check = "0.9" ================================================ FILE: contrib/db_pools/lib/src/config.rs ================================================ use rocket::serde::{Deserialize, Serialize}; /// Base configuration for all database drivers. /// /// A dictionary matching this structure is extracted from the active /// [`Figment`](crate::figment::Figment), scoped to `databases.name`, where /// `name` is the name of the database, by the /// [`Initializer`](crate::Initializer) fairing on ignition and used to /// configure the relevant database and database pool. /// /// With the default provider, these parameters are typically configured in a /// `Rocket.toml` file: /// /// ```toml /// [default.databases.db_name] /// url = "/path/to/db.sqlite" /// /// # Only `url` is required. These have sane defaults and are optional. /// min_connections = 64 /// max_connections = 1024 /// connect_timeout = 5 /// idle_timeout = 120 /// /// # This option is only supported by the `sqlx_sqlite` driver. /// extensions = ["memvfs", "rot13"] /// ``` /// /// Alternatively, a custom provider can be used. For example, a custom `Figment` /// with a global `databases.name` configuration: /// /// ```rust /// # use rocket::launch; /// #[launch] /// fn rocket() -> _ { /// let figment = rocket::Config::figment() /// .merge(("databases.name", rocket_db_pools::Config { /// url: "db:specific@config&url".into(), /// min_connections: None, /// max_connections: 1024, /// connect_timeout: 3, /// idle_timeout: None, /// extensions: None, /// })); /// /// rocket::custom(figment) /// } /// ``` /// /// For general information on configuration in Rocket, see [`rocket::config`]. /// For higher-level details on configuring a database, see the [crate-level /// docs](crate#configuration). // NOTE: Defaults provided by the figment created in the `Initializer` fairing. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(crate = "rocket::serde")] pub struct Config { /// Database-specific connection and configuration URL. /// /// The format of the URL is database specific; consult your database's /// documentation. pub url: String, /// Minimum number of connections to maintain in the pool. /// /// **Note:** `deadpool` drivers do not support and thus ignore this value. /// /// _Default:_ `None`. pub min_connections: Option, /// Maximum number of connections to maintain in the pool. /// /// _Default:_ `workers * 4`. pub max_connections: usize, /// Number of seconds to wait for a connection before timing out. /// /// If the timeout elapses before a connection can be made or retrieved from /// a pool, an error is returned. /// /// _Default:_ `5`. pub connect_timeout: u64, /// Maximum number of seconds to keep a connection alive for. /// /// After a connection is established, it is maintained in a pool for /// efficient connection retrieval. When an `idle_timeout` is set, that /// connection will be closed after the timeout elapses. If an /// `idle_timeout` is not specified, the behavior is driver specific but /// typically defaults to keeping a connection active indefinitely. /// /// _Default:_ `None`. pub idle_timeout: Option, /// A list of database extensions to load at run-time. /// /// **Note:** Only the `sqlx_sqlite` driver supports this option (for SQLite /// extensions) at this time. All other drivers ignore this option. /// /// _Default:_ `None`. pub extensions: Option>, } impl Default for Config { fn default() -> Self { Self { url: Default::default(), min_connections: Default::default(), max_connections: rocket::Config::default().workers * 4, connect_timeout: 5, idle_timeout: Default::default(), extensions: Default::default(), } } } #[cfg(test)] mod tests { use super::Config; #[test] fn default_values_sane() { let config = Config::default(); assert_ne!(config.max_connections, 0); assert_eq!(config.connect_timeout, 5); } } ================================================ FILE: contrib/db_pools/lib/src/database.rs ================================================ use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use rocket::{error, Build, Ignite, Phase, Rocket, Sentinel, Orbit}; use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::request::{FromRequest, Outcome, Request}; use rocket::figment::providers::Serialized; use rocket::http::Status; use crate::Pool; /// Derivable trait which ties a database [`Pool`] with a configuration name. /// /// This trait should rarely, if ever, be implemented manually. Instead, it /// should be derived: /// /// ```rust /// # #[cfg(feature = "deadpool_redis")] mod _inner { /// # use rocket::launch; /// use rocket_db_pools::{deadpool_redis, Database}; /// /// #[derive(Database)] /// #[database("memdb")] /// struct Db(deadpool_redis::Pool); /// /// #[launch] /// fn rocket() -> _ { /// rocket::build().attach(Db::init()) /// } /// # } /// ``` /// /// See the [`Database` derive](derive@crate::Database) for details. pub trait Database: From + DerefMut + Send + Sync + 'static { /// The [`Pool`] type of connections to this database. /// /// When `Database` is derived, this takes the value of the `Inner` type in /// `struct Db(Inner)`. type Pool: Pool; /// The configuration name for this database. /// /// When `Database` is derived, this takes the value `"name"` in the /// `#[database("name")]` attribute. const NAME: &'static str; /// Returns a fairing that initializes the database and its connection pool. /// /// # Example /// /// ```rust /// # #[cfg(feature = "deadpool_postgres")] mod _inner { /// # use rocket::launch; /// use rocket_db_pools::{deadpool_postgres, Database}; /// /// #[derive(Database)] /// #[database("pg_db")] /// struct Db(deadpool_postgres::Pool); /// /// #[launch] /// fn rocket() -> _ { /// rocket::build().attach(Db::init()) /// } /// # } /// ``` fn init() -> Initializer { Initializer::new() } /// Returns a reference to the initialized database in `rocket`. The /// initializer fairing returned by `init()` must have already executed for /// `Option` to be `Some`. This is guaranteed to be the case if the fairing /// is attached and either: /// /// * Rocket is in the [`Orbit`](rocket::Orbit) phase. That is, the /// application is running. This is always the case in request guards /// and liftoff fairings, /// * _or_ Rocket is in the [`Build`](rocket::Build) or /// [`Ignite`](rocket::Ignite) phase and the `Initializer` fairing has /// already been run. This is the case in all fairing callbacks /// corresponding to fairings attached _after_ the `Initializer` /// fairing. /// /// # Example /// /// Run database migrations in an ignite fairing. It is imperative that the /// migration fairing be registered _after_ the `init()` fairing. /// /// ```rust /// # #[cfg(feature = "sqlx_sqlite")] mod _inner { /// # use rocket::launch; /// use rocket::{Rocket, Build}; /// use rocket::fairing::{self, AdHoc}; /// /// use rocket_db_pools::{sqlx, Database}; /// /// #[derive(Database)] /// #[database("sqlite_db")] /// struct Db(sqlx::SqlitePool); /// /// async fn run_migrations(rocket: Rocket) -> fairing::Result { /// if let Some(db) = Db::fetch(&rocket) { /// // run migrations using `db`. get the inner type with &db.0. /// Ok(rocket) /// } else { /// Err(rocket) /// } /// } /// /// #[launch] /// fn rocket() -> _ { /// rocket::build() /// .attach(Db::init()) /// .attach(AdHoc::try_on_ignite("DB Migrations", run_migrations)) /// } /// # } /// ``` fn fetch(rocket: &Rocket

) -> Option<&Self> { if let Some(db) = rocket.state() { return Some(db); } let conn = std::any::type_name::(); error!("`{conn}::init()` is not attached\n\ the fairing must be attached to use `{conn}` in routes."); None } } /// A [`Fairing`] which initializes a [`Database`] and its connection pool. /// /// A value of this type can be created for any type `D` that implements /// [`Database`] via the [`Database::init()`] method on the type. Normally, a /// value of this type _never_ needs to be constructed directly. This /// documentation exists purely as a reference. /// /// This fairing initializes a database pool. Specifically, it: /// /// 1. Reads the configuration at `database.db_name`, where `db_name` is /// [`Database::NAME`]. /// /// 2. Sets [`Config`](crate::Config) defaults on the configuration figment. /// /// 3. Calls [`Pool::init()`]. /// /// 4. Stores the database instance in managed storage, retrievable via /// [`Database::fetch()`]. /// /// The name of the fairing itself is `Initializer`, with `D` replaced with /// the type name `D` unless a name is explicitly provided via /// [`Self::with_name()`]. pub struct Initializer(Option<&'static str>, PhantomData D>); /// A request guard which retrieves a single connection to a [`Database`]. /// /// For a database type of `Db`, a request guard of `Connection` retrieves a /// single connection to `Db`. /// /// The request guard succeeds if the database was initialized by the /// [`Initializer`] fairing and a connection is available within /// [`connect_timeout`](crate::Config::connect_timeout) seconds. /// * If the `Initializer` fairing was _not_ attached, the guard _fails_ with /// status `InternalServerError`. A [`Sentinel`] guards this condition, and so /// this type of error is unlikely to occur. A `None` error is returned. /// * If a connection is not available within `connect_timeout` seconds or /// another error occurs, the guard _fails_ with status `ServiceUnavailable` /// and the error is returned in `Some`. /// /// ## Deref /// /// A type of `Connection` dereferences, mutably and immutably, to the /// native database connection type. The [driver table](crate#supported-drivers) /// lists the concrete native `Deref` types. /// /// # Example /// /// ```rust /// # #[cfg(feature = "sqlx_sqlite")] mod _inner { /// # use rocket::get; /// # type Pool = rocket_db_pools::sqlx::SqlitePool; /// use rocket_db_pools::{Database, Connection}; /// /// #[derive(Database)] /// #[database("db")] /// struct Db(Pool); /// /// #[get("/")] /// async fn db_op(db: Connection) { /// // use `&*db` to get an immutable borrow to the native connection type /// // use `&mut *db` to get a mutable borrow to the native connection type /// } /// # } /// ``` pub struct Connection(::Connection); impl Initializer { /// Returns a database initializer fairing for `D`. /// /// This method should never need to be called manually. See the [crate /// docs](crate) for usage information. pub fn new() -> Self { Self(None, std::marker::PhantomData) } /// Returns a database initializer fairing for `D` with name `name`. /// /// This method should never need to be called manually. See the [crate /// docs](crate) for usage information. pub fn with_name(name: &'static str) -> Self { Self(Some(name), std::marker::PhantomData) } } impl Connection { /// Returns the internal connection value. See the [`Connection` Deref /// column](crate#supported-drivers) for the expected type of this value. /// /// Note that `Connection` derefs to the internal connection type, so /// using this method is likely unnecessary. See [deref](Connection#deref) /// for examples. /// /// # Example /// /// ```rust /// # #[cfg(feature = "sqlx_sqlite")] mod _inner { /// # use rocket::get; /// # type Pool = rocket_db_pools::sqlx::SqlitePool; /// use rocket_db_pools::{Database, Connection}; /// /// #[derive(Database)] /// #[database("db")] /// struct Db(Pool); /// /// #[get("/")] /// async fn db_op(db: Connection) { /// let inner = db.into_inner(); /// } /// # } /// ``` pub fn into_inner(self) -> ::Connection { self.0 } } #[rocket::async_trait] impl Fairing for Initializer { fn info(&self) -> Info { Info { name: self.0.unwrap_or(std::any::type_name::()), kind: Kind::Ignite | Kind::Shutdown, } } async fn on_ignite(&self, rocket: Rocket) -> fairing::Result { let workers: usize = rocket.figment() .extract_inner(rocket::Config::WORKERS) .unwrap_or_else(|_| rocket::Config::default().workers); let figment = rocket.figment() .focus(&format!("databases.{}", D::NAME)) .join(Serialized::default("max_connections", workers * 4)) .join(Serialized::default("connect_timeout", 5)); match ::init(&figment).await { Ok(pool) => Ok(rocket.manage(D::from(pool))), Err(e) => { error!("database initialization failed: {e}"); Err(rocket) } } } async fn on_shutdown(&self, rocket: &Rocket) { if let Some(db) = D::fetch(rocket) { db.close().await; } } } #[rocket::async_trait] impl<'r, D: Database> FromRequest<'r> for Connection { type Error = Option<::Error>; async fn from_request(req: &'r Request<'_>) -> Outcome { match D::fetch(req.rocket()) { Some(db) => match db.get().await { Ok(conn) => Outcome::Success(Connection(conn)), Err(e) => Outcome::Error((Status::ServiceUnavailable, Some(e))), }, None => Outcome::Error((Status::InternalServerError, None)), } } } impl Sentinel for Connection { fn abort(rocket: &Rocket) -> bool { D::fetch(rocket).is_none() } } impl Deref for Connection { type Target = ::Connection; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Connection { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } ================================================ FILE: contrib/db_pools/lib/src/diesel.rs ================================================ //! Re-export of [`diesel`] with prelude types overridden with `async` variants //! from [`diesel_async`]. //! //! # Usage //! //! To use `async` `diesel` support provided here, enable the following //! dependencies in your `Cargo.toml`: //! //! ```toml //! [dependencies] //! rocket = "0.6.0-dev" //! diesel = "2" //! //! [dependencies.rocket_db_pools] //! version = "0.1.0" //! features = ["diesel_mysql"] //! ``` //! //! Then, import `rocket_db_pools::diesel::prelude::*` as well as the //! appropriate pool type and, optionally, [`QueryResult`]. To use macros or //! `diesel` functions, use `diesel::` directly. That is, _do not_ import //! `rocket_db_pools::diesel`. Doing so will, by design, cause import errors. //! //! # Example //! //! ```rust //! # #[macro_use] extern crate rocket; //! # #[cfg(feature = "diesel_mysql")] { //! use rocket_db_pools::{Database, Connection}; //! use rocket_db_pools::diesel::{QueryResult, MysqlPool, prelude::*}; //! //! #[derive(Database)] //! #[database("diesel_mysql")] //! struct Db(MysqlPool); //! //! #[derive(Queryable, Insertable)] //! #[diesel(table_name = posts)] //! struct Post { //! id: i64, //! title: String, //! published: bool, //! } //! //! diesel::table! { //! posts (id) { //! id -> BigInt, //! title -> Text, //! published -> Bool, //! } //! } //! //! #[get("/")] //! async fn list(mut db: Connection) -> QueryResult { //! let post_ids: Vec = posts::table //! .select(posts::id) //! .load(&mut db) //! .await?; //! //! Ok(format!("{post_ids:?}")) //! } //! # } //! ``` /// The [`diesel`] prelude with `sync`-only traits replaced with their /// [`diesel_async`] variants. pub mod prelude { #[doc(inline)] pub use diesel::prelude::*; #[doc(inline)] pub use diesel_async::{AsyncConnection, RunQueryDsl, SaveChangesDsl}; } #[doc(hidden)] pub use diesel::*; #[doc(hidden)] pub use diesel_async::{RunQueryDsl, SaveChangesDsl, *}; #[doc(hidden)] #[cfg(feature = "diesel_postgres")] pub use diesel_async::pg; #[doc(inline)] pub use diesel_async::pooled_connection::deadpool::Pool; #[doc(inline)] pub use diesel_async::async_connection_wrapper::AsyncConnectionWrapper; #[doc(inline)] #[cfg(feature = "diesel_mysql")] pub use diesel_async::AsyncMysqlConnection; #[doc(inline)] #[cfg(feature = "diesel_postgres")] pub use diesel_async::AsyncPgConnection; /// Alias of a `Result` with an error type of [`Debug`] for a `diesel::Error`. /// /// `QueryResult` is a [`Responder`](rocket::response::Responder) when `T` (the /// `Ok` value) is a `Responder`. By using this alias as a route handler's /// return type, the `?` operator can be applied to fallible `diesel` functions /// in the route handler while still providing a valid `Responder` return type. /// /// See the [module level docs](self#example) for a usage example. /// /// [`Debug`]: rocket::response::Debug pub type QueryResult> = Result; /// Type alias for an `async` pool of MySQL connections for `async` [diesel]. /// /// ```rust /// # extern crate rocket; /// # #[cfg(feature = "diesel_mysql")] { /// # use rocket::get; /// use rocket_db_pools::{Database, Connection}; /// use rocket_db_pools::diesel::{MysqlPool, prelude::*}; /// /// #[derive(Database)] /// #[database("my_mysql_db_name")] /// struct Db(MysqlPool); /// /// #[get("/")] /// async fn use_db(mut db: Connection) { /// /* .. */ /// } /// # } /// ``` #[cfg(feature = "diesel_mysql")] pub type MysqlPool = Pool; /// Type alias for an `async` pool of Postgres connections for `async` [diesel]. /// /// ```rust /// # extern crate rocket; /// # #[cfg(feature = "diesel_postgres")] { /// # use rocket::get; /// use rocket_db_pools::{Database, Connection}; /// use rocket_db_pools::diesel::{PgPool, prelude::*}; /// /// #[derive(Database)] /// #[database("my_pg_db_name")] /// struct Db(PgPool); /// /// #[get("/")] /// async fn use_db(mut db: Connection) { /// /* .. */ /// } /// # } /// ``` #[cfg(feature = "diesel_postgres")] pub type PgPool = Pool; ================================================ FILE: contrib/db_pools/lib/src/error.rs ================================================ use std::fmt; /// A general error type for use by [`Pool`](crate::Pool#implementing) /// implementors and returned by the [`Connection`](crate::Connection) request /// guard. #[derive(Debug)] pub enum Error { /// An error that occurred during database/pool initialization. Init(A), /// An error that occurred while retrieving a connection from the pool. Get(B), /// A [`Figment`](crate::figment::Figment) configuration error. Config(crate::figment::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Init(e) => write!(f, "failed to initialize database: {}", e), Error::Get(e) => write!(f, "failed to get db connection: {}", e), Error::Config(e) => write!(f, "bad configuration: {}", e), } } } impl std::error::Error for Error where A: fmt::Debug + fmt::Display, B: fmt::Debug + fmt::Display {} impl From for Error { fn from(e: crate::figment::Error) -> Self { Self::Config(e) } } ================================================ FILE: contrib/db_pools/lib/src/lib.rs ================================================ //! Asynchronous database driver connection pooling integration for Rocket. //! //! # Quickstart //! //! 1. Add `rocket_db_pools` as a dependency with one or more [database driver //! features](#supported-drivers) enabled: //! //! ```toml //! [dependencies.rocket_db_pools] //! version = "0.1.0" //! features = ["sqlx_sqlite"] //! ``` //! //! 2. Choose a name for your database, here `sqlite_logs`. //! [Configure](#configuration) _at least_ a URL for the database: //! //! ```toml //! [default.databases.sqlite_logs] //! url = "/path/to/database.sqlite" //! ``` //! //! 3. [Derive](derive@Database) [`Database`] for a unit type (`Logs` here) //! which wraps the selected driver's [`Pool`] type (see [the driver //! table](#supported-drivers)) and is decorated with `#[database("name")]`. //! Attach `Type::init()` to your application's `Rocket` to initialize the //! database pool: //! //! ```rust //! # #[cfg(feature = "sqlx_sqlite")] mod _inner { //! # use rocket::launch; //! use rocket_db_pools::{sqlx, Database}; //! //! #[derive(Database)] //! #[database("sqlite_logs")] //! struct Logs(sqlx::SqlitePool); //! //! #[launch] //! fn rocket() -> _ { //! rocket::build().attach(Logs::init()) //! } //! # } //! ``` //! //! 4. Use [`Connection`](Connection) as a request guard to retrieve an //! active database connection, which dereferences to the native type in the //! [`Connection` deref](#supported-drivers) column. //! //! ```rust //! # #[cfg(feature = "sqlx_sqlite")] mod _inner { //! # use rocket::{get, response::Responder}; //! # use rocket_db_pools::{sqlx, Database}; //! # #[derive(Database)] //! # #[database("sqlite_logs")] //! # struct Logs(sqlx::SqlitePool); //! # //! # #[derive(Responder)] //! # struct Log(String); //! # //! use rocket_db_pools::Connection; //! use rocket_db_pools::sqlx::Row; //! //! #[get("/")] //! async fn read(mut db: Connection, id: i64) -> Option { //! sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id) //! .fetch_one(&mut **db).await //! .and_then(|r| Ok(Log(r.try_get(0)?))) //! .ok() //! } //! # } //! ``` //! //! Alternatively, use a reference to the database type as a request guard to //! retrieve the entire pool, but note that unlike retrieving a `Connection`, //! doing so does _not_ guarantee that a connection is available: //! //! ```rust //! # #[cfg(feature = "sqlx_sqlite")] mod _inner { //! # use rocket::{get, response::Responder}; //! # use rocket_db_pools::{sqlx, Database}; //! # #[derive(Database)] //! # #[database("sqlite_logs")] //! # struct Logs(sqlx::SqlitePool); //! # //! # #[derive(Responder)] //! # struct Log(String); //! # //! use rocket_db_pools::sqlx::Row; //! //! #[get("/")] //! async fn read(db: &Logs, id: i64) -> Option { //! sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id) //! .fetch_one(&db.0).await //! .and_then(|r| Ok(Log(r.try_get(0)?))) //! .ok() //! } //! # } //! ``` //! //! # Supported Drivers //! //! At present, this crate supports _four_ drivers: [`deadpool`], [`sqlx`], //! [`mongodb`], and [`diesel`]. Each driver may support multiple databases. //! Drivers have a varying degree of support for graceful shutdown, affected by //! the `Type::init()` fairing on Rocket shutdown. //! //! ## `deadpool` (v0.12) //! //! | Database | Feature | [`Pool`] Type | [`Connection`] Deref | //! |----------|-----------------------------|-----------------------------|--------------------------------------| //! | Postgres | `deadpool_postgres` (v0.14) | [`deadpool_postgres::Pool`] | [`deadpool_postgres::ClientWrapper`] | //! | Redis | `deadpool_redis` (v0.16) | [`deadpool_redis::Pool`] | [`deadpool_redis::Connection`] | //! //! On shutdown, new connections are denied. Shutdown _does not_ wait for //! connections to be returned. //! //! ## `sqlx` (v0.7) //! //! | Database | Feature | [`Pool`] Type | [`Connection`] Deref | //! |----------|-----------------|----------------------|------------------------------------------| //! | Postgres | `sqlx_postgres` | [`sqlx::PgPool`] | [`sqlx::pool::PoolConnection`] | //! | MySQL | `sqlx_mysql` | [`sqlx::MySqlPool`] | [`sqlx::pool::PoolConnection`] | //! | SQLite | `sqlx_sqlite` | [`sqlx::SqlitePool`] | [`sqlx::pool::PoolConnection`] | //! //! [`sqlx::PgPool`]: https://docs.rs/sqlx/0.6/sqlx/type.PgPool.html //! [`sqlx::MySqlPool`]: https://docs.rs/sqlx/0.6/sqlx/type.MySqlPool.html //! [`sqlx::SqlitePool`]: https://docs.rs/sqlx/0.6/sqlx/type.SqlitePool.html //! [`sqlx::pool::PoolConnection`]: https://docs.rs/sqlx/0.6/sqlx/pool/struct.PoolConnection.html //! [`sqlx::pool::PoolConnection`]: https://docs.rs/sqlx/0.6/sqlx/pool/struct.PoolConnection.html //! [`sqlx::pool::PoolConnection`]: https://docs.rs/sqlx/0.6/sqlx/pool/struct.PoolConnection.html //! //! On shutdown, new connections are denied. Shutdown waits for connections to //! be returned. //! //! ## `mongodb` (v3) //! //! | Database | Feature | [`Pool`] Type and [`Connection`] Deref | //! |----------|-----------|----------------------------------------| //! | MongoDB | `mongodb` | [`mongodb::Client`] | //! //! Graceful shutdown is not supported. //! //! ## `diesel` (v2) //! //! | Database | Feature | [`Pool`] Type | [`Connection`] Deref | //! |----------|-------------------|-----------------------|----------------------------------| //! | Postgres | `diesel_postgres` | [`diesel::PgPool`] | [`diesel::AsyncPgConnection`] | //! | MySQL | `diesel_mysql` | [`diesel::MysqlPool`] | [`diesel::AsyncMysqlConnection`] | //! //! //! See [`diesel`] for usage details. //! //! On shutdown, new connections are denied. Shutdown _does not_ wait for //! connections to be returned. //! //! ## Enabling Additional Driver Features //! //! Only the minimal features for each driver crate are enabled by //! `rocket_db_pools`. To use additional driver functionality exposed via its //! crate's features, you'll need to depend on the crate directly with those //! features enabled in `Cargo.toml`: //! //! ```toml //! [dependencies.sqlx] //! version = "0.7" //! default-features = false //! features = ["macros", "migrate"] //! //! [dependencies.rocket_db_pools] //! version = "0.1.0" //! features = ["sqlx_sqlite"] //! ``` //! //! # Configuration //! //! Configuration for a database named `db_name` is deserialized from a //! `databases.db_name` configuration parameter into a [`Config`] structure via //! Rocket's [configuration facilities](rocket::config). By default, //! configuration can be provided in `Rocket.toml`: //! //! ```toml //! [default.databases.db_name] //! url = "db.sqlite" //! //! # Only `url` is required. These have sane defaults and are optional. //! min_connections = 64 //! max_connections = 1024 //! connect_timeout = 5 //! idle_timeout = 120 //! //! # This option is only supported by the `sqlx_sqlite` driver. //! extensions = ["memvfs", "rot13"] //! ``` //! //! Or via environment variables: //! //! ```sh //! ROCKET_DATABASES='{db_name={url="db.sqlite",idle_timeout=120}}' //! ``` //! //! See [`Config`] for details on configuration parameters. //! //! **Note:** `deadpool` and `diesel` drivers do not support and thus ignore the //! `min_connections` value. //! //! ## Driver Defaults //! //! Some drivers provide configuration defaults different from the underlying //! database's defaults. A best-effort attempt is made to document those //! differences below: //! //! * `sqlx_sqlite` //! //! - foreign keys : `enabled` //! - journal mode : `WAL` //! - create-missing : `enabled` //! - synchronous : `full` (even when `WAL`) //! - busy timeout : `connection_timeout` //! //! * `sqlx_postgres` //! //! - sslmode : `prefer` //! - statement-cache-capacity : `100` //! - user : result of `whoami` //! //! * `sqlx_mysql` //! //! - sslmode : `PREFERRED` //! - statement-cache-capacity : `100` //! //! # Extending //! //! Any database driver can implement support for this library by implementing //! the [`Pool`] trait. #![doc(html_root_url = "https://api.rocket.rs/master/rocket_db_pools")] #![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")] #![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")] #![deny(missing_docs)] pub use rocket; /// Re-export of the `figment` crate. #[doc(inline)] pub use rocket::figment; #[cfg(any(feature = "diesel_postgres", feature = "diesel_mysql"))] pub mod diesel; #[cfg(feature = "deadpool_postgres")] pub use deadpool_postgres; #[cfg(feature = "deadpool_redis")] pub use deadpool_redis; #[cfg(feature = "mongodb")] pub use mongodb; #[cfg(feature = "sqlx")] pub use sqlx; mod database; mod error; mod pool; mod config; pub use self::database::{Connection, Database, Initializer}; pub use self::error::Error; pub use self::pool::Pool; pub use self::config::Config; pub use rocket_db_pools_codegen::*; ================================================ FILE: contrib/db_pools/lib/src/pool.rs ================================================ use rocket::figment::Figment; #[allow(unused_imports)] use {std::time::Duration, crate::{Error, Config}}; /// Generic [`Database`](crate::Database) driver connection pool trait. /// /// This trait provides a generic interface to various database pooling /// implementations in the Rust ecosystem. It can be implemented by anyone, but /// this crate provides implementations for common drivers. /// /// **Implementations of this trait outside of this crate should be rare. You /// _do not_ need to implement this trait or understand its specifics to use /// this crate.** /// /// ## Async Trait /// /// [`Pool`] is an _async_ trait. Implementations of `Pool` must be decorated /// with an attribute of `#[async_trait]`: /// /// ```rust /// # #[macro_use] extern crate rocket; /// use rocket::figment::Figment; /// use rocket_db_pools::Pool; /// /// # struct MyPool; /// # type Connection = (); /// # type Error = std::convert::Infallible; /// #[rocket::async_trait] /// impl Pool for MyPool { /// type Connection = Connection; /// /// type Error = Error; /// /// async fn init(figment: &Figment) -> Result { /// todo!("initialize and return an instance of the pool"); /// } /// /// async fn get(&self) -> Result { /// todo!("fetch one connection from the pool"); /// } /// /// async fn close(&self) { /// todo!("gracefully shutdown connection pool"); /// } /// } /// ``` /// /// ## Implementing /// /// Implementations of `Pool` typically trace the following outline: /// /// 1. The `Error` associated type is set to [`Error`]. /// /// 2. A [`Config`] is [extracted](Figment::extract()) from the `figment` /// passed to init. /// /// 3. The pool is initialized and returned in `init()`, wrapping /// initialization errors in [`Error::Init`]. /// /// 4. A connection is retrieved in `get()`, wrapping errors in /// [`Error::Get`]. /// /// Concretely, this looks like: /// /// ```rust /// use rocket::figment::Figment; /// use rocket_db_pools::{Pool, Config, Error}; /// # /// # type InitError = std::convert::Infallible; /// # type GetError = std::convert::Infallible; /// # type Connection = (); /// # /// # struct MyPool(Config); /// # impl MyPool { /// # fn new(c: Config) -> Result { /// # Ok(Self(c)) /// # } /// # /// # fn acquire(&self) -> Result { /// # Ok(()) /// # } /// # /// # async fn shutdown(&self) { } /// # } /// /// #[rocket::async_trait] /// impl Pool for MyPool { /// type Connection = Connection; /// /// type Error = Error; /// /// async fn init(figment: &Figment) -> Result { /// // Extract the config from `figment`. /// let config: Config = figment.extract()?; /// /// // Read config values, initialize `MyPool`. Map errors of type /// // `InitError` to `Error` with `Error::Init`. /// let pool = MyPool::new(config).map_err(Error::Init)?; /// /// // Return the fully initialized pool. /// Ok(pool) /// } /// /// async fn get(&self) -> Result { /// // Get one connection from the pool, here via an `acquire()` method. /// // Map errors of type `GetError` to `Error<_, GetError>`. /// self.acquire().map_err(Error::Get) /// } /// /// async fn close(&self) { /// self.shutdown().await; /// } /// } /// ``` #[rocket::async_trait] pub trait Pool: Sized + Send + 'static { /// The connection type managed by this pool, returned by [`Self::get()`]. type Connection; /// The error type returned by [`Self::init()`] and [`Self::get()`]. type Error: std::error::Error; /// Constructs a pool from a [Value](rocket::figment::value::Value). /// /// It is up to each implementor of `Pool` to define its accepted /// configuration value(s) via the `Config` associated type. Most /// integrations provided in `rocket_db_pools` use [`Config`], which /// accepts a (required) `url` and an (optional) `pool_size`. /// /// ## Errors /// /// This method returns an error if the configuration is not compatible, or /// if creating a pool failed due to an unavailable database server, /// insufficient resources, or another database-specific error. async fn init(figment: &Figment) -> Result; /// Asynchronously retrieves a connection from the factory or pool. /// /// ## Errors /// /// This method returns an error if a connection could not be retrieved, /// such as a preconfigured timeout elapsing or when the database server is /// unavailable. async fn get(&self) -> Result; /// Shutdown the connection pool, disallowing any new connections from being /// retrieved and waking up any tasks with active connections. /// /// The returned future may either resolve when all connections are known to /// have closed or at any point prior. Details are implementation specific. async fn close(&self); } #[cfg(feature = "deadpool")] mod deadpool_postgres { use deadpool::{Runtime, managed::{Manager, Pool, PoolError, Object}}; use super::{Duration, Error, Config, Figment}; #[cfg(feature = "diesel")] use diesel_async::pooled_connection::AsyncDieselConnectionManager; pub trait DeadManager: Manager + Sized + Send + 'static { fn new(config: &Config) -> Result; } #[cfg(feature = "deadpool_postgres")] impl DeadManager for deadpool_postgres::Manager { fn new(config: &Config) -> Result { Ok(Self::new(config.url.parse()?, deadpool_postgres::tokio_postgres::NoTls)) } } #[cfg(feature = "deadpool_redis")] impl DeadManager for deadpool_redis::Manager { fn new(config: &Config) -> Result { Self::new(config.url.as_str()) } } #[cfg(feature = "diesel_postgres")] impl DeadManager for AsyncDieselConnectionManager { fn new(config: &Config) -> Result { Ok(Self::new(config.url.as_str())) } } #[cfg(feature = "diesel_mysql")] impl DeadManager for AsyncDieselConnectionManager { fn new(config: &Config) -> Result { Ok(Self::new(config.url.as_str())) } } #[rocket::async_trait] impl>> crate::Pool for Pool where M::Type: Send, C: Send + 'static, M::Error: std::error::Error { type Error = Error>; type Connection = C; async fn init(figment: &Figment) -> Result { let config: Config = figment.extract()?; let manager = M::new(&config).map_err(|e| Error::Init(e.into()))?; Pool::builder(manager) .max_size(config.max_connections) .wait_timeout(Some(Duration::from_secs(config.connect_timeout))) .create_timeout(Some(Duration::from_secs(config.connect_timeout))) .recycle_timeout(config.idle_timeout.map(Duration::from_secs)) .runtime(Runtime::Tokio1) .build() .map_err(|_| Error::Init(PoolError::NoRuntimeSpecified)) } async fn get(&self) -> Result { self.get().await.map_err(Error::Get) } async fn close(&self) { >::close(self) } } } #[cfg(feature = "sqlx")] mod sqlx { use sqlx::ConnectOptions; use super::{Duration, Error, Config, Figment}; use rocket::tracing::level_filters::LevelFilter; type Options = <::Connection as sqlx::Connection>::Options; // Provide specialized configuration for particular databases. fn specialize(__options: &mut dyn std::any::Any, __config: &Config) { #[cfg(feature = "sqlx_sqlite")] if let Some(o) = __options.downcast_mut::() { *o = std::mem::take(o) .busy_timeout(Duration::from_secs(__config.connect_timeout)) .create_if_missing(true); if let Some(ref exts) = __config.extensions { for ext in exts { *o = std::mem::take(o).extension(ext.clone()); } } } } #[rocket::async_trait] impl crate::Pool for sqlx::Pool { type Error = Error; type Connection = sqlx::pool::PoolConnection; async fn init(figment: &Figment) -> Result { let config = figment.extract::()?; let mut opts = config.url.parse::>().map_err(Error::Init)?; specialize(&mut opts, &config); opts = opts.disable_statement_logging(); if let Ok(value) = figment.find_value(rocket::Config::LOG_LEVEL) { if let Some(level) = value.as_str().and_then(|v| v.parse().ok()) { let log_level = match level { LevelFilter::OFF => log::LevelFilter::Off, LevelFilter::ERROR => log::LevelFilter::Error, LevelFilter::WARN => log::LevelFilter::Warn, LevelFilter::INFO => log::LevelFilter::Info, LevelFilter::DEBUG => log::LevelFilter::Debug, LevelFilter::TRACE => log::LevelFilter::Trace, }; opts = opts.log_statements(log_level) .log_slow_statements(log_level, Duration::default()); } } Ok(sqlx::pool::PoolOptions::new() .max_connections(config.max_connections as u32) .acquire_timeout(Duration::from_secs(config.connect_timeout)) .idle_timeout(config.idle_timeout.map(Duration::from_secs)) .min_connections(config.min_connections.unwrap_or_default()) .connect_lazy_with(opts)) } async fn get(&self) -> Result { self.acquire().await.map_err(Error::Get) } async fn close(&self) { >::close(self).await; } } } #[cfg(feature = "mongodb")] mod mongodb { use mongodb::{Client, options::ClientOptions}; use super::{Duration, Error, Config, Figment}; #[rocket::async_trait] impl crate::Pool for Client { type Error = Error; type Connection = Client; async fn init(figment: &Figment) -> Result { let config = figment.extract::()?; let mut opts = ClientOptions::parse(&config.url).await.map_err(Error::Init)?; opts.min_pool_size = config.min_connections; opts.max_pool_size = Some(config.max_connections as u32); opts.max_idle_time = config.idle_timeout.map(Duration::from_secs); opts.connect_timeout = Some(Duration::from_secs(config.connect_timeout)); opts.server_selection_timeout = Some(Duration::from_secs(config.connect_timeout)); Client::with_options(opts).map_err(Error::Init) } async fn get(&self) -> Result { Ok(self.clone()) } async fn close(&self) { // nothing to do for mongodb } } } ================================================ FILE: contrib/db_pools/lib/tests/databases.rs ================================================ macro_rules! check_types_match { ($feature:expr, $name:ident, $Pool:ty, $Conn:ty $(,)?) => ( #[cfg(feature = $feature)] mod $name { use rocket::*; use rocket_db_pools::{Connection, Database}; #[derive(Database)] #[database("foo")] struct Db($Pool); #[get("/")] fn _db(conn: Connection) { let _: &$Conn = &*conn; } } ) } check_types_match!( "deadpool_postgres", deadpool_postgres, deadpool_postgres::Pool, deadpool_postgres::ClientWrapper, ); check_types_match!( "deadpool_redis", deadpool_redis, deadpool_redis::Pool, deadpool_redis::Connection, ); check_types_match!( "sqlx_postgres", sqlx_postgres, sqlx::PgPool, sqlx::pool::PoolConnection, ); check_types_match!( "sqlx_mysql", sqlx_mysql, sqlx::MySqlPool, sqlx::pool::PoolConnection, ); check_types_match!( "sqlx_sqlite", sqlx_sqlite, sqlx::SqlitePool, sqlx::pool::PoolConnection, ); check_types_match!( "mongodb", mongodb, mongodb::Client, mongodb::Client, ); ================================================ FILE: contrib/dyn_templates/Cargo.toml ================================================ [package] name = "rocket_dyn_templates" version = "0.1.0" authors = ["Sergio Benitez "] description = "Dynamic templating engine integration for Rocket." documentation = "https://api.rocket.rs/master/rocket_dyn_templates/" homepage = "https://rocket.rs" repository = "https://github.com/rwf2/Rocket/tree/master/contrib/dyn_templates" readme = "README.md" keywords = ["rocket", "framework", "templates", "templating", "engine"] license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.75" [lints] workspace = true [features] tera = ["dep:tera"] handlebars = ["dep:handlebars"] minijinja = ["dep:minijinja"] [dependencies] walkdir = "2.4" notify = "7" normpath = "1" tera = { version = "1.19.0", optional = true } handlebars = { version = "6.0", optional = true } [dependencies.minijinja] version = "2.0.1" optional = true features = ["loader", "speedups", "json", "urlencode"] [dependencies.rocket] version = "0.6.0-dev" path = "../../core/lib" default-features = false [dev-dependencies] pretty_assertions = "1.4" [package.metadata.docs.rs] all-features = true ================================================ FILE: contrib/dyn_templates/README.md ================================================ # `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/rwf2/Rocket/workflows/CI/badge.svg [ci]: https://github.com/rwf2/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. It supports [Handlebars], [Tera] and [MiniJinja]. [Tera]: https://docs.rs/crate/tera/1 [Handlebars]: https://docs.rs/crate/handlebars/5 [MiniJinja]: https://docs.rs/crate/minijinja/2.0.1 # 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" features = ["handlebars", "tera", "minijinja"] ``` 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, context}; #[get("/")] fn index() -> Template { Template::render("template-name", context! { field: "value" }) } #[launch] fn rocket() -> _ { rocket::build().attach(Template::fairing()) } ``` See the [crate docs] for full details. ================================================ FILE: contrib/dyn_templates/src/context.rs ================================================ use std::path::{Path, PathBuf}; use std::collections::HashMap; use std::error::Error; use crate::engine::Engines; use crate::template::TemplateInfo; use rocket::http::ContentType; use normpath::PathExt; pub(crate) type Callback = Box Result<(), Box> + Send + Sync + 'static>; pub(crate) struct Context { /// The root of the template directory. pub root: PathBuf, /// Mapping from template name to its information. pub templates: HashMap, /// Loaded template engines pub engines: Engines, } pub(crate) use self::manager::ContextManager; impl Context { /// Load all of the templates at `root`, initialize them using the relevant /// template engine, and store all of the initialized state in a `Context` /// structure, which is returned if all goes well. pub fn initialize(root: &Path, callback: &Callback) -> Option { fn is_file_with_ext(entry: &walkdir::DirEntry, ext: &str) -> bool { let is_file = entry.file_type().is_file(); let has_ext = entry.path().extension().map_or(false, |e| e == ext); is_file && has_ext } let root = match root.normalize() { Ok(root) => root.into_path_buf(), Err(e) => { error!("Invalid template directory '{}': {}.", root.display(), e); return None; } }; let mut templates: HashMap = HashMap::new(); for &ext in Engines::ENABLED_EXTENSIONS { for entry in walkdir::WalkDir::new(&root).follow_links(true) { let entry = match entry { Ok(entry) if is_file_with_ext(&entry, ext) => entry, Ok(_) | Err(_) => continue, }; let (template, data_type_str) = split_path(&root, entry.path()); if let Some(info) = templates.get(&*template) { warn!( %template, first_path = %entry.path().display(), second_path = info.path.as_ref().map(|p| display(p.display())), data_type = %info.data_type, "Template name '{template}' can refer to multiple templates.\n\ First path will be used. Second path is ignored." ); continue; } let data_type = data_type_str.as_ref() .and_then(|ext| ContentType::from_extension(ext)) .unwrap_or(ContentType::Text); templates.insert(template, TemplateInfo { path: Some(entry.into_path()), engine_ext: ext, data_type, }); } } let mut engines = Engines::init(&templates)?; if let Err(reason) = callback(&mut engines) { error!(%reason, "template customization callback failed"); return None; } for (name, engine_ext) in engines.templates() { if !templates.contains_key(name) { let data_type = Path::new(name).extension() .and_then(|osstr| osstr.to_str()) .and_then(ContentType::from_extension) .unwrap_or(ContentType::Text); let info = TemplateInfo { path: None, engine_ext, data_type }; templates.insert(name.to_string(), info); } } Some(Context { root, templates, engines }) } } #[cfg(not(debug_assertions))] mod manager { use std::ops::Deref; use super::Context; /// Wraps a Context. With `cfg(debug_assertions)` active, this structure /// additionally provides a method to reload the context at runtime. pub(crate) struct ContextManager(Context); impl ContextManager { pub fn new(ctxt: Context) -> ContextManager { ContextManager(ctxt) } pub fn context<'a>(&'a self) -> impl Deref + 'a { &self.0 } pub fn is_reloading(&self) -> bool { false } } } #[cfg(debug_assertions)] mod manager { use std::ops::{Deref, DerefMut}; use std::sync::{RwLock, Mutex}; use std::sync::mpsc::{channel, Receiver}; use notify::{recommended_watcher, Error, Event, RecommendedWatcher, RecursiveMode, Watcher}; use super::{Callback, Context}; /// Wraps a Context. With `cfg(debug_assertions)` active, this structure /// additionally provides a method to reload the context at runtime. pub(crate) struct ContextManager { /// The current template context, inside an RwLock so it can be updated. context: RwLock, /// A filesystem watcher and the receive queue for its events. watcher: Option<(RecommendedWatcher, Mutex>>)>, } impl ContextManager { pub fn new(ctxt: Context) -> ContextManager { let (tx, rx) = channel(); let watcher = recommended_watcher(tx).and_then(|mut watcher| { watcher.watch(&ctxt.root.canonicalize()?, RecursiveMode::Recursive)?; Ok(watcher) }); let watcher = match watcher { Ok(watcher) => Some((watcher, Mutex::new(rx))), Err(e) => { warn!("live template reloading initialization failed: {e}\n\ live template reloading is unavailable"); None } }; ContextManager { watcher, context: RwLock::new(ctxt), } } pub fn context(&self) -> impl Deref + '_ { self.context.read().unwrap() } pub fn is_reloading(&self) -> bool { self.watcher.is_some() } fn context_mut(&self) -> impl DerefMut + '_ { self.context.write().unwrap() } /// Checks whether any template files have changed on disk. If there /// have been changes since the last reload, all templates are /// reinitialized from disk and the user's customization callback is run /// again. pub fn reload_if_needed(&self, callback: &Callback) { let templates_changes = self.watcher.as_ref() .map(|(_, rx)| rx.lock().expect("fsevents lock").try_iter().count() > 0); if let Some(true) = templates_changes { debug!("template change detected: reloading templates"); let root = self.context().root.clone(); if let Some(new_ctxt) = Context::initialize(&root, callback) { *self.context_mut() = new_ctxt; } else { warn!("error while reloading template\n\ existing templates will remain active.") }; } } } } /// Removes the file path's extension or does nothing if there is none. fn remove_extension(path: &Path) -> PathBuf { let stem = match path.file_stem() { Some(stem) => stem, None => return path.to_path_buf() }; match path.parent() { Some(parent) => parent.join(stem), None => PathBuf::from(stem) } } /// Splits a path into a name that may be used to identify the template, and the /// template's data type, if any. fn split_path(root: &Path, path: &Path) -> (String, Option) { let rel_path = path.strip_prefix(root).unwrap().to_path_buf(); let path_no_ext = remove_extension(&rel_path); let data_type = path_no_ext.extension(); let mut name = remove_extension(&path_no_ext).to_string_lossy().into_owned(); // Ensure template name consistency on Windows systems if cfg!(windows) { name = name.replace('\\', "/"); } (name, data_type.map(|d| d.to_string_lossy().into_owned())) } #[cfg(test)] mod tests { use super::*; #[test] fn template_path_index_html() { for root in &["/", "/a/b/c/", "/a/b/c/d/", "/a/"] { for filename in &["index.html.hbs", "index.html.tera"] { let path = Path::new(root).join(filename); let (name, data_type) = split_path(Path::new(root), &path); assert_eq!(name, "index"); assert_eq!(data_type, Some("html".into())); } } } #[test] fn template_path_subdir_index_html() { for root in &["/", "/a/b/c/", "/a/b/c/d/", "/a/"] { for sub in &["a/", "a/b/", "a/b/c/", "a/b/c/d/"] { for filename in &["index.html.hbs", "index.html.tera"] { let path = Path::new(root).join(sub).join(filename); let (name, data_type) = split_path(Path::new(root), &path); let expected_name = format!("{}index", sub); assert_eq!(name, expected_name.as_str()); assert_eq!(data_type, Some("html".into())); } } } } #[test] fn template_path_doc_examples() { fn name_for(path: &str) -> String { split_path(Path::new("templates/"), &Path::new("templates/").join(path)).0 } assert_eq!(name_for("index.html.hbs"), "index"); assert_eq!(name_for("index.tera"), "index"); assert_eq!(name_for("index.hbs"), "index"); assert_eq!(name_for("dir/index.hbs"), "dir/index"); assert_eq!(name_for("dir/index.html.tera"), "dir/index"); assert_eq!(name_for("index.template.html.hbs"), "index.template"); assert_eq!(name_for("subdir/index.template.html.hbs"), "subdir/index.template"); } } ================================================ FILE: contrib/dyn_templates/src/engine/handlebars.rs ================================================ use std::path::Path; use handlebars::Handlebars; use rocket::serde::Serialize; use crate::engine::Engine; impl Engine for Handlebars<'static> { const EXT: &'static str = "hbs"; fn init<'a>(templates: impl Iterator) -> Option { let mut hb = Handlebars::new(); let mut ok = true; for (template, path) in templates { if let Err(e) = hb.register_template_file(template, path) { error!(template, path = %path.display(), "failed to register Handlebars template: {e}"); ok = false; } } ok.then_some(hb) } fn render(&self, template: &str, context: C) -> Option { if self.get_template(template).is_none() { error!(template, "requested Handlebars template does not exist."); return None; } Handlebars::render(self, template, &context) .map_err(|e| error!("Handlebars render error: {}", e)) .ok() } } ================================================ FILE: contrib/dyn_templates/src/engine/minijinja.rs ================================================ use std::sync::Arc; use std::path::Path; use std::collections::HashMap; use rocket::serde::Serialize; use minijinja::{Environment, Error, ErrorKind, AutoEscape}; use crate::engine::Engine; impl Engine for Environment<'static> { const EXT: &'static str = "j2"; fn init<'a>(templates: impl Iterator) -> Option { let _templates = Arc::new(templates .map(|(k, p)| (k.to_owned(), p.to_owned())) .collect::>()); let templates = _templates.clone(); let mut env = Environment::new(); env.set_loader(move |name| { let Some(path) = templates.get(name) else { return Ok(None); }; match std::fs::read_to_string(path) { Ok(result) => Ok(Some(result)), Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), Err(e) => Err( Error::new(ErrorKind::InvalidOperation, "template read failed").with_source(e) ), } }); let templates = _templates.clone(); env.set_auto_escape_callback(move |name| { templates.get(name) .and_then(|path| path.to_str()) .map(minijinja::default_auto_escape_callback) .unwrap_or(AutoEscape::None) }); Some(env) } fn render(&self, template: &str, context: C) -> Option { let Ok(templ) = self.get_template(template) else { error!(template, "requested template does not exist"); return None; }; match templ.render(context) { Ok(result) => Some(result), Err(e) => { span_error!("templating", template, "failed to render Minijinja template" => { let mut error = Some(&e as &dyn std::error::Error); while let Some(err) = error { error!("{err}"); error = err.source(); } }); None } } } } ================================================ FILE: contrib/dyn_templates/src/engine/mod.rs ================================================ use std::path::Path; use std::collections::HashMap; use rocket::serde::Serialize; use crate::template::TemplateInfo; #[cfg(feature = "tera")] mod tera; #[cfg(feature = "tera")] use ::tera::Tera; #[cfg(feature = "handlebars")] mod handlebars; #[cfg(feature = "handlebars")] use ::handlebars::Handlebars; #[cfg(feature = "minijinja")] mod minijinja; #[cfg(feature = "minijinja")] use ::minijinja::Environment; pub(crate) trait Engine: Send + Sync + Sized + 'static { const EXT: &'static str; fn init<'a>(templates: impl Iterator) -> Option; fn render(&self, name: &str, context: C) -> Option; } /// A structure exposing access to templating engines. /// /// Calling methods on the exposed template engine types may require importing /// types from the respective templating engine library. These types should be /// imported from the reexported crate at the root of `rocket_dyn_templates` to /// avoid version mismatches. For instance, when registering a Tera filter, the /// [`tera::Value`] and [`tera::Result`] types are required. Import them from /// `rocket_dyn_templates::tera`. The example below illustrates this: /// /// ```rust /// # #[cfg(feature = "tera")] { /// use std::collections::HashMap; /// /// use rocket_dyn_templates::{Template, Engines}; /// use rocket_dyn_templates::tera::{self, Value}; /// /// fn my_filter(value: &Value, _: &HashMap) -> tera::Result { /// # /* /// ... /// # */ unimplemented!(); /// } /// /// fn main() { /// rocket::build() /// // ... /// .attach(Template::custom(|engines: &mut Engines| { /// engines.tera.register_filter("my_filter", my_filter); /// })) /// // ... /// # ; /// } /// # } /// ``` /// /// [`tera::Value`]: crate::tera::Value /// [`tera::Result`]: crate::tera::Result pub struct Engines { /// A `Tera` templating engine. /// /// This field is only available when the `tera` feature is enabled. When /// calling methods on the `Tera` instance, ensure you use types imported /// from `rocket_dyn_templates::tera` to avoid version mismatches. #[cfg(feature = "tera")] pub tera: Tera, /// The Handlebars templating engine. /// /// This field is only available when the `handlebars` feature is enabled. /// When calling methods on the `Handlebars` instance, ensure you use types /// imported from `rocket_dyn_templates::handlebars` to avoid version /// mismatches. #[cfg(feature = "handlebars")] pub handlebars: Handlebars<'static>, /// The minijinja templating engine. /// /// This field is only available when the `minijinja` feature is enabled. /// When calling methods on the [`Environment`] instance, ensure you use /// types imported from `rocket_dyn_templates::minijinja` to avoid version /// mismatches. #[cfg(feature = "minijinja")] pub minijinja: Environment<'static>, } impl Engines { pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[ #[cfg(feature = "tera")] Tera::EXT, #[cfg(feature = "handlebars")] Handlebars::EXT, #[cfg(feature = "minijinja")] Environment::EXT, ]; pub(crate) fn init(templates: &HashMap) -> Option { fn inner(templates: &HashMap) -> Option { let named_templates = templates.iter() .filter(|&(_, i)| i.engine_ext == E::EXT) .filter_map(|(k, i)| Some((k.as_str(), i.path.as_ref()?))) .map(|(k, p)| (k, p.as_path())); E::init(named_templates) } Some(Engines { #[cfg(feature = "tera")] tera: match inner::(templates) { Some(tera) => tera, None => return None }, #[cfg(feature = "handlebars")] handlebars: match inner::>(templates) { Some(hb) => hb, None => return None }, #[cfg(feature = "minijinja")] minijinja: match inner::>(templates) { Some(hb) => hb, None => return None }, }) } pub(crate) fn render( &self, name: &str, info: &TemplateInfo, context: C, ) -> Option { #[cfg(feature = "tera")] { if info.engine_ext == Tera::EXT { return Engine::render(&self.tera, name, context); } } #[cfg(feature = "handlebars")] { if info.engine_ext == Handlebars::EXT { return Engine::render(&self.handlebars, name, context); } } #[cfg(feature = "minijinja")] { if info.engine_ext == Environment::EXT { return Engine::render(&self.minijinja, name, context); } } None } /// Returns iterator over template (name, engine_extension). pub(crate) fn templates(&self) -> impl Iterator { #[cfg(feature = "tera")] let tera = self.tera.get_template_names().map(|name| (name, Tera::EXT)); #[cfg(feature = "handlebars")] let handlebars = self.handlebars.get_templates().keys() .map(|name| (name.as_str(), Handlebars::EXT)); #[cfg(feature = "minijinja")] let minijinja = self.minijinja.templates() .map(|(name, _)| (name, Environment::EXT)); #[cfg(not(feature = "tera"))] let tera = std::iter::empty(); #[cfg(not(feature = "handlebars"))] let handlebars = std::iter::empty(); #[cfg(not(feature = "minijinja"))] let minijinja = std::iter::empty(); tera.chain(handlebars).chain(minijinja) } } ================================================ FILE: contrib/dyn_templates/src/engine/tera.rs ================================================ use std::path::Path; use std::error::Error; use tera::{Context, Tera}; use rocket::serde::Serialize; use crate::engine::Engine; impl Engine for Tera { const EXT: &'static str = "tera"; fn init<'a>(templates: impl Iterator) -> Option { // Create the Tera instance. let mut tera = Tera::default(); let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"]; tera.autoescape_on(ext.to_vec()); // Collect into a tuple of (name, path) for Tera. If we register one at // a time, it will complain about unregistered base templates. let files = templates.map(|(name, path)| (path, Some(name))); // Finally try to tell Tera about all of the templates. if let Err(e) = tera.add_template_files(files) { span_error!("templating", "Tera templating initialization failed" => { let mut error = Some(&e as &dyn Error); while let Some(err) = error { error!("{err}"); error = err.source(); } }); None } else { Some(tera) } } fn render(&self, template: &str, context: C) -> Option { if self.get_template(template).is_err() { error!(template, "requested template does not exist"); return None; }; let tera_ctx = Context::from_serialize(context) .map_err(|e| error!("Tera context error: {}.", e)) .ok()?; match Tera::render(self, template, &tera_ctx) { Ok(string) => Some(string), Err(e) => { span_error!("templating", template, "failed to render Tera template" => { let mut error = Some(&e as &dyn Error); while let Some(err) = error { error!("{err}"); error = err.source(); } }); None } } } } ================================================ FILE: contrib/dyn_templates/src/fairing.rs ================================================ use rocket::{Rocket, Build, Orbit}; use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::figment::{Source, value::magic::RelativePathBuf}; use rocket::trace::Trace; use crate::context::{Callback, Context, ContextManager}; use crate::template::DEFAULT_TEMPLATE_DIR; use crate::engine::Engines; /// The TemplateFairing initializes the template system on attach, running /// custom_callback after templates have been loaded. In debug mode, the fairing /// checks for modifications to templates before every request and reloads them /// if necessary. pub struct TemplateFairing { /// The user-provided customization callback, allowing the use of /// functionality specific to individual template engines. In debug mode, /// this callback might be run multiple times as templates are reloaded. pub callback: Callback, } #[rocket::async_trait] impl Fairing for TemplateFairing { fn info(&self) -> Info { let kind = Kind::Ignite | Kind::Liftoff; #[cfg(debug_assertions)] let kind = kind | Kind::Request; Info { kind, name: "Templating" } } /// Initializes the template context. Templates will be searched for in the /// `template_dir` config variable or the default ([DEFAULT_TEMPLATE_DIR]). /// The user's callback, if any was supplied, is called to customize the /// template engines. In debug mode, the `ContextManager::new` method /// initializes a directory watcher for auto-reloading of templates. async fn on_ignite(&self, rocket: Rocket) -> fairing::Result { let configured_dir = rocket.figment() .extract_inner::("template_dir") .map(|path| path.relative()); let path = match configured_dir { Ok(dir) => dir, Err(e) if e.missing() => DEFAULT_TEMPLATE_DIR.into(), Err(e) => { e.trace_error(); return Err(rocket); } }; if let Some(ctxt) = Context::initialize(&path, &self.callback) { Ok(rocket.manage(ContextManager::new(ctxt))) } else { error!("Template initialization failed. Aborting launch."); Err(rocket) } } async fn on_liftoff(&self, rocket: &Rocket) { let cm = rocket.state::() .expect("Template ContextManager registered in on_ignite"); span_info!("templating" => { info!(directory = %Source::from(&*cm.context().root)); info!(engines = ?Engines::ENABLED_EXTENSIONS); }); } #[cfg(debug_assertions)] async fn on_request(&self, req: &mut rocket::Request<'_>, _data: &mut rocket::Data<'_>) { let cm = req.rocket().state::() .expect("Template ContextManager registered in on_ignite"); cm.reload_if_needed(&self.callback); } } ================================================ FILE: contrib/dyn_templates/src/lib.rs ================================================ //! Dynamic templating engine support for Rocket. //! //! 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]. //! //! # Usage //! //! 1. Depend on `rocket_dyn_templates`. Enable the feature(s) corresponding //! to your templating engine(s) of choice: //! //! ```toml //! [dependencies.rocket_dyn_templates] //! version = "0.1.0" //! features = ["handlebars", "tera", "minijinja"] //! ``` //! //! 2. Write your templates inside of the [configurable] //! `${ROCKET_ROOT}/templates`. The filename _must_ end with an extension //! corresponding to an enabled engine. The second-to-last extension should //! correspond to the file's type: //! //! | Engine | Extension | Example | //! |--------------|-----------|--------------------------------------------| //! | [Tera] | `.tera` | `${ROCKET_ROOT}/templates/index.html.tera` | //! | [Handlebars] | `.hbs` | `${ROCKET_ROOT}/templates/index.html.hbs` | //! | [MiniJinja] | `.j2` | `${ROCKET_ROOT}/templates/index.html.j2` | //! //! [configurable]: #configuration //! [Tera]: https://docs.rs/crate/tera/1 //! [Handlebars]: https://docs.rs/crate/handlebars/6 //! [MiniJinja]: https://docs.rs/minijinja/2 //! //! 3. Attach `Template::fairing()` and return a [`Template`] from your routes //! via [`Template::render()`], supplying the name of the template file //! **minus the last two extensions**: //! //! ```rust //! # #[macro_use] extern crate rocket; //! use rocket_dyn_templates::{Template, context}; //! //! #[get("/")] //! fn index() -> Template { //! Template::render("index", context! { field: "value" }) //! } //! //! #[launch] //! fn rocket() -> _ { //! rocket::build().attach(Template::fairing()) //! } //! ``` //! //! ## Configuration //! //! This crate reads one configuration parameter from the configured figment: //! //! * `template_dir` (**default: `templates/`**) //! //! A path to a directory to search for template files in. Relative paths //! are considered relative to the configuration file, or there is no file, //! the current working directory. //! //! For example, to change the default and set `template_dir` to different //! values based on whether the application was compiled for debug or release //! from a `Rocket.toml` file (read by the default figment), you might write: //! //! ```toml //! [debug] //! template_dir = "static/templates" //! //! [release] //! template_dir = "/var/opt/www/templates" //! ``` //! //! **Note:** `template_dir` defaults to `templates/`. It _does not_ need to be //! specified if the default suffices. //! //! See the [configuration chapter] of the guide for more information on //! configuration. //! //! [configuration chapter]: https://rocket.rs/master/guide/configuration //! //! ## Template Naming and Content-Types //! //! Templates are rendered by _name_ via [`Template::render()`], which returns a //! [`Template`] responder. The _name_ of the template is the path to the //! template file, relative to `template_dir`, minus at most two extensions. //! //! The `Content-Type` of the response is automatically determined by the //! non-engine extension using [`ContentType::from_extension()`]. If there is no //! such extension or it is unknown, `text/plain` is used. //! //! The following table contains examples: //! //! | template path | [`Template::render()`] call | content-type | //! |-----------------------------------------------|-----------------------------------|--------------| //! | {template_dir}/index.html.hbs | `render("index")` | HTML | //! | {template_dir}/index.tera | `render("index")` | `text/plain` | //! | {template_dir}/index.hbs | `render("index")` | `text/plain` | //! | {template_dir}/dir/index.hbs | `render("dir/index")` | `text/plain` | //! | {template_dir}/dir/data.json.tera | `render("dir/data")` | JSON | //! | {template_dir}/data.template.xml.hbs | `render("data.template")` | XML | //! | {template_dir}/subdir/index.template.html.hbs | `render("subdir/index.template")` | HTML | //! //! The recommended naming scheme is to use two extensions: one for the file //! type, and one for the template extension. This means that template //! extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, and so //! on. //! //! [`ContentType::from_extension()`]: ../rocket/http/struct.ContentType.html#method.from_extension //! //! ### Rendering Context //! //! In addition to a name, [`Template::render()`] requires a context to use //! during rendering. The context can be any [`Serialize`] type that serializes //! to an `Object` (a dictionary) value. The [`context!`] macro can be used to //! create inline `Serialize`-able context objects. //! //! [`Serialize`]: rocket::serde::Serialize //! //! ```rust //! # #[macro_use] extern crate rocket; //! use rocket::serde::Serialize; //! use rocket_dyn_templates::{Template, context}; //! //! #[get("/")] //! fn index() -> Template { //! // Using the `context! { }` macro. //! Template::render("index", context! { //! site_name: "Rocket - Home Page", //! version: 127, //! }) //! } //! //! #[get("/")] //! fn index2() -> Template { //! #[derive(Serialize)] //! #[serde(crate = "rocket::serde")] //! struct IndexContext { //! site_name: &'static str, //! version: u8 //! } //! //! // Using an existing `IndexContext`, which implements `Serialize`. //! Template::render("index", IndexContext { //! site_name: "Rocket - Home Page", //! version: 127, //! }) //! } //! ``` //! //! ### Discovery, Automatic Reloads, and Engine Customization //! //! As long as one of [`Template::fairing()`], [`Template::custom()`], or //! [`Template::try_custom()`] is [attached], any file in the configured //! `template_dir` ending with a known engine extension (as described in the //! [usage section](#usage)) can be rendered. The latter two fairings allow //! customizations such as registering helpers and templates from strings. //! //! _**Note:** Templates that are registered directly via [`Template::custom()`], //! use whatever name provided during that registration; no extensions are //! automatically removed._ //! //! In debug mode (without the `--release` flag passed to `cargo`), templates //! are **automatically reloaded** from disk when changes are made. In release //! builds, template reloading is disabled to improve performance and cannot be //! enabled. //! //! [attached]: rocket::Rocket::attach() //! //! ### Metadata and Rendering to `String` //! //! The [`Metadata`] request guard allows dynamically querying templating //! metadata, such as whether a template is known to exist //! ([`Metadata::contains_template()`]), and to render templates to `String` //! ([`Metadata::render()`]). #![doc(html_root_url = "https://api.rocket.rs/master/rocket_dyn_templates")] #![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")] #![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")] #[macro_use] extern crate rocket; #[doc(inline)] #[cfg(feature = "tera")] /// The tera templating engine library, reexported. pub use tera; #[doc(inline)] #[cfg(feature = "handlebars")] /// The handlebars templating engine library, reexported. pub use handlebars; #[doc(inline)] #[cfg(feature = "minijinja")] /// The minijinja templating engine library, reexported. pub use minijinja; #[doc(hidden)] pub use rocket::serde; mod engine; mod fairing; mod context; mod metadata; mod template; pub use engine::Engines; pub use metadata::Metadata; pub use template::Template; ================================================ FILE: contrib/dyn_templates/src/metadata.rs ================================================ use std::fmt; use std::borrow::Cow; use rocket::{Request, Rocket, Ignite, Sentinel}; use rocket::http::{Status, ContentType}; use rocket::request::{self, FromRequest}; use rocket::serde::Serialize; use crate::{Template, context::ContextManager}; /// Request guard for dynamically querying template metadata. /// /// # Usage /// /// The `Metadata` type implements Rocket's [`FromRequest`] trait, so it can be /// used as a request guard in any request handler. /// /// ```rust /// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket_dyn_templates; /// use rocket_dyn_templates::{Template, Metadata, context}; /// /// #[get("/")] /// fn homepage(metadata: Metadata) -> Template { /// // Conditionally render a template if it's available. /// # let context = (); /// if metadata.contains_template("some-template") { /// Template::render("some-template", &context) /// } else { /// Template::render("fallback", &context) /// } /// } /// /// fn main() { /// rocket::build() /// .attach(Template::fairing()) /// // ... /// # ; /// } /// ``` pub struct Metadata<'a>(&'a ContextManager); impl Metadata<'_> { /// Returns `true` if the template with the given `name` is currently /// loaded. Otherwise, returns `false`. /// /// # Example /// /// ```rust /// # #[macro_use] extern crate rocket; /// # extern crate rocket_dyn_templates; /// # /// use rocket_dyn_templates::Metadata; /// /// #[get("/")] /// fn handler(metadata: Metadata) { /// // Returns `true` if the template with name `"name"` was loaded. /// let loaded = metadata.contains_template("name"); /// } /// ``` pub fn contains_template(&self, name: &str) -> bool { self.0.context().templates.contains_key(name) } /// Returns `true` if template reloading is enabled. /// /// # Example /// /// ```rust /// # #[macro_use] extern crate rocket; /// # extern crate rocket_dyn_templates; /// # /// use rocket_dyn_templates::Metadata; /// /// #[get("/")] /// fn handler(metadata: Metadata) { /// // Returns `true` if template reloading is enabled. /// let reloading = metadata.reloading(); /// } /// ``` pub fn reloading(&self) -> bool { self.0.is_reloading() } /// Directly render the template named `name` with the context `context` /// into a `String`. Also returns the template's detected `ContentType`. See /// [`Template::render()`] for more details on rendering. /// /// # Examples /// /// ```rust /// # #[macro_use] extern crate rocket; /// use rocket::http::ContentType; /// use rocket_dyn_templates::{Metadata, Template, context}; /// /// #[get("/")] /// fn send_email(metadata: Metadata) -> Option<()> { /// let (mime, string) = metadata.render("email", context! { /// field: "Hello, world!" /// })?; /// /// # /* /// send_email(mime, string).await?; /// # */ /// Some(()) /// } /// /// #[get("/")] /// fn raw_render(metadata: Metadata) -> Option<(ContentType, String)> { /// metadata.render("index", context! { field: "Hello, world!" }) /// } /// /// // Prefer the following, however, which is nearly identical but pithier: /// /// #[get("/")] /// fn render() -> Template { /// Template::render("index", context! { field: "Hello, world!" }) /// } /// ``` pub fn render(&self, name: S, context: C) -> Option<(ContentType, String)> where S: Into>, C: Serialize { Template::render(name.into(), context).finalize(&self.0.context()).ok() } } impl fmt::Debug for Metadata<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_map() .entries(&self.0.context().templates) .finish() } } impl Sentinel for Metadata<'_> { fn abort(rocket: &Rocket) -> bool { if rocket.state::().is_none() { error!( "uninitialized template context: missing `Template::fairing()`.\n\ To use templates, you must attach `Template::fairing()`." ); return true; } false } } /// Retrieves the template metadata. If a template fairing hasn't been attached, /// an error is printed and an empty `Err` with status `InternalServerError` /// (`500`) is returned. #[rocket::async_trait] impl<'r> FromRequest<'r> for Metadata<'r> { type Error = (); async fn from_request(request: &'r Request<'_>) -> request::Outcome { request.rocket().state::() .map(|cm| request::Outcome::Success(Metadata(cm))) .unwrap_or_else(|| { error!( "uninitialized template context: missing `Template::fairing()`.\n\ To use templates, you must attach `Template::fairing()`." ); request::Outcome::Error((Status::InternalServerError, ())) }) } } ================================================ FILE: contrib/dyn_templates/src/template.rs ================================================ use std::borrow::Cow; use std::path::PathBuf; use rocket::{Rocket, Orbit, Ignite, Sentinel}; use rocket::request::Request; use rocket::fairing::Fairing; use rocket::response::{self, Responder}; use rocket::http::{ContentType, Status}; use rocket::figment::{value::Value, error::Error}; use rocket::trace::Trace; use rocket::serde::Serialize; use crate::Engines; use crate::fairing::TemplateFairing; use crate::context::{Context, ContextManager}; pub(crate) const DEFAULT_TEMPLATE_DIR: &str = "templates"; /// Responder that renders a dynamic template. /// /// `Template` serves as a _proxy_ type for rendering a template and _does not_ /// contain the rendered template itself. The template is lazily rendered, at /// response time. To render a template greedily, use [`Template::show()`]. /// /// See the [crate root](crate) for usage details. #[derive(Debug)] pub struct Template { name: Cow<'static, str>, value: Result, } #[derive(Debug)] pub(crate) struct TemplateInfo { /// The complete path, including `template_dir`, to this template, if any. pub(crate) path: Option, /// The extension for the engine of this template. pub(crate) engine_ext: &'static str, /// The extension before the engine extension in the template, if any. pub(crate) data_type: ContentType } impl Template { /// Returns a fairing that initializes and maintains templating state. /// /// This fairing, or the one returned by [`Template::custom()`], _must_ be /// attached to any `Rocket` instance that wishes to render templates. /// Failure to attach this fairing will result in a "Uninitialized template /// context: missing fairing." error message when a template is attempted to /// be rendered. /// /// If you wish to customize the internal templating engines, use /// [`Template::custom()`] instead. /// /// # Example /// /// To attach this fairing, simple call `attach` on the application's /// `Rocket` instance with `Template::fairing()`: /// /// ```rust /// extern crate rocket; /// extern crate rocket_dyn_templates; /// /// use rocket_dyn_templates::Template; /// /// fn main() { /// rocket::build() /// // ... /// .attach(Template::fairing()) /// // ... /// # ; /// } /// ``` pub fn fairing() -> impl Fairing { Template::custom(|_| {}) } /// Returns a fairing that initializes and maintains templating state. /// /// Unlike [`Template::fairing()`], this method allows you to configure /// templating engines via the function `f`. Note that only the enabled /// templating engines will be accessible from the `Engines` type. /// /// This method does not allow the function `f` to fail. If `f` is fallible, /// use [`Template::try_custom()`] instead. /// /// # Example /// /// ```rust /// extern crate rocket; /// extern crate rocket_dyn_templates; /// /// use rocket_dyn_templates::Template; /// /// fn main() { /// rocket::build() /// // ... /// .attach(Template::custom(|engines| { /// // engines.handlebars.register_helper ... /// })) /// // ... /// # ; /// } /// ``` pub fn custom(f: F) -> impl Fairing where F: Fn(&mut Engines) { Self::try_custom(move |engines| { f(engines); Ok(()) }) } /// Returns a fairing that initializes and maintains templating state. /// /// This variant of [`Template::custom()`] allows a fallible `f`. If `f` /// returns an error during initialization, it will cancel the launch. If /// `f` returns an error during template reloading (in debug mode), then the /// newly-reloaded templates are discarded. /// /// # Example /// /// ```rust /// extern crate rocket; /// extern crate rocket_dyn_templates; /// /// use rocket_dyn_templates::Template; /// /// fn main() { /// rocket::build() /// // ... /// .attach(Template::try_custom(|engines| { /// // engines.handlebars.register_helper ... /// Ok(()) /// })) /// // ... /// # ; /// } /// ``` pub fn try_custom(f: F) -> impl Fairing where F: Fn(&mut Engines) -> Result<(), Box> { TemplateFairing { callback: Box::new(f) } } /// Render the template named `name` with the context `context`. The /// `context` is typically created using the [`context!()`](crate::context!) /// macro, but it can be of any type that implements `Serialize`, such as /// `HashMap` or a custom `struct`. /// /// To render a template directly into a string, use /// [`Metadata::render()`](crate::Metadata::render()). /// /// # Examples /// /// Using the `context` macro: /// /// ```rust /// use rocket_dyn_templates::{Template, context}; /// /// let template = Template::render("index", context! { /// foo: "Hello, world!", /// }); /// ``` /// /// Using a `HashMap` as the context: /// /// ```rust /// use std::collections::HashMap; /// use rocket_dyn_templates::Template; /// /// // Create a `context` from a `HashMap`. /// let mut context = HashMap::new(); /// context.insert("foo", "Hello, world!"); /// /// let template = Template::render("index", context); /// ``` #[inline] pub fn render(name: S, context: C) -> Template where S: Into>, C: Serialize { Template { name: name.into(), value: Value::serialize(context), } } /// Render the template named `name` with the context `context` into a /// `String`. This method should **not** be used in any running Rocket /// application. This method should only be used during testing to validate /// `Template` responses. For other uses, use [`render()`](#method.render) /// instead. /// /// The `context` can be of any type that implements `Serialize`. This is /// typically a `HashMap` or a custom `struct`. /// /// Returns `Some` if the template could be rendered. Otherwise, returns /// `None`. If rendering fails, error output is printed to the console. /// `None` is also returned if a `Template` fairing has not been attached. /// /// # Example /// /// ```rust,no_run /// # extern crate rocket; /// # extern crate rocket_dyn_templates; /// use std::collections::HashMap; /// /// use rocket_dyn_templates::Template; /// use rocket::local::blocking::Client; /// /// fn main() { /// let rocket = rocket::build().attach(Template::fairing()); /// let client = Client::untracked(rocket).expect("valid rocket"); /// /// // Create a `context`. Here, just an empty `HashMap`. /// let mut context = HashMap::new(); /// # context.insert("test", "test"); /// let template = Template::show(client.rocket(), "index", context); /// } /// ``` #[inline] pub fn show(rocket: &Rocket, name: S, context: C) -> Option where S: Into>, C: Serialize { let ctxt = rocket.state::() .map(ContextManager::context) .or_else(|| { error!("Uninitialized template context: missing fairing.\n\ To use templates, you must attach `Template::fairing()`.\n\ See the `Template` documentation for more information."); None })?; Template::render(name, context).finalize(&ctxt).ok().map(|v| v.1) } /// Actually render this template given a template context. This method is /// called by the `Template` `Responder` implementation as well as /// `Template::show()`. #[inline(always)] pub(crate) fn finalize(self, ctxt: &Context) -> Result<(ContentType, String), Status> { let template = &*self.name; let info = ctxt.templates.get(template).ok_or_else(|| { let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect(); error!( %template, search_path = %ctxt.root.display(), known_templates = ?ts, "requested template not found" ); Status::InternalServerError })?; let value = self.value.map_err(|e| { span_error!("templating", "template context failed to serialize" => e.trace_error()); Status::InternalServerError })?; let string = ctxt.engines.render(template, info, value).ok_or_else(|| { error!(template, "template failed to render"); Status::InternalServerError })?; Ok((info.data_type.clone(), string)) } } /// Returns a response with the Content-Type derived from the template's /// extension and a fixed-size body containing the rendered template. If /// rendering fails, an `Err` of `Status::InternalServerError` is returned. impl<'r> Responder<'r, 'static> for Template { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { let ctxt = req.rocket() .state::() .ok_or_else(|| { error!( "uninitialized template context: missing `Template::fairing()`.\n\ To use templates, you must attach `Template::fairing()`." ); Status::InternalServerError })?; self.finalize(&ctxt.context())?.respond_to(req) } } impl Sentinel for Template { fn abort(rocket: &Rocket) -> bool { if rocket.state::().is_none() { error!( "Missing `Template::fairing()`.\n\ To use templates, you must attach `Template::fairing()`." ); return true; } false } } /// A macro to easily create a template rendering context. /// /// Invocations of this macro expand to a value of an anonymous type which /// implements [`Serialize`]. Fields can be literal expressions or variables /// captured from a surrounding scope, as long as all fields implement /// `Serialize`. /// /// # Examples /// /// The following code: /// /// ```rust /// # #[macro_use] extern crate rocket; /// # use rocket_dyn_templates::{Template, context}; /// #[get("/")] /// fn render_index(foo: u64) -> Template { /// Template::render("index", context! { /// // Note that shorthand field syntax is supported. /// // This is equivalent to `foo: foo,` /// foo, /// bar: "Hello world", /// }) /// } /// ``` /// /// is equivalent to the following, but without the need to manually define an /// `IndexContext` struct: /// /// ```rust /// # use rocket_dyn_templates::Template; /// # use rocket::serde::Serialize; /// # use rocket::get; /// #[derive(Serialize)] /// # #[serde(crate = "rocket::serde")] /// struct IndexContext<'a> { /// foo: u64, /// bar: &'a str, /// } /// /// #[get("/")] /// fn render_index(foo: u64) -> Template { /// Template::render("index", IndexContext { /// foo, /// bar: "Hello world", /// }) /// } /// ``` /// /// ## Nesting /// /// Nested objects can be created by nesting calls to `context!`: /// /// ```rust /// # use rocket_dyn_templates::context; /// # fn main() { /// let ctx = context! { /// planet: "Earth", /// info: context! { /// mass: 5.97e24, /// radius: "6371 km", /// moons: 1, /// }, /// }; /// # } /// ``` #[macro_export] macro_rules! context { ($($key:ident $(: $value:expr)?),*$(,)?) => {{ use $crate::serde::ser::{Serialize, Serializer, SerializeMap}; use ::std::fmt::{Debug, Formatter}; use ::std::result::Result; #[allow(non_camel_case_types)] struct ContextMacroCtxObject<$($key: Serialize),*> { $($key: $key),* } #[allow(non_camel_case_types)] impl<$($key: Serialize),*> Serialize for ContextMacroCtxObject<$($key),*> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut map = serializer.serialize_map(None)?; $(map.serialize_entry(stringify!($key), &self.$key)?;)* map.end() } } #[allow(non_camel_case_types)] impl<$($key: Debug + Serialize),*> Debug for ContextMacroCtxObject<$($key),*> { fn fmt(&self, f: &mut Formatter<'_>) -> ::std::fmt::Result { f.debug_struct("context!") $(.field(stringify!($key), &self.$key))* .finish() } } ContextMacroCtxObject { $($key $(: $value)?),* } }}; } ================================================ FILE: contrib/dyn_templates/tests/templates/hbs/common/footer.html.hbs ================================================ Done. ================================================ FILE: contrib/dyn_templates/tests/templates/hbs/common/header.html.hbs ================================================ Hello {{ title }}! ================================================ FILE: contrib/dyn_templates/tests/templates/hbs/reload.txt.hbs ================================================ initial ================================================ FILE: contrib/dyn_templates/tests/templates/hbs/test.html.hbs ================================================ {{> hbs/common/header }}

{{ content }}
{{> hbs/common/footer }} ================================================ FILE: contrib/dyn_templates/tests/templates/j2/[test]/html_test.html.j2 ================================================ {% extends "j2/base" %} {% block title %}{{ title }}{% endblock title %} {% block content %} {{ content }} {% endblock content %} ================================================ FILE: contrib/dyn_templates/tests/templates/j2/base.txt.j2 ================================================ {% block head %} h_start title: {% block title %}{% endblock title %} h_end {% endblock head %} {% block content %}{% endblock content %} {% block footer %}foot{% endblock footer %} ================================================ FILE: contrib/dyn_templates/tests/templates/j2/html_test.html.j2 ================================================ {% extends "j2/base" %} {% block title %}{{ title }}{% endblock title %} {% block content %} {{ content }} {% endblock content %} ================================================ FILE: contrib/dyn_templates/tests/templates/j2/txt_test.txt.j2 ================================================ {% extends "j2/base" %} {% block title %}{{ title }}{% endblock title %} {% block content %} {{ content }} {% endblock content %} ================================================ FILE: contrib/dyn_templates/tests/templates/tera/[test]/html_test.html.tera ================================================ {% extends "tera/base" %} {% block title %}{{ title }}{% endblock title %} {% block content %} {{ content }} {% endblock content %} ================================================ FILE: contrib/dyn_templates/tests/templates/tera/base.txt.tera ================================================ {% block head %} h_start title: {% block title %}{% endblock title %} h_end {% endblock head %} {% block content %}{% endblock content %} {% block footer %}foot{% endblock footer -%} ================================================ FILE: contrib/dyn_templates/tests/templates/tera/html_test.html.tera ================================================ {% extends "tera/base" %} {% block title %}{{ title }}{% endblock title %} {% block content %} {{ content }} {% endblock content %} ================================================ FILE: contrib/dyn_templates/tests/templates/tera/txt_test.txt.tera ================================================ {% extends "tera/base" %} {% block title %}{{ title }}{% endblock title %} {% block content %} {{ content }} {% endblock content %} ================================================ FILE: contrib/dyn_templates/tests/templates.rs ================================================ #[macro_use] extern crate rocket; use std::path::{Path, PathBuf}; use rocket::{Rocket, Build}; use rocket::config::Config; use rocket::figment::value::Value; use rocket::serde::{Serialize, Deserialize}; use rocket_dyn_templates::{Template, Metadata, context}; #[get("//")] fn template_check(md: Metadata<'_>, engine: &str, name: &str) -> Option<()> { md.contains_template(&format!("{}/{}", engine, name)).then(|| ()) } #[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 { 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