bonsaidb_core/keyvalue/implementation/
get.rs

1use futures::{Future, FutureExt};
2use serde::Deserialize;
3
4use super::{BuilderState, Command, KeyOperation, KeyValue, Output};
5use crate::keyvalue::{AsyncKeyValue, Value};
6use 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"]
10pub struct Builder<'a, KeyValue> {
11    kv: &'a KeyValue,
12    namespace: Option<String>,
13    key: String,
14    delete: bool,
15}
16impl<'a, K> Builder<'a, K>
17where
18    K: KeyValue,
19{
20    pub(crate) const fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
21        Self {
22            key,
23            kv,
24            namespace,
25            delete: false,
26        }
27    }
28
29    /// Delete the key after retrieving the value.
30    pub const fn and_delete(mut self) -> Self {
31        self.delete = true;
32        self
33    }
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        self.query()?.map(|value| value.deserialize()).transpose()
39    }
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        match self.query()? {
47            Some(value) => value.as_u64().map_or_else(
48                || {
49                    Err(Error::other(
50                        "key-value",
51                        "value not an u64 or would lose precision when converted to an u64",
52                    ))
53                },
54                |value| Ok(Some(value)),
55            ),
56            None => Ok(None),
57        }
58    }
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        match self.query()? {
66            Some(value) => value.as_i64().map_or_else(
67                || {
68                    Err(Error::other(
69                        "key-value",
70                        "value not an i64 or would lose precision when converted to an i64",
71                    ))
72                },
73                |value| Ok(Some(value)),
74            ),
75            None => Ok(None),
76        }
77    }
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        match self.query()? {
85            Some(value) => value.as_f64().map_or_else(
86                || {
87                    Err(Error::other(
88                        "key-value",
89                        "value not an f64 or would lose precision when converted to an f64",
90                    ))
91                },
92                |value| Ok(Some(value)),
93            ),
94            None => Ok(None),
95        }
96    }
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        match self.query()? {
104            Some(value) => value.as_u64_lossy(saturating).map_or_else(
105                || Err(Error::other("key-value", "value not numeric")),
106                |value| Ok(Some(value)),
107            ),
108            None => Ok(None),
109        }
110    }
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        match self.query()? {
118            Some(value) => value.as_i64_lossy(saturating).map_or_else(
119                || Err(Error::other("key-value", "value not numeric")),
120                |value| Ok(Some(value)),
121            ),
122            None => Ok(None),
123        }
124    }
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        match self.query()? {
131            Some(value) => value.as_f64_lossy().map_or_else(
132                || Err(Error::other("key-value", "value not numeric")),
133                |value| Ok(Some(value)),
134            ),
135            None => Ok(None),
136        }
137    }
138
139    /// Retrieves the value for the key, using the configured options.
140    pub fn query(self) -> Result<Option<Value>, Error> {
141        let Self {
142            kv,
143            namespace,
144            key,
145            delete,
146        } = self;
147        let result = kv.execute_key_operation(KeyOperation {
148            namespace,
149            key,
150            command: Command::Get { delete },
151        })?;
152        if let Output::Value(value) = result {
153            Ok(value)
154        } else {
155            unreachable!("Unexpected result from get")
156        }
157    }
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"]
163pub struct AsyncBuilder<'a, KeyValue> {
164    state: BuilderState<'a, Options<'a, KeyValue>, Result<Option<Value>, Error>>,
165}
166
167struct Options<'a, KeyValue> {
168    kv: &'a KeyValue,
169    namespace: Option<String>,
170    key: String,
171    delete: bool,
172}
173
174impl<'a, K> AsyncBuilder<'a, K>
175where
176    K: AsyncKeyValue,
177{
178    pub(crate) const fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
179        Self {
180            state: BuilderState::Pending(Some(Options {
181                key,
182                kv,
183                namespace,
184                delete: false,
185            })),
186        }
187    }
188
189    fn options(&mut self) -> &mut Options<'a, K> {
190        if let BuilderState::Pending(Some(options)) = &mut self.state {
191            options
192        } else {
193            unreachable!("Attempted to use after retrieving the result")
194        }
195    }
196
197    /// Delete the key after retrieving the value.
198    pub fn and_delete(mut self) -> Self {
199        self.options().delete = true;
200        self
201    }
202
203    /// Deserializes the [`Value`] before returning. If the value is a
204    /// [`Numeric`](crate::keyvalue::Numeric), an error will be returned.
205    pub async fn into<V: for<'de> Deserialize<'de>>(self) -> Result<Option<V>, Error> {
206        self.await?.map(|value| value.deserialize()).transpose()
207    }
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    pub async fn into_u64(self) -> Result<Option<u64>, Error> {
214        match self.await? {
215            Some(value) => value.as_u64().map_or_else(
216                || {
217                    Err(Error::other(
218                        "key-value",
219                        "value not an u64 or would lose precision when converted to an u64",
220                    ))
221                },
222                |value| Ok(Some(value)),
223            ),
224            None => Ok(None),
225        }
226    }
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    pub async fn into_i64(self) -> Result<Option<i64>, Error> {
233        match self.await? {
234            Some(value) => value.as_i64().map_or_else(
235                || {
236                    Err(Error::other(
237                        "key-value",
238                        "value not an i64 or would lose precision when converted to an i64",
239                    ))
240                },
241                |value| Ok(Some(value)),
242            ),
243            None => Ok(None),
244        }
245    }
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    pub async fn into_f64(self) -> Result<Option<f64>, Error> {
252        match self.await? {
253            Some(value) => value.as_f64().map_or_else(
254                || {
255                    Err(Error::other(
256                        "key-value",
257                        "value not an f64 or would lose precision when converted to an f64",
258                    ))
259                },
260                |value| Ok(Some(value)),
261            ),
262            None => Ok(None),
263        }
264    }
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    pub async fn into_u64_lossy(self, saturating: bool) -> Result<Option<u64>, Error> {
271        match self.await? {
272            Some(value) => value.as_u64_lossy(saturating).map_or_else(
273                || Err(Error::other("key-value", "value not numeric")),
274                |value| Ok(Some(value)),
275            ),
276            None => Ok(None),
277        }
278    }
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    pub async fn into_i64_lossy(self, saturating: bool) -> Result<Option<i64>, Error> {
285        match self.await? {
286            Some(value) => value.as_i64_lossy(saturating).map_or_else(
287                || Err(Error::other("key-value", "value not numeric")),
288                |value| Ok(Some(value)),
289            ),
290            None => Ok(None),
291        }
292    }
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    pub async fn into_f64_lossy(self) -> Result<Option<f64>, Error> {
298        match self.await? {
299            Some(value) => value.as_f64_lossy().map_or_else(
300                || Err(Error::other("key-value", "value not numeric")),
301                |value| Ok(Some(value)),
302            ),
303            None => Ok(None),
304        }
305    }
306}
307
308impl<'a, K> Future for AsyncBuilder<'a, K>
309where
310    K: AsyncKeyValue,
311{
312    type Output = Result<Option<Value>, Error>;
313
314    fn poll(
315        mut self: std::pin::Pin<&mut Self>,
316        cx: &mut std::task::Context<'_>,
317    ) -> std::task::Poll<Self::Output> {
318        match &mut self.state {
319            BuilderState::Executing(future) => future.as_mut().poll(cx),
320            BuilderState::Pending(builder) => {
321                let Options {
322                    kv,
323                    namespace,
324                    key,
325                    delete,
326                } = builder.take().expect("expected builder to have options");
327                let future = async move {
328                    let result = kv
329                        .execute_key_operation(KeyOperation {
330                            namespace,
331                            key,
332                            command: Command::Get { delete },
333                        })
334                        .await?;
335                    if let Output::Value(value) = result {
336                        Ok(value)
337                    } else {
338                        unreachable!("Unexpected result from get")
339                    }
340                }
341                .boxed();
342
343                self.state = BuilderState::Executing(future);
344                self.poll(cx)
345            }
346        }
347    }
348}