cortex_sdk/
aql_support.rs1use std::fmt;
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub struct AqlBuildError {
5 field: &'static str,
6 value: String,
7 expected: &'static str,
8}
9
10impl AqlBuildError {
11 pub(crate) fn invalid(field: &'static str, value: &str, expected: &'static str) -> Self {
12 Self {
13 field,
14 value: value.to_owned(),
15 expected,
16 }
17 }
18}
19
20impl fmt::Display for AqlBuildError {
21 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
22 write!(
23 formatter,
24 "invalid AQL {} {:?}; expected {}",
25 self.field, self.value, self.expected
26 )
27 }
28}
29
30impl std::error::Error for AqlBuildError {}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum AqlRetrievalMode {
34 Fast,
35 Balanced,
36 Semantic,
37 Audit,
38}
39
40impl AqlRetrievalMode {
41 pub(crate) fn as_str(self) -> &'static str {
42 match self {
43 Self::Fast => "fast",
44 Self::Balanced => "balanced",
45 Self::Semantic => "semantic",
46 Self::Audit => "audit",
47 }
48 }
49}
50
51pub(crate) fn quote_string(value: &str) -> String {
52 let mut quoted = String::with_capacity(value.len() + 2);
53 quoted.push('"');
54 for character in value.chars() {
55 match character {
56 '"' => quoted.push_str("\\\""),
57 '\\' => quoted.push_str("\\\\"),
58 '\n' => quoted.push_str("\\n"),
59 '\r' => quoted.push_str("\\r"),
60 '\t' => quoted.push_str("\\t"),
61 _ => quoted.push(character),
62 }
63 }
64 quoted.push('"');
65 quoted
66}
67
68pub(crate) fn validate_identifier(field: &'static str, value: &str) -> Result<(), AqlBuildError> {
69 let mut chars = value.chars();
70 let Some(first) = chars.next() else {
71 return Err(AqlBuildError::invalid(field, value, "non-empty identifier"));
72 };
73 if !(first == '_' || first.is_ascii_alphabetic()) {
74 return Err(AqlBuildError::invalid(
75 field,
76 value,
77 "identifier starting with '_' or an ASCII letter",
78 ));
79 }
80 if chars.all(|ch| ch == '_' || ch == '-' || ch == ':' || ch.is_ascii_alphanumeric()) {
81 Ok(())
82 } else {
83 Err(AqlBuildError::invalid(
84 field,
85 value,
86 "identifier characters [A-Za-z0-9_:-]",
87 ))
88 }
89}
90
91pub(crate) fn validate_optional_fragment(
92 field: &'static str,
93 value: Option<&str>,
94) -> Result<(), AqlBuildError> {
95 match value {
96 Some(raw) if raw.trim().is_empty() => {
97 Err(AqlBuildError::invalid(field, raw, "non-empty AQL fragment"))
98 }
99 _ => Ok(()),
100 }
101}
102
103pub(crate) fn validate_optional_decimal(
104 field: &'static str,
105 value: Option<&str>,
106) -> Result<(), AqlBuildError> {
107 let Some(value) = value else {
108 return Ok(());
109 };
110 let Some((left, right)) = value.split_once('.') else {
111 return Err(AqlBuildError::invalid(field, value, "decimal literal"));
112 };
113 if !left.is_empty()
114 && !right.is_empty()
115 && left.chars().all(|ch| ch.is_ascii_digit())
116 && right.chars().all(|ch| ch.is_ascii_digit())
117 {
118 Ok(())
119 } else {
120 Err(AqlBuildError::invalid(field, value, "decimal literal"))
121 }
122}