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
use super::{utils, Command, CommandError};
use crate::codec::{BulkString, Request, Response};

/// GET redis command
pub fn Get<T>(key: T) -> utils::BulkOutputCommand
where
    BulkString: From<T>,
{
    utils::BulkOutputCommand(Request::Array(vec![
        Request::from_static("GET"),
        Request::BulkString(key.into()),
    ]))
}

/// SET redis command
///
/// Set key to hold the string value. Command returns true if value is set
/// otherwise it returns false
pub fn Set<T, V>(key: T, value: V) -> SetCommand
where
    BulkString: From<T> + From<V>,
{
    SetCommand {
        req: vec![
            Request::from_bstatic(b"SET"),
            Request::BulkString(key.into()),
            Request::BulkString(value.into()),
        ],
        expire: Expire::None,
        keepttl: false,
        exists: None,
    }
}

enum Expire {
    None,
    Ex(Request),
    Px(Request),
}

pub struct SetCommand {
    req: Vec<Request>,
    expire: Expire,
    keepttl: bool,
    exists: Option<bool>,
}

impl SetCommand {
    /// Set the specified expire time, in seconds.
    pub fn expire_secs(mut self, secs: i64) -> Self {
        self.expire = Expire::Ex(Request::BulkInteger(secs));
        self
    }

    /// Set the specified expire time, in milliseconds.
    pub fn expire_millis(mut self, secs: i64) -> Self {
        self.expire = Expire::Px(Request::BulkInteger(secs));
        self
    }

    /// Only set the key if it already exist.
    pub fn if_exists(mut self) -> Self {
        self.exists = Some(true);
        self
    }

    /// Only set the key if it does not already exist.
    pub fn if_not_exists(mut self) -> Self {
        self.exists = Some(false);
        self
    }

    /// Retain the time to live associated with the key.
    pub fn keepttl(mut self) -> Self {
        self.keepttl = true;
        self
    }
}

impl Command for SetCommand {
    type Output = bool;

    fn to_request(mut self) -> Request {
        // EX|PX
        match self.expire {
            Expire::None => (),
            Expire::Ex(r) => {
                self.req.push(Request::from_bstatic(b"EX"));
                self.req.push(r);
            }
            Expire::Px(r) => {
                self.req.push(Request::from_bstatic(b"PX"));
                self.req.push(r);
            }
        }

        // NX|XX
        if let Some(exists) = self.exists {
            self.req.push(if exists {
                Request::from_bstatic(b"XX")
            } else {
                Request::from_bstatic(b"NX")
            });
        }

        // KEEPTTL
        if self.keepttl {
            self.req.push(Request::from_bstatic(b"KEEPTTL"))
        }

        Request::Array(self.req)
    }

    fn to_output(val: Response) -> Result<Self::Output, CommandError> {
        match val {
            Response::Nil => Ok(false),
            Response::String(string) => match string.as_ref() {
                "OK" => Ok(true),
                _ => Err(CommandError::Output(
                    "Unexpected value within String",
                    Response::String(string),
                )),
            },
            _ => Err(CommandError::Output("Unexpected value", val)),
        }
    }
}

/// INCRBY redis command
///
/// Increments the number stored at `key` by `increment`.
pub fn IncrBy<T, I>(key: T, increment: I) -> utils::IntOutputCommand
where
    BulkString: From<T>,
    i64: From<I>,
{
    utils::IntOutputCommand(Request::Array(vec![
        Request::from_static("INCRBY"),
        Request::BulkString(key.into()),
        Request::BulkString(i64::from(increment).to_string().into()),
    ]))
}