relay-knowledge 1.1.10

Graph-database-based knowledge graph project.
Documentation
use crate::domain::CodeParseStatus;

use super::super::{CodeIndexError, languages::LanguageSpec};

pub(super) const MAX_TEXT_FILE_BYTES: usize = 512 * 1024;

pub(super) fn validate_text_content(
    path: &str,
    bytes: &[u8],
    language: Option<LanguageSpec>,
) -> Result<(CodeParseStatus, Option<String>, Option<String>), CodeIndexError> {
    if bytes.contains(&0) {
        return Ok((
            CodeParseStatus::TextOnly,
            Some("binary file skipped; lexical code retrieval is unavailable".to_owned()),
            None,
        ));
    }

    let mut degraded_reasons = Vec::new();
    let content_bytes = if bytes.len() > MAX_TEXT_FILE_BYTES {
        degraded_reasons.push(format!(
            "file exceeds {MAX_TEXT_FILE_BYTES} byte code index budget"
        ));
        truncate_bytes_at_utf8_boundary(bytes, MAX_TEXT_FILE_BYTES)
    } else {
        bytes
    };
    let content = match String::from_utf8(content_bytes.to_vec()) {
        Ok(content) => content,
        Err(_) => {
            degraded_reasons.push(format!(
                "{path} is not valid UTF-8; using lossy text fallback"
            ));
            String::from_utf8_lossy(content_bytes).into_owned()
        }
    };
    if language.is_none() {
        degraded_reasons
            .push("tree-sitter grammar is not configured for this file extension".to_owned());
    }

    if !degraded_reasons.is_empty() {
        return Ok((
            CodeParseStatus::TextOnly,
            Some(degraded_reasons.join("; ")),
            Some(content),
        ));
    }

    Ok((CodeParseStatus::Parsed, None, Some(content)))
}

fn truncate_bytes_at_utf8_boundary(bytes: &[u8], max_bytes: usize) -> &[u8] {
    let end = bytes.len().min(max_bytes);
    match std::str::from_utf8(&bytes[..end]) {
        Ok(_) => &bytes[..end],
        Err(error) if error.error_len().is_none() => &bytes[..error.valid_up_to()],
        Err(_) => &bytes[..end],
    }
}

pub(super) fn count_lines(bytes: &[u8]) -> usize {
    if bytes.is_empty() {
        return 0;
    }

    bytes.iter().filter(|byte| **byte == b'\n').count() + 1
}