ripr 0.8.0

Find static mutation-exposure gaps before expensive mutation testing
Documentation
use std::collections::BTreeMap;

use serde_json::{Value, json};

use super::{EVIDENCE_HEALTH_SCHEMA_VERSION, EvidenceHealthReport};

pub(crate) fn render_evidence_health_json(report: &EvidenceHealthReport) -> Result<String, String> {
    let value = json!({
        "schema_version": EVIDENCE_HEALTH_SCHEMA_VERSION,
        "tool": "ripr",
        "scope": "repo",
        "status": "advisory",
        "inputs": {
            "root": report.root,
            "mutation_calibration": report.calibration.source,
        },
        "metrics": {
            "seams_total": report.metrics.seams_total,
            "headline_eligible_total": report.metrics.headline_eligible_total,
            "weakly_gripped_total": report.metrics.weakly_gripped_total,
            "ungripped_total": report.metrics.ungripped_total,
            "grip_class_counts": report.metrics.grip_class_counts,
            "stage_state_counts": report.metrics.stage_state_counts,
            "unknown_stage_counts": report.metrics.unknown_stage_counts,
            "unknown_stop_reason_counts": report.metrics.unknown_stop_reason_counts,
            "missing_discriminators_total": report.metrics.missing_discriminators_total,
            "seams_with_missing_discriminators": report.metrics.seams_with_missing_discriminators,
            "missing_discriminator_counts": count_rows_json(
                &report.metrics.missing_discriminator_counts
            ),
            "observed_values_total": report.metrics.observed_values_total,
            "seams_with_observed_values": report.metrics.seams_with_observed_values,
            "observed_value_context_counts": report.metrics.observed_value_context_counts,
            "related_tests_total": report.metrics.related_tests_total,
            "seams_with_related_tests": report.metrics.seams_with_related_tests,
            "related_test_confidence_counts": report.metrics.related_test_confidence_counts,
            "oracle_strength_counts": report.metrics.oracle_strength_counts,
            "oracle_kind_counts": report.metrics.oracle_kind_counts,
            "opaque_oracle_count": report.metrics.opaque_oracle_count,
        },
        "evidence_quality": {
            "canonical_gap_groups_total": report.evidence_quality.canonical_gap_groups_total,
            "duplicate_looking_groups_total": report.evidence_quality.duplicate_looking_groups_total,
            "largest_canonical_groups": report.evidence_quality.largest_canonical_groups.iter().map(|group| {
                json!({
                    "canonical_gap_id": group.canonical_gap_id,
                    "count": group.count,
                    "reported_group_size": group.reported_group_size,
                    "owner": group.owner,
                    "seam_kind": group.seam_kind,
                    "flow_sink": group.flow_sink,
                    "missing_discriminator": group.missing_discriminator,
                    "assertion_shape": group.assertion_shape,
                    "example_seam_id": group.example_seam_id,
                    "example_file": group.example_file,
                })
            }).collect::<Vec<_>>(),
            "actionability_class_counts": report.evidence_quality.actionability_class_counts,
            "static_limitation_stage_counts": report.evidence_quality.static_limitation_stage_counts,
            "static_limitation_reason_counts": count_rows_json(
                &report.evidence_quality.static_limitation_reason_counts
            ),
            "static_limitation_category_counts": report.evidence_quality.static_limitation_category_counts,
            "calibration_availability_counts": report.evidence_quality.calibration_availability_counts,
            "movement_availability": {
                "records_with_seam_id": report.evidence_quality.movement_availability.records_with_seam_id,
                "records_with_canonical_gap_id": report.evidence_quality.movement_availability.records_with_canonical_gap_id,
                "records_with_complete_evidence_path": report.evidence_quality.movement_availability.records_with_complete_evidence_path,
                "records_with_recommendation": report.evidence_quality.movement_availability.records_with_recommendation,
                "records_with_verify_command": report.evidence_quality.movement_availability.records_with_verify_command,
            },
            "top_evidence_quality_risks": report.evidence_quality.top_evidence_quality_risks.iter().map(|risk| {
                json!({
                    "kind": risk.kind,
                    "count": risk.count,
                    "summary": risk.summary,
                })
            }).collect::<Vec<_>>(),
        },
        "calibration": {
            "status": report.calibration.status,
            "source": report.calibration.source,
            "matched_total": report.calibration.matched_total,
            "static_without_runtime_total": report.calibration.static_without_runtime_total,
            "runtime_without_static_total": report.calibration.runtime_without_static_total,
            "ambiguous_file_line_total": report.calibration.ambiguous_file_line_total,
            "unmatched_runtime_total": report.calibration.unmatched_runtime_total,
        },
        "top_static_limitations": report.top_static_limitations.iter().map(|limitation| {
            json!({
                "kind": limitation.kind,
                "count": limitation.count,
                "summary": limitation.summary,
                "example_seam_id": limitation.example_seam_id,
            })
        }).collect::<Vec<_>>(),
    });
    crate::output::json::render_pretty_with_newline(&value, "evidence health")
}

fn count_rows_json(counts: &BTreeMap<String, usize>) -> Vec<Value> {
    counts
        .iter()
        .map(|(label, count)| {
            json!({
                "label": label,
                "count": count,
            })
        })
        .collect()
}