Skip to main content

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}