rok-core 0.6.0

Core primitives for the rok ecosystem — errors, crypto, i18n, config, DI, and more
Documentation
/// A chainable, Laravel-inspired collection API over `Vec<T>`.
///
/// Each method consumes `self` and returns a new collection (or other value),
/// enabling fluent pipelines.
///
/// # Example
///
/// ```rust,ignore
/// use rok_core::collection::RokCollection;
///
/// let result = RokCollection::from(vec![1, 2, 3, 4, 5])
///     .filter(|n| *n > 2)
///     .map(|n| n * 10)
///     .reduce(0, |acc, n| acc + n);
///
/// assert_eq!(result, 70);
/// ```
pub struct RokCollection<T>(Vec<T>);

// ── API response integration ───────────────────────────────────────────────

#[cfg(feature = "api")]
use crate::api::ApiResponse;

#[cfg(feature = "api")]
impl<T: Serialize> RokCollection<T> {
    /// Convert this collection into a standard `ApiResponse::ok()` envelope.
    pub fn to_response(&self) -> ApiResponse {
        ApiResponse::ok(&self.0)
    }
}

// ── construction ──────────────────────────────────────────────────────────────

impl<T> RokCollection<T> {
    pub fn new() -> Self {
        Self(Vec::new())
    }

    pub fn from_vec(items: Vec<T>) -> Self {
        Self(items)
    }
}

impl<T> Default for RokCollection<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T> From<Vec<T>> for RokCollection<T> {
    fn from(v: Vec<T>) -> Self {
        Self(v)
    }
}

impl<T> From<RokCollection<T>> for Vec<T> {
    fn from(c: RokCollection<T>) -> Self {
        c.0
    }
}

// ── IntoCollection trait ──────────────────────────────────────────────────────

/// Convert any iterable type into a [`RokCollection`].
pub trait IntoCollection<T> {
    fn into_collection(self) -> RokCollection<T>;
}

impl<T> IntoCollection<T> for Vec<T> {
    fn into_collection(self) -> RokCollection<T> {
        RokCollection::from_vec(self)
    }
}

impl<T> IntoCollection<T> for RokCollection<T> {
    fn into_collection(self) -> RokCollection<T> {
        self
    }
}

// ── core methods ──────────────────────────────────────────────────────────────

impl<T> RokCollection<T> {
    /// Return the number of items.
    pub fn count(&self) -> usize {
        self.0.len()
    }

    /// Returns true if the collection is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Returns true if the collection is non-empty.
    pub fn is_not_empty(&self) -> bool {
        !self.0.is_empty()
    }

    /// Returns a reference to the inner vector.
    pub fn items(&self) -> &Vec<T> {
        &self.0
    }

    /// Consume and return the inner vector.
    pub fn to_vec(self) -> Vec<T> {
        self.0
    }

    /// Return the first element, or `None`.
    pub fn first(&self) -> Option<&T> {
        self.0.first()
    }

    /// Return the last element, or `None`.
    pub fn last(&self) -> Option<&T> {
        self.0.last()
    }

    /// Returns true if the collection contains a value matching the predicate.
    pub fn contains<F>(&self, predicate: F) -> bool
    where
        F: Fn(&T) -> bool,
    {
        self.0.iter().any(predicate)
    }

    // ── mapping ──

    /// Transform each element.
    pub fn map<U, F>(self, f: F) -> RokCollection<U>
    where
        F: FnMut(T) -> U,
    {
        RokCollection(self.0.into_iter().map(f).collect())
    }

    /// Transform each element by reference.
    pub fn map_ref<U, F>(&self, f: F) -> RokCollection<U>
    where
        F: FnMut(&T) -> U,
    {
        RokCollection(self.0.iter().map(f).collect())
    }

    // ── filtering ──

    /// Keep only elements matching the predicate.
    pub fn filter<F>(self, predicate: F) -> Self
    where
        F: FnMut(&T) -> bool,
    {
        Self(self.0.into_iter().filter(predicate).collect())
    }

    /// Keep only elements NOT matching the predicate.
    pub fn reject<F>(self, mut predicate: F) -> Self
    where
        F: FnMut(&T) -> bool,
    {
        self.filter(|x| !predicate(x))
    }

    // ── reducing ──

    /// Reduce to a single value.
    pub fn reduce<F>(self, initial: T, f: F) -> T
    where
        F: Fn(T, T) -> T,
    {
        self.0.into_iter().fold(initial, f)
    }

    /// Reduce with a different accumulator type.
    pub fn fold<A, F>(self, initial: A, f: F) -> A
    where
        F: FnMut(A, T) -> A,
    {
        self.0.into_iter().fold(initial, f)
    }

