surrealql-language-server 0.1.2

Language Server Protocol implementation for SurrealQL
use std::fmt;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TypeExpr {
    Unknown,
    Scalar(String),
    Record(String),
    Array(Box<TypeExpr>),
    Option(Box<TypeExpr>),
    Union(Vec<TypeExpr>),
    Other(String),
}

impl TypeExpr {
    pub fn parse(input: &str) -> Self {
        let trimmed = input.trim();
        if trimmed.is_empty() {
            return Self::Unknown;
        }

        if let Some(parts) = split_top_level(trimmed, '|') {
            return Self::Union(parts.into_iter().map(Self::parse).collect());
        }

        if let Some(inner) = unwrap_generic(trimmed, "record") {
            return Self::Record(inner.trim().to_string());
        }
        if let Some(inner) = unwrap_generic(trimmed, "array") {
            return Self::Array(Box::new(Self::parse(inner)));
        }
        if let Some(inner) = unwrap_generic(trimmed, "option") {
            return Self::Option(Box::new(Self::parse(inner)));
        }

        if trimmed
            .chars()
            .all(|ch| ch.is_alphanumeric() || matches!(ch, '_' | ':' | '$'))
        {
            return Self::Scalar(trimmed.to_string());
        }

        Self::Other(trimmed.to_string())
    }

    pub fn record_tables(&self) -> Vec<String> {
        match self {
            Self::Record(name) => vec![name.clone()],
            Self::Array(inner) | Self::Option(inner) => inner.record_tables(),
            Self::Union(parts) => parts.iter().flat_map(Self::record_tables).collect(),
            Self::Unknown | Self::Scalar(_) | Self::Other(_) => Vec::new(),
        }
    }
}

impl fmt::Display for TypeExpr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Unknown => write!(f, "unknown"),
            Self::Scalar(value) => write!(f, "{value}"),
            Self::Record(value) => write!(f, "record<{value}>"),
            Self::Array(inner) => write!(f, "array<{inner}>"),
            Self::Option(inner) => write!(f, "option<{inner}>"),
            Self::Union(parts) => {
                let joined = parts
                    .iter()
                    .map(ToString::to_string)
                    .collect::<Vec<_>>()
                    .join(" | ");
                write!(f, "{joined}")
            }
            Self::Other(value) => write!(f, "{value}"),
        }
    }
}

fn unwrap_generic<'a>(input: &'a str, name: &str) -> Option<&'a str> {
    let prefix = format!("{name}<");
    if !input.starts_with(&prefix) || !input.ends_with('>') {
        return None;
    }

    Some(&input[prefix.len()..input.len() - 1])
}

fn split_top_level(input: &str, delimiter: char) -> Option<Vec<&str>> {
    let mut depth = 0i32;
    let mut last = 0usize;
    let mut parts = Vec::new();
    let mut saw_delimiter = false;

    for (index, ch) in input.char_indices() {
        match ch {
            '<' | '(' | '[' | '{' => depth += 1,
            '>' | ')' | ']' | '}' => depth -= 1,
            _ if ch == delimiter && depth == 0 => {
                saw_delimiter = true;
                parts.push(input[last..index].trim());
                last = index + ch.len_utf8();
            }
            _ => {}
        }
    }

    if saw_delimiter {
        parts.push(input[last..].trim());
        Some(parts)
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::TypeExpr;

    #[test]
    fn parses_nested_record_types() {
        let expr = TypeExpr::parse("option<array<record<person>>>");
        assert_eq!(expr.record_tables(), vec!["person".to_string()]);
    }
}