Skip to main content

cortex_sdk/
aql_support.rs

1use 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}