1#[cfg(test)]
8mod tests;
9
10use std::fmt;
11
12use candid::CandidType;
13use icydb_core::db::QueryError;
14use icydb_core::error::{ErrorOrigin as CoreErrorOrigin, InternalError};
15use serde::Deserialize;
16
17use crate::db::DatabaseBootstrapError;
18
19pub use icydb_core::error::{
20 ConstraintValidationFindingOutput, ConstraintValuePath, ConstraintValuePathComponent,
21};
22
23#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
32pub struct DiagnosticFact {
33 tag: u8,
34 value: u64,
35}
36
37impl DiagnosticFact {
38 const fn from_numeric(tag: icydb_diagnostic_code::DiagnosticFactTag, value: u64) -> Self {
39 Self {
40 tag: tag.raw(),
41 value,
42 }
43 }
44
45 #[must_use]
47 pub const fn tag(&self) -> u8 {
48 self.tag
49 }
50
51 #[must_use]
53 pub const fn value(&self) -> u64 {
54 self.value
55 }
56}
57
58#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
64pub struct QueryFieldDiagnostic {
65 role: u8,
66 field: String,
67}
68
69impl QueryFieldDiagnostic {
70 fn from_core(role: icydb_diagnostic_code::QueryFieldRole, field: &str) -> Self {
71 Self {
72 role: role.raw(),
73 field: field.to_owned(),
74 }
75 }
76
77 #[must_use]
79 pub const fn role(&self) -> u8 {
80 self.role
81 }
82
83 #[must_use]
85 pub const fn known_role(&self) -> Option<icydb_diagnostic_code::QueryFieldRole> {
86 icydb_diagnostic_code::QueryFieldRole::known(self.role)
87 }
88
89 #[must_use]
91 pub const fn field(&self) -> &str {
92 self.field.as_str()
93 }
94}
95
96#[cfg_attr(doc, doc = "Error\n\nPublic error payload.")]
101#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
102pub struct Error {
103 code: u16,
104 class: u8,
105 origin: u8,
106 facts: Vec<DiagnosticFact>,
107 query_field: Option<QueryFieldDiagnostic>,
108}
109
110impl Error {
111 #[must_use]
113 pub const fn from_code(
114 code: icydb_diagnostic_code::DiagnosticCode,
115 origin: ErrorOrigin,
116 ) -> Self {
117 Self::from_error_code(code.error_code(), origin)
118 }
119
120 #[must_use]
122 pub const fn from_error_code(
123 code: icydb_diagnostic_code::ErrorCode,
124 origin: ErrorOrigin,
125 ) -> Self {
126 Self {
127 code: code.raw(),
128 class: code.class().wire_code(),
129 origin: origin.wire_code(),
130 facts: Vec::new(),
131 query_field: None,
132 }
133 }
134
135 #[must_use]
140 pub const fn from_kind(kind: ErrorKind, origin: ErrorOrigin) -> Self {
141 let code = kind.diagnostic_code();
142 let error_code =
143 icydb_diagnostic_code::ErrorCode::from_parts(code, Some(kind.diagnostic_detail()));
144
145 Self::from_error_code(error_code, origin)
146 }
147
148 #[must_use]
150 pub const fn from_runtime_boundary(
151 boundary: icydb_diagnostic_code::RuntimeBoundaryCode,
152 origin: ErrorOrigin,
153 ) -> Self {
154 let detail = icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary { boundary };
155 let error_code =
156 icydb_diagnostic_code::ErrorCode::from_parts(detail.diagnostic_code(), Some(detail));
157
158 Self::from_error_code(error_code, origin)
159 }
160
161 #[must_use]
163 pub fn from_diagnostic(diagnostic: icydb_diagnostic_code::Diagnostic) -> Self {
164 Self::from_error_code(diagnostic.error_code(), diagnostic.origin().into())
165 }
166
167 fn from_internal_error(err: &InternalError) -> Self {
168 Self::from_diagnostic_and_facts(err.diagnostic(), err.diagnostic_facts())
169 }
170
171 pub(crate) fn from_diagnostic_and_facts(
172 diagnostic: icydb_diagnostic_code::Diagnostic,
173 facts: Vec<(icydb_diagnostic_code::DiagnosticFactTag, u64)>,
174 ) -> Self {
175 Self::from_diagnostic_facts_and_query_field(diagnostic, facts, None)
176 }
177
178 fn from_diagnostic_facts_and_query_field(
179 diagnostic: icydb_diagnostic_code::Diagnostic,
180 facts: Vec<(icydb_diagnostic_code::DiagnosticFactTag, u64)>,
181 query_field: Option<(icydb_diagnostic_code::QueryFieldRole, &str)>,
182 ) -> Self {
183 if icydb_diagnostic_code::validate_known_diagnostic_fact_schema(
184 diagnostic.error_code(),
185 facts.as_slice(),
186 )
187 .is_err()
188 {
189 return Self::from_kind(
190 ErrorKind::Runtime(RuntimeErrorKind::InvariantViolation),
191 diagnostic.origin().into(),
192 );
193 }
194
195 if let Some((role, field)) = query_field
196 && icydb_diagnostic_code::validate_query_field_schema(
197 diagnostic.error_code(),
198 role.raw(),
199 field,
200 )
201 .is_err()
202 {
203 return Self::from_kind(
204 ErrorKind::Runtime(RuntimeErrorKind::InvariantViolation),
205 diagnostic.origin().into(),
206 );
207 }
208
209 let mut error = Self::from_diagnostic(diagnostic);
210 error.facts = facts
211 .into_iter()
212 .map(|(tag, value)| DiagnosticFact::from_numeric(tag, value))
213 .collect();
214 error.query_field =
215 query_field.map(|(role, field)| QueryFieldDiagnostic::from_core(role, field));
216 error
217 }
218
219 #[must_use]
221 pub const fn code(&self) -> icydb_diagnostic_code::ErrorCode {
222 icydb_diagnostic_code::ErrorCode::from_raw(self.code)
223 }
224
225 #[must_use]
227 pub const fn class(&self) -> icydb_diagnostic_code::ErrorClass {
228 match icydb_diagnostic_code::ErrorClass::from_wire_code(self.class) {
229 Some(class) => class,
230 None => self.code().class(),
231 }
232 }
233
234 #[must_use]
236 pub const fn origin(&self) -> ErrorOrigin {
237 ErrorOrigin::from_wire_code(self.origin)
238 }
239
240 #[must_use]
242 pub fn diagnostic(&self) -> icydb_diagnostic_code::Diagnostic {
243 self.code().diagnostic(self.origin().into())
244 }
245
246 #[must_use]
248 pub const fn diagnostic_code(&self) -> icydb_diagnostic_code::DiagnosticCode {
249 self.code().diagnostic_code()
250 }
251
252 #[must_use]
254 pub const fn facts(&self) -> &[DiagnosticFact] {
255 self.facts.as_slice()
256 }
257
258 #[must_use]
260 pub const fn query_field(&self) -> Option<&QueryFieldDiagnostic> {
261 self.query_field.as_ref()
262 }
263
264 pub fn validated_query_field(
268 &self,
269 ) -> Result<
270 Option<(icydb_diagnostic_code::QueryFieldRole, &str)>,
271 icydb_diagnostic_code::QueryFieldSchemaMismatch,
272 > {
273 let Some(context) = self.query_field.as_ref() else {
274 return Ok(None);
275 };
276 let role = icydb_diagnostic_code::validate_query_field_schema(
277 self.code(),
278 context.role,
279 context.field.as_str(),
280 )?;
281
282 Ok(Some((role, context.field.as_str())))
283 }
284
285 pub(crate) fn core_facts(
286 &self,
287 ) -> Option<Vec<(icydb_diagnostic_code::DiagnosticFactTag, u64)>> {
288 self.facts
289 .iter()
290 .map(|fact| {
291 icydb_diagnostic_code::DiagnosticFactTag::known(fact.tag)
292 .map(|tag| (tag, fact.value))
293 })
294 .collect()
295 }
296}
297
298impl From<InternalError> for Error {
299 fn from(err: InternalError) -> Self {
300 Self::from_internal_error(&err)
301 }
302}
303
304impl From<QueryError> for Error {
305 fn from(err: QueryError) -> Self {
306 Self::from_diagnostic_facts_and_query_field(
307 err.diagnostic(),
308 err.diagnostic_facts(),
309 err.query_field_context(),
310 )
311 }
312}
313
314impl From<DatabaseBootstrapError> for Error {
315 fn from(_err: DatabaseBootstrapError) -> Self {
316 Self::from_kind(
317 ErrorKind::Runtime(RuntimeErrorKind::Internal),
318 ErrorOrigin::Runtime,
319 )
320 }
321}
322
323impl fmt::Display for Error {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 write!(f, "E{}", self.code)
326 }
327}
328
329impl std::error::Error for Error {}
330
331#[cfg_attr(doc, doc = "ErrorKind\n\nPublic error category.")]
332#[derive(Clone, Copy, Debug, Eq, PartialEq)]
333pub enum ErrorKind {
334 Query(QueryErrorKind),
335
336 Runtime(RuntimeErrorKind),
338}
339
340impl ErrorKind {
341 #[must_use]
343 pub const fn diagnostic_code(self) -> icydb_diagnostic_code::DiagnosticCode {
344 match self {
345 Self::Query(kind) => kind.diagnostic_code(),
346 Self::Runtime(kind) => kind.diagnostic_code(),
347 }
348 }
349
350 const fn diagnostic_detail(self) -> icydb_diagnostic_code::DiagnosticDetail {
351 match self {
352 Self::Query(kind) => icydb_diagnostic_code::DiagnosticDetail::QueryKind {
353 kind: kind.diagnostic_kind(),
354 },
355 Self::Runtime(kind) => icydb_diagnostic_code::DiagnosticDetail::RuntimeKind {
356 kind: kind.diagnostic_kind(),
357 },
358 }
359 }
360}
361
362#[cfg_attr(doc, doc = "RuntimeErrorKind\n\nPublic runtime error class.")]
363#[derive(Clone, Copy, Debug, Eq, PartialEq)]
364pub enum RuntimeErrorKind {
365 Corruption,
366 IncompatiblePersistedFormat,
367 InvariantViolation,
368 Conflict,
369 NotFound,
370 Unsupported,
371 Internal,
372}
373
374impl RuntimeErrorKind {
375 #[must_use]
377 pub const fn diagnostic_code(self) -> icydb_diagnostic_code::DiagnosticCode {
378 match self {
379 Self::Corruption => icydb_diagnostic_code::DiagnosticCode::RuntimeCorruption,
380 Self::IncompatiblePersistedFormat => {
381 icydb_diagnostic_code::DiagnosticCode::RuntimeIncompatiblePersistedFormat
382 }
383 Self::InvariantViolation => {
384 icydb_diagnostic_code::DiagnosticCode::RuntimeInvariantViolation
385 }
386 Self::Conflict => icydb_diagnostic_code::DiagnosticCode::RuntimeConflict,
387 Self::NotFound => icydb_diagnostic_code::DiagnosticCode::RuntimeNotFound,
388 Self::Unsupported => icydb_diagnostic_code::DiagnosticCode::RuntimeUnsupported,
389 Self::Internal => icydb_diagnostic_code::DiagnosticCode::RuntimeInternal,
390 }
391 }
392
393 const fn diagnostic_kind(self) -> icydb_diagnostic_code::RuntimeErrorKind {
394 match self {
395 Self::Corruption => icydb_diagnostic_code::RuntimeErrorKind::Corruption,
396 Self::IncompatiblePersistedFormat => {
397 icydb_diagnostic_code::RuntimeErrorKind::IncompatiblePersistedFormat
398 }
399 Self::InvariantViolation => icydb_diagnostic_code::RuntimeErrorKind::InvariantViolation,
400 Self::Conflict => icydb_diagnostic_code::RuntimeErrorKind::Conflict,
401 Self::NotFound => icydb_diagnostic_code::RuntimeErrorKind::NotFound,
402 Self::Unsupported => icydb_diagnostic_code::RuntimeErrorKind::Unsupported,
403 Self::Internal => icydb_diagnostic_code::RuntimeErrorKind::Internal,
404 }
405 }
406}
407
408#[cfg_attr(doc, doc = "QueryErrorKind\n\nPublic query error class.")]
409#[derive(Clone, Copy, Debug, Eq, PartialEq)]
410pub enum QueryErrorKind {
411 Validate,
413
414 Intent,
416
417 Plan,
419
420 UnorderedPagination,
422
423 InvalidContinuationCursor,
425
426 NotFound,
428
429 NotUnique,
431}
432
433impl QueryErrorKind {
434 #[must_use]
436 pub const fn diagnostic_code(self) -> icydb_diagnostic_code::DiagnosticCode {
437 match self {
438 Self::Validate => icydb_diagnostic_code::DiagnosticCode::QueryValidate,
439 Self::Intent => icydb_diagnostic_code::DiagnosticCode::QueryIntent,
440 Self::Plan => icydb_diagnostic_code::DiagnosticCode::QueryPlan,
441 Self::UnorderedPagination => {
442 icydb_diagnostic_code::DiagnosticCode::QueryUnorderedPagination
443 }
444 Self::InvalidContinuationCursor => {
445 icydb_diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor
446 }
447 Self::NotFound => icydb_diagnostic_code::DiagnosticCode::QueryNotFound,
448 Self::NotUnique => icydb_diagnostic_code::DiagnosticCode::QueryNotUnique,
449 }
450 }
451
452 const fn diagnostic_kind(self) -> icydb_diagnostic_code::QueryErrorKind {
453 match self {
454 Self::Validate => icydb_diagnostic_code::QueryErrorKind::Validate,
455 Self::Intent => icydb_diagnostic_code::QueryErrorKind::Intent,
456 Self::Plan => icydb_diagnostic_code::QueryErrorKind::Plan,
457 Self::UnorderedPagination => icydb_diagnostic_code::QueryErrorKind::UnorderedPagination,
458 Self::InvalidContinuationCursor => {
459 icydb_diagnostic_code::QueryErrorKind::InvalidContinuationCursor
460 }
461 Self::NotFound => icydb_diagnostic_code::QueryErrorKind::NotFound,
462 Self::NotUnique => icydb_diagnostic_code::QueryErrorKind::NotUnique,
463 }
464 }
465}
466
467#[cfg_attr(doc, doc = "ErrorOrigin\n\nPublic error origin.")]
468#[derive(Clone, Copy, Debug, Eq, PartialEq)]
469pub enum ErrorOrigin {
470 Cursor,
471 Executor,
472 Identity,
473 Index,
474 Interface,
475 Planner,
476 Query,
477 Recovery,
478 Response,
479 Runtime,
480 Serialize,
481 Store,
482}
483
484impl ErrorOrigin {
485 const fn wire_code(self) -> u8 {
486 match self {
487 Self::Cursor => icydb_diagnostic_code::ErrorOrigin::Cursor.wire_code(),
488 Self::Executor => icydb_diagnostic_code::ErrorOrigin::Executor.wire_code(),
489 Self::Identity => icydb_diagnostic_code::ErrorOrigin::Identity.wire_code(),
490 Self::Index => icydb_diagnostic_code::ErrorOrigin::Index.wire_code(),
491 Self::Interface => icydb_diagnostic_code::ErrorOrigin::Interface.wire_code(),
492 Self::Planner => icydb_diagnostic_code::ErrorOrigin::Planner.wire_code(),
493 Self::Query => icydb_diagnostic_code::ErrorOrigin::Query.wire_code(),
494 Self::Recovery => icydb_diagnostic_code::ErrorOrigin::Recovery.wire_code(),
495 Self::Response => icydb_diagnostic_code::ErrorOrigin::Response.wire_code(),
496 Self::Runtime => icydb_diagnostic_code::ErrorOrigin::Runtime.wire_code(),
497 Self::Serialize => icydb_diagnostic_code::ErrorOrigin::Serialize.wire_code(),
498 Self::Store => icydb_diagnostic_code::ErrorOrigin::Store.wire_code(),
499 }
500 }
501
502 const fn from_wire_code(code: u8) -> Self {
503 match icydb_diagnostic_code::ErrorOrigin::from_wire_code(code) {
504 icydb_diagnostic_code::ErrorOrigin::Cursor => Self::Cursor,
505 icydb_diagnostic_code::ErrorOrigin::Executor => Self::Executor,
506 icydb_diagnostic_code::ErrorOrigin::Identity => Self::Identity,
507 icydb_diagnostic_code::ErrorOrigin::Index => Self::Index,
508 icydb_diagnostic_code::ErrorOrigin::Interface => Self::Interface,
509 icydb_diagnostic_code::ErrorOrigin::Planner => Self::Planner,
510 icydb_diagnostic_code::ErrorOrigin::Query => Self::Query,
511 icydb_diagnostic_code::ErrorOrigin::Recovery => Self::Recovery,
512 icydb_diagnostic_code::ErrorOrigin::Response => Self::Response,
513 icydb_diagnostic_code::ErrorOrigin::Runtime => Self::Runtime,
514 icydb_diagnostic_code::ErrorOrigin::Serialize => Self::Serialize,
515 icydb_diagnostic_code::ErrorOrigin::Store => Self::Store,
516 }
517 }
518}
519
520impl From<CoreErrorOrigin> for ErrorOrigin {
521 fn from(origin: CoreErrorOrigin) -> Self {
522 match origin {
523 CoreErrorOrigin::Cursor => Self::Cursor,
524 CoreErrorOrigin::Executor => Self::Executor,
525 CoreErrorOrigin::Identity => Self::Identity,
526 CoreErrorOrigin::Index => Self::Index,
527 CoreErrorOrigin::Interface => Self::Interface,
528 CoreErrorOrigin::Planner => Self::Planner,
529 CoreErrorOrigin::Query => Self::Query,
530 CoreErrorOrigin::Recovery => Self::Recovery,
531 CoreErrorOrigin::Response => Self::Response,
532 CoreErrorOrigin::Serialize => Self::Serialize,
533 CoreErrorOrigin::Store => Self::Store,
534 }
535 }
536}
537
538impl From<ErrorOrigin> for icydb_diagnostic_code::ErrorOrigin {
539 fn from(origin: ErrorOrigin) -> Self {
540 match origin {
541 ErrorOrigin::Cursor => Self::Cursor,
542 ErrorOrigin::Executor => Self::Executor,
543 ErrorOrigin::Identity => Self::Identity,
544 ErrorOrigin::Index => Self::Index,
545 ErrorOrigin::Interface => Self::Interface,
546 ErrorOrigin::Planner => Self::Planner,
547 ErrorOrigin::Query => Self::Query,
548 ErrorOrigin::Recovery => Self::Recovery,
549 ErrorOrigin::Response => Self::Response,
550 ErrorOrigin::Runtime => Self::Runtime,
551 ErrorOrigin::Serialize => Self::Serialize,
552 ErrorOrigin::Store => Self::Store,
553 }
554 }
555}
556
557impl From<icydb_diagnostic_code::ErrorOrigin> for ErrorOrigin {
558 fn from(origin: icydb_diagnostic_code::ErrorOrigin) -> Self {
559 match origin {
560 icydb_diagnostic_code::ErrorOrigin::Cursor => Self::Cursor,
561 icydb_diagnostic_code::ErrorOrigin::Executor => Self::Executor,
562 icydb_diagnostic_code::ErrorOrigin::Identity => Self::Identity,
563 icydb_diagnostic_code::ErrorOrigin::Index => Self::Index,
564 icydb_diagnostic_code::ErrorOrigin::Interface => Self::Interface,
565 icydb_diagnostic_code::ErrorOrigin::Planner => Self::Planner,
566 icydb_diagnostic_code::ErrorOrigin::Query => Self::Query,
567 icydb_diagnostic_code::ErrorOrigin::Recovery => Self::Recovery,
568 icydb_diagnostic_code::ErrorOrigin::Response => Self::Response,
569 icydb_diagnostic_code::ErrorOrigin::Runtime => Self::Runtime,
570 icydb_diagnostic_code::ErrorOrigin::Serialize => Self::Serialize,
571 icydb_diagnostic_code::ErrorOrigin::Store => Self::Store,
572 }
573 }
574}