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

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

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

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

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

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

            
60
    /// Converts the [`Value`] to an `i64` before returning. If the value is not
61
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `i64`
62
    /// cannot be done without losing data, an error will be returned.
63
    #[allow(clippy::cast_possible_wrap)]
64
    pub fn into_i64(self) -> Result<Option<i64>, Error> {
65
12
        match self.query()? {
66
12
            Some(value) => value.as_i64().map_or_else(
67
12
                || {
68
9
                    Err(Error::other(
69
9
                        "key-value",
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::other(
88
6
                        "key-value",
89
6
                        "value not an f64 or would lose precision when converted to an f64",
90
6
                    ))
91
18
                },
92
18
                |value| Ok(Some(value)),
93
18
            ),
94
            None => Ok(None),
95
        }
96
18
    }
97

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
308
impl<'a, K> Future for AsyncBuilder<'a, K>
309
where
310
    K: AsyncKeyValue,
311
{
312
    type Output = Result<Option<Value>, Error>;
313

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

            
343
186
                self.state = BuilderState::Executing(future);
344
186
                self.poll(cx)
345
            }
346
        }
347
555
    }
348
}