gwp 0.2.0

A standalone, pure Rust gRPC wire protocol for GQL (ISO/IEC 39075)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
//! GQLSTATUS code constants and helpers (ISO/IEC 39075 Chapter 23).
//!
//! Every GQL operation produces a GQLSTATUS code. This module provides
//! well-known code constants and helper methods for constructing and
//! inspecting status values.

use crate::proto;

// ============================================================================
// Success codes (class 00)
// ============================================================================

/// Successful completion.
pub const SUCCESS: &str = "00000";

/// Successful completion with omitted result (DDL, session commands).
pub const OMITTED_RESULT: &str = "00001";

// ============================================================================
// Warning codes (class 01)
// ============================================================================

/// Warning (no subclass).
pub const WARNING: &str = "01000";

/// String data, right truncation.
pub const WARNING_STRING_TRUNCATION: &str = "01004";

/// Graph does not exist.
pub const WARNING_GRAPH_NOT_FOUND: &str = "01G03";

/// Graph type does not exist.
pub const WARNING_GRAPH_TYPE_NOT_FOUND: &str = "01G04";

/// Null value eliminated in set function.
pub const WARNING_NULL_ELIMINATED: &str = "01G11";

// ============================================================================
// No data (class 02)
// ============================================================================

/// No data - query matched nothing.
pub const NO_DATA: &str = "02000";

// ============================================================================
// Informational (class 03)
// ============================================================================

/// Informational (no subclass).
pub const INFORMATIONAL: &str = "03000";

// ============================================================================
// Connection exceptions (class 08)
// ============================================================================

/// Connection exception (no subclass).
pub const CONNECTION_EXCEPTION: &str = "08000";

/// Transaction resolution unknown.
pub const TRANSACTION_RESOLUTION_UNKNOWN: &str = "08007";

// ============================================================================
// Data exceptions (class 22)
// ============================================================================

/// Data exception (no subclass).
pub const DATA_EXCEPTION: &str = "22000";

/// String data, right truncation.
pub const STRING_TRUNCATION: &str = "22001";

/// Numeric value out of range.
pub const NUMERIC_OUT_OF_RANGE: &str = "22003";

/// Null value not allowed.
pub const NULL_NOT_ALLOWED: &str = "22004";

/// Invalid datetime format.
pub const INVALID_DATETIME_FORMAT: &str = "22007";

/// Datetime field overflow.
pub const DATETIME_OVERFLOW: &str = "22008";

/// Substring error.
pub const SUBSTRING_ERROR: &str = "22011";

/// Division by zero.
pub const DIVISION_BY_ZERO: &str = "22012";

/// Interval field overflow.
pub const INTERVAL_FIELD_OVERFLOW: &str = "22015";

/// Invalid character value for cast.
pub const INVALID_CHARACTER_VALUE_FOR_CAST: &str = "22018";

/// Invalid value type.
pub const INVALID_VALUE_TYPE: &str = "22G03";

/// Values not comparable.
pub const NOT_COMPARABLE: &str = "22G04";

/// Negative limit value.
pub const NEGATIVE_LIMIT: &str = "22G05";

/// Invalid element ID.
pub const INVALID_ELEMENT_ID: &str = "22G06";

/// Duplicate node in path.
pub const DUPLICATE_NODE_IN_PATH: &str = "22G07";

/// Duplicate edge in path.
pub const DUPLICATE_EDGE_IN_PATH: &str = "22G08";

/// List data, right truncation.
pub const LIST_DATA_RIGHT_TRUNCATION: &str = "22G09";

/// Incompatible list element types.
pub const INCOMPATIBLE_LIST_ELEMENT_TYPES: &str = "22G0A";

/// Invalid property reference.
pub const INVALID_PROPERTY_REFERENCE: &str = "22G0B";

/// Property not found.
pub const PROPERTY_NOT_FOUND: &str = "22G0C";

/// Invalid label value.
pub const INVALID_LABEL_VALUE: &str = "22G0D";

/// Invalid element type.
pub const INVALID_ELEMENT_TYPE: &str = "22G0E";

/// Incompatible record field types.
pub const INCOMPATIBLE_RECORD_FIELD_TYPES: &str = "22G0F";

/// Record fields do not match.
pub const RECORD_MISMATCH: &str = "22G0U";

