grafeo_common/utils/
gqlstatus.rs1use std::fmt;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub struct GqlStatus {
20 code: [u8; 5],
21}
22
23impl GqlStatus {
24 pub const SUCCESS: Self = Self::from_bytes(*b"00000");
28
29 pub const SUCCESS_OMITTED_RESULT: Self = Self::from_bytes(*b"00001");
31
32 pub const WARNING: Self = Self::from_bytes(*b"01000");
36
37 pub const WARNING_STRING_TRUNCATION: Self = Self::from_bytes(*b"01004");
39
40 pub const WARNING_GRAPH_NOT_EXIST: Self = Self::from_bytes(*b"01G03");
42
43 pub const WARNING_GRAPH_TYPE_NOT_EXIST: Self = Self::from_bytes(*b"01G04");
45
46 pub const WARNING_NULL_ELIMINATED: Self = Self::from_bytes(*b"01G11");
48
49 pub const NO_DATA: Self = Self::from_bytes(*b"02000");
53
54 pub const DATA_EXCEPTION: Self = Self::from_bytes(*b"22000");
58
59 pub const DATA_STRING_TRUNCATION: Self = Self::from_bytes(*b"22001");
61
62 pub const DATA_NUMERIC_OUT_OF_RANGE: Self = Self::from_bytes(*b"22003");
64
65 pub const DATA_NULL_NOT_ALLOWED: Self = Self::from_bytes(*b"22004");
67
68 pub const DATA_DIVISION_BY_ZERO: Self = Self::from_bytes(*b"22012");
70
71 pub const DATA_NEGATIVE_LIMIT: Self = Self::from_bytes(*b"22G02");
73
74 pub const DATA_INVALID_VALUE_TYPE: Self = Self::from_bytes(*b"22G03");
76
77 pub const DATA_VALUES_NOT_COMPARABLE: Self = Self::from_bytes(*b"22G04");
79
80 pub const INVALID_TX_STATE: Self = Self::from_bytes(*b"25000");
84
85 pub const INVALID_TX_ACTIVE: Self = Self::from_bytes(*b"25G01");
87
88 pub const INVALID_TX_READ_ONLY: Self = Self::from_bytes(*b"25G03");
90
91 pub const INVALID_TX_TERMINATION: Self = Self::from_bytes(*b"2D000");
95
96 pub const TX_ROLLBACK: Self = Self::from_bytes(*b"40000");
100
101 pub const TX_COMPLETION_UNKNOWN: Self = Self::from_bytes(*b"40003");
103
104 pub const SYNTAX_ERROR: Self = Self::from_bytes(*b"42000");
108
109 pub const SYNTAX_INVALID: Self = Self::from_bytes(*b"42001");
111
112 pub const SYNTAX_INVALID_REFERENCE: Self = Self::from_bytes(*b"42002");
114
115 pub const DEPENDENT_OBJECT_ERROR: Self = Self::from_bytes(*b"G1000");
119
120 pub const GRAPH_TYPE_VIOLATION: Self = Self::from_bytes(*b"G2000");
124
125 #[must_use]
129 const fn from_bytes(bytes: [u8; 5]) -> Self {
130 Self { code: bytes }
131 }
132
133 #[must_use]
137 pub fn from_str(s: &str) -> Option<Self> {
138 let bytes = s.as_bytes();
139 if bytes.len() != 5 {
140 return None;
141 }
142 if !bytes.iter().all(|b| b.is_ascii_alphanumeric()) {
143 return None;
144 }
145 Some(Self {
146 code: [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]],
147 })
148 }
149
150 #[must_use]
152 pub fn as_str(&self) -> &str {
153 std::str::from_utf8(&self.code).unwrap_or("?????")
155 }
156
157 #[must_use]
159 pub fn class_code(&self) -> &str {
160 &self.as_str()[..2]
161 }
162
163 #[must_use]
165 pub fn subclass_code(&self) -> &str {
166 &self.as_str()[2..]
167 }
168
169 #[must_use]
171 pub fn is_success(&self) -> bool {
172 self.code[0] == b'0' && self.code[1] == b'0'
173 }
174
175 #[must_use]
177 pub fn is_warning(&self) -> bool {
178 self.code[0] == b'0' && self.code[1] == b'1'
179 }
180
181 #[must_use]
183 pub fn is_no_data(&self) -> bool {
184 self.code[0] == b'0' && self.code[1] == b'2'
185 }
186
187 #[must_use]
189 pub fn is_exception(&self) -> bool {
190 !self.is_success() && !self.is_warning() && !self.is_no_data()
191 }
192}
193
194impl fmt::Display for GqlStatus {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 f.write_str(self.as_str())
197 }
198}
199
200impl From<&super::error::Error> for GqlStatus {
202 fn from(err: &super::error::Error) -> Self {
203 use super::error::{Error, QueryErrorKind, TransactionError};
204
205 match err {
206 Error::Query(q) => match q.kind {
207 QueryErrorKind::Lexer | QueryErrorKind::Syntax => GqlStatus::SYNTAX_INVALID,
208 QueryErrorKind::Semantic => GqlStatus::SYNTAX_INVALID_REFERENCE,
209 QueryErrorKind::Optimization => GqlStatus::SYNTAX_ERROR,
210 QueryErrorKind::Execution => GqlStatus::DATA_EXCEPTION,
211 },
212 Error::Transaction(t) => match t {
213 TransactionError::ReadOnly => GqlStatus::INVALID_TX_READ_ONLY,
214 TransactionError::InvalidState(_) => GqlStatus::INVALID_TX_STATE,
215 TransactionError::Aborted
216 | TransactionError::Conflict
217 | TransactionError::WriteConflict(_) => GqlStatus::TX_ROLLBACK,
218 TransactionError::SerializationFailure(_) => GqlStatus::TX_ROLLBACK,
219 TransactionError::Deadlock => GqlStatus::TX_ROLLBACK,
220 TransactionError::Timeout => GqlStatus::INVALID_TX_STATE,
221 },
222 Error::TypeMismatch { .. } => GqlStatus::DATA_INVALID_VALUE_TYPE,
223 Error::InvalidValue(_) => GqlStatus::DATA_EXCEPTION,
224 Error::NodeNotFound(_) | Error::EdgeNotFound(_) => GqlStatus::NO_DATA,
225 Error::PropertyNotFound(_) | Error::LabelNotFound(_) => {
226 GqlStatus::SYNTAX_INVALID_REFERENCE
227 }
228 Error::Storage(_) => GqlStatus::DATA_EXCEPTION,
229 Error::Serialization(_) => GqlStatus::DATA_EXCEPTION,
230 Error::Io(_) => GqlStatus::DATA_EXCEPTION,
231 Error::Internal(_) => GqlStatus::DATA_EXCEPTION,
232 }
233 }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct DiagnosticRecord {
241 pub operation: String,
243 pub operation_code: i32,
245 pub current_schema: Option<String>,
247 pub invalid_reference: Option<String>,
249}
250
251impl DiagnosticRecord {
252 #[must_use]
254 pub fn for_query(operation: impl Into<String>, operation_code: i32) -> Self {
255 Self {
256 operation: operation.into(),
257 operation_code,
258 current_schema: None,
259 invalid_reference: None,
260 }
261 }
262}
263
264pub mod operation_codes {
266 pub const SESSION_SET_SCHEMA: i32 = 1;
268 pub const SESSION_SET_GRAPH: i32 = 2;
270 pub const SESSION_SET_TIME_ZONE: i32 = 3;
272 pub const SESSION_RESET: i32 = 7;
274 pub const SESSION_CLOSE: i32 = 8;
276 pub const START_TRANSACTION: i32 = 50;
278 pub const ROLLBACK: i32 = 51;
280 pub const COMMIT: i32 = 52;
282 pub const CREATE_SCHEMA: i32 = 100;
284 pub const DROP_SCHEMA: i32 = 101;
286 pub const CREATE_GRAPH: i32 = 200;
288 pub const DROP_GRAPH: i32 = 201;
290 pub const CREATE_GRAPH_TYPE: i32 = 300;
292 pub const DROP_GRAPH_TYPE: i32 = 301;
294 pub const INSERT: i32 = 500;
296 pub const SET: i32 = 501;
298 pub const REMOVE: i32 = 502;
300 pub const DELETE: i32 = 503;
302 pub const MATCH: i32 = 600;
304 pub const FILTER: i32 = 601;
306 pub const LET: i32 = 602;
308 pub const FOR: i32 = 603;
310 pub const ORDER_BY_AND_PAGE: i32 = 604;
312 pub const RETURN: i32 = 605;
314 pub const SELECT: i32 = 606;
316 pub const CALL_PROCEDURE: i32 = 800;
318 pub const UNRECOGNIZED: i32 = 0;
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_gqlstatus_constants() {
328 assert_eq!(GqlStatus::SUCCESS.as_str(), "00000");
329 assert_eq!(GqlStatus::NO_DATA.as_str(), "02000");
330 assert_eq!(GqlStatus::SYNTAX_INVALID.as_str(), "42001");
331 assert_eq!(GqlStatus::TX_ROLLBACK.as_str(), "40000");
332 }
333
334 #[test]
335 fn test_gqlstatus_classification() {
336 assert!(GqlStatus::SUCCESS.is_success());
337 assert!(!GqlStatus::SUCCESS.is_warning());
338 assert!(!GqlStatus::SUCCESS.is_exception());
339
340 assert!(GqlStatus::WARNING.is_warning());
341 assert!(!GqlStatus::WARNING.is_success());
342 assert!(!GqlStatus::WARNING.is_exception());
343
344 assert!(GqlStatus::NO_DATA.is_no_data());
345 assert!(!GqlStatus::NO_DATA.is_exception());
346
347 assert!(GqlStatus::SYNTAX_ERROR.is_exception());
348 assert!(GqlStatus::DATA_EXCEPTION.is_exception());
349 assert!(GqlStatus::TX_ROLLBACK.is_exception());
350 }
351
352 #[test]
353 fn test_gqlstatus_class_subclass() {
354 assert_eq!(GqlStatus::SUCCESS.class_code(), "00");
355 assert_eq!(GqlStatus::SUCCESS.subclass_code(), "000");
356
357 assert_eq!(GqlStatus::SYNTAX_INVALID.class_code(), "42");
358 assert_eq!(GqlStatus::SYNTAX_INVALID.subclass_code(), "001");
359
360 assert_eq!(GqlStatus::DATA_DIVISION_BY_ZERO.class_code(), "22");
361 assert_eq!(GqlStatus::DATA_DIVISION_BY_ZERO.subclass_code(), "012");
362 }
363
364 #[test]
365 fn test_gqlstatus_from_str() {
366 assert_eq!(GqlStatus::from_str("00000"), Some(GqlStatus::SUCCESS));
367 assert_eq!(GqlStatus::from_str("0000"), None); assert_eq!(GqlStatus::from_str("000000"), None); assert_eq!(GqlStatus::from_str("00 00"), None); }
371
372 #[test]
373 fn test_gqlstatus_display() {
374 assert_eq!(format!("{}", GqlStatus::SUCCESS), "00000");
375 assert_eq!(format!("{}", GqlStatus::SYNTAX_INVALID), "42001");
376 }
377
378 #[test]
379 fn test_error_to_gqlstatus() {
380 use super::super::error::{Error, QueryError, QueryErrorKind, TransactionError};
381
382 let syntax_err = Error::Query(QueryError::new(QueryErrorKind::Syntax, "bad syntax"));
383 assert_eq!(GqlStatus::from(&syntax_err), GqlStatus::SYNTAX_INVALID);
384
385 let semantic_err = Error::Query(QueryError::new(QueryErrorKind::Semantic, "unknown label"));
386 assert_eq!(
387 GqlStatus::from(&semantic_err),
388 GqlStatus::SYNTAX_INVALID_REFERENCE
389 );
390
391 let tx_err = Error::Transaction(TransactionError::ReadOnly);
392 assert_eq!(GqlStatus::from(&tx_err), GqlStatus::INVALID_TX_READ_ONLY);
393
394 let conflict = Error::Transaction(TransactionError::Conflict);
395 assert_eq!(GqlStatus::from(&conflict), GqlStatus::TX_ROLLBACK);
396
397 let type_err = Error::TypeMismatch {
398 expected: "INT64".into(),
399 found: "STRING".into(),
400 };
401 assert_eq!(
402 GqlStatus::from(&type_err),
403 GqlStatus::DATA_INVALID_VALUE_TYPE
404 );
405 }
406
407 #[test]
408 fn test_diagnostic_record() {
409 let record = DiagnosticRecord::for_query("MATCH STATEMENT", operation_codes::MATCH);
410 assert_eq!(record.operation, "MATCH STATEMENT");
411 assert_eq!(record.operation_code, 600);
412 assert!(record.current_schema.is_none());
413 assert!(record.invalid_reference.is_none());
414 }
415}