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
//! This crate offers an asynchronous Redis client for `no_std` targets.
//! Both RESP2 and RESP3 protocol are supported.
//!
//! This crate consists of three parts:
//! * [network module](crate::network) for network details (connection handling, response management, etc.) + regular command client
//! * [commands module](crate::commands) for Redis command abstractions
//! * [subscription module][crate::subscription] for Redis subscription client
//!
//! ```
//! # async_std::task::block_on(async {
//!# use core::str::FromStr;
//!# use core::net::SocketAddr;
//!# use std_embedded_nal_async::Stack;
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//! let stack = Stack::default();
//! let clock = StandardClock::default();
//!
//! let server_address = SocketAddr::from_str("127.0.0.1:6379").unwrap();
//! let mut connection_handler = ConnectionHandler::resp2(server_address);
//! let client = connection_handler.connect(&stack, Some(&clock)).await.unwrap();
//!
//! let response = client.set("key", "value").await.unwrap();
//! # });
//! ```
extern crate test;
extern crate alloc;
extern crate core;
/// # Redis command abstractions
///
/// This crates includes abstractions for some Redis commands like
/// [AUTH](crate::commands::auth),
/// [HELLO](crate::commands::hello),
/// [GET](crate::commands::set),
/// [SET](crate::commands::set),
/// [PUBLISH](crate::commands::publish), ...
///
/// Each abstraction is implementing the [Command](crate::commands::Command) trait.
///
/// For executing arbitrary (not yet implemented) commands, [CustomCommand](crate::commands::custom)
/// may be used. As alternative you can create new commands by implementing the [Command](crate::commands::Command) trait.
///
/// *Please consider contributing new command abstractions*.
/// # Connection and regular Client logic
///
/// ## Connection handling
///
/// Redis connection is managed by [ConnectionHandler](crate::network::ConnectionHandler).
/// Both [RESP2](https://redis.io/docs/reference/protocol-spec/) and [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) protocol
/// are supported.
///
/// Creating a new connection requires the following two things:
/// * A network stack implementing [embedded-nal-async](<https://docs.rs/embedded-nal-async/latest/embedded_nal_async/>)
/// * A clock implementing [embedded-time](<https://docs.rs/embedded-time/latest/embedded_time/>). Optional if no Timeout is configured.
/// ```
/// # async_std::task::block_on(async {
///# use core::str::FromStr;
///# use core::net::SocketAddr;
///# use std_embedded_nal_async::Stack;
///# use std_embedded_time::StandardClock;
///# use embedded_redis::network::ConnectionHandler;
///#
/// let network_stack = Stack::default();
/// let clock = StandardClock::default();
///
/// // RESP2 protocol
/// let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
/// let _client = connection_handler.connect(&network_stack, Some(&clock)).await.unwrap();
///
/// // RESP3 protocol
/// let mut connection_handler = ConnectionHandler::resp3(SocketAddr::from_str("127.0.0.1:6379").unwrap());
/// let _client = connection_handler.connect(&network_stack, Some(&clock)).await.unwrap();
/// # });
/// ```
///
/// Each `connect()` call opens a new connection owned by the returned client.
///
/// ### Authentication
///
/// Authentication is done in the following way:
/// ```
/// # async_std::task::block_on(async {
///# use core::str::FromStr;
///# use core::net::SocketAddr;
///# use std_embedded_nal_async::Stack;
///# use std_embedded_time::StandardClock;
///# use embedded_redis::network::{ConnectionHandler, Credentials};
///#
///# let network_stack = Stack::default();
///# let clock = StandardClock::default();
///#
///# let server_address = SocketAddr::from_str("127.0.0.1:6379").unwrap();
/// // Password only authentication
/// let mut connection_handler = ConnectionHandler::resp2(server_address);
/// connection_handler.auth(Credentials::password_only("secret123!"));
///
/// # let _client = connection_handler.connect(&network_stack, Some(&clock)).await;
///# let server_address = SocketAddr::from_str("127.0.0.1:6379").unwrap();
///
/// // ACL based authentication
/// let mut connection_handler = ConnectionHandler::resp2(server_address);
/// connection_handler.auth(Credentials::acl("user01", "secret123!"));
/// # let _client = connection_handler.connect(&network_stack, Some(&clock)).await;
/// # });
/// ```
/// ### Timeout
///
/// The client includes a timeout mechanism. This allows setting a time limit for responses from the Redis server:
///
/// ```
/// # async_std::task::block_on(async {
///# use core::str::FromStr;
///# use core::net::SocketAddr;
///# use std_embedded_nal_async::Stack;
///# use std_embedded_time::StandardClock;
///# use embedded_redis::network::{ConnectionHandler, Credentials};
///# use embedded_time::duration::Extensions;
///#
///# let network_stack = Stack::default();
///# let clock = StandardClock::default();
///#
///# let server_address = SocketAddr::from_str("127.0.0.1:6379").unwrap();
/// let mut connection_handler = ConnectionHandler::resp2(server_address);
/// connection_handler.timeout(500_000.microseconds());
/// # let _client = connection_handler.connect(&network_stack, Some(&clock)).await.unwrap();
/// # });
/// ```
/// ### Ping
///
/// Optionally, the PING command can also be used to test the connection.
/// PING is then used to verify every newly opened connection.
///
/// It is recommended to use this option only if a Timeout is configured.
///
/// ```
/// # async_std::task::block_on(async {
///# use core::str::FromStr;
///# use core::net::SocketAddr;
///# use std_embedded_nal_async::Stack;
///# use std_embedded_time::StandardClock;
///# use embedded_redis::network::{ConnectionHandler, Credentials};
///# use embedded_time::duration::Extensions;
///#
///# let network_stack = Stack::default();
///# let clock = StandardClock::default();
///#
///# let server_address = SocketAddr::from_str("127.0.0.1:6379").unwrap();
/// let mut connection_handler = ConnectionHandler::resp2(server_address);
/// connection_handler.timeout(500_000.microseconds());
/// connection_handler.use_ping();
/// # let _client = connection_handler.connect(&network_stack, Some(&clock)).await.unwrap();
/// # let _client = connection_handler.connect(&network_stack, Some(&clock)).await.unwrap();
/// # });
/// ```
///
/// ### Memory optimization
///
/// The following parameters can be used to optimize memory usage respectively to improve heap allocation.
/// The right parameters can also protect against DOS scenarios, when the received data can
/// potentially exceed the memory resources.
/// See [MemoryParameters](crate::network::MemoryParameters) for more details.
///
/// ````
/// # async_std::task::block_on(async {
///# use core::str::FromStr;
///# use core::net::SocketAddr;
///# use std_embedded_nal_async::Stack;
///# use std_embedded_time::StandardClock;
///# use embedded_redis::commands::set::SetCommand;
///# use embedded_redis::network::{ConnectionHandler, MemoryParameters};
///#
///# let stack = Stack::default();
///# let clock = StandardClock::default();
///#
///# let server_address = SocketAddr::from_str("127.0.0.1:6379").unwrap();
/// let mut connection_handler = ConnectionHandler::resp3(server_address);
///
/// connection_handler.memory(MemoryParameters {
/// buffer_size: 512,
/// frame_capacity: 4,
/// memory_limit: Some(4096)
/// });
///
///# let client = connection_handler.connect(&stack, Some(&clock)).await.unwrap();
/// # });
/// ````
///
/// ### Concurrency
///
/// A client owns one connection. Access to that connection is serialized asynchronously, so
/// multiple command operations may be polled concurrently on the same executor. Whether a client
/// can be moved between threads depends on the concrete connection, clock, and protocol types.
///
/// ## Asynchronous response management
///
/// [`Client::send`](crate::network::Client::send) and the command shorthand methods asynchronously
/// wait for and return the evaluated Redis response directly. Polling several command operations
/// concurrently enables pipelining:
/// ```
/// # async_std::task::block_on(async {
///# use core::str::FromStr;
///# use core::net::SocketAddr;
///# use futures_util::future::join;
///# use std_embedded_nal_async::Stack;
///# use std_embedded_time::StandardClock;
///# use embedded_redis::network::ConnectionHandler;
///#
///# let stack = Stack::default();
///# let clock = StandardClock::default();
///#
///# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
///# let client = connection_handler.connect(&stack, Some(&clock)).await.unwrap();
///#
/// let first = client.set("first_key", "first_value");
/// let second = client.set("second_key", "second_value");
///
/// let (first_response, second_response) = join(first, second).await;
/// first_response.unwrap();
/// second_response.unwrap();
/// # });
/// ```
///
/// ### Response type
///
/// Response type dependents on executed command abstractions, e.g. [GetResponse](crate::commands::get::GetResponse)
/// in case of [GET command](crate::commands::get).
///
/// ### Timeout error
///
/// In the event of a timeout error, all other concurrently pending command operations are
/// invalidated, as the assignment of responses can no longer be guaranteed. Those operations
/// return [InvalidFuture](crate::network::CommandErrors::InvalidFuture).
///
/// ### Clean state
///
/// If an in-flight command operation is cancelled after sending its command, `close().await`
/// consumes its pending response before the client is dropped. Dropping the client itself closes
/// its owned connection.
///
/// ````
/// # async_std::task::block_on(async {
///# use core::str::FromStr;
///# use core::net::SocketAddr;
///# use std_embedded_nal_async::Stack;
///# use std_embedded_time::StandardClock;
///# use embedded_redis::commands::set::SetCommand;
///# use embedded_redis::network::ConnectionHandler;
///#
///# let stack = Stack::default();
///# let clock = StandardClock::default();
///#
///# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
///# let client = connection_handler.connect(&stack, Some(&clock)).await.unwrap();
///#
/// let _ = client.set("key", "value").await;
/// client.close().await;
/// # });
/// ````