/// Malformed path.
pub const MALFORMED_PATH: &str = "22G0Z";

// ============================================================================
// Transaction state (class 25)
// ============================================================================

/// Invalid transaction state (no subclass).
pub const INVALID_TRANSACTION_STATE: &str = "25000";

/// Active GQL-transaction already exists.
pub const ACTIVE_TRANSACTION: &str = "25G01";

/// No active GQL-transaction.
pub const NO_ACTIVE_TRANSACTION: &str = "25G02";

/// Read-only GQL-transaction.
pub const READ_ONLY_TRANSACTION: &str = "25G03";

/// GQL-transaction in failed state.
pub const TRANSACTION_FAILED_STATE: &str = "25G04";

// ============================================================================
// Transaction termination (class 2D)
// ============================================================================

/// Invalid transaction termination.
pub const INVALID_TRANSACTION_TERMINATION: &str = "2D000";

// ============================================================================
// Transaction rollback (class 40)
// ============================================================================

/// Transaction rollback.
pub const TRANSACTION_ROLLBACK: &str = "40000";

/// Statement completion unknown.
pub const COMPLETION_UNKNOWN: &str = "40003";

// ============================================================================
// Syntax / access (class 42)
// ============================================================================

/// Syntax error or access rule violation (no subclass).
pub const SYNTAX_OR_ACCESS_ERROR: &str = "42000";

/// Invalid syntax.
pub const INVALID_SYNTAX: &str = "42001";

/// Invalid reference.
pub const INVALID_REFERENCE: &str = "42002";

/// Duplicate definition.
pub const DUPLICATE_DEFINITION: &str = "42004";

/// Ambiguous reference.
pub const AMBIGUOUS_REFERENCE: &str = "42005";

/// Unsupported feature.
pub const UNSUPPORTED_FEATURE: &str = "42006";

/// Duplicate label.
pub const DUPLICATE_LABEL: &str = "42007";

/// Invalid number of arguments.
pub const INVALID_ARGUMENT_COUNT: &str = "42008";

/// Incompatible types.
pub const INCOMPATIBLE_TYPES: &str = "42009";

/// Invalid pattern.
pub const INVALID_PATTERN: &str = "42010";

/// Invalid operand for aggregation.
pub const INVALID_AGGREGATION_OPERAND: &str = "42011";

/// Invalid ordering specification.
pub const INVALID_ORDERING: &str = "42012";

/// Missing mandatory property.
pub const MISSING_MANDATORY_PROPERTY: &str = "42013";

/// Invalid graph modification.
pub const INVALID_GRAPH_MODIFICATION: &str = "42014";

/// Procedure not found.
pub const PROCEDURE_NOT_FOUND: &str = "42015";

// ============================================================================
// Dependent object errors (class G1)
// ============================================================================

/// Dependent objects still exist (no subclass).
pub const DEPENDENT_OBJECTS_EXIST: &str = "G1000";

/// Graph depends on schema.
pub const GRAPH_DEPENDS_ON_SCHEMA: &str = "G1001";

/// Graph type depends on schema.
pub const GRAPH_TYPE_DEPENDS_ON_SCHEMA: &str = "G1002";

/// Graph depends on graph type.
pub const GRAPH_DEPENDS_ON_GRAPH_TYPE: &str = "G1003";

// ============================================================================
// Graph type violation (class G2)
// ============================================================================

/// Graph type violation.
pub const GRAPH_TYPE_VIOLATION: &str = "G2000";

// ============================================================================
// Constructors
// ============================================================================

/// Create a successful `GqlStatus`.
#[must_use]
pub fn success() -> proto::GqlStatus {
    proto::GqlStatus {
        code: SUCCESS.to_owned(),
        message: "successful completion".to_owned(),
        diagnostic: None,
        cause: None,
    }
}

/// Create a successful `GqlStatus` with omitted result.
#[must_use]
pub fn omitted() -> proto::GqlStatus {
    proto::GqlStatus {
        code: OMITTED_RESULT.to_owned(),
        message: "successful completion - omitted result".to_owned(),
        diagnostic: None,
        cause: None,
    }
}

