1
use futures::{Future, FutureExt};
2
use serde::Deserialize;
3

            
4
use super::{BuilderState, Command, KeyOperation, KeyValue, Output};
5
use crate::{
6
    keyvalue::{AsyncKeyValue, Value},
7
    Error,
8
};
9

            
10
/// Builder for a [`Command::Get`] key-value operation.
11
#[must_use = "the key-value operation is not performed until query() is called"]
12
pub struct Builder<'a, KeyValue> {
13
    kv: &'a KeyValue,
14
    namespace: Option<String>,
15
    key: String,
16
    delete: bool,
17
}
18
impl<'a, K> Builder<'a, K>
19
where
20
    K: KeyValue,
21
{
22
133
    pub(crate) fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
23
133
        Self {
24
133
            key,
25
133
            kv,
26
133
            namespace,
27
133
            delete: false,
28
133
        }
29
133
    }
30

            
31
    /// Delete the key after retrieving the value.
32
6
    pub fn and_delete(mut self) -> Self {
33
6
        self.delete = true;
34
6
        self
35
6
    }
36

            
37
    /// Deserializes the [`Value`] before returning. If the value is a
38
    /// [`Numeric`](crate::keyvalue::Numeric), an error will be returned.
39
    pub fn into<V: for<'de> Deserialize<'de>>(self) -> Result<Option<V>, Error> {
40
31
        self.query()?.map(|value| value.deserialize()).transpose()
41
31
    }
42

            
43
    /// Converts the [`Value`] to an `u64` before returning. If the value is not
44
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `u64`
45
    /// cannot be done without losing data, an error will be returned.
46
    #[allow(clippy::cast_sign_loss)]
47
    pub fn into_u64(self) -> Result<Option<u64>, Error> {
48
27
        match self.query()? {
49
27
            Some(value) => value.as_u64().map_or_else(
50
27
                || {
51
9
                    Err(Error::Database(String::from(
52
9
                        "value not an u64 or would lose precision when converted to an u64",
53
9
                    )))
54
27
                },
55
27
                |value| Ok(Some(value)),
56
27
            ),
57
            None => Ok(None),
58
        }
59
27
    }
60

            
61
    /// Converts the [`Value`] to an `i64` before returning. If the value is not
62
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `i64`
63
    /// cannot be done without losing data, an error will be returned.
64
    #[allow(clippy::cast_possible_wrap)]
65
    pub fn into_i64(self) -> Result<Option<i64>, Error> {
66
12
        match self.query()? {
67
12
            Some(value) => value.as_i64().map_or_else(
68
12
                || {
69
9
                    Err(Error::Database(String::from(
70
9
                        "value not an i64 or would lose precision when converted to an i64",
71
9
                    )))
72
12
                },
73
12
                |value| Ok(Some(value)),
74
12
            ),
75
            None => Ok(None),
76
        }
77
12
    }
78

            
79
    /// Converts the [`Value`] to an `f64` before returning. If the value is not
80
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `f64`
81
    /// cannot be done without losing data, an error will be returned.
82
    #[allow(clippy::cast_precision_loss)]
83
    pub fn into_f64(self) -> Result<Option<f64>, Error> {
84
18
        match self.query()? {
85
18
            Some(value) => value.as_f64().map_or_else(
86
18
                || {
87
6
                    Err(Error::Database(String::from(
88
6
                        "value not an f64 or would lose precision when converted to an f64",
89
6
                    )))
90
18
                },
91
18
                |value| Ok(Some(value)),
92
18
            ),
93
            None => Ok(None),
94
        }
95
18
    }
96

            
97
    /// Converts the [`Value`] to an `u64` before returning. If the value is not
98
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If `saturating` is true, no
99
    /// overflows will be allowed during conversion.
100
    #[allow(clippy::cast_sign_loss)]
101
    pub fn into_u64_lossy(self, saturating: bool) -> Result<Option<u64>, Error> {
102
12
        match self.query()? {
103
12
            Some(value) => value.as_u64_lossy(saturating).map_or_else(
104
12
                || Err(Error::Database(String::from("value not numeric"))),
105
12
                |value| Ok(Some(value)),
106
12
            ),
107
            None => Ok(None),
108
        }
109
12
    }
110

            
111
    /// Converts the [`Value`] to an `i64` before returning. If the value is not
112
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If `saturating` is true, no
113
    /// overflows will be allowed during conversion.
114
    #[allow(clippy::cast_possible_wrap)]
