ripr 0.8.0

Find static mutation-exposure gaps before expensive mutation testing
Documentation
use super::FirstUsefulActionInput;
use serde_json::Value;

#[derive(Default)]
pub(super) struct ParsedSources {
    pub(super) pr_guidance: Option<Value>,
    pub(super) assistant_proof: Option<Value>,
    pub(super) gap_ledger: Option<Value>,
    pub(super) ledger: Option<Value>,
    pub(super) baseline_delta: Option<Value>,
    pub(super) receipt: Option<Value>,
    pub(super) gate_decision: Option<Value>,
    pub(super) coverage_frontier: Option<Value>,
    pub(super) editor_context: Option<Value>,
    pub(super) warnings: Vec<String>,
    pub(super) read_errors: Vec<(String, String)>,
}

pub(super) fn parse_sources(input: &FirstUsefulActionInput) -> ParsedSources {
    let mut parsed = ParsedSources::default();
    parsed.pr_guidance = parse_optional_json(
        "PR guidance",
        input.pr_guidance_path.as_deref(),
        &input.pr_guidance_json,
        &mut parsed,
    );
    parsed.assistant_proof = parse_optional_json(
        "assistant proof",
        input.assistant_proof_path.as_deref(),
        &input.assistant_proof_json,
        &mut parsed,
    );
    parsed.gap_ledger = parse_optional_json(
        "gap decision ledger",
        input.gap_ledger_path.as_deref(),
        &input.gap_ledger_json,
        &mut parsed,
    );
    parsed.ledger = parse_optional_json(
        "PR evidence ledger",
        input.ledger_path.as_deref(),
        &input.ledger_json,
        &mut parsed,
    );
    parsed.baseline_delta = parse_optional_json(
        "baseline debt delta",
        input.baseline_delta_path.as_deref(),
        &input.baseline_delta_json,
        &mut parsed,
    );
    parsed.receipt = parse_optional_json(
        "receipt",
        input.receipt_path.as_deref(),
        &input.receipt_json,
        &mut parsed,
    );
    parsed.gate_decision = parse_optional_json(
        "gate decision",
        input.gate_decision_path.as_deref(),
        &input.gate_decision_json,
        &mut parsed,
    );
    parsed.coverage_frontier = parse_optional_json(
        "coverage/grip frontier",
        input.coverage_frontier_path.as_deref(),
        &input.coverage_frontier_json,
        &mut parsed,
    );
    parsed.editor_context = parse_optional_json(
        "editor context",
        input.editor_context_path.as_deref(),
        &input.editor_context_json,
        &mut parsed,
    );
    parsed
}

pub(super) fn parse_optional_json(
    label: &str,
    path: Option<&str>,
    text: &Option<Result<String, String>>,
    parsed: &mut ParsedSources,
) -> Option<Value> {
    let path = path?;
    let Some(text) = text else {
        parsed.warnings.push(format!(
            "{label} path {path} was supplied but no input text was loaded"
        ));
        parsed
            .read_errors
            .push((label.to_string(), path.to_string()));
        return None;
    };
    let text = match text {
        Ok(text) => text,
        Err(error) => {
            parsed
                .warnings
                .push(format!("optional {label} input {path} is invalid: {error}"));
            parsed
                .read_errors
                .push((label.to_string(), path.to_string()));
            return None;
        }
    };
    match serde_json::from_str::<Value>(text) {
        Ok(value) => Some(value),
        Err(error) => {
            parsed
                .warnings
                .push(format!("optional {label} input {path} is invalid: {error}"));
            parsed
                .read_errors
                .push((label.to_string(), path.to_string()));
            None
        }
    }
}