    /// Iterate over elements (consuming).
    pub fn for_each<F>(self, f: F)
    where
        F: FnMut(T),
    {
        self.0.into_iter().for_each(f)
    }

    // ── aggregation ──

    /// Sum of numeric elements.
    pub fn sum(self) -> T
    where
        T: std::ops::Add<Output = T> + Default,
    {
        self.0.into_iter().fold(T::default(), |a, b| a + b)
    }

    /// Average of numeric elements.
    pub fn avg(self) -> f64
    where
        T: Into<f64> + Clone,
    {
        let len = self.0.len();
        if len == 0 {
            return 0.0;
        }
        let sum: f64 = self.0.iter().map(|x| x.clone().into()).sum();
        sum / len as f64
    }

    /// Minimum element.
    pub fn min(self) -> Option<T>
    where
        T: Ord,
    {
        self.0.into_iter().min()
    }

    /// Maximum element.
    pub fn max(self) -> Option<T>
    where
        T: Ord,
    {
        self.0.into_iter().max()
    }

    // ── slicing ──

    /// Take the first `n` elements.
    pub fn take(self, n: usize) -> Self {
        let mut vec = self.0;
        vec.truncate(n);
        Self(vec)
    }

    /// Skip the first `n` elements.
    pub fn skip(self, n: usize) -> Self {
        Self(self.0.into_iter().skip(n).collect())
    }

    /// Return a slice of elements.
    pub fn slice(self, start: usize, length: usize) -> Self {
        Self(self.0.into_iter().skip(start).take(length).collect())
    }

    /// Chunk into collections of at most `size` elements.
    pub fn chunk(self, size: usize) -> RokCollection<Self> {
        let mut chunks = Vec::new();
        let mut current = Vec::new();
        for item in self.0 {
            current.push(item);
            if current.len() >= size {
                chunks.push(Self(std::mem::take(&mut current)));
            }
        }
        if !current.is_empty() {
            chunks.push(Self(current));
        }
        RokCollection(chunks)
    }

    // ── sorting ──

    /// Sort with a comparator.
    pub fn sort_by<F>(mut self, compare: F) -> Self
    where
        F: FnMut(&T, &T) -> std::cmp::Ordering,
    {
        self.0.sort_by(compare);
        self
    }

    /// Sort by a key.
    pub fn sort_by_key<K, F>(mut self, f: F) -> Self
    where
        F: FnMut(&T) -> K,
        K: Ord,
    {
        self.0.sort_by_key(f);
        self
    }

    /// Reverse element order.
    pub fn reverse(mut self) -> Self {
        self.0.reverse();
        self
    }

    // ── grouping ──

    /// Group elements by a key.
    pub fn group_by<K, F>(self, mut f: F) -> RokCollection<(K, Self)>
    where
        K: Eq + std::hash::Hash,
        F: FnMut(&T) -> K,
    {
        let mut map: std::collections::HashMap<K, Vec<T>> = std::collections::HashMap::new();
        for item in self.0 {
            let key = f(&item);
            map.entry(key).or_default().push(item);
        }
        RokCollection(map.into_iter().map(|(k, v)| (k, Self(v))).collect())
    }

    /// Partition into two collections based on a predicate.
    pub fn partition<F>(self, predicate: F) -> (Self, Self)
    where
        F: FnMut(&T) -> bool,
    {
        let (a, b): (Vec<T>, Vec<T>) = self.0.into_iter().partition(predicate);
        (Self(a), Self(b))
    }

    /// Unique elements.
    pub fn unique(self) -> Self
    where
        T: Eq + std::hash::Hash + Clone,
    {
        let mut seen = std::collections::HashSet::new();
        Self(
            self.0
                .into_iter()
                .filter(|x| seen.insert(x.clone()))
                .collect(),
        )
    }

    // ── utility ──

    /// Pipe the collection through a function.
    pub fn pipe<U, F>(self, f: F) -> U
    where
        F: FnOnce(Self) -> U,
    {
        f(self)
    }

    /// Tap into the collection for side effects.
    pub fn tap<F>(self, f: F) -> Self
    where
        F: FnOnce(&Self),
    {
        f(&self);
        self
    }

    /// Join elements into a string with a separator.
    pub fn join(self, sep: &str) -> String
    where
        T: ToString,
    {
        self.0
            .iter()
            .map(|x| x.to_string())
            .collect::<Vec<_>>()
            .join(sep)
    }

    /// Flatten nested collections.
    pub fn flatten<U>(self) -> RokCollection<U>
    where
        T: IntoIterator<Item = U>,
    {
        RokCollection(self.0.into_iter().flat_map(|x| x).collect())
    }
}