mirror of
https://github.com/chatmail/core.git
synced 2026-04-06 07:32:12 +03:00
158 lines
5.7 KiB
Markdown
158 lines
5.7 KiB
Markdown
# Coding conventions
|
|
|
|
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
|
|
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
|
|
|
|
[Clippy]: https://doc.rust-lang.org/clippy/
|
|
|
|
## SQL
|
|
|
|
Multi-line SQL statements should be formatted using string literals,
|
|
for example
|
|
```
|
|
sql.execute(
|
|
"CREATE TABLE messages (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
text TEXT DEFAULT '' NOT NULL -- message text
|
|
) STRICT",
|
|
)
|
|
.await
|
|
.context("CREATE TABLE messages")?;
|
|
```
|
|
|
|
Do not use macros like [`concat!`](https://doc.rust-lang.org/std/macro.concat.html)
|
|
or [`indoc!`](https://docs.rs/indoc).
|
|
Do not escape newlines like this:
|
|
```
|
|
sql.execute(
|
|
"CREATE TABLE messages ( \
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, \
|
|
text TEXT DEFAULT '' NOT NULL \
|
|
) STRICT",
|
|
)
|
|
.await
|
|
.context("CREATE TABLE messages")?;
|
|
```
|
|
Escaping newlines
|
|
is prone to errors like this if space before backslash is missing:
|
|
```
|
|
"SELECT foo\
|
|
FROM bar"
|
|
```
|
|
Literal above results in `SELECT fooFROM bar` string.
|
|
This style also does not allow using `--` comments.
|
|
|
|
---
|
|
|
|
Declare new SQL tables with [`STRICT`](https://sqlite.org/stricttables.html) keyword
|
|
to make SQLite check column types.
|
|
|
|
Declare primary keys with [`AUTOINCREMENT`](https://www.sqlite.org/autoinc.html) keyword.
|
|
This avoids reuse of the row IDs and can avoid dangerous bugs
|
|
like forwarding wrong message because the message was deleted
|
|
and another message took its row ID.
|
|
|
|
Declare all new columns as `NOT NULL`
|
|
and set the `DEFAULT` value if it is optional so the column can be skipped in `INSERT` statements.
|
|
Dealing with `NULL` values both in SQL and in Rust is tricky and we try to avoid it.
|
|
If column is already declared without `NOT NULL`, use `IFNULL` function to provide default value when selecting it.
|
|
Use `HAVING COUNT(*) > 0` clause
|
|
to [prevent aggregate functions such as `MIN` and `MAX` from returning `NULL`](https://stackoverflow.com/questions/66527856/aggregate-functions-max-etc-return-null-instead-of-no-rows).
|
|
|
|
Don't delete unused columns too early, but maybe after several months/releases, unused columns are
|
|
still used by older versions, so deleting them breaks downgrading the core or importing a backup in
|
|
an older version. Also don't change the column type, consider adding a new column with another name
|
|
instead. Finally, never change column semantics, this is especially dangerous because the `STRICT`
|
|
keyword doesn't help here.
|
|
|
|
Consider adding context to `anyhow` errors for SQL statements using `.context()` so that it's
|
|
possible to understand from logs which statement failed. See [Errors](#errors) for more info.
|
|
|
|
## Errors
|
|
|
|
Delta Chat core mostly uses [`anyhow`](https://docs.rs/anyhow/) errors.
|
|
When using [`Context`](https://docs.rs/anyhow/latest/anyhow/trait.Context.html),
|
|
capitalize it but do not add a full stop as the contexts will be separated by `:`.
|
|
For example:
|
|
```
|
|
.with_context(|| format!("Unable to trash message {msg_id}"))
|
|
```
|
|
|
|
All errors should be handled in one of these ways:
|
|
- With `if let Err() =` (incl. logging them into `warn!()`/`err!()`).
|
|
- With `.log_err().ok()`.
|
|
- Bubbled up with `?`.
|
|
|
|
When working with [async streams](https://docs.rs/futures/0.3.31/futures/stream/index.html),
|
|
prefer [`try_next`](https://docs.rs/futures/0.3.31/futures/stream/trait.TryStreamExt.html#method.try_next) over `next()`, e.g. do
|
|
```
|
|
while let Some(event) = stream.try_next().await? {
|
|
todo!();
|
|
}
|
|
```
|
|
instead of
|
|
```
|
|
while let Some(event_res) = stream.next().await {
|
|
todo!();
|
|
}
|
|
```
|
|
as it allows bubbling up the error early with `?`
|
|
with no way to accidentally skip error processing
|
|
with early `continue` or `break`.
|
|
Some streams reading from a connection
|
|
return infinite number of `Some(Err(_))`
|
|
items when connection breaks and not processing
|
|
errors may result in infinite loop.
|
|
|
|
`backtrace` feature is enabled for `anyhow` crate
|
|
and `debug = 1` option is set in the test profile.
|
|
This allows to run `RUST_BACKTRACE=1 cargo test`
|
|
and get a backtrace with line numbers in resultified tests
|
|
which return `anyhow::Result`.
|
|
|
|
`unwrap` and `expect` are not used in the library
|
|
because panics are difficult to debug on user devices.
|
|
However, in the tests `.expect` may be used.
|
|
Follow
|
|
<https://doc.rust-lang.org/core/error/index.html#common-message-styles>
|
|
for `.expect` message style.
|
|
|
|
## BTreeMap vs HashMap
|
|
|
|
Prefer [BTreeMap](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html)
|
|
over [HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html)
|
|
and [BTreeSet](https://doc.rust-lang.org/std/collections/struct.BTreeSet.html)
|
|
over [HashSet](https://doc.rust-lang.org/std/collections/struct.HashSet.html)
|
|
as iterating over these structures returns items in deterministic order.
|
|
|
|
Non-deterministic code may result in difficult to reproduce bugs,
|
|
flaky tests, regression tests that miss bugs
|
|
or different behavior on different devices when processing the same messages.
|
|
|
|
## Logging
|
|
|
|
For logging, use `info!`, `warn!` and `error!` macros.
|
|
Log messages should be capitalized and have a full stop in the end. For example:
|
|
```
|
|
info!(context, "Ignoring addition of {added_addr:?} to {chat_id}.");
|
|
```
|
|
|
|
Format anyhow errors with `{:#}` to print all the contexts like this:
|
|
```
|
|
error!(context, "Failed to set selfavatar timestamp: {err:#}.");
|
|
```
|
|
|
|
## Documentation comments
|
|
|
|
All public modules, methods and fields should be documented.
|
|
This is checked by [`missing_docs`](https://doc.rust-lang.org/rustdoc/lints.html#missing_docs) lint.
|
|
|
|
Private items do not have to be documented,
|
|
but CI uses `cargo doc --document-private-items`
|
|
to build the documentation,
|
|
so it is preferred that new items
|
|
are documented.
|
|
|
|
Follow Rust guidelines for the documentation comments:
|
|
<https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#summary-sentence>
|