# 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 folow 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 somene 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.