cortexdb-sdk 0.2.0-beta.2

Rust HTTP client SDK for CortexDB
Documentation
use std::fmt;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AqlBuildError {
    field: &'static str,
    value: String,
    expected: &'static str,
}

impl AqlBuildError {
    pub(crate) fn invalid(field: &'static str, value: &str, expected: &'static str) -> Self {
        Self {
            field,
            value: value.to_owned(),
            expected,
        }
    }
}

impl fmt::Display for AqlBuildError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "invalid AQL {} {:?}; expected {}",
            self.field, self.value, self.expected
        )
    }
}

impl std::error::Error for AqlBuildError {}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AqlRetrievalMode {
    Fast,
    Balanced,
    Semantic,
    Audit,
}

impl AqlRetrievalMode {
    pub(crate) fn as_str(self) -> &'static str {
        match self {
            Self::Fast => "fast",
            Self::Balanced => "balanced",
            Self::Semantic => "semantic",
            Self::Audit => "audit",
        }
    }
}

pub(crate) fn quote_string(value: &str) -> String {
    let mut quoted = String::with_capacity(value.len() + 2);
    quoted.push('"');
    for character in value.chars() {
        match character {
            '"' => quoted.push_str("\\\""),
            '\\' => quoted.push_str("\\\\"),
            '\n' => quoted.push_str("\\n"),
            '\r' => quoted.push_str("\\r"),
            '\t' => quoted.push_str("\\t"),
            _ => quoted.push(character),
        }
    }
    quoted.push('"');
    quoted
}

pub(crate) fn validate_identifier(field: &'static str, value: &str) -> Result<(), AqlBuildError> {
    let mut chars = value.chars();
    let Some(first) = chars.next() else {
        return Err(AqlBuildError::invalid(field, value, "non-empty identifier"));
    };
    if !(first == '_' || first.is_ascii_alphabetic()) {
        return Err(AqlBuildError::invalid(
            field,
            value,
            "identifier starting with '_' or an ASCII letter",
        ));
    }
    if chars.all(|ch| ch == '_' || ch == '-' || ch == ':' || ch.is_ascii_alphanumeric()) {
        Ok(())
    } else {
        Err(AqlBuildError::invalid(
            field,
            value,
            "identifier characters [A-Za-z0-9_:-]",
        ))
    }
}

pub(crate) fn validate_optional_fragment(
    field: &'static str,
    value: Option<&str>,
) -> Result<(), AqlBuildError> {
    match value {
        Some(raw) if raw.trim().is_empty() => {
            Err(AqlBuildError::invalid(field, raw, "non-empty AQL fragment"))
        }
        _ => Ok(()),
    }
}

pub(crate) fn validate_optional_decimal(
    field: &'static str,
    value: Option<&str>,
) -> Result<(), AqlBuildError> {
    let Some(value) = value else {
        return Ok(());
    };
    let Some((left, right)) = value.split_once('.') else {
        return Err(AqlBuildError::invalid(field, value, "decimal literal"));
    };
    if !left.is_empty()
        && !right.is_empty()
        && left.chars().all(|ch| ch.is_ascii_digit())
        && right.chars().all(|ch| ch.is_ascii_digit())
    {
        Ok(())
    } else {
        Err(AqlBuildError::invalid(field, value, "decimal literal"))
    }
}