115
    pub fn into_i64_lossy(self, saturating: bool) -> Result<Option<i64>, Error> {
116
12
        match self.query()? {
117
12
            Some(value) => value.as_i64_lossy(saturating).map_or_else(
118
12
                || Err(Error::Database(String::from("value not numeric"))),
119
12
                |value| Ok(Some(value)),
120
12
            ),
121
            None => Ok(None),
122
        }
123
12
    }
124

            
125
    /// Converts the [`Value`] to an `f64` before returning. If the value is not
126
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned.
127
    #[allow(clippy::cast_precision_loss)]
128
    pub fn into_f64_lossy(self) -> Result<Option<f64>, Error> {
129
6
        match self.query()? {
130
6
            Some(value) => value.as_f64_lossy().map_or_else(
131
6
                || Err(Error::Database(String::from("value not numeric"))),
132
6
                |value| Ok(Some(value)),
133
6
            ),
134
            None => Ok(None),
135
        }
136
6
    }
137

            
138
    /// Retrieves the value for the key, using the configured options.
139
133
    pub fn query(self) -> Result<Option<Value>, Error> {
140
133
        let Self {
141
133
            kv,
142
133
            namespace,
143
133
            key,
144
133
            delete,
145
133
        } = self;
146
133
        let result = kv.execute_key_operation(KeyOperation {
147
133
            namespace,
148
133
            key,
149
133
            command: Command::Get { delete },
150
133
        })?;
151
133
        if let Output::Value(value) = result {
152
133
            Ok(value)
153
        } else {
154
            unreachable!("Unexpected result from get")
155
        }
156
133
    }
157
}
158

            
159
/// Builder for a [`Command::Get`] key-value operation. Queries the value when
160
/// awaited.
161
#[must_use = "futures do nothing unless you `.await` or poll them"]
162
pub struct AsyncBuilder<'a, KeyValue> {
163
    state: BuilderState<'a, Options<'a, KeyValue>, Result<Option<Value>, Error>>,
164
}
165

            
166
struct Options<'a, KeyValue> {
167
    kv: &'a KeyValue,
168
    namespace: Option<String>,
169
    key: String,
170
    delete: bool,
171
}
172

            
173
impl<'a, K> AsyncBuilder<'a, K>
174
where
175
    K: AsyncKeyValue,
176
{
177
186
    pub(crate) fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
178
186
        Self {
179
186
            state: BuilderState::Pending(Some(Options {
180
186
                key,
181
186
                kv,
182
186
                namespace,
183
186
                delete: false,
184
186
            })),
185
186
        }
186
186
    }
187

            
188
10
    fn options(&mut self) -> &mut Options<'a, K> {
189
10
        if let BuilderState::Pending(Some(options)) = &mut self.state {
190
10
            options
191
        } else {
192
            unreachable!("Attempted to use after retrieving the result")
193
        }
194
10
    }
195

            
196
    /// Delete the key after retrieving the value.
197
10
    pub fn and_delete(mut self) -> Self {
198
10
        self.options().delete = true;
199
10
        self
200
10
    }
201

            
202
    /// Deserializes the [`Value`] before returning. If the value is a
203
    /// [`Numeric`](crate::keyvalue::Numeric), an error will be returned.
204
31
    pub async fn into<V: for<'de> Deserialize<'de>>(self) -> Result<Option<V>, Error> {
205
32
        self.await?.map(|value| value.deserialize()).transpose()
206
31
    }
207

            
208
    /// Converts the [`Value`] to an `u64` before returning. If the value is not
209
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `u64`
210
    /// cannot be done without losing data, an error will be returned.
211
    #[allow(clippy::cast_sign_loss)]
212
22
    pub async fn into_u64(self) -> Result<Option<u64>, Error> {
213
22
        match self.await? {
214
21
            Some(value) => value.as_u64().map_or_else(
215
21
                || {
216
15
                    Err(Error::Database(String::from(
217
15
                        "value not an u64 or would lose precision when converted to an u64",
218
15
                    )))
219
21
                },
220
21
                |value| Ok(Some(value)),
221
21
            ),
222
1
            None => Ok(None),
223
        }
224
22
    }
225

            
226
    /// Converts the [`Value`] to an `i64` before returning. If the value is not
227
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `i64`
228
    /// cannot be done without losing data, an error will be returned.
229
    #[allow(clippy::cast_possible_wrap)]
