Skip to main content

nodedb_types/error/
code.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Stable numeric error codes for programmatic error handling.
4
5use std::fmt;
6
7use serde::{Deserialize, Serialize};
8
9/// Stable numeric error codes for programmatic error handling.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct ErrorCode(pub u16);
12
13/// Defines each named code once, and [`ErrorCode::ALL`] from the same list,
14/// so the list of every code cannot miss a constant.
15macro_rules! error_codes {
16    ( $( $(#[$meta:meta])* $name:ident = $value:literal; )* ) => {
17        impl ErrorCode {
18            $(
19                $(#[$meta])*
20                pub const $name: Self = Self($value);
21            )*
22
23            /// Every named code, in declaration order.
24            pub const ALL: &'static [ErrorCode] = &[ $( Self::$name ),* ];
25        }
26    };
27}
28
29error_codes! {
30    // Write path (1000–1099)
31    CONSTRAINT_VIOLATION = 1000;
32    WRITE_CONFLICT = 1001;
33    DEADLINE_EXCEEDED = 1002;
34    PREVALIDATION_REJECTED = 1003;
35    APPEND_ONLY_VIOLATION = 1010;
36    BALANCE_VIOLATION = 1011;
37    PERIOD_LOCKED = 1012;
38    STATE_TRANSITION_VIOLATION = 1013;
39    TRANSITION_CHECK_VIOLATION = 1014;
40    RETENTION_VIOLATION = 1015;
41    LEGAL_HOLD_ACTIVE = 1016;
42    /// A period-lock reference row exists but does not carry the
43    /// configured `status_column` — a misconfigured column name, not a
44    /// locked period.
45    PERIOD_LOCK_MISCONFIGURED = 1017;
46    TYPE_MISMATCH = 1020;
47    OVERFLOW = 1021;
48    INSUFFICIENT_BALANCE = 1022;
49    RATE_EXCEEDED = 1023;
50    TYPE_GUARD_VIOLATION = 1024;
51    /// A transaction rolled back for a reason other than a serialization
52    /// conflict, such as a participant error. The client retries it.
53    TRANSACTION_ROLLBACK = 1030;
54    /// The statement cannot run inside an explicit transaction block.
55    ACTIVE_SQL_TRANSACTION = 1031;
56
57    // Read path (1100–1199)
58    COLLECTION_NOT_FOUND = 1100;
59    DOCUMENT_NOT_FOUND = 1101;
60    COLLECTION_DRAINING = 1102;
61    COLLECTION_DEACTIVATED = 1103;
62    /// The named database does not exist.
63    DATABASE_NOT_FOUND = 1110;
64    /// A named catalog object (type, role, index, alert, …) other than a
65    /// collection or database does not exist. Generic: use the object name
66    /// in the message for specifics.
67    UNDEFINED_OBJECT = 1111;
68    /// A named catalog object already exists under that name.
69    ALREADY_EXISTS = 1112;
70    /// The target object exists but is not in a state that accepts this
71    /// operation (locked, busy, mid-transition).
72    OBJECT_NOT_READY = 1113;
73    /// A requested value/record does not exist. Generic: use for lookups
74    /// that don't fit `DOCUMENT_NOT_FOUND`'s collection/id shape.
75    NOT_FOUND = 1114;
76    /// A drop or revoke refused because other objects still depend on the
77    /// named object.
78    DEPENDENT_OBJECTS_EXIST = 1115;
79
80    // Query (1200–1299)
81    PLAN_ERROR = 1200;
82    SQL_NOT_ENABLED = 1202;
83    /// A function call names no registered scalar/aggregate/window function.
84    UNDEFINED_FUNCTION = 1203;
85    /// Expression evaluation divided or took a modulus by zero.
86    DIVISION_BY_ZERO = 1204;
87    /// A LIMIT/OFFSET/FETCH bound resolved outside `[0, usize::MAX]`.
88    INVALID_LIMIT_VALUE = 1205;
89    /// A column reference names no column of any relation in scope.
90    UNDEFINED_COLUMN = 1206;
91    /// A bare column name resolves against more than one relation in scope.
92    AMBIGUOUS_COLUMN = 1207;
93    /// A function received a value it cannot compute on: a vector of the
94    /// wrong dimension, an argument of the wrong shape, a malformed path.
95    DATA_EXCEPTION = 1208;
96    /// A statement exceeded a server limit on its own size or depth: a
97    /// recursion depth, a per-transaction staging budget.
98    PROGRAM_LIMIT_EXCEEDED = 1209;
99
100    // Engine ops (1300–1399)
101    ARRAY = 1300;
102
103    // Quota (1400–1499)
104
105    /// The proposed quota allocation would push the sum of all database quotas
106    /// past the configured global ceiling, or the sum of all tenant quotas past
107    /// the database ceiling.
108    QUOTA_OVERCOMMIT = 1400;
109    /// A request was rejected because the calling tenant has exhausted its quota
110    /// (QPS, memory, connections, or storage).
111    TENANT_QUOTA_EXCEEDED = 1401;
112    /// A request was rejected because the target database has exhausted its quota.
113    DATABASE_QUOTA_EXCEEDED = 1402;
114    /// The server is under global resource pressure and cannot accept new requests.
115    SERVER_OVERLOAD = 1403;
116
117    // Clone (1500–1599)
118
119    /// A `CLONE DATABASE` would exceed the maximum clone chain depth of 8.
120    CLONE_DEPTH_EXCEEDED = 1500;
121    /// A mirror database cannot be cloned; promote the mirror first.
122    CANNOT_CLONE_MIRROR = 1501;
123    /// The source database cannot be dropped while clones depend on it.
124    CLONE_DEPENDENCY = 1502;
125    /// A bitemporal `AS OF` query timestamp predates the clone's creation LSN.
126    CLONE_PREDATES_QUERY_TIME = 1503;
127    /// A write targeted a `Shadowed`/`Materializing` clone collection whose
128    /// engine has no copy-on-write support; `MATERIALIZE` the clone first.
129    CLONE_WRITE_REQUIRES_MATERIALIZE = 1504;
130
131    // Mirror (1700–1799)
132
133    /// Write attempted on a mirror database that has not yet been promoted.
134    MIRROR_READ_ONLY = 1700;
135    /// Strong consistency read requested on a mirror; mirrors cannot serve
136    /// strong reads. The client should retry against the source cluster.
137    STALE_READ_NOT_LEADER = 1701;
138    /// Operation requires the mirror to be promoted, but it has not been.
139    MIRROR_NOT_PROMOTED = 1702;
140    /// `DROP DATABASE` targeted the built-in `default` database, which is
141    /// immutable. Shares SQLSTATE `0A000` with `SQL_NOT_ENABLED` and
142    /// `CANNOT_CLONE_MIRROR`, so it must be constructed explicitly rather
143    /// than derived from the bare SQLSTATE string.
144    CANNOT_DROP_DEFAULT_DATABASE = 1710;
145
146    // Move Tenant (1600–1699)
147
148    /// `MOVE TENANT` drain phase timed out; source left unchanged.
149    MOVE_TENANT_DRAIN_TIMEOUT = 1600;
150    /// `MOVE TENANT` pre-flight failed; collection schema incompatibility.
151    MOVE_TENANT_PREFLIGHT_FAILED = 1601;
152    /// `MOVE TENANT` snapshot phase failed; source left unchanged.
153    MOVE_TENANT_SNAPSHOT_FAILED = 1602;
154    /// `MOVE TENANT` cutover phase failed; source still holds the data.
155    MOVE_TENANT_CUTOVER_FAILED = 1603;
156    /// Tenant is already at the target database; `MOVE TENANT` was a no-op.
157    MOVE_TENANT_ALREADY_AT_TARGET = 1604;
158
159    // Backup / Restore (1800–1899)
160    /// RESTORE targeted a tenant different from the one the envelope belongs to.
161    BACKUP_TENANT_MISMATCH = 1800;
162    /// Backup envelope did not decrypt under this server's configured backup KEK.
163    BACKUP_KEY_MISMATCH = 1801;
164
165    // Auth / Security (2000–2099)
166    AUTHORIZATION_DENIED = 2000;
167    AUTH_EXPIRED = 2001;
168    /// Authentication failed: a wrong password, an unknown user, or missing
169    /// credentials. One code for all of them, so a client cannot tell them
170    /// apart.
171    AUTHENTICATION_FAILED = 2002;
172    /// Vector insert or index rejected because the vector dimension exceeds the
173    /// tenant's `max_vector_dim` quota.
174    TENANT_VECTOR_DIM_EXCEEDED = 2010;
175    /// Graph traversal rejected because the requested depth exceeds the tenant's
176    /// `max_graph_depth` quota.
177    TENANT_GRAPH_DEPTH_EXCEEDED = 2011;
178
179    // Protocol handshake (2100–2199)
180    HANDSHAKE_FAILED = 2100;
181
182    // Sync (3000–3099)
183    SYNC_CONNECTION_FAILED = 3000;
184    SYNC_DELTA_REJECTED = 3001;
185    SHAPE_SUBSCRIPTION_FAILED = 3002;
186
187    // Storage (4000–4099)
188    STORAGE = 4000;
189    SEGMENT_CORRUPTED = 4001;
190    COLD_STORAGE = 4002;
191
192    // WAL (4100–4199)
193    WAL = 4100;
194
195    // Serialization (4200–4299)
196    SERIALIZATION = 4200;
197    CODEC = 4201;
198
199    // Config (5000–5099)
200    CONFIG = 5000;
201    BAD_REQUEST = 5001;
202
203    // Cluster (6000–6099)
204    NO_LEADER = 6000;
205    NOT_LEADER = 6001;
206    MIGRATION_IN_PROGRESS = 6002;
207    NODE_UNREACHABLE = 6003;
208    CLUSTER = 6010;
209
210    // Memory (7000–7099)
211    MEMORY_EXHAUSTED = 7000;
212
213    // Encryption (8000–8099)
214    ENCRYPTION = 8000;
215
216    // Internal (9000–9099)
217    INTERNAL = 9000;
218    BRIDGE = 9001;
219    DISPATCH = 9002;
220}
221
222impl fmt::Display for ErrorCode {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        write!(f, "NDB-{:04}", self.0)
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn error_code_display() {
234        assert_eq!(ErrorCode::CONSTRAINT_VIOLATION.to_string(), "NDB-1000");
235        assert_eq!(ErrorCode::INTERNAL.to_string(), "NDB-9000");
236        assert_eq!(ErrorCode::WAL.to_string(), "NDB-4100");
237    }
238
239    #[test]
240    fn every_code_has_a_distinct_value() {
241        let mut seen = std::collections::HashSet::new();
242        for code in ErrorCode::ALL {
243            assert!(seen.insert(code.0), "{code} is defined twice");
244        }
245        assert!(ErrorCode::ALL.contains(&ErrorCode::DATABASE_QUOTA_EXCEEDED));
246        assert!(ErrorCode::ALL.contains(&ErrorCode::DISPATCH));
247    }
248}