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
/*!
# redis-derive
This crate implements the [`FromRedisValue`](redis::FromRedisValue) and [`ToRedisArgs`](redis::ToRedisArgs) traits
from [`redis-rs`](https://github.com/redis-rs/redis-rs) for any struct or enum.
This allows seamless type conversion between Rust structs and Redis hash sets, which is more beneficial than JSON encoding the struct and storing the result in a Redis key because when saving as a Redis hash set, sorting algorithms can be performed without having to move data out of the database.
There is also the benefit of being able to retrieve just one value of the struct in the database.
Initial development was done by @Michaelvanstraten 🙏🏽.
## Features
- **RESP3 Support**: Native support for Redis 7+ protocol features including VerbatimString
- **Hash Field Expiration**: Per-field TTL support using Redis 7.4+ HEXPIRE commands
- **Client-Side Caching**: Automatic cache management with Redis 6+ client caching
- **Cluster Awareness**: Hash tag generation for Redis Cluster deployments
- **Flexible Naming**: Support for various case conversion rules (snake_case, kebab-case, etc.)
- **Comprehensive Error Handling**: Clear error messages for debugging
- **Performance Optimized**: Efficient serialization with minimal allocations
## Usage and Examples
Add this to your `Cargo.toml`:
```toml
[dependencies]
redis-derive = "0.2.0"
redis = "0.32"
```
Import the procedural macros:
```rust
use redis_derive::{FromRedisValue, ToRedisArgs};
```
### Basic Struct Example
```rust
use redis::Commands;
use redis_derive::{FromRedisValue, ToRedisArgs};
#[derive(ToRedisArgs, FromRedisValue, Debug)]
struct User {
id: u64,
username: String,
email: Option<String>,
active: bool,
}
fn main() -> redis::RedisResult<()> {
let client = redis::Client::open("redis://127.0.0.1/")?;
let mut con = client.get_connection()?;
let user = User {
id: 12345,
username: "john_doe".to_string(),
email: Some("john@example.com".to_string()),
active: true,
};
// Store individual fields
con.hset("user:12345", "id", user.id)?;
con.hset("user:12345", "username", &user.username)?;
con.hset("user:12345", "email", &user.email)?;
con.hset("user:12345", "active", user.active)?;
// Retrieve the complete struct
let retrieved_user: User = con.hgetall("user:12345")?;
println!("Retrieved: {:?}", retrieved_user);
Ok(())
}
```
### Enum with Case Conversion
```rust,ignore
use redis_derive::{FromRedisValue, ToRedisArgs};
#[derive(ToRedisArgs, FromRedisValue, Debug, PartialEq)]
#[redis(rename_all = "snake_case")]
enum UserRole {
Administrator, // stored as "administrator"
PowerUser, // stored as "power_user"
RegularUser, // stored as "regular_user"
GuestUser, // stored as "guest_user"
}
// Works seamlessly with Redis
let role = UserRole::PowerUser;
con.set("user:role", &role)?;
let retrieved: UserRole = con.get("user:role")?;
assert_eq!(role, retrieved);
```
## Naming Conventions and Attributes
### Case Conversion Rules
The `rename_all` attribute supports multiple case conversion rules:
```rust
use redis_derive::{FromRedisValue, ToRedisArgs};
#[derive(ToRedisArgs, FromRedisValue)]
#[redis(rename_all = "snake_case")]
enum Status {
InProgress, // → "in_progress"
WaitingForReview, // → "waiting_for_review"
Completed, // → "completed"
}
#[derive(ToRedisArgs, FromRedisValue)]
#[redis(rename_all = "kebab-case")]
enum Priority {
HighPriority, // → "high-priority"
MediumPriority, // → "medium-priority"
LowPriority, // → "low-priority"
}
```
Supported case conversion rules:
- `"lowercase"`: `MyField` → `myfield`
- `"UPPERCASE"`: `MyField` → `MYFIELD`
- `"PascalCase"`: `my_field` → `MyField`
- `"camelCase"`: `my_field` → `myField`
- `"snake_case"`: `MyField` → `my_field`
- `"kebab-case"`: `MyField` → `my-field`
### Important Naming Behavior
**Key insight**: The case conversion applies to **both** serialization and deserialization:
```rust,ignore
// With rename_all = "snake_case"
let role = UserRole::PowerUser;
// Serialization: PowerUser → "power_user"
con.set("key", &role)?;
// Deserialization: "power_user" → PowerUser
let retrieved: UserRole = con.get("key")?;
// Error messages also use converted names:
// "Unknown variant 'admin' for UserRole. Valid variants: [administrator, power_user, regular_user, guest_user]"
```
### Redis Protocol Support
This crate handles multiple Redis value types automatically:
- **BulkString**: Most common for stored hash fields and string values
- **SimpleString**: Direct Redis command responses
- **VerbatimString**: Redis 6+ RESP3 protocol feature (automatically supported)
- **Proper error handling**: Clear messages for nil values and type mismatches
### Advanced Features
#### Hash Field Expiration (Redis 7.4+)
```rust
use redis_derive::{FromRedisValue, ToRedisArgs};
#[derive(ToRedisArgs, FromRedisValue)]
struct SessionData {
user_id: u64,
#[redis(expire = "1800")] // 30 minutes
access_token: String,
#[redis(expire = "7200")] // 2 hours
refresh_token: String,
}
```
#### Cluster-Aware Keys
```rust
use redis_derive::{FromRedisValue, ToRedisArgs};
#[derive(ToRedisArgs, FromRedisValue)]
#[redis(cluster_key = "user_id")]
struct UserProfile {
user_id: u64,
profile_data: String,
}
```
#### Client-Side Caching
```rust
use redis_derive::{FromRedisValue, ToRedisArgs};
#[derive(ToRedisArgs, FromRedisValue)]
#[redis(cache = true, ttl = "600")]
struct CachedData {
id: u64,
data: String,
}
```
## Development and Testing
The crate includes comprehensive examples in the `examples/` directory:
```bash
# Start Redis with Docker
cd examples && docker-compose up -d
# Run basic example
cargo run --example main
# Test all enum deserialization branches
cargo run --example enum_branches
# Debug attribute parsing behavior
cargo run --example debug_attributes
```
## Limitations
- Only unit enums (variants without fields) are currently supported
- Requires redis-rs 0.32.4 or later for full compatibility
## Compatibility
- **Redis**: Compatible with Redis 6+ (RESP2) and Redis 7+ (RESP3)
- **Rust**: MSRV 1.70+ (follows redis-rs requirements)
- **redis-rs**: 0.32.4+ (uses `num_of_args()` instead of deprecated `num_args()`)
License: MIT OR Apache-2.0
*/
use TokenStream;
use ;
/**
This macro implements the [`ToRedisArgs`](redis::ToRedisArgs) trait for a given struct or enum.
It generates efficient serialization code that converts Rust types to Redis arguments.
# Attributes
- `redis(rename_all = "...")`: Transform field/variant names using case conversion rules
- `redis(expire = "seconds")`: Set TTL for hash fields (requires Redis 7.4+)
- `redis(expire_at = "field_name")`: Expire field at timestamp specified by another field
- `redis(cluster_key = "field_name")`: Use specified field for Redis Cluster hash tag generation
- `redis(cache = true)`: Enable client-side caching support
- `redis(ttl = "seconds")`: Default TTL for cached objects
## Case Conversion Rules
- `"lowercase"`: `MyField` → `myfield`
- `"UPPERCASE"`: `MyField` → `MYFIELD`
- `"PascalCase"`: `my_field` → `MyField`
- `"camelCase"`: `my_field` → `myField`
- `"snake_case"`: `MyField` → `my_field`
- `"kebab-case"`: `MyField` → `my-field`
*/
/**
This macro implements the [`FromRedisValue`](redis::FromRedisValue) trait for a given struct or enum.
It generates efficient deserialization code with full RESP3 support and enhanced error handling.
# Attributes
Same attributes as `ToRedisArgs`. The deserialization respects the same naming conventions
and provides helpful error messages for debugging.
# Error Handling
The generated code provides detailed error messages including:
- Expected vs actual Redis value types
- Missing field information
- Type conversion failures with context
- RESP2/RESP3 compatibility notes
*/