use-db-driver 0.1.0

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

//! Driver and backend metadata primitives for `RustUse`.

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

pub use use_db_name::DriverName;

macro_rules! driver_text_type {
    ($type_name:ident) => {
        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
        pub struct $type_name(String);

        impl $type_name {
            /// Creates a driver/backend metadata label.
            ///
            /// # Errors
            ///
            /// Returns [`DriverError`] when the label is empty or contains control characters.
            pub fn new(input: impl AsRef<str>) -> Result<Self, DriverError> {
                validate_text(input.as_ref()).map(|value| Self(value.to_owned()))
            }

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

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

driver_text_type!(DriverVersion);
driver_text_type!(BackendName);
driver_text_type!(BackendFeature);

/// Driver capability metadata.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DriverCapability {
    /// Connection capability.
    #[default]
    Connect,
    /// Transaction capability.
    Transactions,
    /// Pooling capability.
    Pooling,
    /// Migration capability.
    Migrations,
    /// Other or unspecified capability.
    Other,
}

/// Backend kind metadata.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BackendKind {
    /// SQL backend.
    #[default]
    Sql,
    /// Document backend.
    Document,
    /// Key-value backend.
    KeyValue,
    /// Graph backend.
    Graph,
    /// Search backend.
    Search,
    /// Other or unspecified backend.
    Other,
}

/// Error returned by driver/backend metadata constructors.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DriverError {
    /// Label was empty.
    Empty,
    /// Label contained a control character.
    ControlCharacter,
}

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

impl Error for DriverError {}

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

#[cfg(test)]
mod tests {
    use super::{
        BackendFeature, BackendKind, BackendName, DriverCapability, DriverName, DriverVersion,
    };

    #[test]
    fn stores_driver_metadata() -> Result<(), Box<dyn std::error::Error>> {
        let driver = DriverName::new("postgres")?;
        let backend = BackendName::new("postgresql")?;
        let version = DriverVersion::new("1.0")?;
        let feature = BackendFeature::new("json")?;

        assert_eq!(driver.as_str(), "postgres");
        assert_eq!(backend.as_str(), "postgresql");
        assert_eq!(version.as_str(), "1.0");
        assert_eq!(feature.as_str(), "json");
        assert_eq!(DriverCapability::default(), DriverCapability::Connect);
        assert_eq!(BackendKind::default(), BackendKind::Sql);
        Ok(())
    }
}