use crate::config::RoutingMode;
use std::sync::atomic::{AtomicU8, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SessionMode {
PerCommand = 0,
Stateful = 1,
}
impl SessionMode {
#[inline]
#[must_use]
pub const fn is_per_command(self) -> bool {
matches!(self, Self::PerCommand)
}
#[inline]
#[must_use]
pub const fn is_stateful(self) -> bool {
matches!(self, Self::Stateful)
}
#[inline]
const fn to_u8(self) -> u8 {
self as u8
}
#[inline]
const fn from_u8(value: u8) -> Self {
match value {
1 => Self::Stateful,
_ => Self::PerCommand, }
}
}
#[derive(Debug)]
pub struct ModeState {
mode: AtomicU8,
routing_mode: RoutingMode,
}
impl ModeState {
#[inline]
#[must_use]
pub const fn new(initial_mode: SessionMode, routing_mode: RoutingMode) -> Self {
Self {
mode: AtomicU8::new(initial_mode.to_u8()),
routing_mode,
}
}
#[inline]
#[must_use]
pub fn mode(&self) -> SessionMode {
SessionMode::from_u8(self.mode.load(Ordering::Relaxed))
}
#[inline]
#[must_use]
pub const fn routing_mode(&self) -> RoutingMode {
self.routing_mode
}
#[inline]
#[must_use]
pub fn is_per_command(&self) -> bool {
self.mode().is_per_command()
}
#[inline]
#[must_use]
pub fn is_stateful(&self) -> bool {
self.mode().is_stateful()
}
#[inline]
#[must_use]
pub const fn can_switch_mode(&self) -> bool {
matches!(self.routing_mode, RoutingMode::Hybrid)
}
#[inline]
pub fn switch_to_stateful(&self) {
if self.can_switch_mode() {
self.mode
.store(SessionMode::Stateful.to_u8(), Ordering::Relaxed);
}
}
#[inline]
#[must_use]
pub const fn is_per_command_routing(&self) -> bool {
matches!(
self.routing_mode,
RoutingMode::PerCommand | RoutingMode::Hybrid
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_mode_is_per_command() {
assert!(SessionMode::PerCommand.is_per_command());
assert!(!SessionMode::Stateful.is_per_command());
}
#[test]
fn test_session_mode_is_stateful() {
assert!(SessionMode::Stateful.is_stateful());
assert!(!SessionMode::PerCommand.is_stateful());
}
#[test]
fn test_session_mode_roundtrip() {
assert_eq!(
SessionMode::from_u8(SessionMode::PerCommand.to_u8()),
SessionMode::PerCommand
);
assert_eq!(
SessionMode::from_u8(SessionMode::Stateful.to_u8()),
SessionMode::Stateful
);
}
#[test]
fn test_mode_state_new() {
let state = ModeState::new(SessionMode::PerCommand, RoutingMode::PerCommand);
assert_eq!(state.mode(), SessionMode::PerCommand);
assert_eq!(state.routing_mode(), RoutingMode::PerCommand);
}
#[test]
fn test_mode_state_is_per_command() {
let state = ModeState::new(SessionMode::PerCommand, RoutingMode::PerCommand);
assert!(state.is_per_command());
assert!(!state.is_stateful());
}
#[test]
fn test_mode_state_is_stateful() {
let state = ModeState::new(SessionMode::Stateful, RoutingMode::Stateful);
assert!(state.is_stateful());
assert!(!state.is_per_command());
}
#[test]
fn test_can_switch_mode_hybrid() {
let state = ModeState::new(SessionMode::PerCommand, RoutingMode::Hybrid);
assert!(state.can_switch_mode());
}
#[test]
fn test_cannot_switch_mode_stateful() {
let state = ModeState::new(SessionMode::Stateful, RoutingMode::Stateful);
assert!(!state.can_switch_mode());
}
#[test]
fn test_cannot_switch_mode_per_command() {
let state = ModeState::new(SessionMode::PerCommand, RoutingMode::PerCommand);
assert!(!state.can_switch_mode());
}
#[test]
fn test_switch_to_stateful_in_hybrid() {
let state = ModeState::new(SessionMode::PerCommand, RoutingMode::Hybrid);
assert!(state.is_per_command());
state.switch_to_stateful();
assert!(state.is_stateful());
}
#[test]
fn test_switch_to_stateful_noop_in_stateful_mode() {
let state = ModeState::new(SessionMode::Stateful, RoutingMode::Stateful);
assert!(state.is_stateful());
state.switch_to_stateful();
assert!(state.is_stateful());
}
#[test]
fn test_switch_to_stateful_noop_in_per_command_mode() {
let state = ModeState::new(SessionMode::PerCommand, RoutingMode::PerCommand);
assert!(state.is_per_command());
state.switch_to_stateful();
assert!(state.is_per_command());
}
#[test]
fn test_is_per_command_routing() {
let per_cmd = ModeState::new(SessionMode::PerCommand, RoutingMode::PerCommand);
assert!(per_cmd.is_per_command_routing());
let hybrid = ModeState::new(SessionMode::PerCommand, RoutingMode::Hybrid);
assert!(hybrid.is_per_command_routing());
let stateful = ModeState::new(SessionMode::Stateful, RoutingMode::Stateful);
assert!(!stateful.is_per_command_routing());
}
#[test]
fn test_one_way_transition_invariant() {
let state = ModeState::new(SessionMode::PerCommand, RoutingMode::Hybrid);
assert!(state.is_per_command());
state.switch_to_stateful();
assert!(state.is_stateful());
assert!(state.is_stateful());
assert!(!state.is_per_command());
state.switch_to_stateful();
assert!(state.is_stateful());
}
}