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 QueryErrorKind::Timeout => GqlStatus::DATA_EXCEPTION,
212 },
213 Error::Transaction(t) => match t {
214 TransactionError::ReadOnly => GqlStatus::INVALID_TX_READ_ONLY,
215 TransactionError::InvalidState(_) => GqlStatus::INVALID_TX_STATE,
216 TransactionError::Aborted
217 | TransactionError::Conflict
218 | TransactionError::WriteConflict(_) => GqlStatus::TX_ROLLBACK,
219 TransactionError::SerializationFailure(_) => GqlStatus::TX_ROLLBACK,
220 TransactionError::Deadlock => GqlStatus::TX_ROLLBACK,
221 TransactionError::Timeout => GqlStatus::INVALID_TX_STATE,
222 },
223 Error::TypeMismatch { .. } => GqlStatus::DATA_INVALID_VALUE_TYPE,
224 Error::InvalidValue(_) => GqlStatus::DATA_EXCEPTION,
225 Error::NodeNotFound(_) | Error::EdgeNotFound(_) => GqlStatus::NO_DATA,
226 Error::PropertyNotFound(_) | Error::LabelNotFound(_) => {
227 GqlStatus::SYNTAX_INVALID_REFERENCE
228 }
229 Error::Storage(_) => GqlStatus::DATA_EXCEPTION,
230 Error::Serialization(_) => GqlStatus::DATA_EXCEPTION,
231 Error::Io(_) => GqlStatus::DATA_EXCEPTION,
232 Error::Internal(_) => GqlStatus::DATA_EXCEPTION,
233 }
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct DiagnosticRecord {
242 pub operation: String,
244 pub operation_code: i32,
246 pub current_schema: Option<String>,
248 pub invalid_reference: Option<String>,
250}
251
252impl DiagnosticRecord {
253 #[must_use]
255 pub fn for_query(operation: impl Into<String>, operation_code: i32) -> Self {
256 Self {
257 operation: operation.into(),
258 operation_code,
259 current_schema: None,
260 invalid_reference: None,
261 }
262 }
263}
264
265pub mod operation_codes {
267 pub const SESSION_SET_SCHEMA: i32 = 1;
269 pub const SESSION_SET_GRAPH: i32 = 2;
271 pub const SESSION_SET_TIME_ZONE: i32 = 3;
273 pub const SESSION_RESET: i32 = 7;
275 pub const SESSION_CLOSE: i32 = 8;
277 pub const START_TRANSACTION: i32 = 50;
279 pub const ROLLBACK: i32 = 51;
281 pub const COMMIT: i32 = 52;
283 pub const CREATE_SCHEMA: i32 = 100;
285 pub const DROP_SCHEMA: i32 = 101;
287 pub const CREATE_GRAPH: i32 = 200;
289 pub const DROP_GRAPH: i32 = 201;
291 pub const CREATE_GRAPH_TYPE: i32 = 300;
293 pub const DROP_GRAPH_TYPE: i32 = 301;
295 pub const INSERT: i32 = 500;
297 pub const SET: i32 = 501;
299 pub const REMOVE: i32 = 502;
301 pub const DELETE: i32 = 503;
303 pub const MATCH: i32 = 600;
305 pub const FILTER: i32 = 601;
307 pub const LET: i32 = 602;
309 pub const FOR: i32 = 603;
311 pub const ORDER_BY_AND_PAGE: i32 = 604;
313 pub const RETURN: i32 = 605;
315 pub const SELECT: i32 = 606;
317 pub const CALL_PROCEDURE: i32 = 800;
319 pub const UNRECOGNIZED: i32 = 0;
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_gqlstatus_constants() {
329 assert_eq!(GqlStatus::SUCCESS.as_str(), "00000");
330 assert_eq!(GqlStatus::NO_DATA.as_str(), "02000");
331 assert_eq!(GqlStatus::SYNTAX_INVALID.as_str(), "42001");
332 assert_eq!(GqlStatus::TX_ROLLBACK.as_str(), "40000");
333 }
334
335 #[test]
336 fn test_gqlstatus_classification() {
337 assert!(GqlStatus::SUCCESS.is_success());
338 assert!(!GqlStatus::SUCCESS.is_warning());
339 assert!(!GqlStatus::SUCCESS.is_exception());
340
341 assert!(GqlStatus::WARNING.is_warning());
342 assert!(!GqlStatus::WARNING.is_success());
343 assert!(!GqlStatus::WARNING.is_exception());
344
345 assert!(GqlStatus::NO_DATA.is_no_data());
346 assert!(!GqlStatus::NO_DATA.is_exception());
347
348 assert!(GqlStatus::SYNTAX_ERROR.is_exception());
349 assert!(GqlStatus::DATA_EXCEPTION.is_exception());
350 assert!(GqlStatus::TX_ROLLBACK.is_exception());
351 }
352
353 #[test]
354 fn test_gqlstatus_class_subclass() {
355 assert_eq!(GqlStatus::SUCCESS.class_code(), "00");
356 assert_eq!(GqlStatus::SUCCESS.subclass_code(), "000");
357
358 assert_eq!(GqlStatus::SYNTAX_INVALID.class_code(), "42");
359 assert_eq!(GqlStatus::SYNTAX_INVALID.subclass_code(), "001");
360
361 assert_eq!(GqlStatus::DATA_DIVISION_BY_ZERO.class_code(), "22");
362 assert_eq!(GqlStatus::DATA_DIVISION_BY_ZERO.subclass_code(), "012");
363 }
364
365 #[test]
366 fn test_gqlstatus_from_str() {
367 assert_eq!(GqlStatus::from_str("00000"), Some(GqlStatus::SUCCESS));
368 assert_eq!(GqlStatus::from_str("0000"), None); assert_eq!(GqlStatus::from_str("000000"), None); assert_eq!(GqlStatus::from_str("00 00"), None); }
372
373 #[test]
374 fn test_gqlstatus_display() {
375 assert_eq!(format!("{}", GqlStatus::SUCCESS), "00000");
376 assert_eq!(format!("{}", GqlStatus::SYNTAX_INVALID), "42001");
377 }
378
379 #[test]
380 fn test_error_to_gqlstatus() {
381 use super::super::error::{Error, QueryError, QueryErrorKind, TransactionError};
382
383 let syntax_err = Error::Query(QueryError::new(QueryErrorKind::Syntax, "bad syntax"));
384 assert_eq!(GqlStatus::from(&syntax_err), GqlStatus::SYNTAX_INVALID);
385
386 let semantic_err = Error::Query(QueryError::new(QueryErrorKind::Semantic, "unknown label"));
387 assert_eq!(
388 GqlStatus::from(&semantic_err),
389 GqlStatus::SYNTAX_INVALID_REFERENCE
390 );
391
392 let tx_err = Error::Transaction(TransactionError::ReadOnly);
393 assert_eq!(GqlStatus::from(&tx_err), GqlStatus::INVALID_TX_READ_ONLY);
394
395 let conflict = Error::Transaction(TransactionError::Conflict);
396 assert_eq!(GqlStatus::from(&conflict), GqlStatus::TX_ROLLBACK);
397
398 let type_err = Error::TypeMismatch {
399 expected: "INT64".into(),
400 found: "STRING".into(),
401 };
402 assert_eq!(
403 GqlStatus::from(&type_err),
404 GqlStatus::DATA_INVALID_VALUE_TYPE
405 );
406 }
407
408 #[test]
409 fn test_diagnostic_record() {
410 let record = DiagnosticRecord::for_query("MATCH STATEMENT", operation_codes::MATCH);
411 assert_eq!(record.operation, "MATCH STATEMENT");
412 assert_eq!(record.operation_code, 600);
413 assert!(record.current_schema.is_none());
414 assert!(record.invalid_reference.is_none());
415 }
416}