use-db-result 0.1.0

Primitive database result set metadata for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

//! Result set and page metadata primitives for `RustUse`.

/// A cursor position label.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CursorPosition(String);

impl CursorPosition {
    /// Creates a cursor position label.
    #[must_use]
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    /// Returns the cursor position label.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// Total result count metadata.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TotalCount(u64);

impl TotalCount {
    /// Creates total-count metadata.
    #[must_use]
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    /// Returns the count value.
    #[must_use]
    pub const fn value(self) -> u64 {
        self.0
    }
}

/// Whether more results are available.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct HasMore(bool);

impl HasMore {
    /// Creates a has-more marker.
    #[must_use]
    pub const fn new(value: bool) -> Self {
        Self(value)
    }

    /// Returns the marker value.
    #[must_use]
    pub const fn value(self) -> bool {
        self.0
    }
}

/// Result set metadata.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ResultSetMetadata {
    total_count: Option<TotalCount>,
    has_more: HasMore,
}

impl ResultSetMetadata {
    /// Creates result set metadata.
    #[must_use]
    pub const fn new(total_count: Option<TotalCount>, has_more: HasMore) -> Self {
        Self {
            total_count,
            has_more,
        }
    }

    /// Returns total-count metadata.
    #[must_use]
    pub const fn total_count(&self) -> Option<TotalCount> {
        self.total_count
    }

    /// Returns has-more metadata.
    #[must_use]
    pub const fn has_more(&self) -> HasMore {
        self.has_more
    }
}

/// Generic result set container.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ResultSet<T> {
    items: Vec<T>,
    metadata: ResultSetMetadata,
}

impl<T> ResultSet<T> {
    /// Creates a result set.
    #[must_use]
    pub const fn new(items: Vec<T>, metadata: ResultSetMetadata) -> Self {
        Self { items, metadata }
    }

    /// Returns result items.
    #[must_use]
    pub fn items(&self) -> &[T] {
        &self.items
    }

    /// Returns result set metadata.
    #[must_use]
    pub const fn metadata(&self) -> &ResultSetMetadata {
        &self.metadata
    }

    /// Returns the item count.
    #[must_use]
    pub fn len(&self) -> usize {
        self.items.len()
    }

    /// Returns whether the result set is empty.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }
}

/// Page metadata.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct PageInfo {
    cursor: Option<CursorPosition>,
    total_count: Option<TotalCount>,
    has_more: HasMore,
}

impl PageInfo {
    /// Creates page metadata.
    #[must_use]
    pub const fn new(
        cursor: Option<CursorPosition>,
        total_count: Option<TotalCount>,
        has_more: HasMore,
    ) -> Self {
        Self {
            cursor,
            total_count,
            has_more,
        }
    }

    /// Returns the optional cursor.
    #[must_use]
    pub const fn cursor(&self) -> Option<&CursorPosition> {
        self.cursor.as_ref()
    }

    /// Returns has-more metadata.
    #[must_use]
    pub const fn has_more(&self) -> HasMore {
        self.has_more
    }

    /// Returns total-count metadata.
    #[must_use]
    pub const fn total_count(&self) -> Option<TotalCount> {
        self.total_count
    }
}

/// Generic result page.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ResultPage<T> {
    items: Vec<T>,
    page_info: PageInfo,
}

impl<T> ResultPage<T> {
    /// Creates a result page.
    #[must_use]
    pub const fn new(items: Vec<T>, page_info: PageInfo) -> Self {
        Self { items, page_info }
    }

    /// Returns page items.
    #[must_use]
    pub fn items(&self) -> &[T] {
        &self.items
    }

    /// Returns page metadata.
    #[must_use]
    pub const fn page_info(&self) -> &PageInfo {
        &self.page_info
    }
}

#[cfg(test)]
mod tests {
    use super::{
        CursorPosition, HasMore, PageInfo, ResultPage, ResultSet, ResultSetMetadata, TotalCount,
    };

    #[test]
    fn stores_result_sets_and_pages() {
        let metadata = ResultSetMetadata::new(Some(TotalCount::new(2)), HasMore::new(false));
        let result_set = ResultSet::new(vec!["a", "b"], metadata);
        let page_info = PageInfo::new(
            Some(CursorPosition::new("next")),
            Some(TotalCount::new(2)),
            HasMore::new(true),
        );
        let page = ResultPage::new(vec![1, 2], page_info);

        assert_eq!(result_set.len(), 2);
        assert!(!result_set.is_empty());
        assert_eq!(
            result_set.metadata().total_count().expect("total").value(),
            2
        );
        assert_eq!(page.page_info().cursor().expect("cursor").as_str(), "next");
        assert!(page.page_info().has_more().value());
    }
}