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

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

            
7
/// Executes [`Command::Get`] when awaited. Also offers methods to customize the
8
/// options for the operation.
9
#[must_use = "futures do nothing unless you `.await` or poll them"]
10
pub struct Builder<'a, KeyValue> {
11
    state: BuilderState<'a, Options<'a, KeyValue>, Result<Option<Value>, Error>>,
12
}
13

            
14
struct Options<'a, KeyValue> {
15
    kv: &'a KeyValue,
16
    namespace: Option<String>,
17
    key: String,
18
    delete: bool,
19
}
20

            
21
impl<'a, K> Builder<'a, K>
22
where
23
    K: KeyValue,
24
{
25
215
    pub(crate) fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
26
215
        Self {
27
215
            state: BuilderState::Pending(Some(Options {
28
215
                key,
29
215
                kv,
30
215
                namespace,
31
215
                delete: false,
32
215
            })),
33
215
        }
34
215
    }
35

            
36
10
    fn options(&mut self) -> &mut Options<'a, K> {
37
10
        if let BuilderState::Pending(Some(options)) = &mut self.state {
38
10
            options
39
        } else {
40
            unreachable!("Attempted to use after retrieving the result")
41
        }
42
10
    }
43

            
44
    /// Delete the key after retrieving the value.
45
10
    pub fn and_delete(mut self) -> Self {
46
10
        self.options().delete = true;
47
10
        self
48
10
    }
49

            
50
    /// Deserializes the [`Value`] before returning. If the value is a
51
    /// [`Numeric`](crate::keyvalue::Numeric), an error will be returned.
52
43
    pub async fn into<V: for<'de> Deserialize<'de>>(self) -> Result<Option<V>, Error> {
53
43
        self.await?.map(|value| value.deserialize()).transpose()
54
43
    }
55

            
56
    /// Converts the [`Value`] to an `u64` before returning. If the value is not
57
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `u64`
58
    /// cannot be done without losing data, an error will be returned.
59
    #[allow(clippy::cast_sign_loss)]
60
37
    pub async fn into_u64(self) -> Result<Option<u64>, Error> {
61
37
        match self.await? {
62
36
            Some(value) => value.as_u64().map_or_else(
63
36
                || {
64
15
                    Err(Error::Database(String::from(
65
15
                        "value not an u64 or would lose precision when converted to an u64",
66
15
                    )))
67
36
                },
68
36
                |value| Ok(Some(value)),
69
36
            ),
70
1
            None => Ok(None),
71
        }
72
37
    }
73

            
74
    /// Converts the [`Value`] to an `i64` before returning. If the value is not
75
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `i64`
76
    /// cannot be done without losing data, an error will be returned.
77
    #[allow(clippy::cast_possible_wrap)]
78
20
    pub async fn into_i64(self) -> Result<Option<i64>, Error> {
79
20
        match self.await? {
80
20
            Some(value) => value.as_i64().map_or_else(
81
20
                || {
82
15
                    Err(Error::Database(String::from(
83
15
                        "value not an i64 or would lose precision when converted to an i64",
84
15
                    )))
85
20
                },
86
20
                |value| Ok(Some(value)),
87
20
            ),
88
            None => Ok(None),
89
        }
90
20
    }
91

            
92
    /// Converts the [`Value`] to an `f64` before returning. If the value is not
93
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If the conversion to `f64`
94
    /// cannot be done without losing data, an error will be returned.
95
    #[allow(clippy::cast_precision_loss)]
96
30
    pub async fn into_f64(self) -> Result<Option<f64>, Error> {
97
30
        match self.await? {
98
30
            Some(value) => value.as_f64().map_or_else(
99
30
                || {
100
10
                    Err(Error::Database(String::from(
101
10
                        "value not an f64 or would lose precision when converted to an f64",
102
10
                    )))
103
30
                },
104
30
                |value| Ok(Some(value)),
105
30
            ),
106
            None => Ok(None),
107
        }
108
30
    }
109

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

            
124
    /// Converts the [`Value`] to an `i64` before returning. If the value is not
125
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned. If `saturating` is true, no
126
    /// overflows will be allowed during conversion.
127
    #[allow(clippy::cast_possible_wrap)]
128
20
    pub async fn into_i64_lossy(self, saturating: bool) -> Result<Option<i64>, Error> {
129
20
        match self.await? {
130
20
            Some(value) => value.as_i64_lossy(saturating).map_or_else(
131
20
                || Err(Error::Database(String::from("value not numeric"))),
132
20
                |value| Ok(Some(value)),
133
20
            ),
134
            None => Ok(None),
135
        }
136
20
    }
137

            
138
    /// Converts the [`Value`] to an `f64` before returning. If the value is not
139
    /// a [`Numeric`](crate::keyvalue::Numeric), an error will be returned.
140
    #[allow(clippy::cast_precision_loss)]
141
10
    pub async fn into_f64_lossy(self) -> Result<Option<f64>, Error> {
142
10
        match self.await? {
143
10
            Some(value) => value.as_f64_lossy().map_or_else(
144
10
                || Err(Error::Database(String::from("value not numeric"))),
145
10
                |value| Ok(Some(value)),
146
10
            ),
147
            None => Ok(None),
148
        }
149
10
    }
150
}
151

            
152
impl<'a, K> Future for Builder<'a, K>
153
where
154
    K: KeyValue,
155
{
156
    type Output = Result<Option<Value>, Error>;
157

            
158
506
    fn poll(
159
506
        mut self: std::pin::Pin<&mut Self>,
160
506
        cx: &mut std::task::Context<'_>,
161
506
    ) -> std::task::Poll<Self::Output> {
162
506
        match &mut self.state {
163
291
            BuilderState::Executing(future) => future.as_mut().poll(cx),
164
215
            BuilderState::Pending(builder) => {
165
215
                let Options {
166
215
                    kv,
167
215
                    namespace,
168
215
                    key,
169
215
                    delete,
170
215
                } = builder.take().expect("expected builder to have options");
171
215
                let future = async move {
172
215
                    let result = kv
173
215
                        .execute_key_operation(KeyOperation {
174
215
                            namespace,
175
215
                            key,
176
215
                            command: Command::Get { delete },
177
215
                        })
178
76
                        .await?;
179
215
                    if let Output::Value(value) = result {
180
215
                        Ok(value)
181
                    } else {
182
                        unreachable!("Unexpected result from get")
183
                    }
184
215
                }
185
215
                .boxed();
186
215

            
187
215
                self.state = BuilderState::Executing(future);
188
215
                self.poll(cx)
189
            }
190
        }
191
506
    }
192
}