#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DatabaseKind {
#[default]
Relational,
Document,
KeyValue,
Graph,
Search,
TimeSeries,
Columnar,
Object,
Other,
}
impl DatabaseKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Relational => "relational",
Self::Document => "document",
Self::KeyValue => "key-value",
Self::Graph => "graph",
Self::Search => "search",
Self::TimeSeries => "time-series",
Self::Columnar => "columnar",
Self::Object => "object",
Self::Other => "other",
}
}
}
impl fmt::Display for DatabaseKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DatabaseObjectKind {
Database,
Schema,
#[default]
Table,
Collection,
Column,
Index,
Constraint,
Relation,
View,
Migration,
Other,
}
impl DatabaseObjectKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Database => "database",
Self::Schema => "schema",
Self::Table => "table",
Self::Collection => "collection",
Self::Column => "column",
Self::Index => "index",
Self::Constraint => "constraint",
Self::Relation => "relation",
Self::View => "view",
Self::Migration => "migration",
Self::Other => "other",
}
}
}
impl fmt::Display for DatabaseObjectKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
macro_rules! database_label_type {
($type_name:ident, $error_empty:expr) => {
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $type_name(String);
impl $type_name {
pub fn new(input: impl AsRef<str>) -> Result<Self, DatabaseError> {
validate_label(input.as_ref(), $error_empty).map(|value| Self(value.to_owned()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for $type_name {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for $type_name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for $type_name {
type Err = DatabaseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input)
}
}
impl TryFrom<&str> for $type_name {
type Error = DatabaseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
};
}
database_label_type!(DatabaseEngine, DatabaseError::EmptyEngine);
database_label_type!(DatabaseFeature, DatabaseError::EmptyFeature);
database_label_type!(DatabaseCapability, DatabaseError::EmptyCapability);
database_label_type!(DatabaseVersion, DatabaseError::EmptyVersion);
database_label_type!(DatabaseDialect, DatabaseError::EmptyDialect);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DatabaseError {
EmptyEngine,
EmptyFeature,
EmptyCapability,
EmptyVersion,
EmptyDialect,
ControlCharacter,
}
impl fmt::Display for DatabaseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyEngine => formatter.write_str("database engine label cannot be empty"),
Self::EmptyFeature => formatter.write_str("database feature label cannot be empty"),
Self::EmptyCapability => {
formatter.write_str("database capability label cannot be empty")
},
Self::EmptyVersion => formatter.write_str("database version label cannot be empty"),
Self::EmptyDialect => formatter.write_str("database dialect label cannot be empty"),
Self::ControlCharacter => {
formatter.write_str("database label cannot contain control characters")
},
}
}
}
impl Error for DatabaseError {}
pub type DatabaseResult<T> = Result<T, DatabaseError>;
fn validate_label(input: &str, empty_error: DatabaseError) -> Result<&str, DatabaseError> {
if input.chars().any(char::is_control) {
return Err(DatabaseError::ControlCharacter);
}
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(empty_error);
}
Ok(trimmed)
}
#[cfg(test)]
mod tests {
use super::{DatabaseDialect, DatabaseEngine, DatabaseError, DatabaseKind, DatabaseObjectKind};
#[test]
fn formats_database_kinds_and_objects() {
assert_eq!(DatabaseKind::Document.to_string(), "document");
assert_eq!(DatabaseObjectKind::Collection.to_string(), "collection");
}
#[test]
fn validates_labels() -> Result<(), DatabaseError> {
let engine = DatabaseEngine::new(" postgres ")?;
let dialect = DatabaseDialect::new("sql")?;
assert_eq!(engine.as_str(), "postgres");
assert_eq!(dialect.to_string(), "sql");
assert_eq!(DatabaseEngine::new(" "), Err(DatabaseError::EmptyEngine));
Ok(())
}
}