wowlab_engine_adapter_data/remote/cache/
error.rs1use std::{fmt, sync::Arc};
2
3use wowlab_fs::{
4 error,
5 path::{Path, PathBuf},
6};
7use wowlab_supabase::SupabaseError;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11#[non_exhaustive]
12pub enum CacheOperation {
13 CreateDirectory,
14 ReadEntry,
15 WriteEntry,
16 CommitEntry,
17 DeleteEntry,
18 ClearCategory,
19 ReadPatchVersion,
20 WritePatchVersion,
21 FetchRemote,
22 LoadShared,
23}
24
25impl fmt::Display for CacheOperation {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.write_str(match self {
28 Self::CreateDirectory => "create cache directory",
29 Self::ReadEntry => "read cache entry",
30 Self::WriteEntry => "write cache entry",
31 Self::CommitEntry => "commit cache entry",
32 Self::DeleteEntry => "delete cache entry",
33 Self::ClearCategory => "clear cache category",
34 Self::ReadPatchVersion => "read patch version",
35 Self::WritePatchVersion => "write patch version",
36 Self::FetchRemote => "fetch remote data",
37 Self::LoadShared => "load shared cache value",
38 })
39 }
40}
41
42wowlab_engine_macros::define_error! {
43#[non_exhaustive]
45pub struct CacheError {
46 #[source]
47 kind: CacheErrorKind,
48}
49
50#[derive(Debug, thiserror::Error)]
51enum CacheErrorKind {
52 #[error(
53 "cache {operation} failed{entry}: {source}",
54 entry = CacheEntryLocation::new(entry_key.as_deref())
55 )]
56 Filesystem {
57 operation: CacheOperation,
58 entry_key: Option<String>,
59 #[source]
60 source: error::Error,
61 },
62 #[error(
63 "cache {operation} failed{location}: {source}",
64 location = CacheFailureLocation::new(path, entry_key.as_deref())
65 )]
66 Json {
67 operation: CacheOperation,
68 path: PathBuf,
69 entry_key: Option<String>,
70 #[source]
71 source: serde_json::Error,
72 },
73 #[error("cache {operation} failed for {entry_key}: {source}")]
74 Remote {
75 operation: CacheOperation,
76 entry_key: String,
77 #[source]
78 source: SupabaseError,
79 },
80 #[error("Not found: {resource} with {key}={value}")]
81 MissingRow {
82 resource: String,
83 key: String,
84 value: String,
85 },
86 #[error("cache {operation} failed for {entry_key}: {source}")]
87 Shared {
88 operation: CacheOperation,
89 entry_key: String,
90 #[source]
91 source: Arc<CacheError>,
92 },
93}
94}
95
96struct CacheFailureLocation<'a> {
97 path: &'a Path,
98 entry_key: Option<&'a str>,
99}
100
101impl<'a> CacheFailureLocation<'a> {
102 const fn new(path: &'a Path, entry_key: Option<&'a str>) -> Self {
103 Self { path, entry_key }
104 }
105}
106
107impl fmt::Display for CacheFailureLocation<'_> {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 if let Some(entry_key) = self.entry_key {
110 write!(f, " for {entry_key} at {}", self.path.display())
111 } else {
112 write!(f, " at {}", self.path.display())
113 }
114 }
115}
116
117struct CacheEntryLocation<'a>(Option<&'a str>);
118
119impl<'a> CacheEntryLocation<'a> {
120 const fn new(entry_key: Option<&'a str>) -> Self {
121 Self(entry_key)
122 }
123}
124
125impl fmt::Display for CacheEntryLocation<'_> {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 match self.0 {
128 Some(entry_key) => write!(f, " for {entry_key}"),
129 None => Ok(()),
130 }
131 }
132}
133
134impl CacheError {
135 pub(super) fn filesystem(
136 operation: CacheOperation,
137 entry_key: Option<String>,
138 source: error::Error,
139 ) -> Self {
140 Self {
141 kind: CacheErrorKind::Filesystem {
142 operation,
143 entry_key,
144 source,
145 },
146 }
147 }
148
149 pub(super) fn json(
150 operation: CacheOperation,
151 path: impl Into<PathBuf>,
152 entry_key: Option<String>,
153 source: serde_json::Error,
154 ) -> Self {
155 Self {
156 kind: CacheErrorKind::Json {
157 operation,
158 path: path.into(),
159 entry_key,
160 source,
161 },
162 }
163 }
164
165 pub(super) fn remote(entry_key: impl Into<String>, source: SupabaseError) -> Self {
166 Self {
167 kind: CacheErrorKind::Remote {
168 operation: CacheOperation::FetchRemote,
169 entry_key: entry_key.into(),
170 source,
171 },
172 }
173 }
174
175 pub(super) fn missing(
176 resource: impl Into<String>,
177 key: impl Into<String>,
178 value: impl Into<String>,
179 ) -> Self {
180 Self {
181 kind: CacheErrorKind::MissingRow {
182 resource: resource.into(),
183 key: key.into(),
184 value: value.into(),
185 },
186 }
187 }
188
189 pub(super) fn shared(entry_key: impl Into<String>, source: Arc<Self>) -> Self {
190 Self {
191 kind: CacheErrorKind::Shared {
192 operation: CacheOperation::LoadShared,
193 entry_key: entry_key.into(),
194 source,
195 },
196 }
197 }
198
199 #[must_use]
201 pub fn operation(&self) -> CacheOperation {
202 match &self.kind {
203 CacheErrorKind::Filesystem { operation, .. }
204 | CacheErrorKind::Json { operation, .. }
205 | CacheErrorKind::Remote { operation, .. }
206 | CacheErrorKind::Shared { operation, .. } => *operation,
207 CacheErrorKind::MissingRow { .. } => CacheOperation::FetchRemote,
208 }
209 }
210
211 #[must_use]
213 pub fn path(&self) -> Option<&Path> {
214 match &self.kind {
215 CacheErrorKind::Filesystem { source, .. } => Some(source.path()),
216 CacheErrorKind::Json { path, .. } => Some(path),
217 _ => None,
218 }
219 }
220
221 #[must_use]
223 pub fn entry_key(&self) -> Option<&str> {
224 match &self.kind {
225 CacheErrorKind::Filesystem { entry_key, .. }
226 | CacheErrorKind::Json { entry_key, .. } => entry_key.as_deref(),
227 CacheErrorKind::Remote { entry_key, .. } | CacheErrorKind::Shared { entry_key, .. } => {
228 Some(entry_key)
229 }
230 CacheErrorKind::MissingRow { .. } => None,
231 }
232 }
233
234 #[must_use]
236 pub fn missing_row(&self) -> Option<(&str, &str, &str)> {
237 match &self.kind {
238 CacheErrorKind::MissingRow {
239 resource,
240 key,
241 value,
242 } => Some((resource, key, value)),
243 _ => None,
244 }
245 }
246}
247
248impl fmt::Debug for CacheError {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 f.debug_struct("CacheError")
251 .field("operation", &self.operation())
252 .field("entry_key", &self.entry_key())
253 .field("path", &self.path().map(|_| "<redacted>"))
254 .field("source", &"<redacted>")
255 .finish_non_exhaustive()
256 }
257}