Skip to main content

icydb_diagnostic_code/
lib.rs

1//! Module: lib
2//! Responsibility: compact diagnostic identity and public numeric error-code mapping.
3//! Does not own: rich diagnostic prose, Candid wire types, or runtime error construction.
4//! Boundary: maps rich internal diagnostic categories to stable compact public codes.
5//!
6//! This crate intentionally contains no rich diagnostic prose or Candid wire
7//! types. Production canister builds collapse diagnostics to numeric wire
8//! codes before they cross the public canister boundary. `Debug` output is
9//! numeric for the same reason: host tooling can recover labels from the code
10//! table without making every wasm canister retain those labels.
11
12use std::fmt;
13
14mod fact;
15mod query_field;
16
17pub use fact::{
18    DiagnosticAggregateKind, DiagnosticBacklogResource, DiagnosticComponentKind,
19    DiagnosticConstraintContext, DiagnosticConstraintKind, DiagnosticDecodeReason,
20    DiagnosticExecutionBudgetResource, DiagnosticExecutionBudgetScope, DiagnosticExecutionLane,
21    DiagnosticFactSchemaMismatch, DiagnosticFactTag, DiagnosticFunctionKind,
22    DiagnosticMutationOperation, DiagnosticOperatorKind, DiagnosticTypeFamily,
23    MAX_PUBLIC_DIAGNOSTIC_FACTS, pack_u32_pair, unpack_u32_pair,
24    validate_known_diagnostic_fact_schema, validate_raw_diagnostic_fact_schema,
25};
26pub use query_field::{
27    MAX_PUBLIC_QUERY_FIELD_BYTES, QueryFieldRole, QueryFieldSchemaMismatch,
28    validate_query_field_schema,
29};
30
31///
32/// DiagnosticCode
33///
34/// Stable machine-readable diagnostic reason.
35///
36
37#[remain::sorted]
38#[derive(Clone, Copy, Eq, Hash, PartialEq)]
39pub enum DiagnosticCode {
40    QueryAccessRequirement,
41    QueryIntent,
42    QueryInvalidContinuationCursor,
43    QueryNotFound,
44    QueryNotUnique,
45    QueryNumericNotRepresentable,
46    QueryNumericOverflow,
47    QueryPlan,
48    QueryReadAdmission,
49    QueryResultShapeMismatch,
50    QuerySqlSurfaceMismatch,
51    QuerySqlWriteBoundary,
52    QueryUnknownAggregateTargetField,
53    QueryUnorderedPagination,
54    QueryUnsupportedProjection,
55    QueryUnsupportedSqlFeature,
56    QueryValidate,
57    RuntimeConflict,
58    RuntimeCorruption,
59    RuntimeIncompatiblePersistedFormat,
60    RuntimeInternal,
61    RuntimeInvariantViolation,
62    RuntimeNotFound,
63    RuntimeUnsupported,
64    SchemaDdlAdmission,
65    StoreCorruption,
66    StoreInvariantViolation,
67    StoreNotFound,
68}
69
70impl DiagnosticCode {
71    /// Return the broad diagnostic class for this code.
72    #[must_use]
73    pub const fn class(self) -> ErrorClass {
74        match self {
75            Self::StoreCorruption | Self::RuntimeCorruption => ErrorClass::Corruption,
76            Self::RuntimeIncompatiblePersistedFormat => ErrorClass::IncompatiblePersistedFormat,
77            Self::QueryNotFound | Self::StoreNotFound | Self::RuntimeNotFound => {
78                ErrorClass::NotFound
79            }
80            Self::RuntimeConflict => ErrorClass::Conflict,
81            Self::QueryUnsupportedSqlFeature
82            | Self::QueryUnknownAggregateTargetField
83            | Self::QueryUnsupportedProjection
84            | Self::QueryResultShapeMismatch
85            | Self::QuerySqlSurfaceMismatch
86            | Self::QuerySqlWriteBoundary
87            | Self::RuntimeUnsupported => ErrorClass::Unsupported,
88            Self::StoreInvariantViolation | Self::RuntimeInvariantViolation => {
89                ErrorClass::InvariantViolation
90            }
91            Self::RuntimeInternal => ErrorClass::Internal,
92            Self::QueryValidate
93            | Self::QueryIntent
94            | Self::QueryPlan
95            | Self::QueryReadAdmission
96            | Self::QueryAccessRequirement
97            | Self::QueryUnorderedPagination
98            | Self::QueryInvalidContinuationCursor
99            | Self::QueryNotUnique
100            | Self::QueryNumericOverflow
101            | Self::QueryNumericNotRepresentable
102            | Self::SchemaDdlAdmission => ErrorClass::Query,
103        }
104    }
105
106    /// Return the default diagnostic origin for this code.
107    #[must_use]
108    pub const fn origin(self) -> ErrorOrigin {
109        match self {
110            Self::StoreNotFound | Self::StoreCorruption | Self::StoreInvariantViolation => {
111                ErrorOrigin::Store
112            }
113            Self::RuntimeCorruption
114            | Self::RuntimeIncompatiblePersistedFormat
115            | Self::RuntimeInvariantViolation
116            | Self::RuntimeConflict
117            | Self::RuntimeNotFound
118            | Self::RuntimeUnsupported
119            | Self::RuntimeInternal => ErrorOrigin::Runtime,
120            Self::QueryValidate
121            | Self::QueryIntent
122            | Self::QueryPlan
123            | Self::QueryReadAdmission
124            | Self::QueryAccessRequirement
125            | Self::QueryUnorderedPagination
126            | Self::QueryInvalidContinuationCursor
127            | Self::QueryNotFound
128            | Self::QueryNotUnique
129            | Self::QueryNumericOverflow
130            | Self::QueryNumericNotRepresentable
131            | Self::QueryUnknownAggregateTargetField
132            | Self::QueryUnsupportedProjection
133            | Self::QueryResultShapeMismatch
134            | Self::QueryUnsupportedSqlFeature
135            | Self::QuerySqlSurfaceMismatch
136            | Self::QuerySqlWriteBoundary
137            | Self::SchemaDdlAdmission => ErrorOrigin::Query,
138        }
139    }
140
141    /// Return the compact public wire code for this broad diagnostic reason.
142    #[must_use]
143    pub const fn error_code(self) -> ErrorCode {
144        match self {
145            Self::QueryValidate => ErrorCode::QUERY_VALIDATE,
146            Self::QueryIntent => ErrorCode::QUERY_INTENT,
147            Self::QueryPlan => ErrorCode::QUERY_PLAN,
148            Self::QueryReadAdmission => ErrorCode::QUERY_READ_ADMISSION,
149            Self::QueryAccessRequirement => ErrorCode::QUERY_ACCESS_REQUIREMENT,
150            Self::QueryUnorderedPagination => ErrorCode::QUERY_UNORDERED_PAGINATION,
151            Self::QueryInvalidContinuationCursor => ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
152            Self::QueryNotFound => ErrorCode::QUERY_NOT_FOUND,
153            Self::QueryNotUnique => ErrorCode::QUERY_NOT_UNIQUE,
154            Self::QueryNumericOverflow => ErrorCode::QUERY_NUMERIC_OVERFLOW,
155            Self::QueryNumericNotRepresentable => ErrorCode::QUERY_NUMERIC_NOT_REPRESENTABLE,
156            Self::QueryUnknownAggregateTargetField => {
157                ErrorCode::QUERY_UNKNOWN_AGGREGATE_TARGET_FIELD
158            }
159            Self::QueryUnsupportedProjection => ErrorCode::QUERY_UNSUPPORTED_PROJECTION,
160            Self::QueryResultShapeMismatch => ErrorCode::QUERY_RESULT_SHAPE_MISMATCH,
161            Self::QueryUnsupportedSqlFeature => ErrorCode::QUERY_UNSUPPORTED_SQL_FEATURE,
162            Self::QuerySqlSurfaceMismatch => ErrorCode::QUERY_SQL_SURFACE_MISMATCH,
163            Self::QuerySqlWriteBoundary => ErrorCode::QUERY_SQL_WRITE_BOUNDARY,
164            Self::SchemaDdlAdmission => ErrorCode::SCHEMA_DDL_ADMISSION,
165            Self::StoreNotFound => ErrorCode::STORE_NOT_FOUND,
166            Self::StoreCorruption => ErrorCode::STORE_CORRUPTION,
167            Self::StoreInvariantViolation => ErrorCode::STORE_INVARIANT_VIOLATION,
168            Self::RuntimeCorruption => ErrorCode::RUNTIME_CORRUPTION,
169            Self::RuntimeIncompatiblePersistedFormat => {
170                ErrorCode::RUNTIME_INCOMPATIBLE_PERSISTED_FORMAT
171            }
172            Self::RuntimeInvariantViolation => ErrorCode::RUNTIME_INVARIANT_VIOLATION,
173            Self::RuntimeConflict => ErrorCode::RUNTIME_CONFLICT,
174            Self::RuntimeNotFound => ErrorCode::RUNTIME_NOT_FOUND,
175            Self::RuntimeUnsupported => ErrorCode::RUNTIME_UNSUPPORTED,
176            Self::RuntimeInternal => ErrorCode::RUNTIME_INTERNAL,
177        }
178    }
179}
180
181impl fmt::Debug for DiagnosticCode {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        fmt_compact_code(f, self.error_code().raw())
184    }
185}
186
187///
188/// ErrorCode
189///
190/// Stable numeric public error identity.
191///
192/// The public Candid `icydb::Error` stores this value as `nat16` so canister
193/// interfaces do not retain rich diagnostic enum labels. Rich diagnostics can
194/// still be reconstructed by host-side tooling from this leaf code. Before
195/// 1.0.0, the code space is hard-cut to a single compact sequential range.
196///
197
198#[derive(Clone, Copy, Eq, Hash, PartialEq)]
199pub struct ErrorCode(u16);
200
201mod registry;
202
203impl ErrorCode {
204    /// Build an error code from its raw public wire value.
205    #[must_use]
206    pub const fn from_raw(raw: u16) -> Self {
207        Self(raw)
208    }
209
210    /// Return the raw public wire value.
211    #[must_use]
212    pub const fn raw(self) -> u16 {
213        self.0
214    }
215}
216
217impl fmt::Debug for ErrorCode {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        fmt_compact_code(f, self.raw())
220    }
221}
222
223///
224/// ErrorClass
225///
226/// Broad diagnostic class used for recovery decisions.
227///
228
229#[remain::sorted]
230#[derive(Clone, Copy, Eq, Hash, PartialEq)]
231pub enum ErrorClass {
232    Conflict,
233    Corruption,
234    IncompatiblePersistedFormat,
235    Internal,
236    InvariantViolation,
237    NotFound,
238    Query,
239    Unsupported,
240}
241
242impl ErrorClass {
243    /// Return the compact public wire code for this diagnostic class.
244    #[must_use]
245    pub const fn wire_code(self) -> u8 {
246        match self {
247            Self::Query => 1,
248            Self::Corruption => 2,
249            Self::IncompatiblePersistedFormat => 3,
250            Self::NotFound => 4,
251            Self::Internal => 5,
252            Self::Conflict => 6,
253            Self::Unsupported => 7,
254            Self::InvariantViolation => 8,
255        }
256    }
257
258    /// Recover a diagnostic class from its compact public wire code.
259    #[must_use]
260    pub const fn from_wire_code(code: u8) -> Option<Self> {
261        match code {
262            1 => Some(Self::Query),
263            2 => Some(Self::Corruption),
264            3 => Some(Self::IncompatiblePersistedFormat),
265            4 => Some(Self::NotFound),
266            5 => Some(Self::Internal),
267            6 => Some(Self::Conflict),
268            7 => Some(Self::Unsupported),
269            8 => Some(Self::InvariantViolation),
270            _ => None,
271        }
272    }
273}
274
275impl fmt::Debug for ErrorClass {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        fmt_compact_code(f, u16::from(self.wire_code()))
278    }
279}
280
281///
282/// ErrorOrigin
283///
284/// Subsystem that owns the diagnostic.
285///
286
287#[remain::sorted]
288#[derive(Clone, Copy, Eq, Hash, PartialEq)]
289pub enum ErrorOrigin {
290    Cursor,
291    Executor,
292    Identity,
293    Index,
294    Interface,
295    Planner,
296    Query,
297    Recovery,
298    Response,
299    Runtime,
300    Serialize,
301    Store,
302}
303
304impl ErrorOrigin {
305    /// Return the compact public wire code for this diagnostic origin.
306    #[must_use]
307    pub const fn wire_code(self) -> u8 {
308        match self {
309            Self::Cursor => 1,
310            Self::Executor => 2,
311            Self::Identity => 3,
312            Self::Index => 4,
313            Self::Interface => 5,
314            Self::Planner => 6,
315            Self::Query => 7,
316            Self::Recovery => 8,
317            Self::Response => 9,
318            Self::Runtime => 10,
319            Self::Serialize => 11,
320            Self::Store => 12,
321        }
322    }
323
324    /// Recover a known diagnostic origin from its compact public wire code.
325    #[must_use]
326    pub const fn from_known_wire_code(code: u8) -> Option<Self> {
327        match code {
328            1 => Some(Self::Cursor),
329            2 => Some(Self::Executor),
330            3 => Some(Self::Identity),
331            4 => Some(Self::Index),
332            5 => Some(Self::Interface),
333            6 => Some(Self::Planner),
334            7 => Some(Self::Query),
335            8 => Some(Self::Recovery),
336            9 => Some(Self::Response),
337            10 => Some(Self::Runtime),
338            11 => Some(Self::Serialize),
339            12 => Some(Self::Store),
340            _ => None,
341        }
342    }
343
344    /// Recover a diagnostic origin from its compact public wire code.
345    ///
346    /// Unknown origin codes fail closed to `Runtime`, matching the public
347    /// boundary behavior used by the Candid facade.
348    #[must_use]
349    pub const fn from_wire_code(code: u8) -> Self {
350        match Self::from_known_wire_code(code) {
351            Some(origin) => origin,
352            None => Self::Runtime,
353        }
354    }
355}
356
357impl fmt::Debug for ErrorOrigin {
358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359        fmt_compact_code(f, u16::from(self.wire_code()))
360    }
361}
362
363///
364/// QueryErrorKind
365///
366/// Public query error category.
367///
368
369#[repr(u16)]
370#[derive(Clone, Copy, Eq, Hash, PartialEq)]
371pub enum QueryErrorKind {
372    Validate,
373    Intent,
374    Plan,
375    AccessRequirement,
376    UnorderedPagination,
377    InvalidContinuationCursor,
378    NotFound,
379    NotUnique,
380}
381
382impl fmt::Debug for QueryErrorKind {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        fmt_compact_code(f, *self as u16)
385    }
386}
387
388///
389/// QueryProjectionCode
390///
391/// Compact query projection admission/runtime identifier.
392/// Variant order is wire-order significant for public error-code offsets.
393///
394
395#[repr(u16)]
396#[derive(Clone, Copy, Eq, Hash, PartialEq)]
397pub enum QueryProjectionCode {
398    NumericLiteralRequired,
399    NumericScaleArguments,
400    NestedFieldPathPreview,
401    CaseConditionBooleanRequired,
402    NumericInputRequired,
403    TextOrBlobInputRequired,
404    TextInputRequired,
405    TextOrNullArgumentRequired,
406    IntegerOrNullArgumentRequired,
407    UnaryOperandIncompatible,
408    BinaryOperandsIncompatible,
409}
410
411impl fmt::Debug for QueryProjectionCode {
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        fmt_compact_code(f, *self as u16)
414    }
415}
416
417///
418/// QueryReadAdmissionCode
419///
420/// Compact read-admission rejection identifier.
421/// Variant order is wire-order significant for public error-code offsets.
422///
423
424#[repr(u16)]
425#[derive(Clone, Copy, Eq, Hash, PartialEq)]
426pub enum QueryReadAdmissionCode {
427    PublicQueryRequiresLimit,
428    PublicQueryRequiresIndex,
429    UnboundedFullScanRejected,
430    SortRequiresMaterialization,
431    GroupedQueryRequiresLimits,
432    GroupedQueryExceedsBudget,
433    DiagnosticLaneDoesNotExecute,
434    ReturnedRowBoundExceedsPolicy,
435    PrimaryKeyInputExceedsPolicy,
436}
437
438impl fmt::Debug for QueryReadAdmissionCode {
439    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440        fmt_compact_code(f, *self as u16)
441    }
442}
443
444///
445/// QueryResultShapeCode
446///
447/// Compact query-result shape mismatch identifier.
448/// Variant order is wire-order significant for public error-code offsets.
449///
450
451#[repr(u16)]
452#[derive(Clone, Copy, Eq, Hash, PartialEq)]
453pub enum QueryResultShapeCode {
454    ExpectedRows,
455    ExpectedGroupedRows,
456}
457
458impl fmt::Debug for QueryResultShapeCode {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        fmt_compact_code(f, *self as u16)
461    }
462}
463
464///
465/// RuntimeErrorKind
466///
467/// Public runtime error category.
468///
469
470#[repr(u16)]
471#[derive(Clone, Copy, Eq, Hash, PartialEq)]
472pub enum RuntimeErrorKind {
473    Corruption,
474    IncompatiblePersistedFormat,
475    InvariantViolation,
476    Conflict,
477    NotFound,
478    Unsupported,
479    Internal,
480}
481
482impl fmt::Debug for RuntimeErrorKind {
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        fmt_compact_code(f, *self as u16)
485    }
486}
487
488///
489/// RuntimeBoundaryCode
490///
491/// Compact public-runtime boundary identifier.
492/// Variant order is wire-order significant for public error-code offsets.
493///
494
495#[repr(u16)]
496#[derive(Clone, Copy, Eq, Hash, PartialEq)]
497pub enum RuntimeBoundaryCode {
498    SqlSurfaceControllerRequired,
499    SchemaSurfaceControllerRequired,
500    SqlQueryNoConfiguredEntities,
501    SqlQueryEntityNotFound,
502    SqlDdlTargetRequired,
503    SqlDdlEntityNotConfigured,
504    QueryResponseRowsRequired,
505    QueryResponseGroupedRowsRequired,
506    RowProjectionFieldNotConfigured,
507    SqlIntrospectionDisabled,
508    /// A complete accepted mutation omitted a required field.
509    MutationRequiredFieldMissing,
510    /// A logical write would move an accepted managed timestamp backward.
511    MutationManagedTimestampRegression,
512    /// A persisted row's stamp falls outside the accepted layout window.
513    PersistedRowLayoutOutsideAcceptedWindow,
514    /// A persisted row's physical slot count disagrees with its layout stamp.
515    PersistedRowSlotCountMismatch,
516    /// A generated field would collide with an accepted DDL-owned slot.
517    GeneratedFieldAfterDdlField,
518    /// A journaled mutation cannot reserve a representable post-commit revision.
519    JournalMutationRevisionExhausted,
520    /// A final canonical after-image violates one accepted row constraint or gate.
521    ConstraintViolation,
522    /// Accepted row-constraint metadata or its compiled program is inconsistent.
523    AcceptedRowConstraintProgramCorrupt,
524    /// A write conflicts with one incomplete accepted constraint activation.
525    ConstraintActivationWriteBlocked,
526    /// A live generated constraint activation no longer matches its proposal.
527    GeneratedConstraintActivationStale,
528    /// A caller explicitly authored a field owned by accepted database policy.
529    MutationDatabaseOwnedFieldExplicit,
530    /// A mixed structural mutation batch contained no operations.
531    MutationBatchEmpty,
532    /// A mixed structural mutation batch exceeded its operation-count bound.
533    MutationBatchTooManyItems,
534    /// A mixed structural mutation batch exceeded its staged-byte bound.
535    MutationBatchStagedBytesExceeded,
536    /// A mixed structural mutation result exceeded its encoded response bound.
537    MutationBatchResultBytesExceeded,
538    /// A mixed structural mutation batch crossed an accepted store boundary.
539    MutationBatchStoreMismatch,
540    /// A mixed structural mutation batch exceeded its distinct-entity bound.
541    MutationBatchTooManyEntities,
542    /// More than one mixed structural operation targeted the same accepted key.
543    MutationBatchDuplicateKey,
544    /// An operational report or reset endpoint requires a controller caller.
545    OperationalSurfaceControllerRequired,
546    /// An exact-key batch exceeded its admitted input item count.
547    ExactKeyBatchTooManyItems,
548    /// An exact-key batch exceeded its admitted encoded input-key bytes.
549    ExactKeyBatchInputBytesExceeded,
550    /// An exact-key batch exceeded its admitted distinct stored-row bytes.
551    ExactKeyBatchStoredBytesExceeded,
552    /// An exact-key batch exceeded its admitted logical result bytes.
553    ExactKeyBatchResultBytesExceeded,
554    /// One charged execution resource exceeded its absolute safety ceiling.
555    ExecutionBudgetExceeded,
556    /// One indivisible scalar-page unit cannot fit in an otherwise empty page envelope.
557    PageUnitTooLarge,
558    /// Database access was attempted without an active request-execution scope.
559    RequestExecutionScopeRequired,
560    /// An explicit request root conflicts with the root already active for this request.
561    RequestExecutionRootMismatch,
562    /// A successful generated SQL query reply exceeds the deployed IC query-response limit.
563    SqlQueryReplyBytesExceeded,
564    /// Startup recovery remains incomplete and ordinary database work must retry later.
565    DatabaseStartupRecoveryPending,
566    /// An application guard denied access to the generated SQL read surface.
567    SqlSurfacePolicyDenied,
568    /// An application guard denied access to the generated accepted-schema surface.
569    SchemaSurfacePolicyDenied,
570    /// A structural mutation batch exceeded its canonical prepared-commit work bound.
571    MutationBatchCommitWorkExceeded,
572    /// Retained journal debt leaves insufficient bounded convergence capacity.
573    ConvergenceBacklogPressure,
574}
575
576impl fmt::Debug for RuntimeBoundaryCode {
577    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578        fmt_compact_code(f, *self as u16)
579    }
580}
581
582///
583/// SqlFeatureCode
584///
585/// Compact SQL feature identifier used by unsupported-feature diagnostics.
586/// Variant order is wire-order significant for public error-code offsets.
587///
588
589#[repr(u16)]
590#[derive(Clone, Copy, Eq, Hash, PartialEq)]
591pub enum SqlFeatureCode {
592    AggregateFilterClause,
593    AlterStatementBeyondAlterTable,
594    AlterTableAddColumnDuplicateDefault,
595    AlterTableAddColumnModifiers,
596    AlterTableAddStatementBeyondAddColumn,
597    AlterTableAlterColumnDropUnsupportedAction,
598    AlterTableAlterColumnModifiers,
599    AlterTableAlterColumnSetUnsupportedAction,
600    AlterTableAlterColumnUnsupportedAction,
601    AlterTableAlterStatementBeyondAlterColumn,
602    AlterTableDropColumnIfExistsSyntax,
603    AlterTableDropColumnModifiers,
604    AlterTableDropStatementBeyondDropColumn,
605    AlterTableRenameColumnMissingTo,
606    AlterTableRenameColumnModifiers,
607    AlterTableRenameStatementBeyondRenameColumn,
608    AlterTableUnsupportedOperation,
609    ColumnAlias,
610    CreateIndexIfNotExistsSyntax,
611    CreateIndexKeyOrderingModifiers,
612    CreateIndexModifiers,
613    CreateStatementBeyondCreateIndex,
614    DescribeModifier,
615    DdlSchemaVersionDuplicateExpectedClause,
616    DdlSchemaVersionDuplicateSetClause,
617    DropIndexModifiers,
618    DropIndexIfExistsSyntax,
619    DropStatementBeyondDropIndex,
620    ExpressionIndexUnsupportedFunction,
621    Having,
622    Insert,
623    Join,
624    LikePatternBeyondTrailingPrefix,
625    LowerFieldPredicateUnsupported,
626    MultiStatementSql,
627    NestedAggregateInput,
628    NestedProjectionFunctionInArithmetic,
629    OrderByUnsupportedForm,
630    Other,
631    PredicateStartsWithFirstArgument,
632    QuotedIdentifiers,
633    ReturningUnsupportedShape,
634    ScalarFunctionExpressionPosition,
635    ScaleTakingNumericFunctionExpressionPosition,
636    SearchedCaseGroupedOrderBy,
637    ShowColumnsModifiers,
638    ShowEntitiesModifiers,
639    ShowIndexesModifiers,
640    ShowMemoryModifiers,
641    ShowStoresModifiers,
642    ShowUnsupportedCommand,
643    SimpleCaseExpression,
644    StandaloneLiteralProjectionItem,
645    SupportedGroupedOrderByExpressionFamily,
646    SupportedOrderByExpressionFamily,
647    UnionIntersectExcept,
648    UnsupportedFunctionNamespace,
649    Update,
650    UpperFieldPredicateUnsupported,
651    WindowFunction,
652    With,
653    NumericScaleFunctionArguments,
654    OrderByFieldNotOrderable,
655    ShowConstraintsModifiers,
656    AlterTableAddConstraintBeyondCheck,
657    AlterTableAddConstraintModifiers,
658    AlterTableDropConstraintIfExistsSyntax,
659    AlterTableDropConstraintModifiers,
660    AlterTableValidateBeyondConstraint,
661    AlterTableValidateConstraintModifiers,
662    ShowRelationsModifiers,
663}
664
665impl fmt::Debug for SqlFeatureCode {
666    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667        fmt_compact_code(f, *self as u16)
668    }
669}
670
671///
672/// SqlLoweringCode
673///
674/// Compact SQL lowering rejection identifier used after parsing succeeds but
675/// before a statement becomes canonical query intent.
676/// Variant order is wire-order significant for public error-code offsets.
677///
678
679#[repr(u16)]
680#[derive(Clone, Copy, Eq, Hash, PartialEq)]
681pub enum SqlLoweringCode {
682    EntityMismatch,
683    SelectProjectionShape,
684    SelectDistinct,
685    DistinctOrderByProjection,
686    GlobalAggregateProjection,
687    GlobalAggregateGroupBy,
688    SelectGroupByShape,
689    GroupedProjectionExplicitListRequired,
690    GroupedProjectionAggregateRequired,
691    GroupedProjectionNonGroupField,
692    GroupedProjectionScalarAfterAggregate,
693    HavingRequiresGroupBy,
694    SelectHavingShape,
695    AggregateInputExpressions,
696    WhereExpressionShape,
697    ParameterPlacement,
698    SqlDdlExecutionUnsupported,
699}
700
701impl fmt::Debug for SqlLoweringCode {
702    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
703        fmt_compact_code(f, *self as u16)
704    }
705}
706
707///
708/// SqlSurfaceMismatchCode
709///
710/// Compact SQL endpoint surface mismatch identifier.
711/// Variant order is wire-order significant for public error-code offsets.
712///
713
714#[repr(u16)]
715#[derive(Clone, Copy, Eq, Hash, PartialEq)]
716pub enum SqlSurfaceMismatchCode {
717    QueryRejectsInsert,
718    QueryRejectsUpdate,
719    QueryRejectsDelete,
720    MutationRejectsSelect,
721    MutationRejectsExplain,
722    MutationRejectsDescribe,
723    MutationRejectsShowIndexes,
724    MutationRejectsShowColumns,
725    MutationRejectsShowEntities,
726    MutationRejectsShowStores,
727    MutationRejectsShowMemory,
728    MutationRequiresExplicitUpdateIntent,
729    MutationRejectsShowConstraints,
730    MutationRejectsShowRelations,
731}
732
733impl fmt::Debug for SqlSurfaceMismatchCode {
734    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
735        fmt_compact_code(f, *self as u16)
736    }
737}
738
739///
740/// SqlWriteBoundaryCode
741///
742/// Compact SQL write fail-closed boundary identifier.
743/// Variant order is wire-order significant for public error-code offsets.
744///
745
746#[repr(u16)]
747#[derive(Clone, Copy, Eq, Hash, PartialEq)]
748pub enum SqlWriteBoundaryCode {
749    PrimaryKeyLiteralShape,
750    PrimaryKeyLiteralIncompatible,
751    MissingPrimaryKey,
752    MissingRequiredFields,
753    ExplicitManagedField,
754    ExplicitGeneratedField,
755    InsertSelectRequiresScalar,
756    InsertSelectAggregateProjection,
757    InsertSelectWidthMismatch,
758    UpdatePrimaryKeyMutation,
759    InvalidFieldLiteral,
760    UnknownReturningField,
761    DuplicateReturningField,
762    UpdateMissingWherePredicate,
763    WriteOrderByUnsupportedShape,
764    ReturningResponseTooLarge,
765    ReturningRowsTooMany,
766    StagedRowsTooMany,
767    InsertDefaultRequiredField,
768    UpdateDefaultRequiredField,
769    UpdateDefaultDatabaseOwnedField,
770    ExactUpdateAssertionRequired,
771    ExactUpdateAssertionTooHigh,
772    ExactUpdateAffectedRowsExceeded,
773    ExactUpdateWindowUnsupported,
774    ExactUpdateScanBudgetExceeded,
775    ResumableUpdateWindowUnsupported,
776    ResumableUpdateReturningUnsupported,
777    ResumableUpdateRequiresJournaledStore,
778    ResumableUpdateAssignedFieldHasGlobalConstraint,
779    ResumableUpdateScopeDependsOnAssignedField,
780    ResumableUpdateScopeDependencyUnknown,
781    ResumableUpdateContinuationMalformed,
782    ResumableUpdateContinuationTargetMismatch,
783    ResumableUpdateContinuationSchemaMismatch,
784    ResumableUpdateContinuationScopeMismatch,
785    ResumableUpdateContinuationPatchMismatch,
786    ResumableUpdateContinuationBatchPolicyMismatch,
787    ResumableUpdateSingleRowResourceExceeded,
788    ResumableUpdateManagedFieldHasGlobalConstraint,
789    ResumableUpdateContinuationOperationMismatch,
790}
791
792impl fmt::Debug for SqlWriteBoundaryCode {
793    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
794        fmt_compact_code(f, *self as u16)
795    }
796}
797
798///
799/// SchemaDdlAdmissionCode
800///
801/// Compact SQL DDL admission rejection reason.
802/// Variant order is wire-order significant for public error-code offsets.
803///
804
805#[repr(u16)]
806#[derive(Clone, Copy, Eq, Hash, PartialEq)]
807pub enum SchemaDdlAdmissionCode {
808    MissingExpectedSchemaVersion,
809    MissingNextSchemaVersion,
810    StaleExpectedSchemaVersion,
811    InvalidExpectedSchemaVersion,
812    InvalidNextSchemaVersion,
813    AcceptedSchemaChangeWithoutVersionBump,
814    EmptyVersionBump,
815    VersionGap,
816    VersionRollback,
817    FingerprintMethodMismatch,
818    UnsupportedTransitionClass,
819    PhysicalRunnerMissing,
820    ValidationFailed,
821    PublicationRaceLost,
822    InvalidAddColumnDefault,
823    InvalidAlterColumnDefault,
824    GeneratedIndexDropRejected,
825    SchemaRewriteRequiresMigration,
826    SchemaTransitionBudgetExceeded,
827    GeneratedFieldDefaultChangeRejected,
828    GeneratedFieldNullabilityChangeRejected,
829    RowLayoutVersionExhausted,
830}
831
832impl fmt::Debug for SchemaDdlAdmissionCode {
833    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834        fmt_compact_code(f, *self as u16)
835    }
836}
837
838/// Machine-readable source-migration rejection or lifecycle finding.
839#[repr(u16)]
840#[derive(Clone, Copy, Eq, Hash, PartialEq)]
841pub enum SchemaMigrationCode {
842    Unadopted,
843    MissingMigration,
844    VersionGap,
845    Downgrade,
846    EmptyEntityVersionBump,
847    DuplicateEntityTransition,
848    StaleAcceptedHead,
849    PlanChanged,
850    DuplicateRenameSource,
851    DuplicateRenameTarget,
852    UnknownFromObject,
853    UnknownToObject,
854    KindMismatch,
855    IdentityConflict,
856    IncompleteRenameCoverage,
857    UnexplainedSchemaDifference,
858    UnsupportedTransform,
859    TransformFinding,
860    UniqueIndexFinding,
861    RelationFinding,
862    ConstraintFinding,
863    PhysicalRunnerMissing,
864    MigrationInProgress,
865    AbortTooLate,
866    ProgressCorrupt,
867    CandidateMismatch,
868    PublicationRaceLost,
869}
870
871impl SchemaMigrationCode {
872    /// Return the broad diagnostic category for this migration result.
873    #[must_use]
874    pub const fn diagnostic_code(self) -> DiagnosticCode {
875        match self {
876            Self::StaleAcceptedHead
877            | Self::PlanChanged
878            | Self::IdentityConflict
879            | Self::MigrationInProgress
880            | Self::AbortTooLate
881            | Self::PublicationRaceLost => DiagnosticCode::RuntimeConflict,
882            Self::ProgressCorrupt | Self::CandidateMismatch => DiagnosticCode::RuntimeCorruption,
883            Self::Unadopted
884            | Self::MissingMigration
885            | Self::VersionGap
886            | Self::Downgrade
887            | Self::EmptyEntityVersionBump
888            | Self::DuplicateEntityTransition
889            | Self::DuplicateRenameSource
890            | Self::DuplicateRenameTarget
891            | Self::UnknownFromObject
892            | Self::UnknownToObject
893            | Self::KindMismatch
894            | Self::IncompleteRenameCoverage
895            | Self::UnexplainedSchemaDifference
896            | Self::UnsupportedTransform
897            | Self::TransformFinding
898            | Self::UniqueIndexFinding
899            | Self::RelationFinding
900            | Self::ConstraintFinding
901            | Self::PhysicalRunnerMissing => DiagnosticCode::RuntimeUnsupported,
902        }
903    }
904}
905
906impl fmt::Debug for SchemaMigrationCode {
907    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
908        fmt_compact_code(f, *self as u16)
909    }
910}
911
912///
913/// DiagnosticDetail
914///
915/// Small structured diagnostic payload for callers and CLI rendering.
916///
917
918#[remain::sorted]
919#[derive(Clone, Copy, Eq, PartialEq)]
920pub enum DiagnosticDetail {
921    QueryKind { kind: QueryErrorKind },
922    QueryProjection { reason: QueryProjectionCode },
923    QueryReadAdmission { reason: QueryReadAdmissionCode },
924    QueryResultShape { reason: QueryResultShapeCode },
925    RuntimeBoundary { boundary: RuntimeBoundaryCode },
926    RuntimeKind { kind: RuntimeErrorKind },
927    SchemaDdlAdmission { reason: SchemaDdlAdmissionCode },
928    SchemaMigration { reason: SchemaMigrationCode },
929    SqlLowering { reason: SqlLoweringCode },
930    SqlSurfaceMismatch { mismatch: SqlSurfaceMismatchCode },
931    SqlWriteBoundary { boundary: SqlWriteBoundaryCode },
932    UnsupportedSqlFeature { feature: SqlFeatureCode },
933}
934
935impl fmt::Debug for DiagnosticDetail {
936    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
937        fmt_compact_code(
938            f,
939            ErrorCode::from_parts(self.diagnostic_code(), Some(*self)).raw(),
940        )
941    }
942}
943
944///
945/// Diagnostic
946///
947/// Compact public diagnostic payload.
948///
949
950#[derive(Clone, Eq, PartialEq)]
951pub struct Diagnostic {
952    code: DiagnosticCode,
953    origin: ErrorOrigin,
954    detail: Option<DiagnosticDetail>,
955}
956
957impl Diagnostic {
958    /// Build a compact diagnostic from a code and optional structured detail.
959    #[must_use]
960    pub const fn new(
961        code: DiagnosticCode,
962        origin: ErrorOrigin,
963        detail: Option<DiagnosticDetail>,
964    ) -> Self {
965        Self {
966            code,
967            origin,
968            detail,
969        }
970    }
971
972    /// Build a compact diagnostic using the code's default origin.
973    #[must_use]
974    pub const fn from_code(code: DiagnosticCode) -> Self {
975        Self::new(code, code.origin(), None)
976    }
977
978    /// Return the stable diagnostic code.
979    #[must_use]
980    pub const fn code(&self) -> DiagnosticCode {
981        self.code
982    }
983
984    /// Return the diagnostic class.
985    #[must_use]
986    pub const fn class(&self) -> ErrorClass {
987        self.code.class()
988    }
989
990    /// Return the subsystem origin.
991    #[must_use]
992    pub const fn origin(&self) -> ErrorOrigin {
993        self.origin
994    }
995
996    /// Return structured diagnostic detail, when available.
997    #[must_use]
998    pub const fn detail(&self) -> Option<&DiagnosticDetail> {
999        self.detail.as_ref()
1000    }
1001
1002    /// Return the numeric public wire code for this diagnostic.
1003    #[must_use]
1004    pub const fn error_code(&self) -> ErrorCode {
1005        ErrorCode::from_parts(self.code, self.detail)
1006    }
1007}
1008
1009impl fmt::Debug for Diagnostic {
1010    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1011        write!(f, "{}@{}", self.error_code().raw(), self.origin.wire_code())
1012    }
1013}
1014
1015fn fmt_compact_code(f: &mut fmt::Formatter<'_>, raw: u16) -> fmt::Result {
1016    write!(f, "{raw}")
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021    use super::{
1022        Diagnostic, DiagnosticCode, DiagnosticDetail, ErrorClass, ErrorCode, ErrorOrigin,
1023        QueryProjectionCode, QueryReadAdmissionCode, SqlFeatureCode, SqlLoweringCode,
1024        SqlWriteBoundaryCode,
1025        registry::{DETAIL_ERROR_CODES, ORDERED_ERROR_CODES},
1026    };
1027
1028    #[test]
1029    fn diagnostic_from_code_uses_default_origin() {
1030        let diagnostic = Diagnostic::from_code(DiagnosticCode::QueryPlan);
1031
1032        assert_eq!(diagnostic.code(), DiagnosticCode::QueryPlan);
1033        assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1034    }
1035
1036    #[test]
1037    fn diagnostic_code_reports_broad_class() {
1038        assert_eq!(
1039            DiagnosticCode::QueryUnsupportedSqlFeature.class(),
1040            ErrorClass::Unsupported
1041        );
1042        assert_eq!(
1043            DiagnosticCode::QuerySqlSurfaceMismatch.class(),
1044            ErrorClass::Unsupported
1045        );
1046        assert_eq!(DiagnosticCode::QueryPlan.class(), ErrorClass::Query);
1047        assert_eq!(
1048            DiagnosticCode::StoreCorruption.class(),
1049            ErrorClass::Corruption
1050        );
1051    }
1052
1053    #[test]
1054    fn class_and_origin_wire_codes_round_trip() {
1055        for (class, raw) in [
1056            (ErrorClass::Query, 1),
1057            (ErrorClass::Corruption, 2),
1058            (ErrorClass::IncompatiblePersistedFormat, 3),
1059            (ErrorClass::NotFound, 4),
1060            (ErrorClass::Internal, 5),
1061            (ErrorClass::Conflict, 6),
1062            (ErrorClass::Unsupported, 7),
1063            (ErrorClass::InvariantViolation, 8),
1064        ] {
1065            assert_eq!(class.wire_code(), raw);
1066            assert_eq!(ErrorClass::from_wire_code(raw), Some(class));
1067            assert_eq!(format!("{class:?}"), raw.to_string());
1068        }
1069
1070        for (origin, raw) in [
1071            (ErrorOrigin::Cursor, 1),
1072            (ErrorOrigin::Executor, 2),
1073            (ErrorOrigin::Identity, 3),
1074            (ErrorOrigin::Index, 4),
1075            (ErrorOrigin::Interface, 5),
1076            (ErrorOrigin::Planner, 6),
1077            (ErrorOrigin::Query, 7),
1078            (ErrorOrigin::Recovery, 8),
1079            (ErrorOrigin::Response, 9),
1080            (ErrorOrigin::Runtime, 10),
1081            (ErrorOrigin::Serialize, 11),
1082            (ErrorOrigin::Store, 12),
1083        ] {
1084            assert_eq!(origin.wire_code(), raw);
1085            assert_eq!(ErrorOrigin::from_known_wire_code(raw), Some(origin));
1086            assert_eq!(ErrorOrigin::from_wire_code(raw), origin);
1087            assert_eq!(format!("{origin:?}"), raw.to_string());
1088        }
1089
1090        assert_eq!(ErrorClass::from_wire_code(0), None);
1091        assert_eq!(ErrorOrigin::from_known_wire_code(0), None);
1092        assert_eq!(ErrorOrigin::from_wire_code(0), ErrorOrigin::Runtime);
1093    }
1094
1095    #[test]
1096    fn public_error_codes_are_sequential() {
1097        let first = ORDERED_ERROR_CODES
1098            .first()
1099            .expect("public error-code registry is non-empty")
1100            .raw();
1101
1102        assert_eq!(first, 1);
1103
1104        for (index, code) in ORDERED_ERROR_CODES.iter().enumerate() {
1105            let expected = first + u16::try_from(index).expect("test error-code index fits u16");
1106            assert_eq!(code.raw(), expected);
1107            assert_eq!(ErrorCode::known(code.raw()), Some(*code));
1108            assert!(code.is_known());
1109        }
1110
1111        let last = ORDERED_ERROR_CODES
1112            .last()
1113            .expect("public error-code registry is non-empty")
1114            .raw();
1115
1116        assert_eq!(last, 285);
1117    }
1118
1119    #[test]
1120    fn all_public_error_codes_round_trip_through_diagnostic_parts() {
1121        let first = ORDERED_ERROR_CODES
1122            .first()
1123            .expect("public error-code registry is non-empty")
1124            .raw();
1125        let last = ORDERED_ERROR_CODES
1126            .last()
1127            .expect("public error-code registry is non-empty")
1128            .raw();
1129
1130        for raw in first..=last {
1131            let code = ErrorCode::from_raw(raw);
1132            let diagnostic_code = code.diagnostic_code();
1133            let diagnostic_detail = code.diagnostic_detail();
1134            let rebuilt = ErrorCode::from_parts(diagnostic_code, diagnostic_detail);
1135
1136            assert_eq!(rebuilt.raw(), raw);
1137
1138            let diagnostic = code.diagnostic(ErrorOrigin::Runtime);
1139
1140            assert_eq!(diagnostic.code(), diagnostic_code);
1141            assert_eq!(diagnostic.detail(), diagnostic_detail.as_ref());
1142            assert_eq!(diagnostic.error_code().raw(), raw);
1143        }
1144    }
1145
1146    #[test]
1147    fn invalid_raw_error_codes_fail_closed_to_runtime_internal() {
1148        let first_unknown = ORDERED_ERROR_CODES
1149            .last()
1150            .expect("public error-code registry is non-empty")
1151            .raw()
1152            .checked_add(1)
1153            .expect("public error-code registry retains an unknown successor");
1154
1155        for raw in [0, first_unknown, u16::MAX] {
1156            let code = ErrorCode::from_raw(raw);
1157
1158            assert_eq!(ErrorCode::known(raw), None);
1159            assert!(!code.is_known());
1160            assert_eq!(code.diagnostic_code(), DiagnosticCode::RuntimeInternal);
1161            assert_eq!(code.diagnostic_detail(), None);
1162            assert_eq!(code.class(), ErrorClass::Internal);
1163
1164            let diagnostic = code.diagnostic(ErrorOrigin::Query);
1165
1166            assert_eq!(diagnostic.code(), DiagnosticCode::RuntimeInternal);
1167            assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1168            assert_eq!(diagnostic.detail(), None);
1169            assert_eq!(diagnostic.error_code(), ErrorCode::RUNTIME_INTERNAL);
1170        }
1171    }
1172
1173    #[test]
1174    fn from_parts_requires_detail_to_match_broad_code() {
1175        let detail = Some(DiagnosticDetail::UnsupportedSqlFeature {
1176            feature: SqlFeatureCode::Join,
1177        });
1178
1179        assert_eq!(
1180            ErrorCode::from_parts(DiagnosticCode::QueryUnsupportedSqlFeature, detail),
1181            ErrorCode::SQL_FEATURE_JOIN
1182        );
1183        assert_eq!(
1184            ErrorCode::from_parts(DiagnosticCode::QueryPlan, detail),
1185            ErrorCode::QUERY_PLAN
1186        );
1187    }
1188
1189    #[test]
1190    fn detail_bearing_registry_entries_round_trip_directly() {
1191        assert!(!DETAIL_ERROR_CODES.is_empty());
1192
1193        for &(code, diagnostic_code, detail) in DETAIL_ERROR_CODES {
1194            assert_eq!(ErrorCode::from_parts(diagnostic_code, Some(detail)), code);
1195            assert_eq!(code.diagnostic_code(), diagnostic_code);
1196            assert_eq!(code.diagnostic_detail(), Some(detail));
1197            assert_eq!(detail.diagnostic_code(), diagnostic_code);
1198        }
1199    }
1200
1201    #[test]
1202    fn diagnostic_detail_reports_generated_broad_code() {
1203        let detail = DiagnosticDetail::UnsupportedSqlFeature {
1204            feature: SqlFeatureCode::Join,
1205        };
1206
1207        assert_eq!(
1208            detail.diagnostic_code(),
1209            DiagnosticCode::QueryUnsupportedSqlFeature
1210        );
1211        assert_eq!(format!("{detail:?}"), "65");
1212    }
1213
1214    #[test]
1215    fn public_error_codes_reconstruct_shifted_details() {
1216        assert_eq!(
1217            ErrorCode::QUERY_UNKNOWN_AGGREGATE_TARGET_FIELD.diagnostic_code(),
1218            DiagnosticCode::QueryUnknownAggregateTargetField
1219        );
1220        assert_eq!(
1221            ErrorCode::SQL_FEATURE_JOIN.diagnostic_detail(),
1222            Some(DiagnosticDetail::UnsupportedSqlFeature {
1223                feature: SqlFeatureCode::Join,
1224            })
1225        );
1226        assert_eq!(
1227            ErrorCode::QUERY_PROJECTION_NUMERIC_LITERAL_REQUIRED.diagnostic_detail(),
1228            Some(DiagnosticDetail::QueryProjection {
1229                reason: QueryProjectionCode::NumericLiteralRequired,
1230            })
1231        );
1232        assert_eq!(
1233            ErrorCode::QUERY_READ_PUBLIC_REQUIRES_LIMIT.diagnostic_detail(),
1234            Some(DiagnosticDetail::QueryReadAdmission {
1235                reason: QueryReadAdmissionCode::PublicQueryRequiresLimit,
1236            })
1237        );
1238        assert_eq!(
1239            ErrorCode::SQL_LOWERING_DISTINCT_ORDER_BY_PROJECTION.diagnostic_detail(),
1240            Some(DiagnosticDetail::SqlLowering {
1241                reason: SqlLoweringCode::DistinctOrderByProjection,
1242            })
1243        );
1244        assert_eq!(
1245            ErrorCode::SQL_WRITE_RETURNING_RESPONSE_TOO_LARGE.diagnostic_detail(),
1246            Some(DiagnosticDetail::SqlWriteBoundary {
1247                boundary: SqlWriteBoundaryCode::ReturningResponseTooLarge,
1248            })
1249        );
1250        assert_eq!(
1251            ErrorCode::SQL_WRITE_RETURNING_ROWS_TOO_MANY.diagnostic_detail(),
1252            Some(DiagnosticDetail::SqlWriteBoundary {
1253                boundary: SqlWriteBoundaryCode::ReturningRowsTooMany,
1254            })
1255        );
1256    }
1257}