selene_gql/error.rs
1//! Parser error types and GQLSTATUS mappings.
2
3use std::fmt;
4
5use selene_core::feature_register::FeatureId;
6
7use crate::ast::span::SourceSpan;
8
9/// Five-character ISO GQLSTATUS code.
10#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
11pub struct GqlStatus([u8; 5]);
12
13impl GqlStatus {
14 /// Maps to GQLSTATUS 42001 per ISO/IEC 39075:2024 section 23.1 Table 8.
15 pub const SYNTAX_ERROR: Self = Self(*b"42001");
16 /// Maps to GQLSTATUS 42N01, a selene-db implementation-defined subclass
17 /// under standard class 42 per ISO/IEC 39075:2024 section 23.1.
18 pub const FEATURE_NOT_SUPPORTED: Self = Self(*b"42N01");
19 /// Maps to GQLSTATUS 5GQL1, a selene-db implementation-defined class per
20 /// ISO/IEC 39075:2024 section 23.1.
21 pub const PROGRAM_LIMIT_EXCEEDED: Self = Self(*b"5GQL1");
22 /// Maps to GQLSTATUS 5GQL2, a selene-db implementation-defined class per
23 /// ISO/IEC 39075:2024 section 23.1.
24 pub const OPERATION_CANCELLED: Self = Self(*b"5GQL2");
25 /// Maps to GQLSTATUS 5GQL3, a selene-db implementation-defined class per
26 /// ISO/IEC 39075:2024 section 23.1.
27 pub const DEADLINE_EXCEEDED: Self = Self(*b"5GQL3");
28 /// Maps to GQLSTATUS 42N03, a selene-db implementation-defined subclass
29 /// under standard class 42 per ISO/IEC 39075:2024 section 23.1.
30 pub const UNDEFINED_REFERENCE: Self = Self(*b"42N03");
31 /// Maps to GQLSTATUS 42002 per ISO/IEC 39075:2024 section 23.1 Table 8.
32 pub const INVALID_REFERENCE: Self = Self(*b"42002");
33 /// Maps to GQLSTATUS 42N10, a selene-db implementation-defined subclass
34 /// under standard class 42 per ISO/IEC 39075:2024 section 23.1.
35 pub const DUPLICATE_OBJECT: Self = Self(*b"42N10");
36 /// Maps to GQLSTATUS 42012 per ISO/IEC 39075:2024 section 23.1 Table 8
37 /// ("number of node type key labels below supported minimum"). Raised when
38 /// the cardinality of an explicit `<node type key label set>` effective key
39 /// label set is below the implementation-defined (IL003) node-type key
40 /// label set minimum cardinality (§18.2 SR10).
41 pub const NODE_TYPE_KEY_LABELS_BELOW_MINIMUM: Self = Self(*b"42012");
42 /// Maps to GQLSTATUS 42013 per ISO/IEC 39075:2024 section 23.1 Table 8
43 /// ("number of node type key labels exceeds supported maximum"). Raised when
44 /// the cardinality of an explicit `<node type key label set>` effective key
45 /// label set exceeds the implementation-defined (IL003) node-type key label
46 /// set maximum cardinality (§18.2 SR11).
47 pub const NODE_TYPE_KEY_LABELS_EXCEED_MAXIMUM: Self = Self(*b"42013");
48 /// Maps to GQLSTATUS 42014 per ISO/IEC 39075:2024 section 23.1 Table 8
49 /// ("number of edge type key labels below supported minimum"). Raised when
50 /// the cardinality of an explicit `<edge type key label set>` effective key
51 /// label set is below the implementation-defined (IL003) edge-type key label
52 /// set minimum cardinality (§18.3 SR11).
53 pub const EDGE_TYPE_KEY_LABELS_BELOW_MINIMUM: Self = Self(*b"42014");
54 /// Maps to GQLSTATUS 42015 per ISO/IEC 39075:2024 section 23.1 Table 8
55 /// ("number of edge type key labels exceeds supported maximum"). Raised when
56 /// the cardinality of an explicit `<edge type key label set>` effective key
57 /// label set exceeds the implementation-defined (IL003) edge-type key label
58 /// set maximum cardinality (§18.3 SR12).
59 pub const EDGE_TYPE_KEY_LABELS_EXCEED_MAXIMUM: Self = Self(*b"42015");
60 /// Maps to GQLSTATUS 22G03 per ISO/IEC 39075:2024 section 23.1 Table 8.
61 pub const DATATYPE_MISMATCH: Self = Self(*b"22G03");
62 /// Maps to GQLSTATUS 22000 per ISO/IEC 39075:2024 section 23.1 Table 8.
63 pub const DATA_EXCEPTION: Self = Self(*b"22000");
64 /// Maps to GQLSTATUS 22003 per ISO/IEC 39075:2024 section 23.1 Table 8.
65 pub const NUMERIC_VALUE_OUT_OF_RANGE: Self = Self(*b"22003");
66 /// Maps to GQLSTATUS 22004 per ISO/IEC 39075:2024 section 23.1 Table 8.
67 pub const NULL_VALUE_NOT_ALLOWED: Self = Self(*b"22004");
68 /// Maps to GQLSTATUS 22007 per ISO/IEC 39075:2024 section 23.1 Table 8.
69 pub const INVALID_DATETIME_FORMAT: Self = Self(*b"22007");
70 /// Maps to GQLSTATUS 22011 per ISO/IEC 39075:2024 section 23.1 Table 8.
71 pub const SUBSTRING_ERROR: Self = Self(*b"22011");
72 /// Maps to GQLSTATUS 22012 per ISO/IEC 39075:2024 section 23.1 Table 8.
73 pub const DIVISION_BY_ZERO: Self = Self(*b"22012");
74 /// Maps to GQLSTATUS 22018 per ISO/IEC 39075:2024 section 23.1 Table 8.
75 pub const INVALID_CHARACTER_VALUE_FOR_CAST: Self = Self(*b"22018");
76 /// Maps to GQLSTATUS 22001 per ISO/IEC 39075:2024 section 23.1 Table 8.
77 pub const STRING_DATA_RIGHT_TRUNCATION: Self = Self(*b"22001");
78 /// Maps to GQLSTATUS 22009 per ISO/IEC 39075:2024 section 23.1 Table 8
79 /// (data exception — invalid time zone displacement value).
80 pub const INVALID_TIME_ZONE: Self = Self(*b"22009");
81 /// Maps to GQLSTATUS 2201E per ISO/IEC 39075:2024 section 23.1 Table 8.
82 pub const INVALID_ARGUMENT_FOR_NATURAL_LOGARITHM: Self = Self(*b"2201E");
83 /// Maps to GQLSTATUS 2201F per ISO/IEC 39075:2024 section 23.1 Table 8.
84 pub const INVALID_ARGUMENT_FOR_POWER_FUNCTION: Self = Self(*b"2201F");
85 /// Maps to GQLSTATUS 22027 per ISO/IEC 39075:2024 section 23.1 Table 8.
86 pub const TRIM_ERROR: Self = Self(*b"22027");
87 /// Maps to GQLSTATUS 22G02 per ISO/IEC 39075:2024 section 23.1 Table 8.
88 pub const NEGATIVE_LIMIT_VALUE: Self = Self(*b"22G02");
89 /// Maps to GQLSTATUS 22G04 per ISO/IEC 39075:2024 section 23.1 Table 8.
90 pub const VALUES_NOT_COMPARABLE: Self = Self(*b"22G04");
91 /// Maps to GQLSTATUS 22G05 per ISO/IEC 39075:2024 section 23.1 Table 8.
92 pub const INVALID_DATETIME_FUNCTION_FIELD_NAME: Self = Self(*b"22G05");
93 /// Maps to GQLSTATUS 22G06 per ISO/IEC 39075:2024 section 23.1 Table 8.
94 pub const INVALID_DATETIME_FUNCTION_VALUE: Self = Self(*b"22G06");
95 /// Maps to GQLSTATUS 22G07 per ISO/IEC 39075:2024 section 23.1 Table 8.
96 pub const INVALID_DURATION_FUNCTION_FIELD_NAME: Self = Self(*b"22G07");
97 /// Maps to GQLSTATUS 22G0B per ISO/IEC 39075:2024 section 23.1 Table 8.
98 pub const LIST_DATA_RIGHT_TRUNCATION: Self = Self(*b"22G0B");
99 /// Maps to GQLSTATUS 22G0C per ISO/IEC 39075:2024 section 23.1 Table 8.
100 pub const LIST_ELEMENT_ERROR: Self = Self(*b"22G0C");
101 /// Maps to GQLSTATUS 22G0F per ISO/IEC 39075:2024 section 23.1 Table 8
102 /// (data exception — invalid number of paths or groups). Raised when a
103 /// counted shortest path/group count (§16.6, §22.4 GR7) is not a positive
104 /// integer.
105 pub const INVALID_NUMBER_OF_PATHS_OR_GROUPS: Self = Self(*b"22G0F");
106 /// Maps to GQLSTATUS 22G0H per ISO/IEC 39075:2024 section 23.1 Table 8.
107 pub const INVALID_DURATION_FORMAT: Self = Self(*b"22G0H");
108 /// Maps to GQLSTATUS 22G10 per ISO/IEC 39075:2024 section 23.1 Table 8.
109 pub const PATH_DATA_RIGHT_TRUNCATION: Self = Self(*b"22G10");
110 /// Maps to GQLSTATUS 22G14 per ISO/IEC 39075:2024 section 23.1 Table 8.
111 pub const INCOMPATIBLE_TEMPORAL_INSTANT_UNIT_GROUPS: Self = Self(*b"22G14");
112 /// Maps to GQLSTATUS 22G0M per ISO/IEC 39075:2024 section 23.1 Table 8.
113 pub const MULTIPLE_ASSIGNMENTS_TO_GRAPH_ELEMENT_PROPERTY: Self = Self(*b"22G0M");
114 /// Maps to GQLSTATUS 22G0N per ISO/IEC 39075:2024 section 23.1 Table 8.
115 pub const NODE_LABELS_BELOW_SUPPORTED_MINIMUM: Self = Self(*b"22G0N");
116 /// Maps to GQLSTATUS 22G0P per ISO/IEC 39075:2024 section 23.1 Table 8.
117 pub const NODE_LABELS_EXCEED_SUPPORTED_MAXIMUM: Self = Self(*b"22G0P");
118 /// Maps to GQLSTATUS 22G0Q per ISO/IEC 39075:2024 section 23.1 Table 8.
119 pub const EDGE_LABELS_BELOW_SUPPORTED_MINIMUM: Self = Self(*b"22G0Q");
120 /// Maps to GQLSTATUS 22G0R per ISO/IEC 39075:2024 section 23.1 Table 8.
121 pub const EDGE_LABELS_EXCEED_SUPPORTED_MAXIMUM: Self = Self(*b"22G0R");
122 /// Maps to GQLSTATUS 22G0S per ISO/IEC 39075:2024 section 23.1 Table 8.
123 pub const NODE_PROPERTIES_EXCEED_SUPPORTED_MAXIMUM: Self = Self(*b"22G0S");
124 /// Maps to GQLSTATUS 22G0T per ISO/IEC 39075:2024 section 23.1 Table 8.
125 pub const EDGE_PROPERTIES_EXCEED_SUPPORTED_MAXIMUM: Self = Self(*b"22G0T");
126 /// Maps to GQLSTATUS 22G0U per ISO/IEC 39075:2024 section 23.1 Table 8.
127 pub const RECORD_FIELDS_DO_NOT_MATCH: Self = Self(*b"22G0U");
128 /// Maps to GQLSTATUS 22G0X per ISO/IEC 39075:2024 section 23.1 Table 8.
129 pub const RECORD_DATA_FIELD_UNASSIGNABLE: Self = Self(*b"22G0X");
130 /// Maps to GQLSTATUS 22G0Z per ISO/IEC 39075:2024 section 23.1 Table 8.
131 pub const MALFORMED_PATH: Self = Self(*b"22G0Z");
132 /// Maps to GQLSTATUS 25000 per ISO/IEC 39075:2024 section 23.1 Table 8.
133 pub const INVALID_TRANSACTION_STATE: Self = Self(*b"25000");
134 /// Maps to GQLSTATUS 25G01 per ISO/IEC 39075:2024 section 23.1 Table 8.
135 pub const ACTIVE_TRANSACTION: Self = Self(*b"25G01");
136 /// Maps to GQLSTATUS 25G02 per ISO/IEC 39075:2024 section 23.1 Table 8.
137 pub const INVALID_TRANSACTION_STATE_MIXING: Self = Self(*b"25G02");
138 /// Maps to GQLSTATUS 25G03 per ISO/IEC 39075:2024 section 23.1 Table 8.
139 pub const READ_ONLY_TRANSACTION_VIOLATION: Self = Self(*b"25G03");
140 /// Maps to GQLSTATUS 25N02, a selene-db implementation-defined subclass
141 /// under standard class 25 per ISO/IEC 39075:2024 section 23.1.
142 pub const IN_FAILED_TRANSACTION: Self = Self(*b"25N02");
143 /// Maps to GQLSTATUS 2D000 per ISO/IEC 39075:2024 section 23.1 Table 8.
144 pub const INVALID_TRANSACTION_TERMINATION: Self = Self(*b"2D000");
145 /// Maps to GQLSTATUS 2DN01, a selene-db implementation-defined subclass
146 /// under standard class 2D (invalid transaction/session termination) per
147 /// ISO/IEC 39075:2024 section 23.1. Raised when a GQL-request is issued
148 /// against a session already closed by `SESSION CLOSE` (section 7.3).
149 pub const SESSION_CLOSED: Self = Self(*b"2DN01");
150 /// Maps to GQLSTATUS 01G11 per ISO/IEC 39075:2024 section 23.1 Table 8.
151 pub const NULL_VALUE_ELIMINATED_IN_SET_FUNCTION: Self = Self(*b"01G11");
152 /// Maps to GQLSTATUS 01N01, a selene-db implementation-defined subclass
153 /// under standard warning class 01 per ISO/IEC 39075:2024 section 23.1.
154 pub const VALIDATION_MODE_RELAXED_WRITE: Self = Self(*b"01N01");
155 /// Maps to GQLSTATUS 42N04, a selene-db implementation-defined subclass
156 /// under standard class 42 per ISO/IEC 39075:2024 section 23.1.
157 pub const UNKNOWN_PROCEDURE: Self = Self(*b"42N04");
158 /// Maps to GQLSTATUS 22G03 per ISO/IEC 39075:2024 section 23.1 Table 8.
159 pub const INVALID_PROCEDURE_ARGUMENT: Self = Self(*b"22G03");
160 /// Maps to GQLSTATUS 42N28, a selene-db implementation-defined subclass
161 /// under standard class 42 per ISO/IEC 39075:2024 section 23.1.
162 pub const CAPABILITY_VIOLATION: Self = Self(*b"42N28");
163 /// Maps to GQLSTATUS 5GQL0, a selene-db implementation-defined class per
164 /// ISO/IEC 39075:2024 section 23.1. Specific executor diagnostics carry
165 /// detail tags under this single public class.
166 pub const IMPLEMENTATION_DEFINED_ERROR: Self = Self(*b"5GQL0");
167 /// Maps to GQLSTATUS G1001 per ISO/IEC 39075:2024 section 23.1 Table 8.
168 pub const DEPENDENT_OBJECT_STILL_EXISTS: Self = Self(*b"G1001");
169 /// Maps to GQLSTATUS G2000 per ISO/IEC 39075:2024 section 23.1 Table 8.
170 pub const GRAPH_TYPE_VIOLATION: Self = Self(*b"G2000");
171
172 /// Return this status as its 5-character string form.
173 #[must_use]
174 pub fn as_str(&self) -> &str {
175 std::str::from_utf8(&self.0).unwrap_or("5GQL0")
176 }
177
178 /// Return this status's two-character class code.
179 #[must_use]
180 pub const fn class(&self) -> [u8; 2] {
181 [self.0[0], self.0[1]]
182 }
183
184 /// Build a status from a 5-character code already emitted by a lower crate.
185 #[must_use]
186 pub fn from_code(code: &str) -> Option<Self> {
187 let bytes: [u8; 5] = code.as_bytes().try_into().ok()?;
188 bytes.iter().all(u8::is_ascii).then_some(Self(bytes))
189 }
190}
191
192impl fmt::Display for GqlStatus {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.write_str(self.as_str())
195 }
196}
197
198/// GQL parser and flagger error.
199#[derive(Debug, thiserror::Error, miette::Diagnostic)]
200#[non_exhaustive]
201pub enum ParserError {
202 /// Source text did not parse as supported GQL syntax.
203 #[error("{message}")]
204 #[diagnostic(code(SLENE_GQL_42001))]
205 SyntaxError {
206 /// ISO GQLSTATUS code.
207 status: GqlStatus,
208 /// Human-readable diagnostic message.
209 message: String,
210 /// Source span for the parse failure.
211 #[label("here")]
212 span: SourceSpan,
213 /// Optional repair hint.
214 #[help]
215 hint: Option<String>,
216 },
217
218 /// Parsed syntax requires a feature outside the current support set.
219 #[error("feature not supported: {} ({display_name})", feature_id.as_str())]
220 #[diagnostic(code(SLENE_GQL_42N01))]
221 UnsupportedFeature {
222 /// ISO feature identifier.
223 feature_id: FeatureId,
224 /// Human-readable feature name.
225 display_name: &'static str,
226 /// Source span requiring the feature.
227 #[label("requires feature")]
228 span: SourceSpan,
229 /// Static hint for enabling or avoiding the feature.
230 #[help]
231 hint: &'static str,
232 },
233
234 /// Query nesting exceeded the parser's recursion cap.
235 #[error("parser nesting limit exceeded ({limit})")]
236 #[diagnostic(
237 code(SLENE_GQL_5GQL1),
238 help("queries are bounded to 64 nested grouping, list, and record delimiters")
239 )]
240 NestingLimitExceeded {
241 /// Maximum admitted syntactic nesting depth.
242 limit: u32,
243 /// Source span that crossed the limit.
244 #[label("exceeds parser nesting limit")]
245 span: SourceSpan,
246 },
247
248 /// Query exceeded a parser complexity cap that bounds pest's recursive
249 /// descent before it begins.
250 ///
251 /// Maps to GQLSTATUS 5GQL1 (PROGRAM_LIMIT_EXCEEDED), a selene-db
252 /// implementation-defined class per ISO/IEC 39075:2024 section 23.1 (see
253 /// [`GqlStatus::PROGRAM_LIMIT_EXCEEDED`]).
254 ///
255 /// Distinct from [`Self::NestingLimitExceeded`], which bounds *all-bracket*
256 /// net delimiter nesting depth at a looser cap. This variant covers the
257 /// parser's complexity guards, each of which would otherwise drive
258 /// pathological pest behavior or overflow the native stack on a small
259 /// hostile input:
260 ///
261 /// - **`[`-depth** (pre-pest byte scan). pest is not packrat-memoized, so a
262 /// run of *unclosed* `[` nests the three ambiguous `[`-prefixed grammar
263 /// rules and recomputes their failed branches at every level, driving
264 /// super-linear backtracking. Balanced, promptly closed `[` (an edge
265 /// pattern, a flat list) never accrue depth, so legitimate wide paths and
266 /// lists are unaffected.
267 /// - **Zero-delimiter recursion depth** (pre-pest byte scan). A long run of
268 /// leading unary signs (`unary`), `NOT` keywords (`not_expr`), or nested
269 /// `CASE … END` expressions (`case_expr`) recurses pest's descent one stack
270 /// frame per level with no `(`/`[`/`{` delimiter to bound it, overflowing
271 /// the native stack (a non-unwindable crash) on a small input. Signs and
272 /// `NOT` are bounded as runs; `CASE` is bounded by a *monotone*
273 /// over-approximation — a real opener (counted only outside identifier
274 /// positions: property names, map keys, aliases, `YIELD` columns, params)
275 /// adds 1 plus its wrapping run and never decrements, because an identifier
276 /// `END` cannot be soundly distinguished from the keyword closer. The
277 /// conformant cost is that a statement whose combined `CASE`-count and
278 /// nesting pressure exceeds the ceiling (256) is rejected.
279 /// - **Expression nesting depth** (post-build, iterative scan). A flat
280 /// left-associative operator fold (`a OR a OR …`) or postfix chain
281 /// (`a.b.c.…`) parses and builds iteratively but yields a depth-N
282 /// `Box<ValueExpr>` tree whose *recursive* consumers (the Flagger,
283 /// `Drop`, the analyzer) overflow the native stack at ~130k deep. The
284 /// parser rejects any expression deeper than the shared recursion ceiling
285 /// (256) before the Flagger walks it.
286 ///
287 /// The pre-pest guards reject before recursive descent begins; the
288 /// expression-depth guard runs after AST construction and before the
289 /// Flagger. All are deterministic and cheap.
290 #[error("parser complexity limit exceeded (limit {limit})")]
291 #[diagnostic(
292 code(SLENE_GQL_5GQL1),
293 help(
294 "queries are bounded in `[` (list, index, comprehension) nesting depth, in \
295 zero-delimiter recursion depth (consecutive unary signs or `NOT` keywords, or nested \
296 `CASE … END` expressions), and in expression nesting depth (deep operator folds or \
297 access chains); deep nesting of any of these drives pathological parser recursion or \
298 overflows the native stack"
299 )
300 )]
301 ComplexityLimitExceeded {
302 /// Maximum admitted depth for the complexity dimension that was exceeded.
303 limit: u32,
304 /// Source span of the token that crossed the limit.
305 #[label("exceeds parser complexity limit")]
306 span: SourceSpan,
307 },
308
309 /// Source parsed at the grammar level, but no AST builder is implemented yet.
310 ///
311 /// Distinct from [`Self::SyntaxError`] (parse failed) and
312 /// [`Self::UnsupportedFeature`] (specific ISO feature not in the claim list).
313 /// This variant covers grammar surfaces selene-db will support but whose
314 /// builders land in a later brief.
315 #[error("not implemented: {message}")]
316 #[diagnostic(code(SLENE_GQL_42N01))]
317 NotImplemented {
318 /// Human-readable description of the missing capability.
319 message: String,
320 /// Source span requiring the missing capability.
321 #[label("not implemented yet")]
322 span: SourceSpan,
323 /// Pointer to the brief or milestone that lands the capability.
324 #[help]
325 hint: Option<String>,
326 },
327}
328
329impl ParserError {
330 /// Map this error to its 5-character ISO GQLSTATUS code.
331 #[must_use]
332 pub const fn gqlstatus(&self) -> GqlStatus {
333 match self {
334 Self::SyntaxError { status, .. } => *status,
335 Self::UnsupportedFeature { .. } | Self::NotImplemented { .. } => {
336 GqlStatus::FEATURE_NOT_SUPPORTED
337 }
338 Self::NestingLimitExceeded { .. } | Self::ComplexityLimitExceeded { .. } => {
339 GqlStatus::PROGRAM_LIMIT_EXCEEDED
340 }
341 }
342 }
343
344 pub(crate) fn syntax(
345 message: impl Into<String>,
346 span: SourceSpan,
347 hint: Option<String>,
348 ) -> Self {
349 Self::SyntaxError {
350 status: GqlStatus::SYNTAX_ERROR,
351 message: message.into(),
352 span,
353 hint,
354 }
355 }
356
357 /// Build a [`Self::SyntaxError`] carrying an explicit GQLSTATUS code rather
358 /// than the default `42001`. Used by builder-level static validations whose
359 /// ISO diagnostic is a specific data-exception subclass (e.g. counted
360 /// shortest path/group count `22G0F`, §22.4 GR7).
361 pub(crate) fn syntax_with_status(
362 status: GqlStatus,
363 message: impl Into<String>,
364 span: SourceSpan,
365 hint: Option<String>,
366 ) -> Self {
367 Self::SyntaxError {
368 status,
369 message: message.into(),
370 span,
371 hint,
372 }
373 }
374
375 pub(crate) fn not_implemented(
376 message: impl Into<String>,
377 span: SourceSpan,
378 hint: Option<&'static str>,
379 ) -> Self {
380 Self::NotImplemented {
381 message: message.into(),
382 span,
383 hint: hint.map(str::to_owned),
384 }
385 }
386
387 pub(crate) fn empty_program() -> Self {
388 Self::syntax(
389 "empty GQL program",
390 SourceSpan::default(),
391 Some("provide a RETURN statement".into()),
392 )
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::GqlStatus;
399
400 #[test]
401 fn gqlstatus_codes_match_iso_table_8_remap() {
402 let cases = [
403 (GqlStatus::SYNTAX_ERROR, "42001", *b"42"),
404 (GqlStatus::FEATURE_NOT_SUPPORTED, "42N01", *b"42"),
405 (GqlStatus::PROGRAM_LIMIT_EXCEEDED, "5GQL1", *b"5G"),
406 (GqlStatus::OPERATION_CANCELLED, "5GQL2", *b"5G"),
407 (GqlStatus::DEADLINE_EXCEEDED, "5GQL3", *b"5G"),
408 (GqlStatus::UNDEFINED_REFERENCE, "42N03", *b"42"),
409 (GqlStatus::INVALID_REFERENCE, "42002", *b"42"),
410 (GqlStatus::DUPLICATE_OBJECT, "42N10", *b"42"),
411 (
412 GqlStatus::NODE_TYPE_KEY_LABELS_BELOW_MINIMUM,
413 "42012",
414 *b"42",
415 ),
416 (
417 GqlStatus::NODE_TYPE_KEY_LABELS_EXCEED_MAXIMUM,
418 "42013",
419 *b"42",
420 ),
421 (
422 GqlStatus::EDGE_TYPE_KEY_LABELS_BELOW_MINIMUM,
423 "42014",
424 *b"42",
425 ),
426 (
427 GqlStatus::EDGE_TYPE_KEY_LABELS_EXCEED_MAXIMUM,
428 "42015",
429 *b"42",
430 ),
431 (GqlStatus::DATATYPE_MISMATCH, "22G03", *b"22"),
432 (GqlStatus::DATA_EXCEPTION, "22000", *b"22"),
433 (GqlStatus::NUMERIC_VALUE_OUT_OF_RANGE, "22003", *b"22"),
434 (GqlStatus::NULL_VALUE_NOT_ALLOWED, "22004", *b"22"),
435 (GqlStatus::INVALID_DATETIME_FORMAT, "22007", *b"22"),
436 (GqlStatus::SUBSTRING_ERROR, "22011", *b"22"),
437 (GqlStatus::DIVISION_BY_ZERO, "22012", *b"22"),
438 (GqlStatus::INVALID_CHARACTER_VALUE_FOR_CAST, "22018", *b"22"),
439 (GqlStatus::STRING_DATA_RIGHT_TRUNCATION, "22001", *b"22"),
440 (GqlStatus::INVALID_TIME_ZONE, "22009", *b"22"),
441 (
442 GqlStatus::INVALID_ARGUMENT_FOR_NATURAL_LOGARITHM,
443 "2201E",
444 *b"22",
445 ),
446 (
447 GqlStatus::INVALID_ARGUMENT_FOR_POWER_FUNCTION,
448 "2201F",
449 *b"22",
450 ),
451 (GqlStatus::TRIM_ERROR, "22027", *b"22"),
452 (GqlStatus::NEGATIVE_LIMIT_VALUE, "22G02", *b"22"),
453 (GqlStatus::VALUES_NOT_COMPARABLE, "22G04", *b"22"),
454 (
455 GqlStatus::INVALID_DATETIME_FUNCTION_FIELD_NAME,
456 "22G05",
457 *b"22",
458 ),
459 (GqlStatus::INVALID_DATETIME_FUNCTION_VALUE, "22G06", *b"22"),
460 (
461 GqlStatus::INVALID_DURATION_FUNCTION_FIELD_NAME,
462 "22G07",
463 *b"22",
464 ),
465 (GqlStatus::LIST_DATA_RIGHT_TRUNCATION, "22G0B", *b"22"),
466 (GqlStatus::LIST_ELEMENT_ERROR, "22G0C", *b"22"),
467 (
468 GqlStatus::INVALID_NUMBER_OF_PATHS_OR_GROUPS,
469 "22G0F",
470 *b"22",
471 ),
472 (GqlStatus::INVALID_DURATION_FORMAT, "22G0H", *b"22"),
473 (GqlStatus::PATH_DATA_RIGHT_TRUNCATION, "22G10", *b"22"),
474 (
475 GqlStatus::INCOMPATIBLE_TEMPORAL_INSTANT_UNIT_GROUPS,
476 "22G14",
477 *b"22",
478 ),
479 (
480 GqlStatus::MULTIPLE_ASSIGNMENTS_TO_GRAPH_ELEMENT_PROPERTY,
481 "22G0M",
482 *b"22",
483 ),
484 (
485 GqlStatus::NODE_LABELS_BELOW_SUPPORTED_MINIMUM,
486 "22G0N",
487 *b"22",
488 ),
489 (
490 GqlStatus::NODE_LABELS_EXCEED_SUPPORTED_MAXIMUM,
491 "22G0P",
492 *b"22",
493 ),
494 (
495 GqlStatus::EDGE_LABELS_BELOW_SUPPORTED_MINIMUM,
496 "22G0Q",
497 *b"22",
498 ),
499 (
500 GqlStatus::EDGE_LABELS_EXCEED_SUPPORTED_MAXIMUM,
501 "22G0R",
502 *b"22",
503 ),
504 (
505 GqlStatus::NODE_PROPERTIES_EXCEED_SUPPORTED_MAXIMUM,
506 "22G0S",
507 *b"22",
508 ),
509 (
510 GqlStatus::EDGE_PROPERTIES_EXCEED_SUPPORTED_MAXIMUM,
511 "22G0T",
512 *b"22",
513 ),
514 (GqlStatus::RECORD_FIELDS_DO_NOT_MATCH, "22G0U", *b"22"),
515 (GqlStatus::RECORD_DATA_FIELD_UNASSIGNABLE, "22G0X", *b"22"),
516 (GqlStatus::MALFORMED_PATH, "22G0Z", *b"22"),
517 (GqlStatus::INVALID_TRANSACTION_STATE, "25000", *b"25"),
518 (GqlStatus::ACTIVE_TRANSACTION, "25G01", *b"25"),
519 (GqlStatus::INVALID_TRANSACTION_STATE_MIXING, "25G02", *b"25"),
520 (GqlStatus::READ_ONLY_TRANSACTION_VIOLATION, "25G03", *b"25"),
521 (GqlStatus::IN_FAILED_TRANSACTION, "25N02", *b"25"),
522 (GqlStatus::INVALID_TRANSACTION_TERMINATION, "2D000", *b"2D"),
523 (GqlStatus::SESSION_CLOSED, "2DN01", *b"2D"),
524 (
525 GqlStatus::NULL_VALUE_ELIMINATED_IN_SET_FUNCTION,
526 "01G11",
527 *b"01",
528 ),
529 (GqlStatus::VALIDATION_MODE_RELAXED_WRITE, "01N01", *b"01"),
530 (GqlStatus::UNKNOWN_PROCEDURE, "42N04", *b"42"),
531 (GqlStatus::INVALID_PROCEDURE_ARGUMENT, "22G03", *b"22"),
532 (GqlStatus::CAPABILITY_VIOLATION, "42N28", *b"42"),
533 (GqlStatus::IMPLEMENTATION_DEFINED_ERROR, "5GQL0", *b"5G"),
534 (GqlStatus::DEPENDENT_OBJECT_STILL_EXISTS, "G1001", *b"G1"),
535 (GqlStatus::GRAPH_TYPE_VIOLATION, "G2000", *b"G2"),
536 ];
537
538 for (status, code, class) in cases {
539 assert_eq!(status.as_str(), code);
540 assert_eq!(status.class(), class);
541 assert!(
542 selene_core::gqlstatus_name(code).is_some(),
543 "GQLSTATUS {code} is public in selene-gql but missing from selene-core names"
544 );
545 }
546 }
547
548 #[test]
549 fn gqlstatus_from_code_accepts_five_ascii_bytes() {
550 assert_eq!(
551 GqlStatus::from_code("G2000"),
552 Some(GqlStatus::GRAPH_TYPE_VIOLATION)
553 );
554 assert_eq!(GqlStatus::from_code("2200"), None);
555 }
556}