use crate::{
client::{prepare_command, PreparedCommand},
commands::SetCondition,
resp::{
cmd, CollectionResponse, CommandArgs, PrimitiveResponse, SingleArg, SingleArgCollection,
ToArgs, Value,
},
};
use serde::de::DeserializeOwned;
/// A group of Redis commands related to [`RedisJson`](https://redis.io/docs/stack/json/)
///
/// # See Also
/// [RedisJson Commands](https://redis.io/commands/?group=json)
pub trait JsonCommands<'a> {
/// Append the json `values` into the array at `path` after the last element in it
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
/// * `values` - one or more values to append to one or more arrays.
///
/// # Return
/// A collection of integer replies for each path, the array's new size,
/// or nil, if the matching JSON value is not an array.
///
/// # See Also
/// [ ](https://redis.io/commands/json.arrappend/)
#[must_use]
fn json_arrappend(
self,
key: K,
path: P,
values: VV,
) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
V: SingleArg,
VV: SingleArgCollection,
R: CollectionResponse>,
{
prepare_command(self, cmd("JSON.ARRAPPEND").arg(key).arg(path).arg(values))
}
/// Search for the first occurrence of a scalar JSON value in an array
///
/// # Arguments
/// * `key` - The key to parse.
/// * `path`- The JSONPath to specify.
/// * `value` - value index to find in one or more arrays.
///
/// # Return
/// A collection of integer replies for each path,
///
/// the first position in the array of each JSON value that matches the path,
/// -1 if unfound in the array, or nil, if the matching JSON value is not an array.
///
/// # See Also
/// [ ](https://redis.io/commands/json.arrindex/)
#[must_use]
fn json_arrindex(
self,
key: K,
path: P,
value: V,
options: JsonArrIndexOptions,
) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
V: SingleArg,
R: CollectionResponse>,
{
prepare_command(
self,
cmd("JSON.ARRINDEX")
.arg(key)
.arg(path)
.arg(value)
.arg(options),
)
}
/// Insert the json `values` into the array at `path` before the `index` (shifts to the right)
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
/// * `index`- The position in the array where you want to insert a value.
/// The index must be in the array's range.
/// Inserting at index 0 prepends to the array.
/// Negative index values start from the end of the array.
/// * `values` - one or more values to insert in one or more arrays.
///
/// # Return
/// A collection of integer replies for each path,
/// the array's new size, or nil,
/// if the matching JSON value is not an array.
///
/// # See Also
/// [ ](https://redis.io/commands/json.arrinsert/)
#[must_use]
fn json_arrinsert(
self,
key: K,
path: P,
index: isize,
values: VV,
) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
V: SingleArg,
VV: SingleArgCollection,
R: CollectionResponse>,
{
prepare_command(
self,
cmd("JSON.ARRINSERT")
.arg(key)
.arg(path)
.arg(index)
.arg(values),
)
}
/// Report the length of the JSON array at `path` in `key`
///
/// # Arguments
/// * `key` - The key to parse.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// A collection of integer replies, an integer for each matching value,
/// each is the array's length, or nil, if the matching value is not an array.
///
/// # See Also
/// [ ](https://redis.io/commands/json.arrlen/)
#[must_use]
fn json_arrlen(self, key: K, path: P) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: CollectionResponse>,
{
prepare_command(self, cmd("JSON.ARRLEN").arg(key).arg(path))
}
/// Remove and return an element from the index in the array
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
/// * `index`- is position in the array to start popping from.
/// Default is -1, meaning the last element.
/// Out-of-range indexes round to their respective array ends.
/// Popping an empty array returns null.
///
/// # Return
/// A collection of bulk string replies for each path, each reply is the popped JSON value,
/// or nil, if the matching JSON value is not an array.
///
/// # See Also
/// [ ](https://redis.io/commands/json.arrpop/)
#[must_use]
fn json_arrpop(
self,
key: K,
path: P,
index: isize,
) -> PreparedCommand<'a, Self, RR>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: PrimitiveResponse + DeserializeOwned,
RR: CollectionResponse,
{
prepare_command(self, cmd("JSON.ARRPOP").arg(key).arg(path).arg(index))
}
/// Remove and return an element from the index in the array
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
/// * `start`- The index of the first element to keep (previous elements are trimmed).
/// * `stop` - the index of the last element to keep (following elements are trimmed), including the last element.
/// Negative values are interpreted as starting from the end.
///
/// # Return
/// A collection of integer replies for each path, the array's new size,
/// or nil, if the matching JSON value is not an array.
///
/// # See Also
/// [ ](https://redis.io/commands/json.arrtrim/)
#[must_use]
fn json_arrtrim(
self,
key: K,
path: P,
start: isize,
stop: isize,
) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: CollectionResponse>,
{
prepare_command(
self,
cmd("JSON.ARRTRIM").arg(key).arg(path).arg(start).arg(stop),
)
}
/// Clear container values (arrays/objects) and set numeric values to 0
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// The number of values cleared.
///
/// # See Also
/// [ ](https://redis.io/commands/json.clear/)
#[must_use]
fn json_clear(self, key: K, path: P) -> PreparedCommand<'a, Self, usize>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
{
prepare_command(self, cmd("JSON.CLEAR").arg(key).arg(path))
}
/// Report a value's memory usage in bytes
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// A collection of integer replies for each path, the value size in bytes
///
/// # See Also
/// [ ](https://redis.io/commands/json.debug-memory/)
#[must_use]
fn json_debug_memory(self, key: K, path: P) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: CollectionResponse,
{
prepare_command(self, cmd("JSON.DEBUG").arg("MEMORY").arg(key).arg(path))
}
/// Delete a value
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// The number of paths deleted (0 or more).
///
/// # See Also
/// [ ](https://redis.io/commands/json.del/)
#[must_use]
fn json_del(self, key: K, path: P) -> PreparedCommand<'a, Self, usize>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
{
prepare_command(self, cmd("JSON.DEL").arg(key).arg(path))
}
/// See [`json_del`](JsonCommands::json_del)
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// The number of paths deleted (0 or more).
///
/// # See Also
/// [ ](https://redis.io/commands/json.forget/)
#[must_use]
fn json_forget(self, key: K, path: P) -> PreparedCommand<'a, Self, usize>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
{
prepare_command(self, cmd("JSON.FORGET").arg(key).arg(path))
}
/// Return the value at path in JSON serialized form
///
/// # Arguments
/// * `key` - The key to parse.
/// * `options`- See [`JsonOptions`](JsonGetOptions)
///
/// # Return
/// A collection of bulk string replies. Each string is the JSON serialization of each JSON value that matches a path
///
/// # See Also
/// [ ](https://redis.io/commands/json.get/)
#[must_use]
fn json_get(self, key: K, options: JsonGetOptions) -> PreparedCommand<'a, Self, V>
where
Self: Sized,
K: SingleArg,
V: PrimitiveResponse,
{
prepare_command(self, cmd("JSON.GET").arg(key).arg(options))
}
/// Return the values at `path` from multiple `key` arguments
///
/// # Arguments
/// * `key` - The key to parse.
/// * `options`- See [`JsonOptions`](JsonGetOptions)
///
/// # Return
/// A collection of bulk string replies specified as the JSON serialization of the value at each key's path.
///
/// # See Also
/// [ ](https://redis.io/commands/json.mget/)
#[must_use]
fn json_mget(self, keys: KK, path: P) -> PreparedCommand<'a, Self, VV>
where
Self: Sized,
K: SingleArg,
KK: SingleArgCollection,
P: SingleArg,
V: PrimitiveResponse + DeserializeOwned,
VV: CollectionResponse,
{
prepare_command(self, cmd("JSON.MGET").arg(keys).arg(path))
}
/// Increment the number value stored at path by number
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
/// * `value` - number value to increment.
///
/// # Return
/// A bulk string reply specified as a stringified new value for each path,
/// or nil, if the matching JSON value is not a number.
///
/// # See Also
/// [ ](https://redis.io/commands/json.numincrby/)
#[must_use]
fn json_numincrby(self, key: K, path: P, value: V) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
V: SingleArg,
R: PrimitiveResponse,
{
prepare_command(self, cmd("JSON.NUMINCRBY").arg(key).arg(path).arg(value))
}
/// Multiply the number value stored at path by number
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
/// * `value` - number value to increment.
///
/// # Return
/// A bulk string reply specified as a stringified new value for each path,
/// or nil, if the matching JSON value is not a number.
///
/// # See Also
/// [ ](https://redis.io/commands/json.nummultby/)
#[must_use]
fn json_nummultby(self, key: K, path: P, value: V) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
V: SingleArg,
R: PrimitiveResponse,
{
prepare_command(self, cmd("JSON.NUMMULTBY").arg(key).arg(path).arg(value))
}
/// Return the keys in the object that's referenced by `path`
///
/// # Arguments
/// * `key` - The key to parse.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// A collection of collection replies for each path,
/// a collection of the key names in the object as a bulk string reply,
/// or an empty collection if the matching JSON value is not an object.
///
/// # See Also
/// [ ](https://redis.io/commands/json.objkeys/)
#[must_use]
fn json_objkeys(self, key: K, path: P) -> PreparedCommand<'a, Self, RR>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: PrimitiveResponse + DeserializeOwned,
RR: CollectionResponse>,
{
prepare_command(self, cmd("JSON.OBJKEYS").arg(key).arg(path))
}
/// Report the number of keys in the JSON object at `path` in `key`
///
/// # Arguments
/// * `key` - The key to parse.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// A collection of integer replies for each path specified as the number of keys in the object or nil,
/// if the matching JSON value is not an object.
///
/// # See Also
/// [ ](https://redis.io/commands/json.objlen/)
#[must_use]
fn json_objlen(self, key: K, path: P) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: CollectionResponse>,
{
prepare_command(self, cmd("JSON.OBJLEN").arg(key).arg(path))
}
/// Return the JSON in key in
/// [`Redis serialization protocol specification`](https://redis.io/docs/reference/protocol-spec) form
///
/// # Arguments
/// * `key` - The key to parse.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// A collection of [`Values`](crate::resp::Value)
///
/// This command uses the following mapping from JSON to RESP:
/// * JSON `null` maps to the bulk string reply.
/// * JSON `false` and `true` values map to the simple string reply.
/// * JSON number maps to the integer reply or bulk string reply, depending on type.
/// * JSON string maps to the bulk string reply.
/// * JSON array is represented as an array reply in which the first element is the simple string reply `[`, followed by the array's elements.
/// * JSON object is represented as an array reply in which the first element is the simple string reply `{`.
/// Each successive entry represents a key-value pair as a two-entry array reply of the bulk string reply.
///
/// # See Also
/// [ ](https://redis.io/commands/json.resp/)
#[must_use]
fn json_resp(self, key: K, path: P) -> PreparedCommand<'a, Self, VV>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
VV: CollectionResponse,
{
prepare_command(self, cmd("JSON.RESP").arg(key).arg(path))
}
/// Set the JSON value at `path` in `key`
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path` - JSONPath to specify.\
/// For new Redis keys the path must be the root.\
/// For existing keys, when the entire path exists, the value that it contains is replaced with the json value.\
/// For existing keys, when the path exists, except for the last element, a new child is added with the json value.
/// * `value`- The value to set at the specified path
/// * `condition`- See [`SetCondition`](crate::commands::SetCondition)
///
/// # See Also
/// [ ](https://redis.io/commands/json.set/)
#[must_use]
fn json_set(
self,
key: K,
path: P,
value: V,
condition: SetCondition,
) -> PreparedCommand<'a, Self, ()>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
V: SingleArg,
{
prepare_command(
self,
cmd("JSON.SET").arg(key).arg(path).arg(value).arg(condition),
)
}
/// Append the json-string values to the string at path
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
/// * `value` - number value to increment.
///
/// # Return
/// A collection of integer replies for each path, the string's new length, or nil, if the matching JSON value is not a string.
///
/// # See Also
/// [ ](https://redis.io/commands/json.strappend/)
#[must_use]
fn json_strappend(self, key: K, path: P, value: V) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
V: SingleArg,
R: CollectionResponse>,
{
prepare_command(self, cmd("JSON.STRAPPEND").arg(key).arg(path).arg(value))
}
/// Report the length of the JSON String at `path` in `key`
///
/// # Arguments
/// * `key` - The key to parse.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// returns by recursive descent a collection of integer replies for each path,
/// the array's length, or nil, if the matching JSON value is not a string.
///
/// # See Also
/// [ ](https://redis.io/commands/json.strlen/)
#[must_use]
fn json_strlen(self, key: K, path: P) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: CollectionResponse>,
{
prepare_command(self, cmd("JSON.STRLEN").arg(key).arg(path))
}
/// Toggle a Boolean value stored at `path`
///
/// # Arguments
/// * `key` - The key to modify.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// A collection of integer replies for each path, the new value (0 if false or 1 if true),
/// or nil for JSON values matching the path that are not Boolean.
///
/// # See Also
/// [ ](https://redis.io/commands/json.toggle/)
#[must_use]
fn json_toggle(self, key: K, path: P) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: CollectionResponse>,
{
prepare_command(self, cmd("JSON.TOGGLE").arg(key).arg(path))
}
/// Report the type of JSON value at `path`
///
/// # Arguments
/// * `key` - The key to parse.
/// * `path`- The JSONPath to specify.
///
/// # Return
/// A collection of string replies for each path, specified as the value's type.
///
/// # See Also
/// [ ](https://redis.io/commands/json.type/)
#[must_use]
fn json_type(self, key: K, path: P) -> PreparedCommand<'a, Self, RR>
where
Self: Sized,
K: SingleArg,
P: SingleArg,
R: PrimitiveResponse + DeserializeOwned,
RR: CollectionResponse,
{
prepare_command(self, cmd("JSON.TYPE").arg(key).arg(path))
}
}
/// Options for the [`json_get`](JsonCommands::json_get) command
#[derive(Default)]
pub struct JsonGetOptions {
command_args: CommandArgs,
}
impl JsonGetOptions {
/// Sets the indentation string for nested levels.
#[must_use]
pub fn indent(mut self, indent: I) -> Self {
Self {
command_args: self.command_args.arg("INDENT").arg(indent).build(),
}
}
/// Sets the string that's printed at the end of each line.
#[must_use]
pub fn newline(mut self, newline: NL) -> Self {
Self {
command_args: self.command_args.arg("NEWLINE").arg(newline).build(),
}
}
/// Sets the string that's put between a key and a value.
#[must_use]
pub fn space(mut self, space: S) -> Self {
Self {
command_args: self.command_args.arg("SPACE").arg(space).build(),
}
}
/// JSONPath to specify
#[must_use]
pub fn path>(mut self, paths: PP) -> Self {
Self {
command_args: self.command_args.arg(paths).build(),
}
}
}
impl ToArgs for JsonGetOptions {
fn write_args(&self, args: &mut CommandArgs) {
args.arg(&self.command_args);
}
}
/// Options for the [`json_arrindex`](JsonCommands::json_arrindex) command
#[derive(Default)]
pub struct JsonArrIndexOptions {
command_args: CommandArgs,
}
impl JsonArrIndexOptions {
/// Inclusive start value to specify in a slice of the array to search.
///
/// Default is 0.
#[must_use]
pub fn start(mut self, start: usize) -> Self {
Self {
command_args: self.command_args.arg(start).build(),
}
}
/// Exclusive stop value to specify in a slice of the array to search, including the last element.
///
/// Default is 0.
/// Negative values are interpreted as starting from the end.
#[must_use]
pub fn stop(mut self, stop: isize) -> Self {
Self {
command_args: self.command_args.arg(stop).build(),
}
}
}
impl ToArgs for JsonArrIndexOptions {
fn write_args(&self, args: &mut CommandArgs) {
args.arg(&self.command_args);
}
}