rustis 0.22.0

Redis async driver for Rust
Documentation
/*!
Defines types related to the [`RESP`](https://redis.io/docs/reference/protocol-spec/) protocol and their encoding/decoding

# Object Model

**rustis** provides an object model in the form of a generic data struct, comparable to the XML DOM,
and which matches perfectly the RESP protocol: the enum [`resp::Value`](Value).

Each variant of this enum matches a [`RESP`](https://redis.io/docs/reference/protocol-spec/) type.

Because, navigating through a [`resp::Value`](Value) instance can be verbose and requires a lot of pattern matching,
**rustis** provides a [`resp::Value`](Value) to Rust type conversion with a [serde](https://serde.rs/)
deserializer implementation of a [`resp::Value`](Value) reference.

This conversion is easily accessible through the associate function [`Value::into`](Value::into).

# Command arguments

**rustis** provides an idiomatic way to pass arguments to [commands](crate::commands).
Basically a [`Command`] is a built through a builder which accepts a command name and one ore more command arguments.

The only requirement for the command argument is that they must implement the serde [`Serialize`](serde::Serialize) trait.
It gives to **rustis** a great flexibility to accept many type of arguments for the same command.

#### Example
```
use rustis::{
    client::Client,
    commands::{FlushingMode, ServerCommands, StringCommands},
    Result,
};
use serde::Serialize;

#[derive(Serialize)]
pub struct MyI32(i32);

#[tokio::main]
async fn main() -> Result<()> {
    // Connect the client to a Redis server from its IP and port
    let client = Client::connect("127.0.0.1:6379").await?;

    // Flush all existing data in Redis
    client.flushdb(FlushingMode::Sync).await?;

    client.set("key", 12).await?;
    client.set("key", 12i64).await?;
    client.set("key", 12.12).await?;
    client.set("key", true).await?;
    client.set("key", "value").await?;
    client.set("key", "value".to_owned()).await?;
    client.set("key", 'c').await?;
    client.set("key", MyI32(12)).await?;

    Ok(())
}
```
## Byte arguments and serde limitations

Due to how serde handles byte types, passing raw byte values like `&[u8]`,
`Vec<u8>`, or byte literals like `b"val"` directly as command arguments
will **not** produce a single RESP bulk string. Instead, serde serializes
them as sequences of individual integer values, resulting in a runtime error.

This is a fundamental serde limitation: without specialization, there is no
way to distinguish a `&[u8]` from any other `&[T]` at the trait level.
Note that `&str` works correctly because it is a distinct type, not a slice.

To pass raw bytes as a single bulk string argument, use the provided adapter types:

- [`BulkString`] for owned byte data (`Vec<u8>`) — moves ownership, zero allocation
- [`RefBulkString`] for borrowed byte data (`&[u8]`) — zero allocation

#### Example
```
use rustis::{
    client::Client,
    commands::StringCommands,
    resp::{BulkString, RefBulkString},
    Result,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::connect("127.0.0.1:6379").await?;

    // &[u8]: use RefBulkString (zero allocation, borrowed)
    client.set("key", RefBulkString::new(b"val")).await?;

    // Vec<u8>: use BulkString (zero allocation, owned)
    client.set("key", BulkString::new(b"val".to_vec())).await?;

    Ok(())
}
```
# Command results

**rustis** provides an idiomatic way to convert command results into Rust types with the help of [serde](serde.rs)

You will notice that each built-in command returns a [`PreparedCommand<R>`](crate::client::PreparedCommand)
struct where `R` represents the [`Response`] of the command.

The different command traits implementations ([`Client`](crate::client::Client), [`Pipeline`](crate::client::Pipeline)
 or [`Transaction`](crate::client::Transaction)) add a constraint on the reponse `R`:
 it must implement serde [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) trait.

 Indeed, **rustis** provides a serde deserializer over the RESP wire format.
 Each custom struct or enum defined as a response of a built-command implements
 serde [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) trait,
 in order to deserialize it automatically from a RESP Buffer.

## Warning: a `nil` reply decodes as a neutral value
When the response type is not an [`Option`], a `nil` reply is converted to the neutral value of
that type instead of being rejected: `0` for every integer width, `0.0` for `f32`/`f64`, `false`
for `bool`, `'\0'` for `char` and `""` for `String`.

A missing key is therefore indistinguishable from a key holding that neutral value — a counter,
a quota or a balance that expired reads as zero, with no error to tell you.

Declare `Option<R>` to recover the distinction: it is honoured before any conversion and yields
`None` on `nil`.

#### Example
```
use rustis::{
    client::Client,
    commands::{FlushingMode, ServerCommands, StringCommands},
    Result,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::connect("127.0.0.1:6379").await?;
    client.flushall(FlushingMode::Sync).await?;

    // the key does not exist: `nil` becomes `0`, as if it held `0`
    let counter: i64 = client.get("counter").await?;
    assert_eq!(0, counter);

    // `Option` keeps the two apart
    let counter: Option<i64> = client.get("counter").await?;
    assert_eq!(None, counter);

    client.set("counter", 0).await?;
    let counter: Option<i64> = client.get("counter").await?;
    assert_eq!(Some(0), counter);

    Ok(())
}
```
*/
// This module is fed directly by server bytes: every length, cardinality and
// offset here is attacker-controlled, so an out-of-bounds index is reachable
// from the wire rather than from a local mistake. Indexing is denied, not
// warned; see the panic policy in `lib.rs`.
#![deny(clippy::indexing_slicing)]
// Same reasoning applied to `as`, which `arithmetic_side_effects` does not cover:
// on a wire-supplied value a narrowing cast truncates, a signed-to-unsigned one
// wraps and a float-to-integer one saturates or maps NaN to zero — silently, in
// debug as in release. Every conversion of a decoded value must therefore be
// `TryFrom` or a documented-exact `as`, each surviving cast carrying an
// `#[expect(…, reason = "…")]` naming the invariant that makes it exact.
#![deny(
    clippy::cast_possible_truncation,
    clippy::cast_precision_loss,
    clippy::cast_sign_loss,
    clippy::cast_possible_wrap,
    clippy::cast_lossless
)]

pub(crate) use arg_counter::*;
pub(crate) use arg_serializer::*;
#[cfg(feature = "bench")]
pub use bench_support::*;
pub(crate) use buffer_decoder::*;
pub use bulk_string::*;
pub use command::*;
pub use command_args::*;
pub(crate) use command_encoder::*;
pub use fast_path_command_builder::*;
#[cfg(feature = "json")]
pub use json::*;
pub(crate) use resp_batch_deserializer::*;
pub(crate) use resp_buf::*;
pub(crate) use resp_deserializer::*;
pub(crate) use resp_frame_parser::*;
pub(crate) use resp_response::*;
pub(crate) use resp_scalar::*;
pub(crate) use resp_tags::*;
pub(crate) use resp_tape::*;
pub use response::*;
pub use util::*;
pub use value::*;
pub(crate) use value_deserialize::*;

mod arg_counter;
mod arg_serializer;
#[cfg(feature = "bench")]
mod bench_support;
mod buffer_decoder;
mod bulk_string;
mod command;
mod command_args;
mod command_encoder;
mod fast_path_command_builder;
#[cfg(feature = "json")]
mod json;
mod resp_batch_deserializer;
mod resp_buf;
mod resp_deserializer;
mod resp_frame_parser;
mod resp_response;
mod resp_scalar;
mod resp_tags;
mod resp_tape;
mod response;
mod util;
mod value;
mod value_deserialize;
mod value_deserializer;