blob: 2ee5f9a29ebbdd0bacdc794d7d057c59c7f37f15 [file] [log] [blame]
// can't use rustfmt here because it screws up the file.
#![cfg_attr(rustfmt, rustfmt_skip)]
use crate::cmd::{cmd, Cmd};
use crate::connection::ConnectionLike;
use crate::pipeline::Pipeline;
use crate::types::{FromRedisValue, RedisResult, ToRedisArgs};
use crate::RedisError;
#[cfg(feature = "cluster")]
use crate::commands::ClusterPipeline;
use serde::ser::Serialize;
macro_rules! implement_json_commands {
(
$lifetime: lifetime
$(
$(#[$attr:meta])+
fn $name:ident<$($tyargs:ident : $ty:ident),*>(
$($argname:ident: $argty:ty),*) $body:block
)*
) => (
/// Implements RedisJSON commands for connection like objects. This
/// allows you to send commands straight to a connection or client. It
/// is also implemented for redis results of clients which makes for
/// very convenient access in some basic cases.
///
/// This allows you to use nicer syntax for some common operations.
/// For instance this code:
///
/// ```rust,no_run
/// # fn do_something() -> redis::RedisResult<()> {
/// let client = redis::Client::open("redis://127.0.0.1/")?;
/// let mut con = client.get_connection()?;
/// redis::cmd("SET").arg("my_key").arg(42).execute(&mut con);
/// assert_eq!(redis::cmd("GET").arg("my_key").query(&mut con), Ok(42));
/// # Ok(()) }
/// ```
///
/// Will become this:
///
/// ```rust,no_run
/// # fn do_something() -> redis::RedisResult<()> {
/// use redis::Commands;
/// let client = redis::Client::open("redis://127.0.0.1/")?;
/// let mut con = client.get_connection()?;
/// con.set("my_key", 42)?;
/// assert_eq!(con.get("my_key"), Ok(42));
/// # Ok(()) }
/// ```
pub trait JsonCommands : ConnectionLike + Sized {
$(
$(#[$attr])*
#[inline]
#[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
fn $name<$lifetime, $($tyargs: $ty, )* RV: FromRedisValue>(
&mut self $(, $argname: $argty)*) -> RedisResult<RV>
{ Cmd::$name($($argname),*)?.query(self) }
)*
}
impl Cmd {
$(
$(#[$attr])*
#[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
pub fn $name<$lifetime, $($tyargs: $ty),*>($($argname: $argty),*) -> RedisResult<Self> {
$body
}
)*
}
/// Implements RedisJSON commands over asynchronous connections. This
/// allows you to send commands straight to a connection or client.
///
/// This allows you to use nicer syntax for some common operations.
/// For instance this code:
///
/// ```rust,no_run
/// use redis::JsonAsyncCommands;
/// # async fn do_something() -> redis::RedisResult<()> {
/// let client = redis::Client::open("redis://127.0.0.1/")?;
/// let mut con = client.get_async_connection().await?;
/// redis::cmd("SET").arg("my_key").arg(42i32).query_async(&mut con).await?;
/// assert_eq!(redis::cmd("GET").arg("my_key").query_async(&mut con).await, Ok(42i32));
/// # Ok(()) }
/// ```
///
/// Will become this:
///
/// ```rust,no_run
/// use redis::JsonAsyncCommands;
/// use serde_json::json;
/// # async fn do_something() -> redis::RedisResult<()> {
/// use redis::Commands;
/// let client = redis::Client::open("redis://127.0.0.1/")?;
/// let mut con = client.get_async_connection().await?;
/// con.json_set("my_key", "$", &json!({"item": 42i32})).await?;
/// assert_eq!(con.json_get("my_key", "$").await, Ok(String::from(r#"[{"item":42}]"#)));
/// # Ok(()) }
/// ```
#[cfg(feature = "aio")]
pub trait JsonAsyncCommands : crate::aio::ConnectionLike + Send + Sized {
$(
$(#[$attr])*
#[inline]
#[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
fn $name<$lifetime, $($tyargs: $ty + Send + Sync + $lifetime,)* RV>(
& $lifetime mut self
$(, $argname: $argty)*
) -> $crate::types::RedisFuture<'a, RV>
where
RV: FromRedisValue,
{
Box::pin(async move {
$body?.query_async(self).await
})
}
)*
}
/// Implements RedisJSON commands for pipelines. Unlike the regular
/// commands trait, this returns the pipeline rather than a result
/// directly. Other than that it works the same however.
impl Pipeline {
$(
$(#[$attr])*
#[inline]
#[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
pub fn $name<$lifetime, $($tyargs: $ty),*>(
&mut self $(, $argname: $argty)*
) -> RedisResult<&mut Self> {
self.add_command($body?);
Ok(self)
}
)*
}
/// Implements RedisJSON commands for cluster pipelines. Unlike the regular
/// commands trait, this returns the cluster pipeline rather than a result
/// directly. Other than that it works the same however.
#[cfg(feature = "cluster")]
impl ClusterPipeline {
$(
$(#[$attr])*
#[inline]
#[allow(clippy::extra_unused_lifetimes, clippy::needless_lifetimes)]
pub fn $name<$lifetime, $($tyargs: $ty),*>(
&mut self $(, $argname: $argty)*
) -> RedisResult<&mut Self> {
self.add_command($body?);
Ok(self)
}
)*
}
)
}
implement_json_commands! {
'a
/// Append the JSON `value` to the array at `path` after the last element in it.
fn json_arr_append<K: ToRedisArgs, P: ToRedisArgs, V: Serialize>(key: K, path: P, value: &'a V) {
let mut cmd = cmd("JSON.ARRAPPEND");
cmd.arg(key)
.arg(path)
.arg(serde_json::to_string(value)?);
Ok::<_, RedisError>(cmd)
}
/// Index array at `path`, returns first occurance of `value`
fn json_arr_index<K: ToRedisArgs, P: ToRedisArgs, V: Serialize>(key: K, path: P, value: &'a V) {
let mut cmd = cmd("JSON.ARRINDEX");
cmd.arg(key)
.arg(path)
.arg(serde_json::to_string(value)?);
Ok::<_, RedisError>(cmd)
}
/// Same as `json_arr_index` except takes a `start` and a `stop` value, setting these to `0` will mean
/// they make no effect on the query
///
/// The default values for `start` and `stop` are `0`, so pass those in if you want them to take no effect
fn json_arr_index_ss<K: ToRedisArgs, P: ToRedisArgs, V: Serialize>(key: K, path: P, value: &'a V, start: &'a isize, stop: &'a isize) {
let mut cmd = cmd("JSON.ARRINDEX");
cmd.arg(key)
.arg(path)
.arg(serde_json::to_string(value)?)
.arg(start)
.arg(stop);
Ok::<_, RedisError>(cmd)
}
/// Inserts the JSON `value` in the array at `path` before the `index` (shifts to the right).
///
/// `index` must be withing the array's range.
fn json_arr_insert<K: ToRedisArgs, P: ToRedisArgs, V: Serialize>(key: K, path: P, index: i64, value: &'a V) {
let mut cmd = cmd("JSON.ARRINSERT");
cmd.arg(key)
.arg(path)
.arg(index)
.arg(serde_json::to_string(value)?);
Ok::<_, RedisError>(cmd)
}
/// Reports the length of the JSON Array at `path` in `key`.
fn json_arr_len<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.ARRLEN");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Removes and returns an element from the `index` in the array.
///
/// `index` defaults to `-1` (the end of the array).
fn json_arr_pop<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P, index: i64) {
let mut cmd = cmd("JSON.ARRPOP");
cmd.arg(key)
.arg(path)
.arg(index);
Ok::<_, RedisError>(cmd)
}
/// Trims an array so that it contains only the specified inclusive range of elements.
///
/// This command is extremely forgiving and using it with out-of-range indexes will not produce an error.
/// There are a few differences between how RedisJSON v2.0 and legacy versions handle out-of-range indexes.
fn json_arr_trim<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P, start: i64, stop: i64) {
let mut cmd = cmd("JSON.ARRTRIM");
cmd.arg(key)
.arg(path)
.arg(start)
.arg(stop);
Ok::<_, RedisError>(cmd)
}
/// Clears container values (Arrays/Objects), and sets numeric values to 0.
fn json_clear<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.CLEAR");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Deletes a value at `path`.
fn json_del<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.DEL");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Gets JSON Value(s) at `path`.
///
/// Runs `JSON.GET` is key is singular, `JSON.MGET` if there are multiple keys.
fn json_get<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd(if key.is_single_arg() { "JSON.GET" } else { "JSON.MGET" });
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Increments the number value stored at `path` by `number`.
fn json_num_incr_by<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P, value: i64) {
let mut cmd = cmd("JSON.NUMINCRBY");
cmd.arg(key)
.arg(path)
.arg(value);
Ok::<_, RedisError>(cmd)
}
/// Returns the keys in the object that's referenced by `path`.
fn json_obj_keys<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.OBJKEYS");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Reports the number of keys in the JSON Object at `path` in `key`.
fn json_obj_len<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.OBJLEN");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Sets the JSON Value at `path` in `key`.
fn json_set<K: ToRedisArgs, P: ToRedisArgs, V: Serialize>(key: K, path: P, value: &'a V) {
let mut cmd = cmd("JSON.SET");
cmd.arg(key)
.arg(path)
.arg(serde_json::to_string(value)?);
Ok::<_, RedisError>(cmd)
}
/// Appends the `json-string` values to the string at `path`.
fn json_str_append<K: ToRedisArgs, P: ToRedisArgs, V: ToRedisArgs>(key: K, path: P, value: V) {
let mut cmd = cmd("JSON.STRAPPEND");
cmd.arg(key)
.arg(path)
.arg(value);
Ok::<_, RedisError>(cmd)
}
/// Reports the length of the JSON String at `path` in `key`.
fn json_str_len<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.STRLEN");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Toggle a `boolean` value stored at `path`.
fn json_toggle<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.TOGGLE");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
/// Reports the type of JSON value at `path`.
fn json_type<K: ToRedisArgs, P: ToRedisArgs>(key: K, path: P) {
let mut cmd = cmd("JSON.TYPE");
cmd.arg(key)
.arg(path);
Ok::<_, RedisError>(cmd)
}
}
impl<T> JsonCommands for T where T: ConnectionLike {}
#[cfg(feature = "aio")]
impl<T> JsonAsyncCommands for T where T: crate::aio::ConnectionLike + Send + Sized {}