pipecircuit 0.0.24

Correct. Robust. Fast. Portable. Pick four.
Documentation
use super::super::dense_key_index::{DenseKeyIndex, required_buckets};
use super::super::inconsistency::invariant_violation;
use super::super::{ColumnLease, IndexKey};
use super::{UniqueIndex, UniqueIndexBacking, UniqueIndexInconsistency};

pub type CallerUniqueIndex<'storage, Key> =
    UniqueIndex<Key, CallerUniqueIndexStorage<'storage, Key>>;

#[derive(Debug)]
pub struct CallerUniqueIndexStorage<'storage, Key: IndexKey> {
    pub keys: ColumnLease<'storage, Key>,
    pub buckets: &'storage mut [u32],
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UniqueIndexStorageError {
    CapacityOverflow { maximum_keys: usize },
    KeysNotEmpty { keys: usize },
    KeyCapacityInsufficient { required: usize, available: usize },
    BucketCapacityInsufficient { required: usize, available: usize },
}

#[derive(Debug)]
pub struct UniqueIndexStorageRefusal<'storage, Key: IndexKey> {
    pub storage: CallerUniqueIndexStorage<'storage, Key>,
    pub reason: UniqueIndexStorageError,
}

impl<Key: IndexKey> UniqueIndex<Key> {
    pub fn required_buckets(maximum_keys: usize) -> Result<usize, UniqueIndexStorageError> {
        if u32::try_from(maximum_keys).is_err() {
            return Err(UniqueIndexStorageError::CapacityOverflow { maximum_keys });
        }
        required_buckets(maximum_keys)
            .ok_or(UniqueIndexStorageError::CapacityOverflow { maximum_keys })
    }

    pub fn try_from_storage(
        maximum_keys: usize,
        storage: CallerUniqueIndexStorage<'_, Key>,
    ) -> Result<CallerUniqueIndex<'_, Key>, UniqueIndexStorageRefusal<'_, Key>> {
        let validation = (|| {
            let buckets = Self::required_buckets(maximum_keys)?;
            if !storage.keys.is_empty() {
                return Err(UniqueIndexStorageError::KeysNotEmpty {
                    keys: storage.keys.len(),
                });
            }
            if storage.keys.capacity() < maximum_keys {
                return Err(UniqueIndexStorageError::KeyCapacityInsufficient {
                    required: maximum_keys,
                    available: storage.keys.capacity(),
                });
            }
            if storage.buckets.len() < buckets {
                return Err(UniqueIndexStorageError::BucketCapacityInsufficient {
                    required: buckets,
                    available: storage.buckets.len(),
                });
            }
            Ok(())
        })();
        if let Err(reason) = validation {
            return Err(UniqueIndexStorageRefusal { storage, reason });
        }
        Ok(UniqueIndex {
            maximum_keys,
            keys: storage.keys,
            key_index: DenseKeyIndex::from_buckets(storage.buckets),
        })
    }
}

impl<'storage, Key: IndexKey> UniqueIndexBacking<Key> for CallerUniqueIndexStorage<'storage, Key> {
    type Keys = ColumnLease<'storage, Key>;
    type Buckets = &'storage mut [u32];

    fn keys(keys: &Self::Keys) -> &[Key] {
        keys.as_slice()
    }

    fn keys_mut(keys: &mut Self::Keys) -> &mut [Key] {
        keys.as_mut_slice()
    }

    fn append(keys: &mut Self::Keys, key: Key) {
        if keys.push(key).is_err() {
            invariant_violation(UniqueIndexInconsistency::KeyStorageFull {
                capacity: keys.capacity(),
            });
        }
    }

    fn remove(keys: &mut Self::Keys, row: usize) {
        if keys.swap_remove(row).is_none() {
            invariant_violation(UniqueIndexInconsistency::KeyUnavailable { row });
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::mem::MaybeUninit;

    #[test]
    #[should_panic(expected = "KeyStorageFull")]
    fn corrupt_capacity_cannot_silently_lose_a_key() {
        let mut storage = [];
        let mut keys = ColumnLease::new(&mut storage);
        CallerUniqueIndexStorage::append(&mut keys, 1_u64);
    }

    #[test]
    #[should_panic(expected = "KeyUnavailable")]
    fn corrupt_row_cannot_silently_skip_removal() {
        let mut storage = [MaybeUninit::<u64>::uninit(); 1];
        let mut keys = ColumnLease::new(&mut storage);
        CallerUniqueIndexStorage::remove(&mut keys, 0);
    }
}