# Contributing
This document gives some background on how the library is structured and how to contribute.
## General
* Run rustfmt and clippy before submitting any changes.
* Formatting should adhere to [rustfmt.toml](./rustfmt.toml), i.e. by running `cargo +nightly fmt --all`
* VS Code users should use the checked-in settings in the [.vscode](./.vscode) directory
* Please use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary).
## File Structure
The code has the following structure:
* The [commands](src/commands) folder contains the public interface and private implementation for each of the Redis
commands, organized by category. This is roughly the same categorization used by
the [public docs](https://redis.io/commands/). Each of these public command category interfaces are exposed as a trait
with default implementations for each command.
* The [clients](src/clients) folder contains public client structs that implement and/or override the traits
from [the command category traits folder](src/commands/impls). The [interfaces](src/interfaces.rs) file contains the
shared traits required by most of the command category traits, such as `ClientLike`.
* The [monitor](src/monitor) folder contains the implementation of the `MONITOR` command and the parser for the response
stream.
* The [protocol](src/protocol) folder contains the implementation of the base `Connection` struct and the logic for
splitting a connection to interact with reader and writer halves in separate tasks.
The [TLS interface](src/protocol/tls.rs) is also implemented here.
* The [router](src/router) folder contains the logic that implements the sentinel and cluster interfaces. Clients
interact with this struct via a message passing interface. The interface exposed by the `Router` attempts to hide all
the complexity associated with sentinel or clustered deployments.
* The [trace](src/trace) folder contains the tracing implementation. Span IDs are manually tracked on each command as
they move across tasks.
* The [types](src/types) folder contains the type definitions used by the public interface. The Redis interface is
relatively loosely typed but Rust can support more strongly typed interfaces. The types in this module aim to support
an optional but more strongly typed interface for callers.
* The [modules](src/modules) folder contains smaller helper interfaces such as a
lazy [Backchannel](src/modules/backchannel.rs) connection interface and
the [response type conversion logic](src/modules/response.rs).
## Examples
### Add A New Command
This example shows how to add `MGET` to the commands.
1. Add the new variant to the `RedisCommandKind` enum, if needed.
```rust
pub enum RedisCommandKind {
Mget,
}
impl RedisCommandKind {
pub fn to_str_debug(&self) -> &str {
match *self {
RedisCommandKind::Mget => "MGET",
}
}
pub fn cmd_str(&self) -> Str {
match *self {
RedisCommandKind::Mget => "MGET"
}
}
}
```
2. Create the private function implementing the command in [src/commands/impls/keys.rs](src/commands/impls/keys.rs).
```rust
pub async fn mget<C: ClientLike>(client: &C, keys: MultipleKeys) -> Result<RedisValue, RedisError> {
utils::check_empty_keys(&keys)?;
let frame = utils::request_response(client, move || {
Ok((RedisCommandKind::Mget, keys.into_values()))
})
.await?;
protocol_utils::frame_to_results(frame)
}
```
Or use one of the shorthand helper functions or macros.
```rust
pub async fn mget<C: ClientLike>(client: &C, keys: MultipleKeys) -> Result<RedisValue, RedisError> {
utils::check_empty_keys(&keys)?;
args_values_cmd(client, keys.into_values()).await
}
```
3. Create the public function in the [src/commands/interfaces/keys.rs](src/commands/interfaces/keys.rs) file.
```rust
pub trait KeysInterface: ClientLike {
fn mget<R, K>(&self, keys: K) -> impl Future<Output=RedisResult<R>> + Send
where
R: FromRedis,
K: Into<MultipleKeys> + Send,
{
async move {
into!(keys);
commands::keys::mget(self, keys).await?.convert()
}
}
}
```
4. Implement the interface on the client structs, if needed.
In the [RedisClient](src/clients/redis.rs) file.
```rust
impl KeysInterface for RedisClient {}
```
In the [transaction](src/clients/transaction.rs) file.
```rust
impl KeysInterface for Transaction {}
```
## Adding Tests
Integration tests are in the [tests/integration](tests/integration) folder organized by category. See the
tests [README](tests/README.md) for more information.
Using `MGET` as an example again:
1. Write tests in the [keys](tests/integration/keys/mod.rs) file.
```rust
pub async fn should_mget_values(client: RedisClient, _: RedisConfig) -> Result<(), RedisError> {
let expected: Vec<(&str, RedisValue)> = vec![("a{1}", 1.into()), ("b{1}", 2.into()), ("c{1}", 3.into())];
for (key, value) in expected.iter() {
let _: () = client.set(key, value.clone(), None, None, false).await?;
}
let values: Vec<i64> = client.mget(vec!["a{1}", "b{1}", "c{1}"]).await?;
assert_eq!(values, vec![1, 2, 3]);
Ok(())
}
```
2. Call the tests from the [centralized server tests](tests/integration/centralized.rs).
```rust
mod keys {
centralized_test!(keys, should_mget_values);
}
```
3. Call the tests from the [cluster server tests](tests/integration/clustered.rs).
```rust
mod keys {
cluster_test!(keys, should_mget_values);
}
```
These macros will generate test wrapper functions to call your test 8 times based on the following options:
* Clustered vs centralized deployments
* Pipelined vs non-pipelined clients
* RESP2 vs RESP3 protocol modes