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
191
    pub(crate) fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
26
191
        Self {
27
191
            state: BuilderState::Pending(Some(Options {
28
191
                key,
29
191
                kv,
30
191
                namespace,
31
191
                delete: false,
32
191
            })),
33
191
        }
34
191
    }
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
31
    pub async fn into<V: for<'de> Deserialize<'de>>(self) -> Result<Option<V>, Error> {
53
31
        self.await?.map(|value| value.deserialize()).transpose()
54
31
    }
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
25
    pub async fn into_u64(self) -> Result<Option<u64>, Error> {
61
25
        match self.await? {
62
24
            Some(value) => value.as_u64().map_or_else(
63
24
                || {
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
24
                },
68
24
                |value| Ok(Some(value)),
69
24
            ),
70
1
            None => Ok(None),
71
        }
72
25
    }
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
461
    fn poll(
159
461
        mut self: std::pin::Pin<&mut Self>,
160
461
        cx: &mut std::task::Context<'_>,
161
461
    ) -> std::task::Poll<Self::Output> {
162
461
        match &mut self.state {
163
270
            BuilderState::Executing(future) => future.as_mut().poll(cx),
164
191
            BuilderState::Pending(builder) => {
165
191
                let Options {
166
191
                    kv,
167
191
                    namespace,
168
191
                    key,
169
191
                    delete,
170
191
                } = builder.take().expect("expected builder to have options");
171
191
                let future = async move {
172
191
                    let result = kv
173
191
                        .execute_key_operation(KeyOperation {
174
191
                            namespace,
175
191
                            key,
176
191
                            command: Command::Get { delete },
177
191
                        })
178
79
                        .await?;
179
191
                    if let Output::Value(value) = result {
180
191
                        Ok(value)
181
                    } else {
182
                        unreachable!("Unexpected result from get")
183
                    }
184
191
                }
185
191
                .boxed();
186
191

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