cts-common 0.34.1-alpha.1

Common types and traits used across the CipherStash ecosystem
Documentation
use either::Either;
use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, collections::HashSet, hash::Hash};

/// A serialization helper for fields that are serialized as a single scalar value an array.
///
/// This type is a simplified equivalent of [`ArrayOrValue`] and should eventually replace it.
/// It will never have an implementation (beyond the derived traits).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub(crate) enum ScalarOrArray<T> {
    Scalar(T),
    Array(Vec<T>),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub(super) struct ArrayOrValue<T>(
    #[serde(bound(
        serialize = "T: Serialize",
        deserialize = "T: Deserialize<'de> + Eq + Hash"
    ))]
    #[serde(with = "either::serde_untagged")]
    pub(super) Either<T, HashSet<T>>,
);

impl<T> ArrayOrValue<T>
where
    T: Eq + std::hash::Hash + Clone + 'static,
{
    /// Creates a new audience with a single value.
    pub(super) fn single(value: T) -> Self {
        Self(Either::Left(value))
    }

    /// Adds an item to the list of items.
    /// Converts a single value into a vector if it is not already one.
    pub(super) fn add(self, value: impl Into<T>) -> Self {
        let value: T = value.into();
        match self.0 {
            Either::Left(existing) => {
                if existing == value {
                    // No change if the value is already present
                    return Self(Either::Left(existing));
                }
                let mut set = HashSet::new();
                set.insert(existing);
                set.insert(value);
                Self(Either::Right(set))
            }
            Either::Right(mut set) => {
                if set.is_empty() {
                    // If the set is empty, we can convert it to a single value
                    return Self(Either::Left(value));
                }
                set.insert(value);
                Self(Either::Right(set))
            }
        }
    }

    pub(super) fn contains<Q>(&self, value: &Q) -> bool
    where
        T: Borrow<Q> + PartialEq<Q>,
        Q: Eq + Hash,
        Q: ?Sized,
    {
        match &self.0 {
            Either::Left(v) => v == value,
            Either::Right(set) => set.contains(value),
        }
    }

    pub(super) fn iter(&self) -> Box<dyn Iterator<Item = T> + '_> {
        match &self.0 {
            Either::Left(value) => Box::new(std::iter::once(value).cloned()),
            Either::Right(set) => Box::new(set.iter().cloned()),
        }
    }

    pub(super) fn len(&self) -> usize {
        match &self.0 {
            Either::Left(_) => 1,
            Either::Right(set) => set.len(),
        }
    }
}

impl<T> PartialEq for ArrayOrValue<T>
where
    T: Eq + Hash + Clone,
{
    fn eq(&self, other: &Self) -> bool {
        match (&self.0, &other.0) {
            (Either::Left(a), Either::Left(b)) => a == b,
            (Either::Right(a), Either::Right(b)) => a == b,
            _ => false,
        }
    }
}

impl<T> FromIterator<T> for ArrayOrValue<T>
where
    T: Eq + std::hash::Hash + Clone,
{
    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
        let mut set = HashSet::new();
        for item in iter {
            set.insert(item);
        }
        if set.len() == 1 {
            Self(Either::Left(set.into_iter().next().unwrap()))
        } else {
            Self(Either::Right(set))
        }
    }
}

impl<T> Default for ArrayOrValue<T> {
    fn default() -> Self {
        Self(Either::Right(HashSet::default()))
    }
}

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

    #[test]
    fn single() {
        let array_or_value = ArrayOrValue::single("value");
        assert_eq!(array_or_value.0, Either::Left("value"));
    }

    #[test]
    fn multiple() {
        let array_or_value = ArrayOrValue::single("value")
            .add("another_value")
            .add("value"); // Adding a duplicate should not change the set

        if let Either::Right(set) = array_or_value.0 {
            assert_eq!(set.len(), 2);
            assert!(set.contains(&"value"));
            assert!(set.contains(&"another_value"));
        } else {
            panic!("Expected Either::Right, got {:?}", array_or_value.0);
        }
    }

    mod a_single {
        use super::*;

        #[test]
        fn contains_a_matching_literal() {
            let array_or_value = ArrayOrValue::single("value");
            assert!(!array_or_value.contains(&"non_existent_value"));
        }

        #[test]
        fn does_not_contains_a_different_literal() {
            let array_or_value = ArrayOrValue::single("value");
            assert!(!array_or_value.contains(&"non_existent_value"));
        }
    }

    mod an_empty_set {
        use super::*;

        #[test]
        fn becomes_single_when_adding() {
            let empty = ArrayOrValue::default();
            let single = ArrayOrValue::single("value");
            let added = empty.add("value");
            assert_eq!(added, single);
        }
    }

    // TODO: mutplei
}