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 TransactionId(String);
12
13impl TransactionId {
14 pub fn new(input: impl AsRef<str>) -> Result<Self, TransactionError> {
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
30#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
32pub enum TransactionMode {
33 #[default]
35 ReadOnly,
36 ReadWrite,
38}
39
40#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
42pub enum TransactionState {
43 #[default]
45 Started,
46 Active,
48 Committed,
50 RolledBack,
52 Failed,
54 Unknown,
56}
57
58#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
60pub enum TransactionIsolation {
61 ReadUncommitted,
63 #[default]
65 ReadCommitted,
66 RepeatableRead,
68 Serializable,
70 Snapshot,
72}
73
74impl TransactionIsolation {
75 #[must_use]
77 pub const fn as_str(self) -> &'static str {
78 match self {
79 Self::ReadUncommitted => "read uncommitted",
80 Self::ReadCommitted => "read committed",
81 Self::RepeatableRead => "repeatable read",
82 Self::Serializable => "serializable",
83 Self::Snapshot => "snapshot",
84 }
85 }
86}
87
88#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
90pub enum TransactionOutcome {
91 #[default]
93 Committed,
94 RolledBack,
96 Unknown,
98}
99
100#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
102pub enum TransactionBoundary {
103 #[default]
105 Begin,
106 Commit,
108 Rollback,
110 Savepoint,
112}
113
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub enum TransactionError {
117 Empty,
119 ControlCharacter,
121}
122
123impl fmt::Display for TransactionError {
124 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self {
126 Self::Empty => formatter.write_str("transaction label cannot be empty"),
127 Self::ControlCharacter => {
128 formatter.write_str("transaction label cannot contain control characters")
129 },
130 }
131 }
132}
133
134impl Error for TransactionError {}
135
136fn validate_text(input: &str) -> Result<&str, TransactionError> {
137 if input.chars().any(char::is_control) {
138 return Err(TransactionError::ControlCharacter);
139 }
140 let trimmed = input.trim();
141 if trimmed.is_empty() {
142 return Err(TransactionError::Empty);
143 }
144 Ok(trimmed)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::{TransactionId, TransactionIsolation, TransactionMode, TransactionOutcome};
150
151 #[test]
152 fn stores_transaction_metadata() -> Result<(), Box<dyn std::error::Error>> {
153 let id = TransactionId::new("tx-1")?;
154
155 assert_eq!(id.as_str(), "tx-1");
156 assert_eq!(TransactionIsolation::Serializable.as_str(), "serializable");
157 assert_eq!(TransactionMode::default(), TransactionMode::ReadOnly);
158 assert_eq!(TransactionOutcome::default(), TransactionOutcome::Committed);
159 Ok(())
160 }
161}