redis/commands/json.rs
1use crate::cmd::{cmd, Cmd};
2use crate::connection::ConnectionLike;
3use crate::pipeline::Pipeline;
4use crate::types::{FromRedisValue, RedisResult, ToRedisArgs, ToSingleRedisArg};
5use crate::RedisError;
6
7#[cfg(feature = "cluster")]
8use crate::commands::ClusterPipeline;
9
10use serde::ser::Serialize;
11
12macro_rules! implement_json_commands {
13 (
14 $lifetime: lifetime
15 $(
16 $(#[$attr:meta])+
17 fn $name:ident<$($tyargs:ident : $ty:ident),*>(
18 $($argname:ident: $argty:ty),*) $body:block
19 )*
20 ) => (
21
22 /// Implements RedisJSON commands for connection like objects. This
23 /// allows you to send commands straight to a connection or client. It
24 /// is also implemented for redis results of clients which makes for
25 /// very convenient access in some basic cases.
26 ///
27 /// This allows you to use nicer syntax for some common operations.
28 /// For instance this code:
29 ///
30 /// ```rust,no_run
31 /// use redis::JsonCommands;
32 /// use serde_json::json;
33 /// # fn do_something() -> redis::RedisResult<()> {
34 /// let client = redis::Client::open("redis://127.0.0.1/")?;
35 /// let mut con = client.get_connection()?;
36 /// redis::cmd("JSON.SET").arg("my_key").arg("$").arg(&json!({"item": 42i32}).to_string()).exec(&mut con).unwrap();
37 /// assert_eq!(redis::cmd("JSON.GET").arg("my_key").arg("$").query(&mut con), Ok(String::from(r#"[{"item":42}]"#)));
38 /// # Ok(()) }
39 /// ```
40 ///
41 /// Will become this:
42 ///
43 /// ```rust,no_run
44 /// use redis::JsonCommands;
45 /// use serde_json::json;
46 /// # fn do_something() -> redis::RedisResult<()> {
47 /// let client = redis::Client::open("redis://127.0.0.1/")?;
48 /// let mut con = client.get_connection()?;
49 /// let _: () = con.json_set("my_key", "$", &json!({"item": 42i32}).to_string())?;
50 /// assert_eq!(con.json_get("my_key", "$"), Ok(String::from(r#"[{"item":42}]"#)));
51 /// assert_eq!(con.json_get("my_key", "$.item"), Ok(String::from(r#"[42]"#)));
52 /// # Ok(()) }
53 /// ```
54 ///
55 /// With RedisJSON commands, you have to note that all results will be wrapped
56 /// in square brackets (or empty brackets if not found). If you want to deserialize it
57 /// with e.g. `serde_json` you have to use `Vec<T>` for your output type instead of `T`.
58 pub trait JsonCommands : ConnectionLike + Sized {
59 $(
60 $(#[$attr])*
61 #[inline]
62 #[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
63 fn $name<$lifetime, $($tyargs: $ty, )* RV: FromRedisValue>(
64 &mut self $(, $argname: $argty)*) -> RedisResult<RV>
65 { Cmd::$name($($argname),*)?.query(self) }
66 )*
67 }
68
69 impl Cmd {
70 $(
71 $(#[$attr])*
72 #[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
73 pub fn $name<$lifetime, $($tyargs: $ty),*>($($argname: $argty),*) -> RedisResult<Self> {
74 $body
75 }
76 )*
77 }
78
79 /// Implements RedisJSON commands over asynchronous connections. This
80 /// allows you to send commands straight to a connection or client.
81 ///
82 /// This allows you to use nicer syntax for some common operations.
83 /// For instance this code:
84 ///
85 /// ```rust,no_run
86 /// use redis::JsonAsyncCommands;
87 /// use serde_json::json;
88 /// # async fn do_something() -> redis::RedisResult<()> {
89 /// let client = redis::Client::open("redis://127.0.0.1/")?;
90 /// let mut con = client.get_multiplexed_async_connection().await?;
91 /// redis::cmd("JSON.SET").arg("my_key").arg("$").arg(&json!({"item": 42i32}).to_string()).exec_async(&mut con).await?;
92 /// assert_eq!(redis::cmd("JSON.GET").arg("my_key").arg("$").query_async(&mut con).await, Ok(String::from(r#"[{"item":42}]"#)));
93 /// # Ok(()) }
94 /// ```
95 ///
96 /// Will become this:
97 ///
98 /// ```rust,no_run
99 /// use redis::JsonAsyncCommands;
100 /// use serde_json::json;
101 /// # async fn do_something() -> redis::RedisResult<()> {
102 /// use redis::Commands;
103 /// let client = redis::Client::open("redis://127.0.0.1/")?;
104 /// let mut con = client.get_multiplexed_async_connection().await?;
105 /// let _: () = con.json_set("my_key", "$", &json!({"item": 42i32}).to_string()).await?;
106 /// assert_eq!(con.json_get("my_key", "$").await, Ok(String::from(r#"[{"item":42}]"#)));
107 /// assert_eq!(con.json_get("my_key", "$.item").await, Ok(String::from(r#"[42]"#)));
108 /// # Ok(()) }
109 /// ```
110 ///
111 /// With RedisJSON commands, you have to note that all results will be wrapped
112 /// in square brackets (or empty brackets if not found). If you want to deserialize it
113 /// with e.g. `serde_json` you have to use `Vec<T>` for your output type instead of `T`.
114 ///
115 #[cfg(feature = "aio")]
116 pub trait JsonAsyncCommands : crate::aio::ConnectionLike + Send + Sized {
117 $(
118 $(#[$attr])*
119 #[inline]
120 #[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
121 fn $name<$lifetime, $($tyargs: $ty + Send + Sync + $lifetime,)* RV>(
122 & $lifetime mut self
123 $(, $argname: $argty)*
124 ) -> $crate::types::RedisFuture<'a, RV>
125 where
126 RV: FromRedisValue,
127 {
128 Box::pin(async move {
129 $body?.query_async(self).await
130 })
131 }
132 )*
133 }
134
135 /// Implements RedisJSON commands for pipelines. Unlike the regular
136 /// commands trait, this returns the pipeline rather than a result
137 /// directly. Other than that it works the same however.
138 impl Pipeline {
139 $(
140 $(#[$attr])*
141 #[inline]
142 #[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
143 pub fn $name<$lifetime, $($tyargs: $ty),*>(
144 &mut self $(, $argname: $argty)*
145 ) -> RedisResult<&mut Self> {
146 self.add_command($body?);
147 Ok(self)
148 }
149 )*
150 }
151
152 /// Implements RedisJSON commands for cluster pipelines. Unlike the regular
153 /// commands trait, this returns the cluster pipeline rather than a result
154 /// directly. Other than that it works the same however.
155 #[cfg(feature = "cluster")]
156 impl ClusterPipeline {
157 $(
158 $(#[$attr])*
159 #[inline]
160 #[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
161 pub fn $name<$lifetime, $($tyargs: $ty),*>(
162 &mut self $(, $argname: $argty)*
163 ) -> RedisResult<&mut Self> {
164 self.add_command($body?);
165 Ok(self)
166 }
167 )*
168 }
169
170 )
171}
172
173implement_json_commands! {
174 'a
175
176 /// Append the JSON `value` to the array at `path` after the last element in it.
177 fn json_arr_append<K: ToSingleRedisArg, P: ToSingleRedisArg, V: Serialize>(key: K, path: P, value: &'a V) {
178 let mut cmd = cmd("JSON.ARRAPPEND");
179
180 cmd.arg(key)
181 .arg(path)
182 .arg(serde_json::to_string(value)?);
183
184 Ok::<_, RedisError>(cmd)
185 }
186
187 /// Index array at `path`, returns first occurrence of `value`
188 fn json_arr_index<K: ToSingleRedisArg, P: ToSingleRedisArg, V: Serialize>(key: K, path: P, value: &'a V) {
189 let mut cmd = cmd("JSON.ARRINDEX");
190
191 cmd.arg(key)
192 .arg(path)
193 .arg(serde_json::to_string(value)?);
194
195 Ok::<_, RedisError>(cmd)
196 }
197
198 /// Same as `json_arr_index` except takes a `start` and a `stop` value, setting these to `0` will mean
199 /// they make no effect on the query
200 ///
201 /// The default values for `start` and `stop` are `0`, so pass those in if you want them to take no effect
202 fn json_arr_index_ss<K: ToSingleRedisArg, P: ToSingleRedisArg, V: Serialize>(key: K, path: P, value: &'a V, start: &'a isize, stop: &'a isize) {
203 let mut cmd = cmd("JSON.ARRINDEX");
204
205 cmd.arg(key)
206 .arg(path)
207 .arg(serde_json::to_string(value)?)
208 .arg(start)
209 .arg(stop);
210
211 Ok::<_, RedisError>(cmd)
212 }
213
214 /// Inserts the JSON `value` in the array at `path` before the `index` (shifts to the right).
215 ///
216 /// `index` must be within the array's range.
217 fn json_arr_insert<K: ToSingleRedisArg, P: ToSingleRedisArg, V: Serialize>(key: K, path: P, index: i64, value: &'a V) {
218 let mut cmd = cmd("JSON.ARRINSERT");
219
220 cmd.arg(key)
221 .arg(path)
222 .arg(index)
223 .arg(serde_json::to_string(value)?);
224
225 Ok::<_, RedisError>(cmd)
226
227 }
228
229 /// Reports the length of the JSON Array at `path` in `key`.
230 fn json_arr_len<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
231 let mut cmd = cmd("JSON.ARRLEN");
232
233 cmd.arg(key)
234 .arg(path);
235
236 Ok::<_, RedisError>(cmd)
237 }
238
239 /// Removes and returns an element from the `index` in the array.
240 ///
241 /// `index` defaults to `-1` (the end of the array).
242 fn json_arr_pop<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P, index: i64) {
243 let mut cmd = cmd("JSON.ARRPOP");
244
245 cmd.arg(key)
246 .arg(path)
247 .arg(index);
248
249 Ok::<_, RedisError>(cmd)
250 }
251
252 /// Trims an array so that it contains only the specified inclusive range of elements.
253 ///
254 /// This command is extremely forgiving and using it with out-of-range indexes will not produce an error.
255 /// There are a few differences between how RedisJSON v2.0 and legacy versions handle out-of-range indexes.
256 fn json_arr_trim<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P, start: i64, stop: i64) {
257 let mut cmd = cmd("JSON.ARRTRIM");
258
259 cmd.arg(key)
260 .arg(path)
261 .arg(start)
262 .arg(stop);
263
264 Ok::<_, RedisError>(cmd)
265 }
266
267 /// Clears container values (Arrays/Objects), and sets numeric values to 0.
268 fn json_clear<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
269 let mut cmd = cmd("JSON.CLEAR");
270
271 cmd.arg(key)
272 .arg(path);
273
274 Ok::<_, RedisError>(cmd)
275 }
276
277 /// Deletes a value at `path`.
278 fn json_del<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
279 let mut cmd = cmd("JSON.DEL");
280
281 cmd.arg(key)
282 .arg(path);
283
284 Ok::<_, RedisError>(cmd)
285 }
286
287 /// Gets JSON Value at `path`.
288 ///
289 /// With RedisJSON commands, you have to note that all results will be wrapped
290 /// in square brackets (or empty brackets if not found). If you want to deserialize it
291 /// with e.g. `serde_json` you have to use `Vec<T>` for your output type instead of `T`.
292 fn json_get<K: ToSingleRedisArg, P: ToRedisArgs>(key: K, path: P) {
293 let mut cmd = cmd("JSON.GET");
294
295 cmd.arg(key)
296 .arg(path);
297
298 Ok::<_, RedisError>(cmd)
299 }
300
301 /// Gets JSON Values at `path`.
302 ///
303 /// With RedisJSON commands, you have to note that all results will be wrapped
304 /// in square brackets (or empty brackets if not found). If you want to deserialize it
305 /// with e.g. `serde_json` you have to use `Vec<T>` for your output type instead of `T`.
306 fn json_mget<K: ToRedisArgs, P: ToSingleRedisArg>(key: K, path: P) {
307 let mut cmd = cmd("JSON.MGET");
308
309 cmd.arg(key)
310 .arg(path);
311
312 Ok::<_, RedisError>(cmd)
313 }
314
315 /// Increments the number value stored at `path` by `number`.
316 fn json_num_incr_by<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P, value: i64) {
317 let mut cmd = cmd("JSON.NUMINCRBY");
318
319 cmd.arg(key)
320 .arg(path)
321 .arg(value);
322
323 Ok::<_, RedisError>(cmd)
324 }
325
326 /// Returns the keys in the object that's referenced by `path`.
327 fn json_obj_keys<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
328 let mut cmd = cmd("JSON.OBJKEYS");
329
330 cmd.arg(key)
331 .arg(path);
332
333 Ok::<_, RedisError>(cmd)
334 }
335
336 /// Reports the number of keys in the JSON Object at `path` in `key`.
337 fn json_obj_len<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
338 let mut cmd = cmd("JSON.OBJLEN");
339
340 cmd.arg(key)
341 .arg(path);
342
343 Ok::<_, RedisError>(cmd)
344 }
345
346 /// Sets the JSON Value at `path` in `key`.
347 fn json_set<K: ToSingleRedisArg, P: ToSingleRedisArg, V: Serialize>(key: K, path: P, value: &'a V) {
348 let mut cmd = cmd("JSON.SET");
349
350 cmd.arg(key)
351 .arg(path)
352 .arg(serde_json::to_string(value)?);
353
354 Ok::<_, RedisError>(cmd)
355 }
356
357 /// Sets the value at the path per key, for every given tuple.
358 fn json_mset<K: ToSingleRedisArg, P: ToSingleRedisArg, V: Serialize>(key_path_values: &'a [(K,P,V)]) {
359 let mut cmd = cmd("JSON.MSET");
360
361 for (key, path, value) in key_path_values {
362 cmd.arg(key)
363 .arg(path)
364 .arg(serde_json::to_string(value)?);
365 }
366
367 Ok::<_, RedisError>(cmd)
368 }
369
370 /// Appends the `json-string` values to the string at `path`.
371 fn json_str_append<K: ToSingleRedisArg, P: ToSingleRedisArg, V: ToSingleRedisArg>(key: K, path: P, value: V) {
372 let mut cmd = cmd("JSON.STRAPPEND");
373
374 cmd.arg(key)
375 .arg(path)
376 .arg(value);
377
378 Ok::<_, RedisError>(cmd)
379 }
380
381 /// Reports the length of the JSON String at `path` in `key`.
382 fn json_str_len<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
383 let mut cmd = cmd("JSON.STRLEN");
384
385 cmd.arg(key)
386 .arg(path);
387
388 Ok::<_, RedisError>(cmd)
389 }
390
391 /// Toggle a `boolean` value stored at `path`.
392 fn json_toggle<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
393 let mut cmd = cmd("JSON.TOGGLE");
394
395 cmd.arg(key)
396 .arg(path);
397
398 Ok::<_, RedisError>(cmd)
399 }
400
401 /// Reports the type of JSON value at `path`.
402 fn json_type<K: ToSingleRedisArg, P: ToSingleRedisArg>(key: K, path: P) {
403 let mut cmd = cmd("JSON.TYPE");
404
405 cmd.arg(key)
406 .arg(path);
407
408 Ok::<_, RedisError>(cmd)
409 }
410}
411
412impl<T> JsonCommands for T where T: ConnectionLike {}
413
414#[cfg(feature = "aio")]
415impl<T> JsonAsyncCommands for T where T: crate::aio::ConnectionLike + Send + Sized {}