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    WriteScanBudgetExceeded,
774    ResumableUpdateWindowUnsupported,
775    ResumableUpdateReturningUnsupported,
776    ResumableUpdateExpressionUnsupported,
777    ResumableUpdateRequiresJournaledStore,
778    ResumableUpdateAssignedFieldHasGlobalConstraint,
779    ResumableUpdateScopeDependsOnAssignedField,
780    ResumableUpdateScopeDependencyUnknown,
781    ResumableUpdateContinuationMalformed,
782    ResumableUpdateContinuationTargetMismatch,
783    ResumableUpdateContinuationSchemaMismatch,
784    ResumableUpdateContinuationScopeMismatch,
785    ResumableUpdateContinuationPatchMismatch,
786    ResumableUpdateContinuationBatchPolicyMismatch,
787    ResumableUpdateManagedFieldHasGlobalConstraint,
788}
789
790impl fmt::Debug for SqlWriteBoundaryCode {
791    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
792        fmt_compact_code(f, *self as u16)
793    }
794}
795
796///
797/// SchemaDdlAdmissionCode
798///
799/// Compact SQL DDL admission rejection reason.
800/// Variant order is wire-order significant for public error-code offsets.
801///
802
803#[repr(u16)]
804#[derive(Clone, Copy, Eq, Hash, PartialEq)]
805pub enum SchemaDdlAdmissionCode {
806    MissingExpectedSchemaVersion,
807    MissingNextSchemaVersion,
808    StaleExpectedSchemaVersion,
809    InvalidExpectedSchemaVersion,
810    InvalidNextSchemaVersion,
811    AcceptedSchemaChangeWithoutVersionBump,
812    EmptyVersionBump,
813    VersionGap,
814    VersionRollback,
815    FingerprintMethodMismatch,
816    UnsupportedTransitionClass,
817    PhysicalRunnerMissing,
818    ValidationFailed,
819    PublicationRaceLost,
820    InvalidAddColumnDefault,
821    InvalidAlterColumnDefault,
822    GeneratedIndexDropRejected,
823    SchemaRewriteRequiresMigration,
824    SchemaTransitionBudgetExceeded,
825    GeneratedFieldDefaultChangeRejected,
826    GeneratedFieldNullabilityChangeRejected,
827    RowLayoutVersionExhausted,
828}
829
830impl fmt::Debug for SchemaDdlAdmissionCode {
831    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
832        fmt_compact_code(f, *self as u16)
833    }
834}
835
836/// Machine-readable source-migration rejection or lifecycle finding.
837#[repr(u16)]
838#[derive(Clone, Copy, Eq, Hash, PartialEq)]
839pub enum SchemaMigrationCode {
840    Unadopted,
841    MissingMigration,
842    VersionGap,
843    Downgrade,
844    EmptyEntityVersionBump,
845    StaleAcceptedHead,
846    PlanChanged,
847    UnknownFromObject,
848    UnknownToObject,
849    KindMismatch,
850    IdentityConflict,
851    UnexplainedSchemaDifference,
852    UnsupportedTransform,
853    PhysicalRunnerMissing,
854    MigrationInProgress,
855    AbortTooLate,
856    ProgressCorrupt,
857    CandidateMismatch,
858    PublicationRaceLost,
859}
860
861impl SchemaMigrationCode {
862    /// Return the broad diagnostic category for this migration result.
863    #[must_use]
864    pub const fn diagnostic_code(self) -> DiagnosticCode {
865        match self {
866            Self::StaleAcceptedHead
867            | Self::PlanChanged
868            | Self::IdentityConflict
869            | Self::MigrationInProgress
870            | Self::AbortTooLate
871            | Self::PublicationRaceLost => DiagnosticCode::RuntimeConflict,
872            Self::ProgressCorrupt | Self::CandidateMismatch => DiagnosticCode::RuntimeCorruption,
873            Self::Unadopted
874            | Self::MissingMigration
875            | Self::VersionGap
876            | Self::Downgrade
877            | Self::EmptyEntityVersionBump
878            | Self::UnknownFromObject
879            | Self::UnknownToObject
880            | Self::KindMismatch
881            | Self::UnexplainedSchemaDifference
882            | Self::UnsupportedTransform
883            | Self::PhysicalRunnerMissing => DiagnosticCode::RuntimeUnsupported,
884        }
885    }
886}
887
888impl fmt::Debug for SchemaMigrationCode {
889    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
890        fmt_compact_code(f, *self as u16)
891    }
892}
893
894///
895/// DiagnosticDetail
896///
897/// Small structured diagnostic payload for callers and CLI rendering.
898///
899
900#[remain::sorted]
901#[derive(Clone, Copy, Eq, PartialEq)]
902pub enum DiagnosticDetail {
903    QueryKind { kind: QueryErrorKind },
904    QueryProjection { reason: QueryProjectionCode },
905    QueryReadAdmission { reason: QueryReadAdmissionCode },
906    RuntimeBoundary { boundary: RuntimeBoundaryCode },
907    RuntimeKind { kind: RuntimeErrorKind },
908    SchemaDdlAdmission { reason: SchemaDdlAdmissionCode },
909    SchemaMigration { reason: SchemaMigrationCode },
910    SqlLowering { reason: SqlLoweringCode },
911    SqlSurfaceMismatch { mismatch: SqlSurfaceMismatchCode },
912    SqlWriteBoundary { boundary: SqlWriteBoundaryCode },
913    UnsupportedSqlFeature { feature: SqlFeatureCode },
914}
915
916impl fmt::Debug for DiagnosticDetail {
917    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
918        fmt_compact_code(
919            f,
920            ErrorCode::from_parts(self.diagnostic_code(), Some(*self)).raw(),
921        )
922    }
923}
924
925///
926/// Diagnostic
927///
928/// Compact public diagnostic payload.
929///
930
931#[derive(Clone, Eq, PartialEq)]
932pub struct Diagnostic {
933    code: DiagnosticCode,
934    origin: ErrorOrigin,
935    detail: Option<DiagnosticDetail>,
936}
937
938impl Diagnostic {
939    /// Build a compact diagnostic from a code and optional structured detail.
940    #[must_use]
941    pub const fn new(
942        code: DiagnosticCode,
943        origin: ErrorOrigin,
944        detail: Option<DiagnosticDetail>,
945    ) -> Self {
946        Self {
947            code,
948            origin,
949            detail,
950        }
951    }
952
953    /// Build a compact diagnostic using the code's default origin.
954    #[must_use]
955    pub const fn from_code(code: DiagnosticCode) -> Self {
956        Self::new(code, code.origin(), None)
957    }
958
959    /// Return the stable diagnostic code.
960    #[must_use]
961    pub const fn code(&self) -> DiagnosticCode {
962        self.code
963    }
964
965    /// Return the diagnostic class.
966    #[must_use]
967    pub const fn class(&self) -> ErrorClass {
968        self.code.class()
969    }
970
971    /// Return the subsystem origin.
972    #[must_use]
973    pub const fn origin(&self) -> ErrorOrigin {
974        self.origin
975    }
976
977    /// Return structured diagnostic detail, when available.
978    #[must_use]
979    pub const fn detail(&self) -> Option<&DiagnosticDetail> {
980        self.detail.as_ref()
981    }
982
983    /// Return the numeric public wire code for this diagnostic.
984    #[must_use]
985    pub const fn error_code(&self) -> ErrorCode {
986        ErrorCode::from_parts(self.code, self.detail)
987    }
988}
989
990impl fmt::Debug for Diagnostic {
991    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
992        write!(f, "{}@{}", self.error_code().raw(), self.origin.wire_code())
993    }
994}
995
996fn fmt_compact_code(f: &mut fmt::Formatter<'_>, raw: u16) -> fmt::Result {
997    write!(f, "{raw}")
998}
999
1000#[cfg(test)]
1001mod tests {
1002    use super::{
1003        Diagnostic, DiagnosticCode, DiagnosticDetail, ErrorClass, ErrorCode, ErrorOrigin,
1004        QueryProjectionCode, QueryReadAdmissionCode, SqlFeatureCode, SqlLoweringCode,
1005        SqlWriteBoundaryCode,
1006        registry::{DETAIL_ERROR_CODES, ORDERED_ERROR_CODES},
1007    };
1008
1009    #[test]
1010    fn diagnostic_from_code_uses_default_origin() {
1011        let diagnostic = Diagnostic::from_code(DiagnosticCode::QueryPlan);
1012
1013        assert_eq!(diagnostic.code(), DiagnosticCode::QueryPlan);
1014        assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1015    }
1016
1017    #[test]
1018    fn diagnostic_code_reports_broad_class() {
1019        assert_eq!(
1020            DiagnosticCode::QueryUnsupportedSqlFeature.class(),
1021            ErrorClass::Unsupported
1022        );
1023        assert_eq!(
1024            DiagnosticCode::QuerySqlSurfaceMismatch.class(),
1025            ErrorClass::Unsupported
1026        );
1027        assert_eq!(DiagnosticCode::QueryPlan.class(), ErrorClass::Query);
1028        assert_eq!(
1029            DiagnosticCode::StoreCorruption.class(),
1030            ErrorClass::Corruption
1031        );
1032    }
1033
1034    #[test]
1035    fn class_and_origin_wire_codes_round_trip() {
1036        for (class, raw) in [
1037            (ErrorClass::Query, 1),
1038            (ErrorClass::Corruption, 2),
1039            (ErrorClass::IncompatiblePersistedFormat, 3),
1040            (ErrorClass::NotFound, 4),
1041            (ErrorClass::Internal, 5),
1042            (ErrorClass::Conflict, 6),
1043            (ErrorClass::Unsupported, 7),
1044            (ErrorClass::InvariantViolation, 8),
1045        ] {
1046            assert_eq!(class.wire_code(), raw);
1047            assert_eq!(ErrorClass::from_wire_code(raw), Some(class));
1048            assert_eq!(format!("{class:?}"), raw.to_string());
1049        }
1050
1051        for (origin, raw) in [
1052            (ErrorOrigin::Cursor, 1),
1053            (ErrorOrigin::Executor, 2),
1054            (ErrorOrigin::Identity, 3),
1055            (ErrorOrigin::Index, 4),
1056            (ErrorOrigin::Interface, 5),
1057            (ErrorOrigin::Planner, 6),
1058            (ErrorOrigin::Query, 7),
1059            (ErrorOrigin::Recovery, 8),
1060            (ErrorOrigin::Response, 9),
1061            (ErrorOrigin::Runtime, 10),
1062            (ErrorOrigin::Serialize, 11),
1063            (ErrorOrigin::Store, 12),
1064        ] {
1065            assert_eq!(origin.wire_code(), raw);
1066            assert_eq!(ErrorOrigin::from_known_wire_code(raw), Some(origin));
1067            assert_eq!(ErrorOrigin::from_wire_code(raw), origin);
1068            assert_eq!(format!("{origin:?}"), raw.to_string());
1069        }
1070
1071        assert_eq!(ErrorClass::from_wire_code(0), None);
1072        assert_eq!(ErrorOrigin::from_known_wire_code(0), None);
1073        assert_eq!(ErrorOrigin::from_wire_code(0), ErrorOrigin::Runtime);
1074    }
1075
1076    #[test]
1077    fn public_error_codes_are_sequential() {
1078        let first = ORDERED_ERROR_CODES
1079            .first()
1080            .expect("public error-code registry is non-empty")
1081            .raw();
1082
1083        assert_eq!(first, 1);
1084
1085        for (index, code) in ORDERED_ERROR_CODES.iter().enumerate() {
1086            let expected = first + u16::try_from(index).expect("test error-code index fits u16");
1087            assert_eq!(code.raw(), expected);
1088            assert_eq!(ErrorCode::known(code.raw()), Some(*code));
1089            assert!(code.is_known());
1090        }
1091
1092        let last = ORDERED_ERROR_CODES
1093            .last()
1094            .expect("public error-code registry is non-empty")
1095            .raw();
1096
1097        assert_eq!(last, 282);
1098    }
1099
1100    #[test]
1101    fn all_public_error_codes_round_trip_through_diagnostic_parts() {
1102        let first = ORDERED_ERROR_CODES
1103            .first()
1104            .expect("public error-code registry is non-empty")
1105            .raw();
1106        let last = ORDERED_ERROR_CODES
1107            .last()
1108            .expect("public error-code registry is non-empty")
1109            .raw();
1110
1111        for raw in first..=last {
1112            let code = ErrorCode::from_raw(raw);
1113            let diagnostic_code = code.diagnostic_code();
1114            let diagnostic_detail = code.diagnostic_detail();
1115            let rebuilt = ErrorCode::from_parts(diagnostic_code, diagnostic_detail);
1116
1117            assert_eq!(rebuilt.raw(), raw);
1118
1119            let diagnostic = code.diagnostic(ErrorOrigin::Runtime);
1120
1121            assert_eq!(diagnostic.code(), diagnostic_code);
1122            assert_eq!(diagnostic.detail(), diagnostic_detail.as_ref());
1123            assert_eq!(diagnostic.error_code().raw(), raw);
1124        }
1125    }
1126
1127    #[test]
1128    fn invalid_raw_error_codes_fail_closed_to_runtime_internal() {
1129        let first_unknown = ORDERED_ERROR_CODES
1130            .last()
1131            .expect("public error-code registry is non-empty")
1132            .raw()
1133            .checked_add(1)
1134            .expect("public error-code registry retains an unknown successor");
1135
1136        for raw in [0, first_unknown, u16::MAX] {
1137            let code = ErrorCode::from_raw(raw);
1138
1139            assert_eq!(ErrorCode::known(raw), None);
1140            assert!(!code.is_known());
1141            assert_eq!(code.diagnostic_code(), DiagnosticCode::RuntimeInternal);
1142            assert_eq!(code.diagnostic_detail(), None);
1143            assert_eq!(code.class(), ErrorClass::Internal);
1144
1145            let diagnostic = code.diagnostic(ErrorOrigin::Query);
1146
1147            assert_eq!(diagnostic.code(), DiagnosticCode::RuntimeInternal);
1148            assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1149            assert_eq!(diagnostic.detail(), None);
1150            assert_eq!(diagnostic.error_code(), ErrorCode::RUNTIME_INTERNAL);
1151        }
1152    }
1153
1154    #[test]
1155    fn from_parts_requires_detail_to_match_broad_code() {
1156        let detail = Some(DiagnosticDetail::UnsupportedSqlFeature {
1157            feature: SqlFeatureCode::Join,
1158        });
1159
1160        assert_eq!(
1161            ErrorCode::from_parts(DiagnosticCode::QueryUnsupportedSqlFeature, detail),
1162            ErrorCode::SQL_FEATURE_JOIN
1163        );
1164        assert_eq!(
1165            ErrorCode::from_parts(DiagnosticCode::QueryPlan, detail),
1166            ErrorCode::QUERY_PLAN
1167        );
1168    }
1169
1170    #[test]
1171    fn detail_bearing_registry_entries_round_trip_directly() {
1172        assert!(!DETAIL_ERROR_CODES.is_empty());
1173
1174        for &(code, diagnostic_code, detail) in DETAIL_ERROR_CODES {
1175            assert_eq!(ErrorCode::from_parts(diagnostic_code, Some(detail)), code);
1176            assert_eq!(code.diagnostic_code(), diagnostic_code);
1177            assert_eq!(code.diagnostic_detail(), Some(detail));
1178            assert_eq!(detail.diagnostic_code(), diagnostic_code);
1179        }
1180    }
1181
1182    #[test]
1183    fn diagnostic_detail_reports_generated_broad_code() {
1184        let detail = DiagnosticDetail::UnsupportedSqlFeature {
1185            feature: SqlFeatureCode::Join,
1186        };
1187
1188        assert_eq!(
1189            detail.diagnostic_code(),
1190            DiagnosticCode::QueryUnsupportedSqlFeature
1191        );
1192        assert_eq!(format!("{detail:?}"), "61");
1193    }
1194
1195    #[test]
1196    fn public_error_codes_reconstruct_shifted_details() {
1197        assert_eq!(
1198            ErrorCode::QUERY_UNKNOWN_AGGREGATE_TARGET_FIELD.diagnostic_code(),
1199            DiagnosticCode::QueryUnknownAggregateTargetField
1200        );
1201        assert_eq!(
1202            ErrorCode::SQL_FEATURE_JOIN.diagnostic_detail(),
1203            Some(DiagnosticDetail::UnsupportedSqlFeature {
1204                feature: SqlFeatureCode::Join,
1205            })
1206        );
1207        assert_eq!(
1208            ErrorCode::QUERY_PROJECTION_NUMERIC_LITERAL_REQUIRED.diagnostic_detail(),
1209            Some(DiagnosticDetail::QueryProjection {
1210                reason: QueryProjectionCode::NumericLiteralRequired,
1211            })
1212        );
1213        assert_eq!(
1214            ErrorCode::QUERY_READ_PUBLIC_REQUIRES_LIMIT.diagnostic_detail(),
1215            Some(DiagnosticDetail::QueryReadAdmission {
1216                reason: QueryReadAdmissionCode::PublicQueryRequiresLimit,
1217            })
1218        );
1219        assert_eq!(
1220            ErrorCode::SQL_LOWERING_DISTINCT_ORDER_BY_PROJECTION.diagnostic_detail(),
1221            Some(DiagnosticDetail::SqlLowering {
1222                reason: SqlLoweringCode::DistinctOrderByProjection,
1223            })
1224        );
1225        assert_eq!(
1226            ErrorCode::SQL_WRITE_RETURNING_RESPONSE_TOO_LARGE.diagnostic_detail(),
1227            Some(DiagnosticDetail::SqlWriteBoundary {
1228                boundary: SqlWriteBoundaryCode::ReturningResponseTooLarge,
1229            })
1230        );
1231        assert_eq!(
1232            ErrorCode::SQL_WRITE_RETURNING_ROWS_TOO_MANY.diagnostic_detail(),
1233            Some(DiagnosticDetail::SqlWriteBoundary {
1234                boundary: SqlWriteBoundaryCode::ReturningRowsTooMany,
1235            })
1236        );
1237    }
1238}