1use serde::{Deserialize, Serialize};
2use tea_protocol::{RecordId, SessionId, SessionSequence};
3use thiserror::Error;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum SessionStoreErrorCode {
9 SessionNotFound,
11 SessionAlreadyExists,
13 SequenceConflict,
15 InvalidRecord,
17 InvalidReference,
19 UnsupportedSchemaVersion,
21 CorruptionDetected,
23 TransactionFailed,
25 StorageUnavailable,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Error)]
31pub enum SessionReplayError {
32 #[error("session log is empty")]
34 EmptyLog,
35 #[error("session creation record is missing or duplicated")]
37 InvalidCreation,
38 #[error("record belongs to session {actual}, expected {expected}")]
40 SessionMismatch {
41 expected: SessionId,
43 actual: SessionId,
45 },
46 #[error("record sequence is {actual}, expected {expected}")]
48 SequenceMismatch {
49 expected: SessionSequence,
51 actual: SessionSequence,
53 },
54 #[error("session sequence overflow")]
56 SequenceOverflow,
57 #[error("duplicate record ID: {record_id}")]
59 DuplicateRecord {
60 record_id: RecordId,
62 },
63 #[error("duplicate session entity: {entity}")]
65 DuplicateEntity {
66 entity: &'static str,
68 },
69 #[error("invalid session reference: {reference}")]
71 InvalidReference {
72 reference: &'static str,
74 },
75 #[error("invalid session transition: {transition}")]
77 InvalidTransition {
78 transition: &'static str,
80 },
81}
82
83impl SessionReplayError {
84 #[must_use]
86 pub const fn store_code(&self) -> SessionStoreErrorCode {
87 match self {
88 Self::InvalidReference { .. } => SessionStoreErrorCode::InvalidReference,
89 Self::SequenceMismatch { .. }
90 | Self::SequenceOverflow
91 | Self::DuplicateRecord { .. }
92 | Self::DuplicateEntity { .. }
93 | Self::SessionMismatch { .. }
94 | Self::EmptyLog
95 | Self::InvalidCreation => SessionStoreErrorCode::CorruptionDetected,
96 Self::InvalidTransition { .. } => SessionStoreErrorCode::InvalidRecord,
97 }
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Error)]
103#[error("{code:?}: {message}")]
104pub struct SessionStoreError {
105 code: SessionStoreErrorCode,
106 message: String,
107}
108
109impl SessionStoreError {
110 #[must_use]
112 pub fn new(code: SessionStoreErrorCode, message: impl Into<String>) -> Self {
113 let mut message = message.into();
114 if message.len() > 4096 {
115 let boundary = message
116 .char_indices()
117 .map(|(index, _)| index)
118 .take_while(|index| *index <= 4096)
119 .last()
120 .unwrap_or(0);
121 message.truncate(boundary);
122 }
123 Self { code, message }
124 }
125
126 #[must_use]
128 pub const fn code(&self) -> SessionStoreErrorCode {
129 self.code
130 }
131
132 #[must_use]
134 pub fn message(&self) -> &str {
135 &self.message
136 }
137}
138
139impl From<SessionReplayError> for SessionStoreError {
140 fn from(error: SessionReplayError) -> Self {
141 Self::new(error.store_code(), error.to_string())
142 }
143}