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