1#[cfg_attr(
14 feature = "wire",
15 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
16)]
17#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub enum DiagnosticCode {
19 QueryValidate,
20 QueryIntent,
21 QueryPlan,
22 QueryAccessRequirement,
23 QueryUnorderedPagination,
24 QueryInvalidContinuationCursor,
25 QueryNotFound,
26 QueryNotUnique,
27 QueryNumericOverflow,
28 QueryNumericNotRepresentable,
29 QueryUnsupportedSqlFeature,
30 SchemaDdlAdmission,
31 StoreNotFound,
32 StoreCorruption,
33 StoreInvariantViolation,
34 RuntimeCorruption,
35 RuntimeIncompatiblePersistedFormat,
36 RuntimeInvariantViolation,
37 RuntimeConflict,
38 RuntimeNotFound,
39 RuntimeUnsupported,
40 RuntimeInternal,
41}
42
43impl DiagnosticCode {
44 #[must_use]
46 pub const fn class(self) -> ErrorClass {
47 match self {
48 Self::StoreCorruption | Self::RuntimeCorruption => ErrorClass::Corruption,
49 Self::RuntimeIncompatiblePersistedFormat => ErrorClass::IncompatiblePersistedFormat,
50 Self::QueryNotFound | Self::StoreNotFound | Self::RuntimeNotFound => {
51 ErrorClass::NotFound
52 }
53 Self::RuntimeConflict => ErrorClass::Conflict,
54 Self::QueryUnsupportedSqlFeature | Self::RuntimeUnsupported => ErrorClass::Unsupported,
55 Self::StoreInvariantViolation | Self::RuntimeInvariantViolation => {
56 ErrorClass::InvariantViolation
57 }
58 Self::RuntimeInternal => ErrorClass::Internal,
59 Self::QueryValidate
60 | Self::QueryIntent
61 | Self::QueryPlan
62 | Self::QueryAccessRequirement
63 | Self::QueryUnorderedPagination
64 | Self::QueryInvalidContinuationCursor
65 | Self::QueryNotUnique
66 | Self::QueryNumericOverflow
67 | Self::QueryNumericNotRepresentable
68 | Self::SchemaDdlAdmission => ErrorClass::Query,
69 }
70 }
71
72 #[must_use]
74 pub const fn origin(self) -> ErrorOrigin {
75 match self {
76 Self::StoreNotFound | Self::StoreCorruption | Self::StoreInvariantViolation => {
77 ErrorOrigin::Store
78 }
79 Self::RuntimeCorruption
80 | Self::RuntimeIncompatiblePersistedFormat
81 | Self::RuntimeInvariantViolation
82 | Self::RuntimeConflict
83 | Self::RuntimeNotFound
84 | Self::RuntimeUnsupported
85 | Self::RuntimeInternal => ErrorOrigin::Runtime,
86 Self::QueryValidate
87 | Self::QueryIntent
88 | Self::QueryPlan
89 | Self::QueryAccessRequirement
90 | Self::QueryUnorderedPagination
91 | Self::QueryInvalidContinuationCursor
92 | Self::QueryNotFound
93 | Self::QueryNotUnique
94 | Self::QueryNumericOverflow
95 | Self::QueryNumericNotRepresentable
96 | Self::QueryUnsupportedSqlFeature
97 | Self::SchemaDdlAdmission => ErrorOrigin::Query,
98 }
99 }
100}
101
102#[cfg_attr(
109 feature = "wire",
110 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
111)]
112#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
113pub enum ErrorClass {
114 Query,
115 Corruption,
116 IncompatiblePersistedFormat,
117 NotFound,
118 Internal,
119 Conflict,
120 Unsupported,
121 InvariantViolation,
122}
123
124#[cfg_attr(
131 feature = "wire",
132 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
133)]
134#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
135pub enum ErrorOrigin {
136 Cursor,
137 Executor,
138 Identity,
139 Index,
140 Interface,
141 Planner,
142 Query,
143 Recovery,
144 Response,
145 Runtime,
146 Serialize,
147 Store,
148}
149
150#[cfg_attr(
157 feature = "wire",
158 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
159)]
160#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
161pub enum QueryErrorKind {
162 Validate,
163 Intent,
164 Plan,
165 AccessRequirement,
166 UnorderedPagination,
167 InvalidContinuationCursor,
168 NotFound,
169 NotUnique,
170}
171
172#[cfg_attr(
179 feature = "wire",
180 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
181)]
182#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
183pub enum RuntimeErrorKind {
184 Corruption,
185 IncompatiblePersistedFormat,
186 InvariantViolation,
187 Conflict,
188 NotFound,
189 Unsupported,
190 Internal,
191}
192
193#[cfg_attr(
200 feature = "wire",
201 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
202)]
203#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
204pub enum SqlFeatureCode {
205 AlterTableUnsupportedOperation,
206 ColumnAlias,
207 DescribeModifier,
208 DropStatementBeyondDropIndex,
209 Join,
210 MultiStatementSql,
211 OrderByUnsupportedForm,
212 ParameterBinding,
213 ParameterizedSchemaVersion,
214 ReturningUnsupportedShape,
215 ShowUnsupportedCommand,
216 UnionIntersectExcept,
217 WindowFunction,
218}
219
220#[cfg_attr(
227 feature = "wire",
228 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
229)]
230#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
231pub enum SchemaDdlAdmissionCode {
232 MissingExpectedSchemaVersion,
233 MissingNextSchemaVersion,
234 StaleExpectedSchemaVersion,
235 InvalidExpectedSchemaVersion,
236 InvalidNextSchemaVersion,
237 AcceptedSchemaChangeWithoutVersionBump,
238 EmptyVersionBump,
239 VersionGap,
240 VersionRollback,
241 FingerprintMethodMismatch,
242 UnsupportedTransitionClass,
243 PhysicalRunnerMissing,
244 ValidationFailed,
245 PublicationRaceLost,
246}
247
248#[cfg_attr(
255 feature = "wire",
256 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
257)]
258#[derive(Clone, Debug, Eq, PartialEq)]
259pub enum DiagnosticDetail {
260 QueryKind { kind: QueryErrorKind },
261 RuntimeKind { kind: RuntimeErrorKind },
262 SchemaDdlAdmission { reason: SchemaDdlAdmissionCode },
263 UnsupportedSqlFeature { feature: SqlFeatureCode },
264}
265
266#[cfg_attr(
273 feature = "wire",
274 derive(candid::CandidType, serde::Deserialize, serde::Serialize)
275)]
276#[derive(Clone, Debug, Eq, PartialEq)]
277pub struct Diagnostic {
278 code: DiagnosticCode,
279 origin: ErrorOrigin,
280 detail: Option<DiagnosticDetail>,
281}
282
283impl Diagnostic {
284 #[must_use]
286 pub const fn new(
287 code: DiagnosticCode,
288 origin: ErrorOrigin,
289 detail: Option<DiagnosticDetail>,
290 ) -> Self {
291 Self {
292 code,
293 origin,
294 detail,
295 }
296 }
297
298 #[must_use]
300 pub const fn from_code(code: DiagnosticCode) -> Self {
301 Self::new(code, code.origin(), None)
302 }
303
304 #[must_use]
306 pub const fn code(&self) -> DiagnosticCode {
307 self.code
308 }
309
310 #[must_use]
312 pub const fn class(&self) -> ErrorClass {
313 self.code.class()
314 }
315
316 #[must_use]
318 pub const fn origin(&self) -> ErrorOrigin {
319 self.origin
320 }
321
322 #[must_use]
324 pub const fn detail(&self) -> Option<&DiagnosticDetail> {
325 self.detail.as_ref()
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::{Diagnostic, DiagnosticCode, ErrorClass, ErrorOrigin};
332
333 #[test]
334 fn diagnostic_from_code_uses_default_origin() {
335 let diagnostic = Diagnostic::from_code(DiagnosticCode::QueryPlan);
336
337 assert_eq!(diagnostic.code(), DiagnosticCode::QueryPlan);
338 assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
339 }
340
341 #[test]
342 fn diagnostic_code_reports_broad_class() {
343 assert_eq!(
344 DiagnosticCode::QueryUnsupportedSqlFeature.class(),
345 ErrorClass::Unsupported
346 );
347 assert_eq!(DiagnosticCode::QueryPlan.class(), ErrorClass::Query);
348 assert_eq!(
349 DiagnosticCode::StoreCorruption.class(),
350 ErrorClass::Corruption
351 );
352 }
353}