Skip to main content

use_db_column/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Column metadata primitives for `RustUse`.
5
6use core::fmt;
7use std::error::Error;
8
9use use_db_name::{ColumnName, TableName};
10
11/// A column reference with optional table qualification.
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub struct ColumnRef {
14    table: Option<TableName>,
15    column: ColumnName,
16}
17
18impl ColumnRef {
19    /// Creates an unqualified column reference.
20    #[must_use]
21    pub const fn new(column: ColumnName) -> Self {
22        Self {
23            table: None,
24            column,
25        }
26    }
27
28    /// Creates a table-qualified column reference.
29    #[must_use]
30    pub const fn qualified(table: TableName, column: ColumnName) -> Self {
31        Self {
32            table: Some(table),
33            column,
34        }
35    }
36
37    /// Returns the optional table name.
38    #[must_use]
39    pub const fn table(&self) -> Option<&TableName> {
40        self.table.as_ref()
41    }
42
43    /// Returns the column name.
44    #[must_use]
45    pub const fn column(&self) -> &ColumnName {
46        &self.column
47    }
48}
49
50macro_rules! column_text_type {
51    ($type_name:ident, $empty_error:expr) => {
52        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
53        pub struct $type_name(String);
54
55        impl $type_name {
56            /// Creates a column metadata label.
57            ///
58            /// # Errors
59            ///
60            /// Returns [`ColumnError`] when the label is empty or contains control characters.
61            pub fn new(input: impl AsRef<str>) -> Result<Self, ColumnError> {
62                validate_text(input.as_ref(), $empty_error).map(|value| Self(value.to_owned()))
63            }
64
65            /// Returns the stored label.
66            #[must_use]
67            pub fn as_str(&self) -> &str {
68                &self.0
69            }
70        }
71
72        impl fmt::Display for $type_name {
73            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
74                formatter.write_str(self.as_str())
75            }
76        }
77    };
78}
79
80column_text_type!(ColumnTypeLabel, ColumnError::EmptyTypeLabel);
81column_text_type!(ColumnDefault, ColumnError::EmptyDefault);
82
83/// Column nullability metadata.
84#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
85pub enum Nullability {
86    /// Null values are allowed.
87    Nullable,
88    /// Null values are rejected.
89    #[default]
90    NotNull,
91    /// Nullability is unknown or unspecified.
92    Unknown,
93}
94
95/// A one-based column ordinal.
96#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
97pub struct ColumnOrdinal(u32);
98
99impl ColumnOrdinal {
100    /// Creates a one-based column ordinal.
101    #[must_use]
102    pub const fn new(value: u32) -> Option<Self> {
103        if value == 0 { None } else { Some(Self(value)) }
104    }
105
106    /// Returns the ordinal value.
107    #[must_use]
108    pub const fn value(self) -> u32 {
109        self.0
110    }
111}
112
113/// Column metadata.
114#[derive(Clone, Debug, Eq, PartialEq)]
115pub struct ColumnMetadata {
116    reference: ColumnRef,
117    type_label: Option<ColumnTypeLabel>,
118    default: Option<ColumnDefault>,
119    nullability: Nullability,
120    ordinal: Option<ColumnOrdinal>,
121}
122
123impl ColumnMetadata {
124    /// Creates column metadata.
125    #[must_use]
126    pub const fn new(reference: ColumnRef) -> Self {
127        Self {
128            reference,
129            type_label: None,
130            default: None,
131            nullability: Nullability::Unknown,
132            ordinal: None,
133        }
134    }
135
136    /// Sets the type label.
137    #[must_use]
138    pub fn with_type_label(mut self, type_label: ColumnTypeLabel) -> Self {
139        self.type_label = Some(type_label);
140        self
141    }
142
143    /// Sets the default label.
144    #[must_use]
145    pub fn with_default(mut self, default: ColumnDefault) -> Self {
146        self.default = Some(default);
147        self
148    }
149
150    /// Sets nullability metadata.
151    #[must_use]
152    pub const fn with_nullability(mut self, nullability: Nullability) -> Self {
153        self.nullability = nullability;
154        self
155    }
156
157    /// Sets the column ordinal.
158    #[must_use]
159    pub const fn with_ordinal(mut self, ordinal: ColumnOrdinal) -> Self {
160        self.ordinal = Some(ordinal);
161        self
162    }
163
164    /// Returns the column reference.
165    #[must_use]
166    pub const fn reference(&self) -> &ColumnRef {
167        &self.reference
168    }
169
170    /// Returns the type label.
171    #[must_use]
172    pub const fn type_label(&self) -> Option<&ColumnTypeLabel> {
173        self.type_label.as_ref()
174    }
175
176    /// Returns nullability metadata.
177    #[must_use]
178    pub const fn nullability(&self) -> Nullability {
179        self.nullability
180    }
181}
182
183/// Error returned by column metadata constructors.
184#[derive(Clone, Copy, Debug, Eq, PartialEq)]
185pub enum ColumnError {
186    /// The type label was empty.
187    EmptyTypeLabel,
188    /// The default label was empty.
189    EmptyDefault,
190    /// Text contained a control character.
191    ControlCharacter,
192}
193
194impl fmt::Display for ColumnError {
195    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
196        match self {
197            Self::EmptyTypeLabel => formatter.write_str("column type label cannot be empty"),
198            Self::EmptyDefault => formatter.write_str("column default label cannot be empty"),
199            Self::ControlCharacter => {
200                formatter.write_str("column metadata label cannot contain control characters")
201            },
202        }
203    }
204}
205
206impl Error for ColumnError {}
207
208fn validate_text(input: &str, empty_error: ColumnError) -> Result<&str, ColumnError> {
209    if input.chars().any(char::is_control) {
210        return Err(ColumnError::ControlCharacter);
211    }
212    let trimmed = input.trim();
213    if trimmed.is_empty() {
214        return Err(empty_error);
215    }
216    Ok(trimmed)
217}
218
219#[cfg(test)]
220mod tests {
221    use super::{
222        ColumnError, ColumnMetadata, ColumnOrdinal, ColumnRef, ColumnTypeLabel, Nullability,
223    };
224    use use_db_name::{ColumnName, TableName};
225
226    #[test]
227    fn stores_column_metadata() -> Result<(), Box<dyn std::error::Error>> {
228        let reference = ColumnRef::qualified(TableName::new("users")?, ColumnName::new("id")?);
229        let metadata = ColumnMetadata::new(reference)
230            .with_type_label(ColumnTypeLabel::new("uuid")?)
231            .with_nullability(Nullability::NotNull)
232            .with_ordinal(ColumnOrdinal::new(1).expect("nonzero ordinal"));
233
234        assert_eq!(
235            metadata.reference().table().expect("table").as_str(),
236            "users"
237        );
238        assert_eq!(metadata.type_label().expect("type label").as_str(), "uuid");
239        assert_eq!(metadata.nullability(), Nullability::NotNull);
240        assert_eq!(ColumnTypeLabel::new(" "), Err(ColumnError::EmptyTypeLabel));
241        Ok(())
242    }
243}