use-db-row 0.1.0

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

//! Row metadata primitives for `RustUse`.

use core::fmt;
use std::error::Error;

/// A row identifier label.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RowId(String);

impl RowId {
    /// Creates a row identifier.
    ///
    /// # Errors
    ///
    /// Returns [`RowError`] when the identifier is empty or contains control characters.
    pub fn new(input: impl AsRef<str>) -> Result<Self, RowError> {
        validate_text(input.as_ref()).map(|value| Self(value.to_owned()))
    }

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

impl fmt::Display for RowId {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

/// A one-based row number.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RowNumber(u64);

impl RowNumber {
    /// Creates a one-based row number.
    #[must_use]
    pub const fn new(value: u64) -> Option<Self> {
        if value == 0 { None } else { Some(Self(value)) }
    }

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

/// A row count.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RowCount(u64);

impl RowCount {
    /// Creates a row count.
    #[must_use]
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

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

/// A count of affected rows.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AffectedRows(u64);

impl AffectedRows {
    /// Creates an affected-row count.
    #[must_use]
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

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

/// Broad row status.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum RowStatus {
    /// Row is active/current.
    #[default]
    Active,
    /// Row is soft-deleted or removed.
    Deleted,
    /// Row is archived.
    Archived,
    /// Status is unknown.
    Unknown,
}

/// Error returned by row primitives.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RowError {
    /// Text was empty.
    Empty,
    /// Text contained a control character.
    ControlCharacter,
}

impl fmt::Display for RowError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Empty => formatter.write_str("row identifier cannot be empty"),
            Self::ControlCharacter => {
                formatter.write_str("row identifier cannot contain control characters")
            },
        }
    }
}

impl Error for RowError {}

fn validate_text(input: &str) -> Result<&str, RowError> {
    if input.chars().any(char::is_control) {
        return Err(RowError::ControlCharacter);
    }
    let trimmed = input.trim();
    if trimmed.is_empty() {
        return Err(RowError::Empty);
    }
    Ok(trimmed)
}

#[cfg(test)]
mod tests {
    use super::{AffectedRows, RowCount, RowError, RowId, RowNumber, RowStatus};

    #[test]
    fn stores_row_primitives() -> Result<(), RowError> {
        let id = RowId::new("row-1")?;
        let number = RowNumber::new(1).expect("nonzero row number");
        let count = RowCount::new(3);
        let affected = AffectedRows::new(2);

        assert_eq!(id.to_string(), "row-1");
        assert_eq!(number.value(), 1);
        assert_eq!(count.value(), 3);
        assert_eq!(affected.value(), 2);
        assert_eq!(RowStatus::Active, RowStatus::default());
        Ok(())
    }
}