1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/*!
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.
A [`resp::Value`](Value) is read either variant by variant, through the accessors
[`as_str`](Value::as_str), [`as_bytes`](Value::as_bytes), [`as_i64`](Value::as_i64),
[`as_f64`](Value::as_f64), [`as_bool`](Value::as_bool), [`as_array`](Value::as_array),
[`as_map`](Value::as_map), [`as_error`](Value::as_error), [`is_null`](Value::is_null) and
[`get`](Value::get) — each answering [`None`] when the variant does not match — or all at once,
by converting it to a Rust type. **rustis** provides that conversion with a
[serde](https://serde.rs/) deserializer implementation of a [`resp::Value`](Value) reference,
reached through the associate function [`Value::into`](Value::into).
A command whose reply shape is known is best deserialized straight into the type that models it:
`Value` is the fallback for the replies that are not, and [`RawResponse`] the one for a caller
that wants the RESP bytes rather than a value.
# 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(())
}
```
## Why `impl Serialize` and not a trait of **rustis**' own
A trait of our own — `SingleArg`, `IntoArgs`, whatever the name — would let the compiler check
that a key is a single value. **rustis** does not define one, and cannot, because of Rust's
orphan rule: an `impl` is only allowed in the crate that defines the trait or the crate that
defines the type. Writing
```text
// in your own crate
impl rustis::resp::SingleArg for uuid::Uuid {}
// error[E0117]: only traits defined in the current crate
// can be implemented for types defined outside of it
```
since neither the trait nor the type belongs to your crate. (The example does not compile for a
second reason: `SingleArg` does not exist, which is what this section is about.)
Every third-party type would then need a newtype wrapper at each call site: `uuid::Uuid`,
`serde_json::Value`, `chrono::DateTime`, `rust_decimal::Decimal`. `Serialize` has no such problem,
being already implemented by those crates themselves.
`serde_json::Value` shows the trait could not even be honest where the orphan rule allows it.
One type, five argument counts: `Value::String` and `Value::Number` write one argument,
`Value::Null` writes none, `Value::Array` writes one per element and `Value::Object` two per
entry. A trait is a predicate on a *type*; the count is a property of the *value*. No `impl` can
answer for `Value`.
## Argument counts are checked, not typed
The count is therefore checked where a key is added, since a mistake there is otherwise silent.
`None` and an empty collection write no argument at all, which leaves the command a key short —
and, in Cluster mode, with no hash slot, so it is routed to a **random node** instead of the one
that owns the key. A struct or a sequence writes several arguments where one key was meant.
Both fail the command with
[`InvalidKeyArity`](crate::ClientError::InvalidKeyArity), naming the command and the count:
```
use rustis::{client::Client, commands::StringCommands, ClientError, ErrorKind, Result};
#[tokio::main]
async fn main() -> Result<()> {
let client = Client::connect("127.0.0.1:6379").await?;
// a key that serializes to no argument at all
let result: Result<String> = client.get(None::<String>).await;
assert!(matches!(
result.unwrap_err().kind(),
ErrorKind::Client(ClientError::InvalidKeyArity { .. })
));
Ok(())
}
```
The check is on the count alone, so it costs nothing in flexibility: any foreign type is a valid
key as soon as it writes one argument, whether or not **rustis** has ever heard of it. A
`uuid::Uuid` passes, and so does `Value::String` — while `Value::Array` does not, which is the
distinction a marker trait was unable to make.
Values are not checked: a struct or a map as a value is the point of `HSET`
(`client.hset("user:1", my_struct)`), so any count is legitimate there.
# 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` is the response type the caller declares.
`R` must implement serde
[`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html), which is
the only constraint there is: nothing relates it at compile time to what the server actually
answers.
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.
## A `nil` reply needs an `Option`
Redis answers `nil` for a key, a field or an element that does not exist. Read as a number, a
string or a `char`, that absence has no honest value: `0` and `""` are values a present key can
hold, so returning one would make the missing key indistinguishable from it.
A `nil` read as a scalar therefore fails the command with
[`UnexpectedNil`](crate::ClientError::UnexpectedNil), and [`Option`] is the type that accepts it.
The rule reaches inside the reply: an element of a collection and a field of a struct are scalars
too, which is why `HMGET` is read as `Vec<Option<String>>`.
Three readings keep the `nil`. A *collection* stays empty — an absent list read as `Vec<String>`
has no elements, and a byte string has no bytes. [`Value`] carries it as [`Value::Null`]. And a
`bool` reads it as `false`, the server answering `nil` to say a conditional write did not happen.
#### 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, and `0` would be a lie
assert!(client.get::<i64>("counter").await.is_err());
// `Option` accepts the absence
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(())
}
```
## The reply below serde
A caller that wants no Rust type out of a reply — a proxy forwarding it to
another connection, a bridge to another protocol, a reader of a shape no type
models — asks [`Client::send_raw`](crate::client::Client::send_raw) for it. It
answers a [`RawResponse`], the reply's RESP bytes, which the client hands back as
it received them.
That is the layer below [`Value`]: no tree is built, no payload is copied twice,
and nothing is decoded, so the reply keeps what a value cannot spell back — the
server's rendering of a float, a verbatim string's own tag, an error's exact
wording. The bytes are copied out of the read buffer rather than borrowed from
it, the connection recycling that buffer across replies.
*/
// 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`.
// 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.
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;