Skip to main content

nodedb_types/error/
details.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Machine-matchable structured error details.
4
5use serde::{Deserialize, Serialize};
6
7/// Structured error details for programmatic matching.
8///
9/// Clients match on the variant to determine the error category, then
10/// extract structured fields. The `message` on [`crate::error::NodeDbError`]
11/// carries the human-readable explanation.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(tag = "kind", rename_all = "snake_case")]
14#[non_exhaustive]
15pub enum ErrorDetails {
16    // Write path
17    #[serde(rename = "constraint_violation")]
18    ConstraintViolation { collection: String },
19    #[serde(rename = "write_conflict")]
20    WriteConflict {
21        collection: String,
22        document_id: String,
23    },
24    #[serde(rename = "deadline_exceeded")]
25    DeadlineExceeded,
26    #[serde(rename = "prevalidation_rejected")]
27    PrevalidationRejected { constraint: String },
28    #[serde(rename = "append_only_violation")]
29    AppendOnlyViolation { collection: String },
30    #[serde(rename = "balance_violation")]
31    BalanceViolation { collection: String },
32    #[serde(rename = "period_locked")]
33    PeriodLocked { collection: String },
34    #[serde(rename = "state_transition_violation")]
35    StateTransitionViolation { collection: String },
36    #[serde(rename = "transition_check_violation")]
37    TransitionCheckViolation { collection: String },
38    #[serde(rename = "type_guard_violation")]
39    TypeGuardViolation { collection: String },
40    #[serde(rename = "retention_violation")]
41    RetentionViolation { collection: String },
42    #[serde(rename = "legal_hold_active")]
43    LegalHoldActive { collection: String },
44    #[serde(rename = "type_mismatch")]
45    TypeMismatch { collection: String },
46    #[serde(rename = "overflow")]
47    Overflow { collection: String },
48    #[serde(rename = "insufficient_balance")]
49    InsufficientBalance { collection: String },
50    #[serde(rename = "rate_exceeded")]
51    RateExceeded { gate: String },
52
53    // Read path
54    #[serde(rename = "collection_not_found")]
55    CollectionNotFound { collection: String },
56    #[serde(rename = "document_not_found")]
57    DocumentNotFound {
58        collection: String,
59        document_id: String,
60    },
61    #[serde(rename = "collection_draining")]
62    CollectionDraining { collection: String },
63    #[serde(rename = "collection_deactivated")]
64    CollectionDeactivated {
65        collection: String,
66        /// Wall-clock nanoseconds when retention elapses and the
67        /// collection becomes unrecoverable. Clients can render a
68        /// human-readable countdown.
69        retention_expires_at_ns: u64,
70        /// Copy-pasteable SQL the user can run to restore the
71        /// collection. Populated with the actual name, so the error
72        /// is actionable without further lookup.
73        undrop_hint: String,
74    },
75
76    // Query
77    #[serde(rename = "plan_error")]
78    PlanError { phase: String, detail: String },
79    #[serde(rename = "fan_out_exceeded")]
80    FanOutExceeded { shards_touched: u16, limit: u16 },
81    #[serde(rename = "sql_not_enabled")]
82    SqlNotEnabled,
83
84    // Auth
85    #[serde(rename = "authorization_denied")]
86    AuthorizationDenied { resource: String },
87    #[serde(rename = "auth_expired")]
88    AuthExpired,
89    /// Tenant quota: vector dimension exceeds `max_vector_dim`.
90    #[serde(rename = "tenant_vector_dim_exceeded")]
91    TenantVectorDimExceeded { dim: u32, limit: u32 },
92    /// Tenant quota: graph traversal depth exceeds `max_graph_depth`.
93    #[serde(rename = "tenant_graph_depth_exceeded")]
94    TenantGraphDepthExceeded { depth: u32, limit: u32 },
95
96    // Protocol handshake
97    #[serde(rename = "handshake_failed")]
98    HandshakeFailed {
99        /// Numeric error code sent by the server (0=BadMagic, 1=VersionMismatch, 2=Malformed).
100        server_code: u8,
101    },
102
103    // Sync
104    #[serde(rename = "sync_connection_failed")]
105    SyncConnectionFailed,
106    #[serde(rename = "sync_delta_rejected")]
107    SyncDeltaRejected {
108        compensation: Option<crate::sync::compensation::CompensationHint>,
109    },
110    #[serde(rename = "shape_subscription_failed")]
111    ShapeSubscriptionFailed { shape_id: String },
112
113    // Storage (opaque infrastructure)
114    #[serde(rename = "storage")]
115    Storage {
116        component: String,
117        op: String,
118        detail: String,
119    },
120    #[serde(rename = "segment_corrupted")]
121    SegmentCorrupted {
122        segment_id: u64,
123        corruption: String,
124        detail: String,
125    },
126    #[serde(rename = "cold_storage")]
127    ColdStorage {
128        backend: String,
129        op: String,
130        detail: String,
131    },
132    #[serde(rename = "wal")]
133    Wal { stage: String, detail: String },
134
135    // Serialization
136    #[serde(rename = "serialization")]
137    Serialization { format: String },
138    #[serde(rename = "codec")]
139    Codec {
140        codec: String,
141        op: String,
142        detail: String,
143    },
144
145    // Config
146    #[serde(rename = "config")]
147    Config,
148    #[serde(rename = "bad_request")]
149    BadRequest,
150
151    // Cluster
152    #[serde(rename = "no_leader")]
153    NoLeader,
154    #[serde(rename = "not_leader")]
155    NotLeader { leader_addr: String },
156    #[serde(rename = "migration_in_progress")]
157    MigrationInProgress,
158    #[serde(rename = "node_unreachable")]
159    NodeUnreachable,
160    #[serde(rename = "cluster")]
161    Cluster,
162
163    // Memory
164    #[serde(rename = "memory_exhausted")]
165    MemoryExhausted { engine: String },
166
167    // Encryption
168    #[serde(rename = "encryption")]
169    Encryption { cipher: String, detail: String },
170
171    // Engine ops
172    #[serde(rename = "array")]
173    Array { array: String },
174
175    // Quota
176    #[serde(rename = "quota_overcommit")]
177    QuotaOvercommit { field: String },
178    #[serde(rename = "quota_exceeded")]
179    QuotaExceeded { scope: String },
180    #[serde(rename = "server_overload")]
181    ServerOverload,
182
183    // Clone DDL
184    #[serde(rename = "clone_depth_exceeded")]
185    CloneDepthExceeded { depth: u32, limit: u32 },
186    #[serde(rename = "cannot_clone_mirror")]
187    CannotCloneMirror { database: String },
188    #[serde(rename = "clone_dependency")]
189    CloneDependency { dependents: Vec<String> },
190    #[serde(rename = "clone_predates_query_time")]
191    ClonePredatesQueryTime { as_of_lsn: u64, created_at_lsn: u64 },
192
193    // Mirror DDL
194    /// Write attempted on a mirror database that has not been promoted.
195    #[serde(rename = "mirror_read_only")]
196    MirrorReadOnly { database: String },
197    /// Strong read requested on a mirror; the client should contact the source.
198    #[serde(rename = "stale_read_not_leader")]
199    StaleReadNotLeader {
200        database: String,
201        /// Hint: source cluster endpoint the client should redirect to.
202        source_cluster: String,
203    },
204    /// Operation requires the database to be a promoted mirror.
205    #[serde(rename = "mirror_not_promoted")]
206    MirrorNotPromoted { database: String },
207
208    // Move Tenant DDL
209    #[serde(rename = "move_tenant_drain_timeout")]
210    MoveTenantDrainTimeout { tenant: String, source_db: String },
211    #[serde(rename = "move_tenant_preflight_failed")]
212    MoveTenantPreflightFailed { tenant: String, detail: String },
213    #[serde(rename = "move_tenant_snapshot_failed")]
214    MoveTenantSnapshotFailed { tenant: String, detail: String },
215    #[serde(rename = "move_tenant_cutover_failed")]
216    MoveTenantCutoverFailed { tenant: String, detail: String },
217    #[serde(rename = "move_tenant_already_at_target")]
218    MoveTenantAlreadyAtTarget { tenant: String, target_db: String },
219
220    // Bridge / Dispatch / Internal
221    #[serde(rename = "bridge")]
222    Bridge {
223        plane: String,
224        op: String,
225        detail: String,
226    },
227    #[serde(rename = "dispatch")]
228    Dispatch { stage: String, detail: String },
229    #[serde(rename = "internal")]
230    Internal { component: String, detail: String },
231}