230
20
    pub async fn into_i64(self) -> Result<Option<i64>, Error> {
231
20
        match self.await? {
232
20
            Some(value) => value.as_i64().map_or_else(
233
20
                || {
234
15
                    Err(Error::Database(String::from(
235
15
                        "value not an i64 or would lose precision when converted to an i64",
236
15
                    )))
237
20
                },
238
20
                |value| Ok(Some(value)),
239
20
            ),
240
            None => Ok(None),
241
        }
242
20
    }
243

            
244
    /// Converts the [`Value`] to an `f64` before returning. If the value is not
245
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `f64`
246
    /// cannot be done without losing data, an error will be returned.
247
    #[allow(clippy::cast_precision_loss)]
248
30
    pub async fn into_f64(self) -> Result<Option<f64>, Error> {
249
30
        match self.await? {
250
30
            Some(value) => value.as_f64().map_or_else(
251
30
                || {
252
10
                    Err(Error::Database(String::from(
253
10
                        "value not an f64 or would lose precision when converted to an f64",
254
10
                    )))
255
30
                },
256
30
                |value| Ok(Some(value)),
257
30
            ),
258
            None => Ok(None),
259
        }
260
30
    }
261

            
262
    /// Converts the [`Value`] to an `u64` before returning. If the value is not
263
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If `saturating` is true, no
264
    /// overflows will be allowed during conversion.
265
    #[allow(clippy::cast_sign_loss)]
266
20
    pub async fn into_u64_lossy(self, saturating: bool) -> Result<Option<u64>, Error> {
267
20
        match self.await? {
268
20
            Some(value) => value.as_u64_lossy(saturating).map_or_else(
269
20
                || Err(Error::Database(String::from("value not numeric"))),
270
20
                |value| Ok(Some(value)),
271
20
            ),
272
            None => Ok(None),
273
        }
274
20
    }
275

            
276
    /// Converts the [`Value`] to an `i64` before returning. If the value is not
277
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If `saturating` is true, no
278
    /// overflows will be allowed during conversion.
279
    #[allow(clippy::cast_possible_wrap)]
280
20
    pub async fn into_i64_lossy(self, saturating: bool) -> Result<Option<i64>, Error> {
281
20
        match self.await? {
282
20
            Some(value) => value.as_i64_lossy(saturating).map_or_else(
283
20
                || Err(Error::Database(String::from("value not numeric"))),
284
20
                |value| Ok(Some(value)),
285
20
            ),
286
            None => Ok(None),
287
        }
288
20
    }
289

            
290
    /// Converts the [`Value`] to an `f64` before returning. If the value is not
291
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned.
292
    #[allow(clippy::cast_precision_loss)]
293
10
    pub async fn into_f64_lossy(self) -> Result<Option<f64>, Error> {
294
10
        match self.await? {
295
10
            Some(value) => value.as_f64_lossy().map_or_else(
296
10
                || Err(Error::Database(String::from("value not numeric"))),
297
10
                |value| Ok(Some(value)),
298
10
            ),
299
            None => Ok(None),
300
        }
301
10
    }
302
}
303

            
304
impl<'a, K> Future for AsyncBuilder<'a, K>
305
where
306
    K: AsyncKeyValue,
307
{
308
    type Output = Result<Option<Value>, Error>;
309

            
310
559
    fn poll(
311
559
        mut self: std::pin::Pin<&mut Self>,
312
559
        cx: &mut std::task::Context<'_>,
313
559
    ) -> std::task::Poll<Self::Output> {
314
559
        match &mut self.state {
315
373
            BuilderState::Executing(future) => future.as_mut().poll(cx),
316
186
            BuilderState::Pending(builder) => {
317
186
                let Options {
318
186
                    kv,
319
186
                    namespace,
320
186
                    key,
321
186
                    delete,
322
186
                } = builder.take().expect("expected builder to have options");
323
186
                let future = async move {
324
186
                    let result = kv
325
186
                        .execute_key_operation(KeyOperation {
326
186
                            namespace,
327
186
                            key,
328
186
                            command: Command::Get { delete },
329
187
                        })
330
187
                        .await?;
331
186
                    if let Output::Value(value) = result {
332
186
                        Ok(value)
333
                    } else {
334
                        unreachable!("Unexpected result from get")
335
                    }
336
186
                }
337
186
                .boxed();
338
186

            
339
186
                self.state = BuilderState::Executing(future);
340
186
                self.poll(cx)
341
            }
342
        }
343
559
    }
344
}