1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
7use std::error::Error;
8
9#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct RowId(String);
12
13impl RowId {
14 pub fn new(input: impl AsRef<str>) -> Result<Self, RowError> {
20 validate_text(input.as_ref()).map(|value| Self(value.to_owned()))
21 }
22
23 #[must_use]
25 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28}
29
30impl fmt::Display for RowId {
31 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32 formatter.write_str(self.as_str())
33 }
34}
35
36#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
38pub struct RowNumber(u64);
39
40impl RowNumber {
41 #[must_use]
43 pub const fn new(value: u64) -> Option<Self> {
44 if value == 0 { None } else { Some(Self(value)) }
45 }
46
47 #[must_use]
49 pub const fn value(self) -> u64 {
50 self.0
51 }
52}
53
54#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
56pub struct RowCount(u64);
57
58impl RowCount {
59 #[must_use]
61 pub const fn new(value: u64) -> Self {
62 Self(value)
63 }
64
65 #[must_use]
67 pub const fn value(self) -> u64 {
68 self.0
69 }
70}
71
72#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
74pub struct AffectedRows(u64);
75
76impl AffectedRows {
77 #[must_use]
79 pub const fn new(value: u64) -> Self {
80 Self(value)
81 }
82
83 #[must_use]
85 pub const fn value(self) -> u64 {
86 self.0
87 }
88}
89
90#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
92pub enum RowStatus {
93 #[default]
95 Active,
96 Deleted,
98 Archived,
100 Unknown,
102}
103
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
106pub enum RowError {
107 Empty,
109 ControlCharacter,
111}
112
113impl fmt::Display for RowError {
114 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self {
116 Self::Empty => formatter.write_str("row identifier cannot be empty"),
117 Self::ControlCharacter => {
118 formatter.write_str("row identifier cannot contain control characters")
119 },
120 }
121 }
122}
123
124impl Error for RowError {}
125
126fn validate_text(input: &str) -> Result<&str, RowError> {
127 if input.chars().any(char::is_control) {
128 return Err(RowError::ControlCharacter);
129 }
130 let trimmed = input.trim();
131 if trimmed.is_empty() {
132 return Err(RowError::Empty);
133 }
134 Ok(trimmed)
135}
136
137#[cfg(test)]
138mod tests {
139 use super::{AffectedRows, RowCount, RowError, RowId, RowNumber, RowStatus};
140
141 #[test]
142 fn stores_row_primitives() -> Result<(), RowError> {
143 let id = RowId::new("row-1")?;
144 let number = RowNumber::new(1).expect("nonzero row number");
145 let count = RowCount::new(3);
146 let affected = AffectedRows::new(2);
147
148 assert_eq!(id.to_string(), "row-1");
149 assert_eq!(number.value(), 1);
150 assert_eq!(count.value(), 3);
151 assert_eq!(affected.value(), 2);
152 assert_eq!(RowStatus::Active, RowStatus::default());
153 Ok(())
154 }
155}