use crate::error::Result;
pub trait Transform<In>: Send + Sync + 'static {
type Output: Send + 'static;
fn apply(&self, index: usize, item: In) -> Result<Self::Output>;
fn is_identity(&self) -> bool {
false
}
fn then<T>(self, next: T) -> Then<Self, T>
where
Self: Sized,
T: Transform<Self::Output>,
{
Then {
first: self,
second: next,
}
}
fn apply_batch(&self, indices: &[usize], items: Vec<In>) -> Result<Vec<Self::Output>> {
items
.into_iter()
.zip(indices)
.map(|(item, &index)| self.apply(index, item))
.collect()
}
}
impl<In, Out, F> Transform<In> for F
where
F: Fn(usize, In) -> Result<Out> + Send + Sync + 'static,
Out: Send + 'static,
{
type Output = Out;
fn apply(&self, index: usize, item: In) -> Result<Out> {
self(index, item)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Identity;
impl<In: Send + 'static> Transform<In> for Identity {
type Output = In;
fn apply_batch(&self, _indices: &[usize], items: Vec<In>) -> Result<Vec<In>> {
Ok(items)
}
fn apply(&self, _index: usize, item: In) -> Result<In> {
Ok(item)
}
fn is_identity(&self) -> bool {
true
}
}
#[derive(Clone, Debug)]
pub struct Then<A, B> {
first: A,
second: B,
}
impl<In, A, B> Transform<In> for Then<A, B>
where
A: Transform<In>,
B: Transform<A::Output>,
{
type Output = B::Output;
fn apply(&self, index: usize, item: In) -> Result<Self::Output> {
self.second.apply(index, self.first.apply(index, item)?)
}
}
pub(crate) fn apply_all<In, T: Transform<In>>(
transform: &T,
indices: &[usize],
items: Vec<In>,
) -> Result<Vec<T::Output>> {
if items.len() != indices.len() {
return Err(format!(
"get_batch returned {} items for {} indices",
items.len(),
indices.len()
)
.into());
}
transform.apply_batch(indices, items)
}
#[cfg(test)]
mod tests {
use super::{Identity, Transform, apply_all};
use crate::error::Result;
struct AddOne;
impl Transform<u32> for AddOne {
type Output = u32;
fn apply(&self, _index: usize, item: u32) -> Result<u32> {
item.checked_add(1).ok_or_else(|| "overflow".into())
}
}
#[test]
fn struct_and_closure_chain() {
let t =
AddOne.then(|index: usize, x: u32| -> Result<String> { Ok(format!("{index}<{x}>")) });
assert_eq!(t.apply(3, 1).unwrap(), "3<2>");
assert!(!t.is_identity());
assert!(Transform::<u32>::is_identity(&Identity));
}
#[test]
fn first_error_stops_the_chain() {
let t = AddOne.then(|_: usize, _: u32| -> Result<u32> { panic!("not reached") });
assert_eq!(t.apply(0, u32::MAX).unwrap_err().to_string(), "overflow");
}
#[test]
fn apply_all_pairs_items_with_indices_and_checks_the_count() {
let tag = |index: usize, x: u32| -> Result<(usize, u32)> { Ok((index, x)) };
assert_eq!(
apply_all(&tag, &[5, 2], vec![10, 20]).unwrap(),
vec![(5, 10), (2, 20)]
);
let err = apply_all(&tag, &[5, 2], vec![10]).unwrap_err();
assert_eq!(err.to_string(), "get_batch returned 1 items for 2 indices");
}
#[test]
fn identity_hands_the_batch_back_as_is() {
let items = vec![10u32, 20];
let buffer = items.as_ptr();
let out = apply_all(&Identity, &[5, 2], items).unwrap();
assert_eq!(out, vec![10, 20]);
assert_eq!(out.as_ptr(), buffer);
assert!(apply_all(&Identity, &[5, 2], vec![10u32]).is_err());
}
}