/// Create a no-data `GqlStatus`.
#[must_use]
pub fn no_data() -> proto::GqlStatus {
    proto::GqlStatus {
        code: NO_DATA.to_owned(),
        message: "no data".to_owned(),
        diagnostic: None,
        cause: None,
    }
}

/// Create an error `GqlStatus` with the given code and message.
#[must_use]
pub fn error(code: &str, message: impl Into<String>) -> proto::GqlStatus {
    proto::GqlStatus {
        code: code.to_owned(),
        message: message.into(),
        diagnostic: None,
        cause: None,
    }
}

/// Create a warning `GqlStatus` with the given code and message.
#[must_use]
pub fn warning(code: &str, message: impl Into<String>) -> proto::GqlStatus {
    proto::GqlStatus {
        code: code.to_owned(),
        message: message.into(),
        diagnostic: None,
        cause: None,
    }
}

/// Create an informational `GqlStatus` with the given code and message.
#[must_use]
pub fn informational(code: &str, message: impl Into<String>) -> proto::GqlStatus {
    proto::GqlStatus {
        code: code.to_owned(),
        message: message.into(),
        diagnostic: None,
        cause: None,
    }
}

/// Create an error `GqlStatus` with diagnostic context.
#[must_use]
pub fn error_with_diagnostic(
    code: &str,
    message: impl Into<String>,
    operation: impl Into<String>,
    operation_code: i32,
) -> proto::GqlStatus {
    proto::GqlStatus {
        code: code.to_owned(),
        message: message.into(),
        diagnostic: Some(proto::DiagnosticRecord {
            operation: operation.into(),
            operation_code,
            current_schema: None,
            invalid_reference: None,
        }),
        cause: None,
    }
}

// ============================================================================
// Inspection helpers
// ============================================================================

/// Returns the class (first 2 characters) of a GQLSTATUS code.
#[must_use]
pub fn class(code: &str) -> &str {
    if code.len() >= 2 { &code[..2] } else { code }
}

/// Returns true if the code represents a successful completion (class 00).
#[must_use]
pub fn is_success(code: &str) -> bool {
    class(code) == "00"
}

/// Returns true if the code represents a warning (class 01).
#[must_use]
pub fn is_warning(code: &str) -> bool {
    class(code) == "01"
}

/// Returns true if the code represents no data (class 02).
#[must_use]
pub fn is_no_data(code: &str) -> bool {
    class(code) == "02"
}

/// Returns true if the code represents an informational status (class 03).
#[must_use]
pub fn is_informational(code: &str) -> bool {
    class(code) == "03"
}

/// Returns true if the code represents an exception (class >= 08).
#[must_use]
pub fn is_exception(code: &str) -> bool {
    let c = class(code);
    // Exception classes: 08 and above (numeric), or letter-starting (G2, etc.)
    if c.len() < 2 {
        return false;
    }
    let first = c.as_bytes()[0];
    // Letter-starting classes are always exceptions
    if first.is_ascii_alphabetic() {
        return true;
    }
    // Numeric classes >= 08 are exceptions (00=success, 01=warn, 02=nodata, 03=info)
    c >= "08"
}

// ============================================================================
// Operation code constants (ISO/IEC 39075 Table 9)
// ============================================================================

