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    /// An admitted exact count has no available exact-cardinality metadata.
41    QueryExactCountMetadataUnavailable,
42    QueryIntent,
43    QueryInvalidContinuationCursor,
44    QueryNotFound,
45    QueryNotUnique,
46    QueryNumericNotRepresentable,
47    QueryNumericOverflow,
48    QueryPlan,
49    QueryReadAdmission,
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::QueryExactCountMetadataUnavailable
82            | Self::QueryUnsupportedSqlFeature
83            | Self::QueryUnknownAggregateTargetField
84            | Self::QueryUnsupportedProjection
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::QueryUnorderedPagination
97            | Self::QueryInvalidContinuationCursor
98            | Self::QueryNotUnique
99            | Self::QueryNumericOverflow
100            | Self::QueryNumericNotRepresentable
101            | Self::SchemaDdlAdmission => ErrorClass::Query,
102        }
103    }
104
105    /// Return the default diagnostic origin for this code.
106    #[must_use]
107    pub const fn origin(self) -> ErrorOrigin {
108        match self {
109            Self::StoreNotFound | Self::StoreCorruption | Self::StoreInvariantViolation => {
110                ErrorOrigin::Store
111            }
112            Self::RuntimeCorruption
113            | Self::RuntimeIncompatiblePersistedFormat
114            | Self::RuntimeInvariantViolation
115            | Self::RuntimeConflict
116            | Self::RuntimeNotFound
117            | Self::RuntimeUnsupported
118            | Self::RuntimeInternal => ErrorOrigin::Runtime,
119            Self::QueryExactCountMetadataUnavailable
120            | Self::QueryValidate
121            | Self::QueryIntent
122            | Self::QueryPlan
123            | Self::QueryReadAdmission
124            | Self::QueryUnorderedPagination
125            | Self::QueryInvalidContinuationCursor
126            | Self::QueryNotFound
127            | Self::QueryNotUnique
128            | Self::QueryNumericOverflow
129            | Self::QueryNumericNotRepresentable
130            | Self::QueryUnknownAggregateTargetField
131            | Self::QueryUnsupportedProjection
132            | Self::QueryUnsupportedSqlFeature
133            | Self::QuerySqlSurfaceMismatch
134            | Self::QuerySqlWriteBoundary
135            | Self::SchemaDdlAdmission => ErrorOrigin::Query,
136        }
137    }
138
139    /// Return the compact public wire code for this broad diagnostic reason.
140    #[must_use]
141    pub const fn error_code(self) -> ErrorCode {
142        match self {
143            Self::QueryExactCountMetadataUnavailable => {
144                ErrorCode::QUERY_EXACT_COUNT_METADATA_UNAVAILABLE
145            }
146            Self::QueryValidate => ErrorCode::QUERY_VALIDATE,
147            Self::QueryIntent => ErrorCode::QUERY_INTENT,
148            Self::QueryPlan => ErrorCode::QUERY_PLAN,
149            Self::QueryReadAdmission => ErrorCode::QUERY_READ_ADMISSION,
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::QueryUnsupportedSqlFeature => ErrorCode::QUERY_UNSUPPORTED_SQL_FEATURE,
161            Self::QuerySqlSurfaceMismatch => ErrorCode::QUERY_SQL_SURFACE_MISMATCH,
162            Self::QuerySqlWriteBoundary => ErrorCode::QUERY_SQL_WRITE_BOUNDARY,
163            Self::SchemaDdlAdmission => ErrorCode::SCHEMA_DDL_ADMISSION,
164            Self::StoreNotFound => ErrorCode::STORE_NOT_FOUND,
165            Self::StoreCorruption => ErrorCode::STORE_CORRUPTION,
166            Self::StoreInvariantViolation => ErrorCode::STORE_INVARIANT_VIOLATION,
167            Self::RuntimeCorruption => ErrorCode::RUNTIME_CORRUPTION,
168            Self::RuntimeIncompatiblePersistedFormat => {
169                ErrorCode::RUNTIME_INCOMPATIBLE_PERSISTED_FORMAT
170            }
171            Self::RuntimeInvariantViolation => ErrorCode::RUNTIME_INVARIANT_VIOLATION,
172            Self::RuntimeConflict => ErrorCode::RUNTIME_CONFLICT,
173            Self::RuntimeNotFound => ErrorCode::RUNTIME_NOT_FOUND,
174            Self::RuntimeUnsupported => ErrorCode::RUNTIME_UNSUPPORTED,
175            Self::RuntimeInternal => ErrorCode::RUNTIME_INTERNAL,
176        }
177    }
178}
179
180impl fmt::Debug for DiagnosticCode {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        fmt_compact_code(f, self.error_code().raw())
183    }
184}
185
186///
187/// ErrorCode
188///
189/// Stable numeric public error identity.
190///
191/// The public Candid `icydb::Error` stores this value as `nat16` so canister
192/// interfaces do not retain rich diagnostic enum labels. Rich diagnostics can
193/// still be reconstructed by host-side tooling from this leaf code. Before
194/// 1.0.0, the code space is hard-cut to a single compact sequential range.
195///
196
197#[derive(Clone, Copy, Eq, Hash, PartialEq)]
198pub struct ErrorCode(u16);
199
200mod registry;
201
202impl ErrorCode {
203    /// Build an error code from its raw public wire value.
204    #[must_use]
205    pub const fn from_raw(raw: u16) -> Self {
206        Self(raw)
207    }
208
209    /// Return the raw public wire value.
210    #[must_use]
211    pub const fn raw(self) -> u16 {
212        self.0
213    }
214}
215
216impl fmt::Debug for ErrorCode {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        fmt_compact_code(f, self.raw())
219    }
220}
221
222///
223/// ErrorClass
224///
225/// Broad diagnostic class used for recovery decisions.
226///
227
228#[remain::sorted]
229#[derive(Clone, Copy, Eq, Hash, PartialEq)]
230pub enum ErrorClass {
231    Conflict,
232    Corruption,
233    IncompatiblePersistedFormat,
234    Internal,
235    InvariantViolation,
236    NotFound,
237    Query,
238    Unsupported,
239}
240
241impl ErrorClass {
242    /// Return the compact public wire code for this diagnostic class.
243    #[must_use]
244    pub const fn wire_code(self) -> u8 {
245        match self {
246            Self::Query => 1,
247            Self::Corruption => 2,
248            Self::IncompatiblePersistedFormat => 3,
249            Self::NotFound => 4,
250            Self::Internal => 5,
251            Self::Conflict => 6,
252            Self::Unsupported => 7,
253            Self::InvariantViolation => 8,
254        }
255    }
256
257    /// Recover a diagnostic class from its compact public wire code.
258    #[must_use]
259    pub const fn from_wire_code(code: u8) -> Option<Self> {
260        match code {
261            1 => Some(Self::Query),
262            2 => Some(Self::Corruption),
263            3 => Some(Self::IncompatiblePersistedFormat),
264            4 => Some(Self::NotFound),
265            5 => Some(Self::Internal),
266            6 => Some(Self::Conflict),
267            7 => Some(Self::Unsupported),
268            8 => Some(Self::InvariantViolation),
269            _ => None,
270        }
271    }
272}
273
274impl fmt::Debug for ErrorClass {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        fmt_compact_code(f, u16::from(self.wire_code()))
277    }
278}
279
280///
281/// ErrorOrigin
282///
283/// Subsystem that owns the diagnostic.
284///
285
286#[remain::sorted]
287#[derive(Clone, Copy, Eq, Hash, PartialEq)]
288pub enum ErrorOrigin {
289    Cursor,
290    Executor,
291    Identity,
292    Index,
293    Interface,
294    Planner,
295    Query,
296    Recovery,
297    Response,
298    Runtime,
299    Serialize,
300    Store,
301}
302
303impl ErrorOrigin {
304    /// Return the compact public wire code for this diagnostic origin.
305    #[must_use]
306    pub const fn wire_code(self) -> u8 {
307        match self {
308            Self::Cursor => 1,
309            Self::Executor => 2,
310            Self::Identity => 3,
311            Self::Index => 4,
312            Self::Interface => 5,
313            Self::Planner => 6,
314            Self::Query => 7,
315            Self::Recovery => 8,
316            Self::Response => 9,
317            Self::Runtime => 10,
318            Self::Serialize => 11,
319            Self::Store => 12,
320        }
321    }
322
323    /// Recover a known diagnostic origin from its compact public wire code.
324    #[must_use]
325    pub const fn from_known_wire_code(code: u8) -> Option<Self> {
326        match code {
327            1 => Some(Self::Cursor),
328            2 => Some(Self::Executor),
329            3 => Some(Self::Identity),
330            4 => Some(Self::Index),
331            5 => Some(Self::Interface),
332            6 => Some(Self::Planner),
333            7 => Some(Self::Query),
334            8 => Some(Self::Recovery),
335            9 => Some(Self::Response),
336            10 => Some(Self::Runtime),
337            11 => Some(Self::Serialize),
338            12 => Some(Self::Store),
339            _ => None,
340        }
341    }
342
343    /// Recover a diagnostic origin from its compact public wire code.
344    ///
345    /// Unknown origin codes fail closed to `Runtime`, matching the public
346    /// boundary behavior used by the Candid facade.
347    #[must_use]
348    pub const fn from_wire_code(code: u8) -> Self {
349        match Self::from_known_wire_code(code) {
350            Some(origin) => origin,
351            None => Self::Runtime,
352        }
353    }
354}
355
356impl fmt::Debug for ErrorOrigin {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        fmt_compact_code(f, u16::from(self.wire_code()))
359    }
360}
361
362///
363/// QueryErrorKind
364///
365/// Public query error category.
366///
367
368#[repr(u16)]
369#[derive(Clone, Copy, Eq, Hash, PartialEq)]
370pub enum QueryErrorKind {
371    Validate,
372    Intent,
373    Plan,
374    UnorderedPagination,
375    InvalidContinuationCursor,
376    NotFound,
377    NotUnique,
378}
379
380impl fmt::Debug for QueryErrorKind {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        fmt_compact_code(f, *self as u16)
383    }
384}
385
386///
387/// QueryProjectionCode
388///
389/// Compact query projection admission/runtime identifier.
390/// Variant order is wire-order significant for public error-code offsets.
391///
392
393#[repr(u16)]
394#[derive(Clone, Copy, Eq, Hash, PartialEq)]
395pub enum QueryProjectionCode {
396    NumericLiteralRequired,
397    NumericScaleArguments,
398    NestedFieldPathPreview,
399    CaseConditionBooleanRequired,
400    NumericInputRequired,
401    TextOrBlobInputRequired,
402    TextInputRequired,
403    TextOrNullArgumentRequired,
404    IntegerOrNullArgumentRequired,
405    UnaryOperandIncompatible,
406    BinaryOperandsIncompatible,
407}
408
409impl fmt::Debug for QueryProjectionCode {
410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411        fmt_compact_code(f, *self as u16)
412    }
413}
414
415///
416/// QueryReadAdmissionCode
417///
418/// Compact read-admission rejection identifier.
419/// Variant order is wire-order significant for public error-code offsets.
420///
421
422#[repr(u16)]
423#[derive(Clone, Copy, Eq, Hash, PartialEq)]
424pub enum QueryReadAdmissionCode {
425    PublicQueryRequiresLimit,
426    PublicQueryRequiresIndex,
427    UnboundedFullScanRejected,
428    SortRequiresMaterialization,
429    GroupedQueryRequiresLimits,
430    GroupedQueryExceedsBudget,
431    DiagnosticLaneDoesNotExecute,
432    ReturnedRowBoundExceedsPolicy,
433    PrimaryKeyInputExceedsPolicy,
434    /// Authored query expressions or values exceed the shared depth ceiling.
435    InputDepthExceeded,
436    /// Authored query components exceed the shared node ceiling.
437    InputNodesExceeded,
438    /// Authored query payload exceeds the shared byte ceiling.
439    InputBytesExceeded,
440    /// Logical explain describes a request before any cursor boundary.
441    ExplainDoesNotAcceptCursor,
442}
443
444impl fmt::Debug for QueryReadAdmissionCode {
445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446        fmt_compact_code(f, *self as u16)
447    }
448}
449
450///
451/// RuntimeErrorKind
452///
453/// Public runtime error category.
454///
455
456#[repr(u16)]
457#[derive(Clone, Copy, Eq, Hash, PartialEq)]
458pub enum RuntimeErrorKind {
459    Corruption,
460    IncompatiblePersistedFormat,
461    InvariantViolation,
462    Conflict,
463    NotFound,
464    Unsupported,
465    Internal,
466}
467
468impl fmt::Debug for RuntimeErrorKind {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        fmt_compact_code(f, *self as u16)
471    }
472}
473
474///
475/// RuntimeBoundaryCode
476///
477/// Compact public-runtime boundary identifier.
478/// Variant order is wire-order significant for public error-code offsets.
479///
480
481#[repr(u16)]
482#[derive(Clone, Copy, Eq, Hash, PartialEq)]
483pub enum RuntimeBoundaryCode {
484    SqlSurfaceControllerRequired,
485    SchemaSurfaceControllerRequired,
486    SqlQueryNoConfiguredEntities,
487    SqlQueryEntityNotFound,
488    SqlDdlTargetRequired,
489    SqlDdlEntityNotConfigured,
490    SqlIntrospectionDisabled,
491    /// A complete accepted mutation omitted a required field.
492    MutationRequiredFieldMissing,
493    /// A logical write would move an accepted managed timestamp backward.
494    MutationManagedTimestampRegression,
495    /// A persisted row's stamp falls outside the accepted layout window.
496    PersistedRowLayoutOutsideAcceptedWindow,
497    /// A persisted row's physical slot count disagrees with its layout stamp.
498    PersistedRowSlotCountMismatch,
499    /// A generated field would collide with an accepted DDL-owned slot.
500    GeneratedFieldAfterDdlField,
501    /// A journaled mutation cannot reserve a representable post-commit revision.
502    JournalMutationRevisionExhausted,
503    /// A final canonical after-image violates one accepted row constraint or gate.
504    ConstraintViolation,
505    /// Accepted row-constraint metadata or its compiled program is inconsistent.
506    AcceptedRowConstraintProgramCorrupt,
507    /// A write conflicts with one incomplete accepted constraint activation.
508    ConstraintActivationWriteBlocked,
509    /// A live generated constraint activation no longer matches its proposal.
510    GeneratedConstraintActivationStale,
511    /// A caller explicitly authored a field owned by accepted database policy.
512    MutationDatabaseOwnedFieldExplicit,
513    /// A mixed structural mutation batch contained no operations.
514    MutationBatchEmpty,
515    /// A mixed structural mutation batch exceeded its operation-count bound.
516    MutationBatchTooManyItems,
517    /// A mixed structural mutation batch exceeded its staged-byte bound.
518    MutationBatchStagedBytesExceeded,
519    /// A mixed structural mutation result exceeded its encoded response bound.
520    MutationBatchResultBytesExceeded,
521    /// A mixed structural mutation batch crossed an accepted store boundary.
522    MutationBatchStoreMismatch,
523    /// A mixed structural mutation batch exceeded its distinct-entity bound.
524    MutationBatchTooManyEntities,
525    /// More than one mixed structural operation targeted the same accepted key.
526    MutationBatchDuplicateKey,
527    /// An operational report or reset endpoint requires a controller caller.
528    OperationalSurfaceControllerRequired,
529    /// An exact-key batch exceeded its admitted input item count.
530    ExactKeyBatchTooManyItems,
531    /// An exact-key batch exceeded its admitted encoded input-key bytes.
532    ExactKeyBatchInputBytesExceeded,
533    /// An exact-key batch exceeded its admitted distinct stored-row bytes.
534    ExactKeyBatchStoredBytesExceeded,
535    /// An exact-key batch exceeded its admitted logical result bytes.
536    ExactKeyBatchResultBytesExceeded,
537    /// One charged execution resource exceeded its absolute safety ceiling.
538    ExecutionBudgetExceeded,
539    /// One indivisible scalar-page unit cannot fit in an otherwise empty page envelope.
540    PageUnitTooLarge,
541    /// Database access was attempted without an active request-execution scope.
542    RequestExecutionScopeRequired,
543    /// An explicit request root conflicts with the root already active for this request.
544    RequestExecutionRootMismatch,
545    /// A successful generated SQL query reply exceeds the deployed IC query-response limit.
546    SqlQueryReplyBytesExceeded,
547    QueryExplainOutputExceeded,
548    /// A derived logical explain access tree exceeds the diagnostic depth limit.
549    QueryExplainDepthExceeded,
550    /// Startup recovery remains incomplete and ordinary database work must retry later.
551    DatabaseStartupRecoveryPending,
552    /// An application guard denied access to the generated SQL read surface.
553    SqlSurfacePolicyDenied,
554    /// An application guard denied access to the generated accepted-schema surface.
555    SchemaSurfacePolicyDenied,
556    /// A structural mutation batch exceeded its canonical prepared-commit work bound.
557    MutationBatchCommitWorkExceeded,
558    /// Retained journal debt leaves insufficient bounded convergence capacity.
559    ConvergenceBacklogPressure,
560    /// Requested manager bucket pages differ from the existing memory layout.
561    MemoryBucketSizeMismatch,
562    /// Host grants cannot resolve or authorize current logical allocations.
563    MemoryAllocationResolutionFailed,
564    /// Current declarations omitted a historical IcyDB database namespace.
565    MemoryNamespaceRemoved,
566    /// Current database controls or store allocations lack a complete role set.
567    MemoryAllocationRolesIncomplete,
568    /// Current memory declarations violate their bounded identity/role contract.
569    MemoryDeclarationInvalid,
570    /// Current declaration requirements differ from established memory authority.
571    MemoryDeclarationSnapshotMismatch,
572    /// A historical allocation required for journal recovery cannot be selected.
573    MemoryHistoricalJournalUnavailable,
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    ShowColumnsModifiers,
637    ShowEntitiesModifiers,
638    ShowIndexesModifiers,
639    ShowMemoryModifiers,
640    ShowStoresModifiers,
641    ShowUnsupportedCommand,
642    SimpleCaseExpression,
643    StandaloneLiteralProjectionItem,
644    UnionIntersectExcept,
645    UnsupportedFunctionNamespace,
646    Update,
647    UpperFieldPredicateUnsupported,
648    WindowFunction,
649    With,
650    NumericScaleFunctionArguments,
651    OrderByFieldNotOrderable,
652    ShowConstraintsModifiers,
653    AlterTableAddConstraintBeyondCheck,
654    AlterTableAddConstraintModifiers,
655    AlterTableDropConstraintIfExistsSyntax,
656    AlterTableDropConstraintModifiers,
657    AlterTableValidateBeyondConstraint,
658    AlterTableValidateConstraintModifiers,
659    ShowRelationsModifiers,
660}
661
662impl fmt::Debug for SqlFeatureCode {
663    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664        fmt_compact_code(f, *self as u16)
665    }
666}
667
668///
669/// SqlLoweringCode
670///
671/// Compact SQL lowering rejection identifier used after parsing succeeds but
672/// before a statement becomes canonical query intent.
673/// Variant order is wire-order significant for public error-code offsets.
674///
675
676#[repr(u16)]
677#[derive(Clone, Copy, Eq, Hash, PartialEq)]
678pub enum SqlLoweringCode {
679    EntityMismatch,
680    SelectProjectionShape,
681    SelectDistinct,
682    DistinctOrderByProjection,
683    GlobalAggregateProjection,
684    GlobalAggregateGroupBy,
685    SelectGroupByShape,
686    GroupedProjectionExplicitListRequired,
687    GroupedProjectionAggregateRequired,
688    GroupedProjectionNonGroupField,
689    GroupedProjectionScalarAfterAggregate,
690    HavingRequiresGroupBy,
691    SelectHavingShape,
692    AggregateInputExpressions,
693    WhereExpressionShape,
694    ParameterPlacement,
695    SqlDdlExecutionUnsupported,
696    BindingCount,
697    BindingFamily,
698    BindingLimit,
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    PrimaryKeyLiteralIncompatible,
750    MissingPrimaryKey,
751    MissingRequiredFields,
752    ExplicitManagedField,
753    ExplicitGeneratedField,
754    InsertSelectRequiresScalar,
755    InsertSelectAggregateProjection,
756    InsertSelectWidthMismatch,
757    UpdatePrimaryKeyMutation,
758    InvalidFieldLiteral,
759    UnknownReturningField,
760    DuplicateReturningField,
761    UpdateMissingWherePredicate,
762    WriteOrderByUnsupportedShape,
763    ReturningResponseTooLarge,
764    ReturningRowsTooMany,
765    StagedRowsTooMany,
766    InsertDefaultRequiredField,
767    UpdateDefaultRequiredField,
768    UpdateDefaultDatabaseOwnedField,
769    ExactUpdateAssertionRequired,
770    ExactUpdateAssertionTooHigh,
771    ExactUpdateAffectedRowsExceeded,
772    ExactUpdateWindowUnsupported,
773    ExactUpdateScanBudgetExceeded,
774    ResumableUpdateWindowUnsupported,
775    ResumableUpdateReturningUnsupported,
776    ResumableUpdateRequiresJournaledStore,
777    ResumableUpdateAssignedFieldHasGlobalConstraint,
778    ResumableUpdateScopeDependsOnAssignedField,
779    ResumableUpdateScopeDependencyUnknown,
780    ResumableUpdateContinuationMalformed,
781    ResumableUpdateContinuationTargetMismatch,
782    ResumableUpdateContinuationSchemaMismatch,
783    ResumableUpdateContinuationScopeMismatch,
784    ResumableUpdateContinuationPatchMismatch,
785    ResumableUpdateContinuationBatchPolicyMismatch,
786    ResumableUpdateManagedFieldHasGlobalConstraint,
787}
788
789impl fmt::Debug for SqlWriteBoundaryCode {
790    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
791        fmt_compact_code(f, *self as u16)
792    }
793}
794
795///
796/// SchemaDdlAdmissionCode
797///
798/// Compact SQL DDL admission rejection reason.
799/// Variant order is wire-order significant for public error-code offsets.
800///
801
802#[repr(u16)]
803#[derive(Clone, Copy, Eq, Hash, PartialEq)]
804pub enum SchemaDdlAdmissionCode {
805    MissingExpectedSchemaVersion,
806    MissingNextSchemaVersion,
807    StaleExpectedSchemaVersion,
808    InvalidExpectedSchemaVersion,
809    InvalidNextSchemaVersion,
810    AcceptedSchemaChangeWithoutVersionBump,
811    EmptyVersionBump,
812    VersionGap,
813    VersionRollback,
814    FingerprintMethodMismatch,
815    UnsupportedTransitionClass,
816    PhysicalRunnerMissing,
817    ValidationFailed,
818    PublicationRaceLost,
819    InvalidAddColumnDefault,
820    InvalidAlterColumnDefault,
821    GeneratedIndexDropRejected,
822    SchemaRewriteRequiresMigration,
823    SchemaTransitionBudgetExceeded,
824    GeneratedFieldDefaultChangeRejected,
825    GeneratedFieldNullabilityChangeRejected,
826    RowLayoutVersionExhausted,
827}
828
829impl fmt::Debug for SchemaDdlAdmissionCode {
830    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
831        fmt_compact_code(f, *self as u16)
832    }
833}
834
835/// Machine-readable source-migration rejection or lifecycle finding.
836#[repr(u16)]
837#[derive(Clone, Copy, Eq, Hash, PartialEq)]
838pub enum SchemaMigrationCode {
839    Unadopted,
840    MissingMigration,
841    VersionGap,
842    Downgrade,
843    EmptyEntityVersionBump,
844    StaleAcceptedHead,
845    PlanChanged,
846    UnknownFromObject,
847    UnknownToObject,
848    KindMismatch,
849    IdentityConflict,
850    UnexplainedSchemaDifference,
851    UnsupportedTransform,
852    PhysicalRunnerMissing,
853    MigrationInProgress,
854    AbortTooLate,
855    ProgressCorrupt,
856    CandidateMismatch,
857    PublicationRaceLost,
858}
859
860impl SchemaMigrationCode {
861    /// Return the broad diagnostic category for this migration result.
862    #[must_use]
863    pub const fn diagnostic_code(self) -> DiagnosticCode {
864        match self {
865            Self::StaleAcceptedHead
866            | Self::PlanChanged
867            | Self::IdentityConflict
868            | Self::MigrationInProgress
869            | Self::AbortTooLate
870            | Self::PublicationRaceLost => DiagnosticCode::RuntimeConflict,
871            Self::ProgressCorrupt | Self::CandidateMismatch => DiagnosticCode::RuntimeCorruption,
872            Self::Unadopted
873            | Self::MissingMigration
874            | Self::VersionGap
875            | Self::Downgrade
876            | Self::EmptyEntityVersionBump
877            | Self::UnknownFromObject
878            | Self::UnknownToObject
879            | Self::KindMismatch
880            | Self::UnexplainedSchemaDifference
881            | Self::UnsupportedTransform
882            | Self::PhysicalRunnerMissing => DiagnosticCode::RuntimeUnsupported,
883        }
884    }
885}
886
887impl fmt::Debug for SchemaMigrationCode {
888    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
889        fmt_compact_code(f, *self as u16)
890    }
891}
892
893///
894/// DiagnosticDetail
895///
896/// Small structured diagnostic payload for callers and CLI rendering.
897///
898
899#[remain::sorted]
900#[derive(Clone, Copy, Eq, PartialEq)]
901pub enum DiagnosticDetail {
902    QueryKind { kind: QueryErrorKind },
903    QueryProjection { reason: QueryProjectionCode },
904    QueryReadAdmission { reason: QueryReadAdmissionCode },
905    RuntimeBoundary { boundary: RuntimeBoundaryCode },
906    RuntimeKind { kind: RuntimeErrorKind },
907    SchemaDdlAdmission { reason: SchemaDdlAdmissionCode },
908    SchemaMigration { reason: SchemaMigrationCode },
909    SqlLowering { reason: SqlLoweringCode },
910    SqlSurfaceMismatch { mismatch: SqlSurfaceMismatchCode },
911    SqlWriteBoundary { boundary: SqlWriteBoundaryCode },
912    UnsupportedSqlFeature { feature: SqlFeatureCode },
913}
914
915impl fmt::Debug for DiagnosticDetail {
916    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
917        fmt_compact_code(
918            f,
919            ErrorCode::from_parts(self.diagnostic_code(), Some(*self)).raw(),
920        )
921    }
922}
923
924///
925/// Diagnostic
926///
927/// Compact public diagnostic payload.
928///
929
930#[derive(Clone, Eq, PartialEq)]
931pub struct Diagnostic {
932    code: DiagnosticCode,
933    origin: ErrorOrigin,
934    detail: Option<DiagnosticDetail>,
935}
936
937impl Diagnostic {
938    /// Build a compact diagnostic from a code and optional structured detail.
939    #[must_use]
940    pub const fn new(
941        code: DiagnosticCode,
942        origin: ErrorOrigin,
943        detail: Option<DiagnosticDetail>,
944    ) -> Self {
945        Self {
946            code,
947            origin,
948            detail,
949        }
950    }
951
952    /// Build a compact diagnostic using the code's default origin.
953    #[must_use]
954    pub const fn from_code(code: DiagnosticCode) -> Self {
955        Self::new(code, code.origin(), None)
956    }
957
958    /// Return the stable diagnostic code.
959    #[must_use]
960    pub const fn code(&self) -> DiagnosticCode {
961        self.code
962    }
963
964    /// Return the diagnostic class.
965    #[must_use]
966    pub const fn class(&self) -> ErrorClass {
967        self.code.class()
968    }
969
970    /// Return the subsystem origin.
971    #[must_use]
972    pub const fn origin(&self) -> ErrorOrigin {
973        self.origin
974    }
975
976    /// Return structured diagnostic detail, when available.
977    #[must_use]
978    pub const fn detail(&self) -> Option<&DiagnosticDetail> {
979        self.detail.as_ref()
980    }
981
982    /// Return the numeric public wire code for this diagnostic.
983    #[must_use]
984    pub const fn error_code(&self) -> ErrorCode {
985        ErrorCode::from_parts(self.code, self.detail)
986    }
987}
988
989impl fmt::Debug for Diagnostic {
990    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
991        write!(f, "{}@{}", self.error_code().raw(), self.origin.wire_code())
992    }
993}
994
995fn fmt_compact_code(f: &mut fmt::Formatter<'_>, raw: u16) -> fmt::Result {
996    write!(f, "{raw}")
997}
998
999#[cfg(test)]
1000mod tests {
1001    use super::{
1002        Diagnostic, DiagnosticCode, DiagnosticDetail, ErrorClass, ErrorCode, ErrorOrigin,
1003        QueryProjectionCode, QueryReadAdmissionCode, SqlFeatureCode, SqlLoweringCode,
1004        SqlWriteBoundaryCode,
1005        registry::{DETAIL_ERROR_CODES, ORDERED_ERROR_CODES},
1006    };
1007
1008    #[test]
1009    fn diagnostic_from_code_uses_default_origin() {
1010        let diagnostic = Diagnostic::from_code(DiagnosticCode::QueryPlan);
1011
1012        assert_eq!(diagnostic.code(), DiagnosticCode::QueryPlan);
1013        assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1014    }
1015
1016    #[test]
1017    fn diagnostic_code_reports_broad_class() {
1018        assert_eq!(
1019            DiagnosticCode::QueryUnsupportedSqlFeature.class(),
1020            ErrorClass::Unsupported
1021        );
1022        assert_eq!(
1023            DiagnosticCode::QuerySqlSurfaceMismatch.class(),
1024            ErrorClass::Unsupported
1025        );
1026        assert_eq!(DiagnosticCode::QueryPlan.class(), ErrorClass::Query);
1027        assert_eq!(
1028            DiagnosticCode::StoreCorruption.class(),
1029            ErrorClass::Corruption
1030        );
1031    }
1032
1033    #[test]
1034    fn class_and_origin_wire_codes_round_trip() {
1035        for (class, raw) in [
1036            (ErrorClass::Query, 1),
1037            (ErrorClass::Corruption, 2),
1038            (ErrorClass::IncompatiblePersistedFormat, 3),
1039            (ErrorClass::NotFound, 4),
1040            (ErrorClass::Internal, 5),
1041            (ErrorClass::Conflict, 6),
1042            (ErrorClass::Unsupported, 7),
1043            (ErrorClass::InvariantViolation, 8),
1044        ] {
1045            assert_eq!(class.wire_code(), raw);
1046            assert_eq!(ErrorClass::from_wire_code(raw), Some(class));
1047            assert_eq!(format!("{class:?}"), raw.to_string());
1048        }
1049
1050        for (origin, raw) in [
1051            (ErrorOrigin::Cursor, 1),
1052            (ErrorOrigin::Executor, 2),
1053            (ErrorOrigin::Identity, 3),
1054            (ErrorOrigin::Index, 4),
1055            (ErrorOrigin::Interface, 5),
1056            (ErrorOrigin::Planner, 6),
1057            (ErrorOrigin::Query, 7),
1058            (ErrorOrigin::Recovery, 8),
1059            (ErrorOrigin::Response, 9),
1060            (ErrorOrigin::Runtime, 10),
1061            (ErrorOrigin::Serialize, 11),
1062            (ErrorOrigin::Store, 12),
1063        ] {
1064            assert_eq!(origin.wire_code(), raw);
1065            assert_eq!(ErrorOrigin::from_known_wire_code(raw), Some(origin));
1066            assert_eq!(ErrorOrigin::from_wire_code(raw), origin);
1067            assert_eq!(format!("{origin:?}"), raw.to_string());
1068        }
1069
1070        assert_eq!(ErrorClass::from_wire_code(0), None);
1071        assert_eq!(ErrorOrigin::from_known_wire_code(0), None);
1072        assert_eq!(ErrorOrigin::from_wire_code(0), ErrorOrigin::Runtime);
1073    }
1074
1075    #[test]
1076    fn public_error_codes_are_sequential() {
1077        let first = ORDERED_ERROR_CODES
1078            .first()
1079            .expect("public error-code registry is non-empty")
1080            .raw();
1081
1082        assert_eq!(first, 1);
1083
1084        for (index, code) in ORDERED_ERROR_CODES.iter().enumerate() {
1085            let expected = first + u16::try_from(index).expect("test error-code index fits u16");
1086            assert_eq!(code.raw(), expected);
1087            assert_eq!(ErrorCode::known(code.raw()), Some(*code));
1088            assert!(code.is_known());
1089        }
1090
1091        let last = ORDERED_ERROR_CODES
1092            .last()
1093            .expect("public error-code registry is non-empty")
1094            .raw();
1095
1096        assert_eq!(last, 281);
1097    }
1098
1099    #[test]
1100    fn all_public_error_codes_round_trip_through_diagnostic_parts() {
1101        let first = ORDERED_ERROR_CODES
1102            .first()
1103            .expect("public error-code registry is non-empty")
1104            .raw();
1105        let last = ORDERED_ERROR_CODES
1106            .last()
1107            .expect("public error-code registry is non-empty")
1108            .raw();
1109
1110        for raw in first..=last {
1111            let code = ErrorCode::from_raw(raw);
1112            let diagnostic_code = code.diagnostic_code();
1113            let diagnostic_detail = code.diagnostic_detail();
1114            let rebuilt = ErrorCode::from_parts(diagnostic_code, diagnostic_detail);
1115
1116            assert_eq!(rebuilt.raw(), raw);
1117
1118            let diagnostic = code.diagnostic(ErrorOrigin::Runtime);
1119
1120            assert_eq!(diagnostic.code(), diagnostic_code);
1121            assert_eq!(diagnostic.detail(), diagnostic_detail.as_ref());
1122            assert_eq!(diagnostic.error_code().raw(), raw);
1123        }
1124    }
1125
1126    #[test]
1127    fn invalid_raw_error_codes_fail_closed_to_runtime_internal() {
1128        let first_unknown = ORDERED_ERROR_CODES
1129            .last()
1130            .expect("public error-code registry is non-empty")
1131            .raw()
1132            .checked_add(1)
1133            .expect("public error-code registry retains an unknown successor");
1134
1135        for raw in [0, first_unknown, u16::MAX] {
1136            let code = ErrorCode::from_raw(raw);
1137
1138            assert_eq!(ErrorCode::known(raw), None);
1139            assert!(!code.is_known());
1140            assert_eq!(code.diagnostic_code(), DiagnosticCode::RuntimeInternal);
1141            assert_eq!(code.diagnostic_detail(), None);
1142            assert_eq!(code.class(), ErrorClass::Internal);
1143
1144            let diagnostic = code.diagnostic(ErrorOrigin::Query);
1145
1146            assert_eq!(diagnostic.code(), DiagnosticCode::RuntimeInternal);
1147            assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1148            assert_eq!(diagnostic.detail(), None);
1149            assert_eq!(diagnostic.error_code(), ErrorCode::RUNTIME_INTERNAL);
1150        }
1151    }
1152
1153    #[test]
1154    fn from_parts_requires_detail_to_match_broad_code() {
1155        let detail = Some(DiagnosticDetail::UnsupportedSqlFeature {
1156            feature: SqlFeatureCode::Join,
1157        });
1158
1159        assert_eq!(
1160            ErrorCode::from_parts(DiagnosticCode::QueryUnsupportedSqlFeature, detail),
1161            ErrorCode::SQL_FEATURE_JOIN
1162        );
1163        assert_eq!(
1164            ErrorCode::from_parts(DiagnosticCode::QueryPlan, detail),
1165            ErrorCode::QUERY_PLAN
1166        );
1167    }
1168
1169    #[test]
1170    fn detail_bearing_registry_entries_round_trip_directly() {
1171        assert!(!DETAIL_ERROR_CODES.is_empty());
1172
1173        for &(code, diagnostic_code, detail) in DETAIL_ERROR_CODES {
1174            assert_eq!(ErrorCode::from_parts(diagnostic_code, Some(detail)), code);
1175            assert_eq!(code.diagnostic_code(), diagnostic_code);
1176            assert_eq!(code.diagnostic_detail(), Some(detail));
1177            assert_eq!(detail.diagnostic_code(), diagnostic_code);
1178        }
1179    }
1180
1181    #[test]
1182    fn diagnostic_detail_reports_generated_broad_code() {
1183        let detail = DiagnosticDetail::UnsupportedSqlFeature {
1184            feature: SqlFeatureCode::Join,
1185        };
1186
1187        assert_eq!(
1188            detail.diagnostic_code(),
1189            DiagnosticCode::QueryUnsupportedSqlFeature
1190        );
1191        assert_eq!(format!("{detail:?}"), "61");
1192    }
1193
1194    #[test]
1195    fn public_error_codes_reconstruct_shifted_details() {
1196        assert_eq!(
1197            ErrorCode::QUERY_UNKNOWN_AGGREGATE_TARGET_FIELD.diagnostic_code(),
1198            DiagnosticCode::QueryUnknownAggregateTargetField
1199        );
1200        assert_eq!(
1201            ErrorCode::SQL_FEATURE_JOIN.diagnostic_detail(),
1202            Some(DiagnosticDetail::UnsupportedSqlFeature {
1203                feature: SqlFeatureCode::Join,
1204            })
1205        );
1206        assert_eq!(
1207            ErrorCode::QUERY_PROJECTION_NUMERIC_LITERAL_REQUIRED.diagnostic_detail(),
1208            Some(DiagnosticDetail::QueryProjection {
1209                reason: QueryProjectionCode::NumericLiteralRequired,
1210            })
1211        );
1212        assert_eq!(
1213            ErrorCode::QUERY_READ_PUBLIC_REQUIRES_LIMIT.diagnostic_detail(),
1214            Some(DiagnosticDetail::QueryReadAdmission {
1215                reason: QueryReadAdmissionCode::PublicQueryRequiresLimit,
1216            })
1217        );
1218        assert_eq!(
1219            ErrorCode::SQL_LOWERING_DISTINCT_ORDER_BY_PROJECTION.diagnostic_detail(),
1220            Some(DiagnosticDetail::SqlLowering {
1221                reason: SqlLoweringCode::DistinctOrderByProjection,
1222            })
1223        );
1224        assert_eq!(
1225            ErrorCode::SQL_WRITE_RETURNING_RESPONSE_TOO_LARGE.diagnostic_detail(),
1226            Some(DiagnosticDetail::SqlWriteBoundary {
1227                boundary: SqlWriteBoundaryCode::ReturningResponseTooLarge,
1228            })
1229        );
1230        assert_eq!(
1231            ErrorCode::SQL_WRITE_RETURNING_ROWS_TOO_MANY.diagnostic_detail(),
1232            Some(DiagnosticDetail::SqlWriteBoundary {
1233                boundary: SqlWriteBoundaryCode::ReturningRowsTooMany,
1234            })
1235        );
1236    }
1237}