rototo 0.1.0-alpha.4

Control plane for runtime configuration of your application.
Documentation
use crate::diagnostics::RototoRuleId;
use crate::diagnostics::{EntityId, LintDiagnostic, LintStage};

use super::super::source::{DocumentKind, SourceDocument};

pub(super) fn read_error_diagnostic(document: &SourceDocument, read_error: &str) -> LintDiagnostic {
    LintDiagnostic::rototo(
        parse_failed_rule(&document.kind),
        LintStage::Parse,
        entity_for_document(document),
        document.document_location(),
        format!("failed to read {}: {read_error}", document.path),
    )
}

pub(super) fn toml_span_parse_diagnostic(
    document: &SourceDocument,
    err: &::toml_span::Error,
) -> LintDiagnostic {
    let start = floor_char_boundary(&document.text, err.span.start.min(document.text.len()));
    let end = parse_error_end(document, start, err.span.end);
    let location = if start == end {
        document.document_location()
    } else {
        document.span_location(start..end)
    };
    LintDiagnostic::rototo(
        parse_failed_rule(&document.kind),
        LintStage::Parse,
        entity_for_document(document),
        location,
        format!("failed to parse {}: {err}", document.path),
    )
}

fn parse_error_end(document: &SourceDocument, start: usize, raw_end: usize) -> usize {
    let bounded_end =
        ceil_char_boundary(&document.text, raw_end.min(document.text.len())).max(start);
    let Some(slice) = document.text.get(start..bounded_end) else {
        return start;
    };
    let Some(relative_newline) = slice.find('\n') else {
        return bounded_end;
    };
    start + relative_newline + 1
}

pub(super) fn json_parse_diagnostic(
    document: &SourceDocument,
    err: &::serde_json::Error,
) -> LintDiagnostic {
    let line = err.line().saturating_sub(1);
    let column = err.column().saturating_sub(1);
    let start = document.line_index.offset_for_line_character(line, column);
    let end = start.saturating_add(1).min(document.text.len());
    LintDiagnostic::rototo(
        RototoRuleId::SchemaParseFailed,
        LintStage::Parse,
        entity_for_document(document),
        document.span_location(start..end),
        format!("failed to parse {}: {err}", document.path),
    )
}

fn floor_char_boundary(text: &str, mut offset: usize) -> usize {
    while offset > 0 && !text.is_char_boundary(offset) {
        offset -= 1;
    }
    offset
}

fn ceil_char_boundary(text: &str, mut offset: usize) -> usize {
    while offset < text.len() && !text.is_char_boundary(offset) {
        offset += 1;
    }
    offset
}

fn parse_failed_rule(kind: &DocumentKind) -> RototoRuleId {
    match kind {
        DocumentKind::Manifest => RototoRuleId::WorkspaceManifestParseFailed,
        DocumentKind::Qualifier { .. } => RototoRuleId::QualifierParseFailed,
        DocumentKind::Variable { .. } => RototoRuleId::VariableParseFailed,
        DocumentKind::Resource { .. } => RototoRuleId::ResourceParseFailed,
        DocumentKind::ResourceObject { .. } => RototoRuleId::ResourceObjectParseFailed,
        DocumentKind::Schema => RototoRuleId::SchemaParseFailed,
        DocumentKind::CustomLint => RototoRuleId::CustomLintFailed,
    }
}

fn entity_for_document(document: &SourceDocument) -> EntityId {
    match &document.kind {
        DocumentKind::Manifest => EntityId::Manifest,
        DocumentKind::Qualifier { id } => EntityId::Qualifier { id: id.clone() },
        DocumentKind::Variable { id } => EntityId::Variable { id: id.clone() },
        DocumentKind::Resource { id } => EntityId::Resource { id: id.clone() },
        DocumentKind::ResourceObject {
            resource_id,
            object_id,
        } => EntityId::ResourceObject {
            resource: resource_id.clone(),
            key: object_id.clone(),
        },
        DocumentKind::Schema => EntityId::Schema {
            path: document.path.clone(),
        },
        DocumentKind::CustomLint => EntityId::CustomLint {
            path: document.path.clone(),
        },
    }
}