#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
use std::error::Error;
use use_db_name::{ColumnName, TableName};
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ColumnRef {
table: Option<TableName>,
column: ColumnName,
}
impl ColumnRef {
#[must_use]
pub const fn new(column: ColumnName) -> Self {
Self {
table: None,
column,
}
}
#[must_use]
pub const fn qualified(table: TableName, column: ColumnName) -> Self {
Self {
table: Some(table),
column,
}
}
#[must_use]
pub const fn table(&self) -> Option<&TableName> {
self.table.as_ref()
}
#[must_use]
pub const fn column(&self) -> &ColumnName {
&self.column
}
}
macro_rules! column_text_type {
($type_name:ident, $empty_error: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, ColumnError> {
validate_text(input.as_ref(), $empty_error).map(|value| Self(value.to_owned()))
}
#[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())
}
}
};
}
column_text_type!(ColumnTypeLabel, ColumnError::EmptyTypeLabel);
column_text_type!(ColumnDefault, ColumnError::EmptyDefault);
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Nullability {
Nullable,
#[default]
NotNull,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ColumnOrdinal(u32);
impl ColumnOrdinal {
#[must_use]
pub const fn new(value: u32) -> Option<Self> {
if value == 0 { None } else { Some(Self(value)) }
}
#[must_use]
pub const fn value(self) -> u32 {
self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ColumnMetadata {
reference: ColumnRef,
type_label: Option<ColumnTypeLabel>,
default: Option<ColumnDefault>,
nullability: Nullability,
ordinal: Option<ColumnOrdinal>,
}
impl ColumnMetadata {
#[must_use]
pub const fn new(reference: ColumnRef) -> Self {
Self {
reference,
type_label: None,
default: None,
nullability: Nullability::Unknown,
ordinal: None,
}
}
#[must_use]
pub fn with_type_label(mut self, type_label: ColumnTypeLabel) -> Self {
self.type_label = Some(type_label);
self
}
#[must_use]
pub fn with_default(mut self, default: ColumnDefault) -> Self {
self.default = Some(default);
self
}
#[must_use]
pub const fn with_nullability(mut self, nullability: Nullability) -> Self {
self.nullability = nullability;
self
}
#[must_use]
pub const fn with_ordinal(mut self, ordinal: ColumnOrdinal) -> Self {
self.ordinal = Some(ordinal);
self
}
#[must_use]
pub const fn reference(&self) -> &ColumnRef {
&self.reference
}
#[must_use]
pub const fn type_label(&self) -> Option<&ColumnTypeLabel> {
self.type_label.as_ref()
}
#[must_use]
pub const fn nullability(&self) -> Nullability {
self.nullability
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ColumnError {
EmptyTypeLabel,
EmptyDefault,
ControlCharacter,
}
impl fmt::Display for ColumnError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyTypeLabel => formatter.write_str("column type label cannot be empty"),
Self::EmptyDefault => formatter.write_str("column default label cannot be empty"),
Self::ControlCharacter => {
formatter.write_str("column metadata label cannot contain control characters")
},
}
}
}
impl Error for ColumnError {}
fn validate_text(input: &str, empty_error: ColumnError) -> Result<&str, ColumnError> {
if input.chars().any(char::is_control) {
return Err(ColumnError::ControlCharacter);
}
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(empty_error);
}
Ok(trimmed)
}
#[cfg(test)]
mod tests {
use super::{
ColumnError, ColumnMetadata, ColumnOrdinal, ColumnRef, ColumnTypeLabel, Nullability,
};
use use_db_name::{ColumnName, TableName};
#[test]
fn stores_column_metadata() -> Result<(), Box<dyn std::error::Error>> {
let reference = ColumnRef::qualified(TableName::new("users")?, ColumnName::new("id")?);
let metadata = ColumnMetadata::new(reference)
.with_type_label(ColumnTypeLabel::new("uuid")?)
.with_nullability(Nullability::NotNull)
.with_ordinal(ColumnOrdinal::new(1).expect("nonzero ordinal"));
assert_eq!(
metadata.reference().table().expect("table").as_str(),
"users"
);
assert_eq!(metadata.type_label().expect("type label").as_str(), "uuid");
assert_eq!(metadata.nullability(), Nullability::NotNull);
assert_eq!(ColumnTypeLabel::new(" "), Err(ColumnError::EmptyTypeLabel));
Ok(())
}
}