/// Session SET SCHEMA operation.
pub const OP_SESSION_SET_SCHEMA: i32 = 1;
/// Session SET GRAPH operation.
pub const OP_SESSION_SET_GRAPH: i32 = 2;
/// Session SET TIME ZONE operation.
pub const OP_SESSION_SET_TIME_ZONE: i32 = 3;
/// Session SET PARAMETER operation.
pub const OP_SESSION_SET_PARAMETER: i32 = 4;
/// Session RESET operation.
pub const OP_SESSION_RESET: i32 = 5;
/// Session CLOSE operation.
pub const OP_SESSION_CLOSE: i32 = 6;
/// START TRANSACTION statement.
pub const OP_START_TRANSACTION: i32 = 100;
/// COMMIT statement.
pub const OP_COMMIT: i32 = 101;
/// ROLLBACK statement.
pub const OP_ROLLBACK: i32 = 102;
/// CREATE SCHEMA statement.
pub const OP_CREATE_SCHEMA: i32 = 200;
/// DROP SCHEMA statement.
pub const OP_DROP_SCHEMA: i32 = 201;
/// CREATE GRAPH statement.
pub const OP_CREATE_GRAPH: i32 = 300;
/// DROP GRAPH statement.
pub const OP_DROP_GRAPH: i32 = 301;
/// CREATE GRAPH TYPE statement.
pub const OP_CREATE_GRAPH_TYPE: i32 = 400;
/// DROP GRAPH TYPE statement.
pub const OP_DROP_GRAPH_TYPE: i32 = 401;
/// INSERT statement.
pub const OP_INSERT_STATEMENT: i32 = 500;
/// SET statement.
pub const OP_SET_STATEMENT: i32 = 501;
/// REMOVE statement.
pub const OP_REMOVE_STATEMENT: i32 = 502;
/// DELETE statement.
pub const OP_DELETE_STATEMENT: i32 = 503;
/// MATCH statement.
pub const OP_MATCH_STATEMENT: i32 = 600;
/// OPTIONAL MATCH.
pub const OP_OPTIONAL_MATCH: i32 = 601;
/// FILTER statement.
pub const OP_FILTER_STATEMENT: i32 = 602;
/// LET statement.
pub const OP_LET_STATEMENT: i32 = 603;
/// FOR statement.
pub const OP_FOR_STATEMENT: i32 = 604;
/// ORDER BY clause.
pub const OP_ORDER_BY: i32 = 605;
/// RETURN statement.
pub const OP_RETURN_STATEMENT: i32 = 700;
/// CALL procedure statement.
pub const OP_CALL_PROCEDURE: i32 = 800;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn success_status() {
        let s = success();
        assert_eq!(s.code, "00000");
        assert!(is_success(&s.code));
        assert!(!is_exception(&s.code));
    }

    #[test]
    fn omitted_status() {
        let s = omitted();
        assert_eq!(s.code, "00001");
        assert!(is_success(&s.code));
    }

    #[test]
    fn no_data_status() {
        let s = no_data();
        assert_eq!(s.code, "02000");
        assert!(is_no_data(&s.code));
        assert!(!is_success(&s.code));
        assert!(!is_exception(&s.code));
    }

    #[test]
    fn error_status() {
        let s = error(INVALID_SYNTAX, "unexpected token");
        assert_eq!(s.code, "42001");
        assert!(is_exception(&s.code));
        assert!(!is_success(&s.code));
    }

    #[test]
    fn error_with_diagnostic_status() {
        let s = error_with_diagnostic(
            NUMERIC_OUT_OF_RANGE,
            "value 999 exceeds INT8 range",
            "MATCH STATEMENT",
            600,
        );
        assert_eq!(s.code, "22003");
        assert!(is_exception(&s.code));
        let d = s.diagnostic.unwrap();
        assert_eq!(d.operation, "MATCH STATEMENT");
        assert_eq!(d.operation_code, 600);
    }

    #[test]
    fn warning_classification() {
        assert!(is_warning(WARNING));
        assert!(!is_exception(WARNING));
        assert!(!is_success(WARNING));
    }

    #[test]
    fn graph_type_violation_is_exception() {
        assert!(is_exception(GRAPH_TYPE_VIOLATION));
    }

    #[test]
    fn class_extraction() {
        assert_eq!(class("00000"), "00");
        assert_eq!(class("42001"), "42");
        assert_eq!(class("G2000"), "G2");
    }

    #[test]
    fn warning_constructor() {
        let s = warning(WARNING_GRAPH_NOT_FOUND, "graph 'test' does not exist");
        assert_eq!(s.code, "01G03");
        assert!(is_warning(&s.code));
        assert!(!is_exception(&s.code));
    }

    #[test]
    fn informational_constructor() {
        let s = informational(INFORMATIONAL, "operation completed with information");
        assert_eq!(s.code, "03000");
        assert!(is_informational(&s.code));
        assert!(!is_exception(&s.code));
    }

    #[test]
    fn connection_exception_is_exception() {
        assert!(is_exception(CONNECTION_EXCEPTION));
        assert!(is_exception(TRANSACTION_RESOLUTION_UNKNOWN));
    }

    #[test]
    fn dependent_objects_is_exception() {
        assert!(is_exception(DEPENDENT_OBJECTS_EXIST));
        assert!(is_exception(GRAPH_DEPENDS_ON_SCHEMA));
    }
}