1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
7use std::error::Error;
8
9use use_db_name::{ColumnName, TableName};
10
11#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub struct ColumnRef {
14 table: Option<TableName>,
15 column: ColumnName,
16}
17
18impl ColumnRef {
19 #[must_use]
21 pub const fn new(column: ColumnName) -> Self {
22 Self {
23 table: None,
24 column,
25 }
26 }
27
28 #[must_use]
30 pub const fn qualified(table: TableName, column: ColumnName) -> Self {
31 Self {
32 table: Some(table),
33 column,
34 }
35 }
36
37 #[must_use]
39 pub const fn table(&self) -> Option<&TableName> {
40 self.table.as_ref()
41 }
42
43 #[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 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 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
85pub enum Nullability {
86 Nullable,
88 #[default]
90 NotNull,
91 Unknown,
93}
94
95#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
97pub struct ColumnOrdinal(u32);
98
99impl ColumnOrdinal {
100 #[must_use]
102 pub const fn new(value: u32) -> Option<Self> {
103 if value == 0 { None } else { Some(Self(value)) }
104 }
105
106 #[must_use]
108 pub const fn value(self) -> u32 {
109 self.0
110 }
111}
112
113#[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 #[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 #[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 #[must_use]
145 pub fn with_default(mut self, default: ColumnDefault) -> Self {
146 self.default = Some(default);
147 self
148 }
149
150 #[must_use]
152 pub const fn with_nullability(mut self, nullability: Nullability) -> Self {
153 self.nullability = nullability;
154 self
155 }
156
157 #[must_use]
159 pub const fn with_ordinal(mut self, ordinal: ColumnOrdinal) -> Self {
160 self.ordinal = Some(ordinal);
161 self
162 }
163
164 #[must_use]
166 pub const fn reference(&self) -> &ColumnRef {
167 &self.reference
168 }
169
170 #[must_use]
172 pub const fn type_label(&self) -> Option<&ColumnTypeLabel> {
173 self.type_label.as_ref()
174 }
175
176 #[must_use]
178 pub const fn nullability(&self) -> Nullability {
179 self.nullability
180 }
181}
182
183#[derive(Clone, Copy, Debug, Eq, PartialEq)]
185pub enum ColumnError {
186 EmptyTypeLabel,
188 EmptyDefault,
190 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}