use crate::LixError;
pub(crate) trait ExactValue<K> {
fn matches_exact_key(&self, key: &K) -> bool;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ExactBatch<'keys, K, V> {
requested: &'keys [K],
values: Vec<Option<V>>,
}
impl<'keys, K, V> ExactBatch<'keys, K, V>
where
V: ExactValue<K>,
{
pub(crate) fn try_new(
context: &str,
requested: &'keys [K],
values: Vec<Option<V>>,
) -> Result<Self, LixError> {
if values.len() != requested.len() {
return Err(LixError::new(
LixError::CODE_INTERNAL_ERROR,
format!(
"{context} exact read returned {} values for {} requested keys",
values.len(),
requested.len()
),
));
}
if let Some(index) = requested.iter().zip(&values).position(|(key, value)| {
value
.as_ref()
.is_some_and(|value| !value.matches_exact_key(key))
}) {
return Err(LixError::new(
LixError::CODE_INTERNAL_ERROR,
format!(
"{context} exact read returned a value whose identity does not match requested key at slot {index}"
),
));
}
Ok(Self { requested, values })
}
pub(crate) fn len(&self) -> usize {
self.requested.len()
}
pub(crate) fn iter(&self) -> impl ExactSizeIterator<Item = (&K, Option<&V>)> {
self.requested
.iter()
.zip(&self.values)
.map(|(key, value)| (key, value.as_ref()))
}
}
impl<'keys, K, V> IntoIterator for ExactBatch<'keys, K, V> {
type Item = (&'keys K, Option<V>);
type IntoIter = std::iter::Zip<std::slice::Iter<'keys, K>, std::vec::IntoIter<Option<V>>>;
fn into_iter(self) -> Self::IntoIter {
self.requested.iter().zip(self.values)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, Eq, PartialEq)]
struct Value(u32);
impl ExactValue<u32> for Value {
fn matches_exact_key(&self, key: &u32) -> bool {
self.0 == *key
}
}
#[test]
fn rejects_short_long_and_mismatched_results() {
let keys = [1, 2];
for values in [vec![Some(Value(1))], vec![Some(Value(1)), None, None]] {
assert!(ExactBatch::try_new("test", &keys, values).is_err());
}
let error = ExactBatch::try_new("test", &keys, vec![Some(Value(2)), Some(Value(2))])
.expect_err("mismatched identity must fail");
assert!(
error
.to_string()
.contains("does not match requested key at slot 0")
);
}
#[test]
fn preserves_duplicate_missing_and_requested_order() {
let keys = [2, 1, 2, 3];
let batch = ExactBatch::try_new(
"test",
&keys,
vec![Some(Value(2)), Some(Value(1)), Some(Value(2)), None],
)
.expect("valid exact batch");
assert_eq!(
batch
.into_iter()
.map(|(key, value)| (*key, value.map(|value| value.0)))
.collect::<Vec<_>>(),
[(2, Some(2)), (1, Some(1)), (2, Some(2)), (3, None)]
);
}
}