use futures::{Future, FutureExt};
use serde::Deserialize;
use super::{BuilderState, Command, KeyOperation, KeyValue, Output};
use crate::{
keyvalue::{AsyncKeyValue, Value},
Error,
};
#[must_use = "the key-value operation is not performed until query() is called"]
pub struct Builder<'a, KeyValue> {
kv: &'a KeyValue,
namespace: Option<String>,
key: String,
delete: bool,
}
impl<'a, K> Builder<'a, K>
where
K: KeyValue,
{
pub(crate) fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
Self {
key,
kv,
namespace,
delete: false,
}
}
pub fn and_delete(mut self) -> Self {
self.delete = true;
self
}
pub fn into<V: for<'de> Deserialize<'de>>(self) -> Result<Option<V>, Error> {
self.query()?.map(|value| value.deserialize()).transpose()
}
#[allow(clippy::cast_sign_loss)]
pub fn into_u64(self) -> Result<Option<u64>, Error> {
match self.query()? {
Some(value) => value.as_u64().map_or_else(
|| {
Err(Error::Database(String::from(
"value not an u64 or would lose precision when converted to an u64",
)))
},
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_possible_wrap)]
pub fn into_i64(self) -> Result<Option<i64>, Error> {
match self.query()? {
Some(value) => value.as_i64().map_or_else(
|| {
Err(Error::Database(String::from(
"value not an i64 or would lose precision when converted to an i64",
)))
},
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_precision_loss)]
pub fn into_f64(self) -> Result<Option<f64>, Error> {
match self.query()? {
Some(value) => value.as_f64().map_or_else(
|| {
Err(Error::Database(String::from(
"value not an f64 or would lose precision when converted to an f64",
)))
},
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_sign_loss)]
pub fn into_u64_lossy(self, saturating: bool) -> Result<Option<u64>, Error> {
match self.query()? {
Some(value) => value.as_u64_lossy(saturating).map_or_else(
|| Err(Error::Database(String::from("value not numeric"))),
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_possible_wrap)]
pub fn into_i64_lossy(self, saturating: bool) -> Result<Option<i64>, Error> {
match self.query()? {
Some(value) => value.as_i64_lossy(saturating).map_or_else(
|| Err(Error::Database(String::from("value not numeric"))),
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_precision_loss)]
pub fn into_f64_lossy(self) -> Result<Option<f64>, Error> {
match self.query()? {
Some(value) => value.as_f64_lossy().map_or_else(
|| Err(Error::Database(String::from("value not numeric"))),
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
pub fn query(self) -> Result<Option<Value>, Error> {
let Self {
kv,
namespace,
key,
delete,
} = self;
let result = kv.execute_key_operation(KeyOperation {
namespace,
key,
command: Command::Get { delete },
})?;
if let Output::Value(value) = result {
Ok(value)
} else {
unreachable!("Unexpected result from get")
}
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncBuilder<'a, KeyValue> {
state: BuilderState<'a, Options<'a, KeyValue>, Result<Option<Value>, Error>>,
}
struct Options<'a, KeyValue> {
kv: &'a KeyValue,
namespace: Option<String>,
key: String,
delete: bool,
}
impl<'a, K> AsyncBuilder<'a, K>
where
K: AsyncKeyValue,
{
pub(crate) fn new(kv: &'a K, namespace: Option<String>, key: String) -> Self {
Self {
state: BuilderState::Pending(Some(Options {
key,
kv,
namespace,
delete: false,
})),
}
}
fn options(&mut self) -> &mut Options<'a, K> {
if let BuilderState::Pending(Some(options)) = &mut self.state {
options
} else {
unreachable!("Attempted to use after retrieving the result")
}
}
pub fn and_delete(mut self) -> Self {
self.options().delete = true;
self
}
pub async fn into<V: for<'de> Deserialize<'de>>(self) -> Result<Option<V>, Error> {
self.await?.map(|value| value.deserialize()).transpose()
}
#[allow(clippy::cast_sign_loss)]
pub async fn into_u64(self) -> Result<Option<u64>, Error> {
match self.await? {
Some(value) => value.as_u64().map_or_else(
|| {
Err(Error::Database(String::from(
"value not an u64 or would lose precision when converted to an u64",
)))
},
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_possible_wrap)]
pub async fn into_i64(self) -> Result<Option<i64>, Error> {
match self.await? {
Some(value) => value.as_i64().map_or_else(
|| {
Err(Error::Database(String::from(
"value not an i64 or would lose precision when converted to an i64",
)))
},
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_precision_loss)]
pub async fn into_f64(self) -> Result<Option<f64>, Error> {
match self.await? {
Some(value) => value.as_f64().map_or_else(
|| {
Err(Error::Database(String::from(
"value not an f64 or would lose precision when converted to an f64",
)))
},
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_sign_loss)]
pub async fn into_u64_lossy(self, saturating: bool) -> Result<Option<u64>, Error> {
match self.await? {
Some(value) => value.as_u64_lossy(saturating).map_or_else(
|| Err(Error::Database(String::from("value not numeric"))),
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_possible_wrap)]
pub async fn into_i64_lossy(self, saturating: bool) -> Result<Option<i64>, Error> {
match self.await? {
Some(value) => value.as_i64_lossy(saturating).map_or_else(
|| Err(Error::Database(String::from("value not numeric"))),
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
#[allow(clippy::cast_precision_loss)]
pub async fn into_f64_lossy(self) -> Result<Option<f64>, Error> {
match self.await? {
Some(value) => value.as_f64_lossy().map_or_else(
|| Err(Error::Database(String::from("value not numeric"))),
|value| Ok(Some(value)),
),
None => Ok(None),
}
}
}
impl<'a, K> Future for AsyncBuilder<'a, K>
where
K: AsyncKeyValue,
{
type Output = Result<Option<Value>, Error>;
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
match &mut self.state {
BuilderState::Executing(future) => future.as_mut().poll(cx),
BuilderState::Pending(builder) => {
let Options {
kv,
namespace,
key,
delete,
} = builder.take().expect("expected builder to have options");
let future = async move {
let result = kv
.execute_key_operation(KeyOperation {
namespace,
key,
command: Command::Get { delete },
})
.await?;
if let Output::Value(value) = result {
Ok(value)
} else {
unreachable!("Unexpected result from get")
}
}
.boxed();
self.state = BuilderState::Executing(future);
self.poll(cx)
}
}
}
}