rototo 0.1.0-alpha.8

Control plane for runtime configuration of your application.
Documentation
use super::*;

#[derive(Debug, Serialize)]
pub(super) struct PackageLintJson<'a> {
    package: String,
    documents: &'a [rototo::model::SourceDocumentSummary],
    diagnostics: &'a [LintDiagnostic],
}

pub(crate) fn print_package_lint(lint: &PackageLint, json: bool, quiet: bool) -> Result<()> {
    if json {
        println!(
            "{}",
            serde_json::to_string_pretty(&PackageLintJson {
                package: lint.root.display().to_string(),
                documents: &lint.documents,
                diagnostics: &lint.diagnostics,
            })
            .map_err(|err| RototoError::new(err.to_string()))?
        );
        return Ok(());
    }

    if lint.diagnostics.is_empty() {
        if quiet {
            return Ok(());
        }
        println!("{}", style::ok_line(&lint.root.display().to_string()));
        return Ok(());
    }

    print_diagnostics(&lint.diagnostics);
    Ok(())
}

pub(crate) fn print_diagnostic_catalog_entry(
    diagnostic: &DiagnosticCatalogEntry,
    json: bool,
) -> Result<()> {
    if json {
        println!(
            "{}",
            serde_json::to_string_pretty(diagnostic)
                .map_err(|err| RototoError::new(err.to_string()))?
        );
        return Ok(());
    }

    println!("{}", diagnostic.rule);
    if let Some(entity) = &diagnostic.entity {
        println!("  entity: {}", diagnostic_entity_label(entity));
    }
    println!("  severity: {}", severity_label(&diagnostic.severity));
    println!("  title: {}", diagnostic.title);
    println!("  help: {}", diagnostic.help);
    Ok(())
}

pub(super) fn print_diagnostics(diagnostics: &[LintDiagnostic]) {
    for diagnostic in diagnostics {
        println!(
            "{}: {}: {}",
            style::severity_prefix(&diagnostic.severity, &diagnostic.rule.as_string()),
            style::info(&diagnostic_location_label(diagnostic)),
            diagnostic.message
        );
        println!("  {} {}", style::dim("help:"), style::dim(&diagnostic.help));
        for related in &diagnostic.related {
            println!(
                "  {} {}: {}",
                style::dim("note:"),
                style::info(&diagnostic_location_label_for_location(&related.location)),
                related.message
            );
        }
    }
}

pub(super) fn diagnostic_location_label(diagnostic: &LintDiagnostic) -> String {
    diagnostic_location_label_for_location(&diagnostic.primary)
}

pub(super) fn diagnostic_location_label_for_location(location: &DiagnosticLocation) -> String {
    let Some(range) = location.range else {
        return location.path.clone();
    };
    format!(
        "{}:{}:{}",
        location.path,
        range.start.line + 1,
        range.start.character + 1
    )
}

pub(super) fn semantic_target_label(target: &SemanticTarget) -> String {
    match &target.field {
        Some(field) => format!(
            "{}.{}",
            semantic_entity_label(&target.entity),
            semantic_field_label(field)
        ),
        None => semantic_entity_label(&target.entity),
    }
}

pub(super) fn semantic_entity_label(entity: &SemanticEntity) -> String {
    match entity {
        SemanticEntity::Package => "package".to_owned(),
        SemanticEntity::Manifest => "manifest".to_owned(),
        SemanticEntity::List { id } => format!("list:{id}"),
        SemanticEntity::Layer { id } => format!("layer:{id}"),
        SemanticEntity::Governance => "governance".to_owned(),
        SemanticEntity::Variable { id } => format!("variable:{id}"),
        SemanticEntity::EvaluationContext { id } => format!("evaluation-context:{id}"),
        SemanticEntity::EvaluationContextSample {
            evaluation_context,
            key,
        } => {
            format!("evaluation-context:{evaluation_context}.entry:{key}")
        }
        SemanticEntity::Catalog { id } => format!("catalog:{id}"),
        SemanticEntity::CatalogEntry { catalog, key } => format!("catalog:{catalog}.value:{key}"),
        SemanticEntity::Value { variable, key } => format!("variable:{variable}.value:{key}"),
        SemanticEntity::Rule { variable, index } => {
            format!("variable:{variable}.rule[{index}]")
        }
        SemanticEntity::CustomLint { path } => format!("lint:{path}"),
    }
}

pub(super) fn semantic_field_label(field: &SemanticField) -> String {
    match field {
        SemanticField::PackageExtends => "extends".to_owned(),
        SemanticField::SchemaVersion => "schema_version".to_owned(),
        SemanticField::Description => "description".to_owned(),
        SemanticField::VariableType => "type".to_owned(),
        SemanticField::VariableSchema => "schema".to_owned(),
        SemanticField::VariableValues => "values".to_owned(),
        SemanticField::VariableResolve => "resolve".to_owned(),
        SemanticField::VariableResolveDefault => "resolve.default".to_owned(),
        SemanticField::VariableRuleWhen => "when".to_owned(),
        SemanticField::VariableRuleValue => "value".to_owned(),
        SemanticField::VariableQueryFilter => "filter".to_owned(),
        SemanticField::VariableQuerySort => "sort".to_owned(),
        SemanticField::VariableAllocation => "allocation".to_owned(),
        SemanticField::VariableAssignValue => "assign.value".to_owned(),
        SemanticField::Value => "value".to_owned(),
        SemanticField::ValueJsonPath { path } => format!("value.{}", path.join(".")),
        SemanticField::SchemaJson => "json".to_owned(),
        SemanticField::SchemaJsonPath { path } => format!("json.{}", path.join(".")),
        SemanticField::EvaluationContextSample => "entry".to_owned(),
        SemanticField::CatalogEntry => "value".to_owned(),
    }
}

pub(super) fn compact_json_option(value: &Option<serde_json::Value>) -> Result<String> {
    match value {
        Some(value) => compact_json(value),
        None => Ok("<none>".to_owned()),
    }
}

pub(super) fn variable_rule_condition(rule: &rototo::model::RulePathwayInspectReport) -> &str {
    rule.when.as_deref().unwrap_or("<missing>")
}

pub(super) fn severity_label(severity: &Severity) -> &'static str {
    match severity {
        Severity::Error => "error",
        Severity::Warning => "warning",
    }
}

pub(super) fn diagnostic_entity_label(entity: &DiagnosticEntity) -> &'static str {
    match entity {
        DiagnosticEntity::Package => "package",
        DiagnosticEntity::List => "list",
        DiagnosticEntity::Layer => "layer",
        DiagnosticEntity::Governance => "governance",
        DiagnosticEntity::Variable => "variable",
        DiagnosticEntity::EvaluationContext => "evaluation_context",
        DiagnosticEntity::EvaluationContextSample => "evaluation_context_sample",
        DiagnosticEntity::Catalog => "catalog",
        DiagnosticEntity::CatalogEntry => "catalog_entry",
        DiagnosticEntity::Value => "value",
        DiagnosticEntity::Rule => "rule",
    }
}