#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
use std::error::Error;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TransactionId(String);
impl TransactionId {
pub fn new(input: impl AsRef<str>) -> Result<Self, TransactionError> {
validate_text(input.as_ref()).map(|value| Self(value.to_owned()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TransactionMode {
#[default]
ReadOnly,
ReadWrite,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TransactionState {
#[default]
Started,
Active,
Committed,
RolledBack,
Failed,
Unknown,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TransactionIsolation {
ReadUncommitted,
#[default]
ReadCommitted,
RepeatableRead,
Serializable,
Snapshot,
}
impl TransactionIsolation {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::ReadUncommitted => "read uncommitted",
Self::ReadCommitted => "read committed",
Self::RepeatableRead => "repeatable read",
Self::Serializable => "serializable",
Self::Snapshot => "snapshot",
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TransactionOutcome {
#[default]
Committed,
RolledBack,
Unknown,
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TransactionBoundary {
#[default]
Begin,
Commit,
Rollback,
Savepoint,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TransactionError {
Empty,
ControlCharacter,
}
impl fmt::Display for TransactionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("transaction label cannot be empty"),
Self::ControlCharacter => {
formatter.write_str("transaction label cannot contain control characters")
},
}
}
}
impl Error for TransactionError {}
fn validate_text(input: &str) -> Result<&str, TransactionError> {
if input.chars().any(char::is_control) {
return Err(TransactionError::ControlCharacter);
}
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(TransactionError::Empty);
}
Ok(trimmed)
}
#[cfg(test)]
mod tests {
use super::{TransactionId, TransactionIsolation, TransactionMode, TransactionOutcome};
#[test]
fn stores_transaction_metadata() -> Result<(), Box<dyn std::error::Error>> {
let id = TransactionId::new("tx-1")?;
assert_eq!(id.as_str(), "tx-1");
assert_eq!(TransactionIsolation::Serializable.as_str(), "serializable");
assert_eq!(TransactionMode::default(), TransactionMode::ReadOnly);
assert_eq!(TransactionOutcome::default(), TransactionOutcome::Committed);
Ok(())
}
}