hen 0.20.1

Run protocol-aware API request collections from the command line or through MCP.
Documentation
use serde_json::{json, Value};

use crate::{automation, error::HenError};

use super::{
    artifacts::{request_failure_json, run_record_json},
    common::{execution_trace_json, with_type},
    json::{hen_diagnostic_json, hen_error_json, inspection_result_json},
    BodyReportOptions,
};

pub fn run_outcome_ndjson(
    outcome: &automation::RunOutcome,
    body_options: BodyReportOptions,
) -> String {
    let mut lines = vec![json!({
        "type": "run",
        "collection": {
            "path": outcome.collection.path.display().to_string(),
            "name": outcome.collection.name,
            "description": outcome.collection.description,
            "availableEnvironments": outcome.collection.available_environments,
            "selectedEnvironment": outcome.collection.selected_environment,
        },
        "plan": outcome.plan,
        "selectedRequests": outcome.selected_requests,
        "primaryTarget": outcome.primary_target,
        "executionFailed": outcome.execution_failed,
        "interrupted": outcome.interrupted.is_some(),
        "interruptSignal": outcome.interrupted.map(|signal| signal.as_str()),
        "recordCount": outcome.records.len(),
        "failureCount": outcome.failures.len(),
        "traceCount": outcome.trace.len(),
    })];

    lines.extend(
        outcome
            .records
            .iter()
            .map(|record| with_type(run_record_json(record, body_options), "record")),
    );
    lines.extend(
        outcome
            .failures
            .iter()
            .map(|failure| with_type(request_failure_json(failure, body_options), "failure")),
    );
    lines.extend(
        outcome
            .trace
            .iter()
            .map(|entry| with_type(execution_trace_json(entry), "trace")),
    );

    render_ndjson(lines)
}

pub fn verification_result_ndjson(result: &automation::VerificationResult) -> String {
    verification_diagnostics_result_ndjson(&automation::VerificationDiagnosticsResult {
        ok: true,
        path: result.path.clone(),
        summary: Some(result.summary.clone()),
        required_inputs: result.required_inputs.clone(),
        diagnostics: Vec::new(),
        error: None,
    })
}

pub fn verification_diagnostics_result_ndjson(
    result: &automation::VerificationDiagnosticsResult,
) -> String {
    let mut lines = vec![json!({
        "type": "verify",
        "ok": result.ok,
        "path": result.path.as_ref().map(|path| path.display().to_string()),
        "name": result.summary.as_ref().map(|summary| summary.name.clone()),
        "description": result.summary.as_ref().map(|summary| summary.description.clone()),
        "availableEnvironments": result.summary.as_ref().map(|summary| summary.available_environments.clone()).unwrap_or_default(),
        "requestCount": result.summary.as_ref().map(|summary| summary.requests.len()).unwrap_or(0),
        "requiredInputCount": result.required_inputs.len(),
        "diagnosticCount": result.diagnostics.len(),
    })];

    if let Some(summary) = result.summary.as_ref() {
        lines.extend(summary.requests.iter().map(|request| {
            json!({
                "type": "request",
                "index": request.index,
                "description": request.description,
                "method": request.method,
                "url": request.url,
                "protocol": request.protocol,
                "protocolContext": request.protocol_context,
            })
        }));
    }

    lines.extend(result.required_inputs.iter().map(|prompt| {
        json!({
            "type": "requiredInput",
            "name": prompt.name,
            "default": prompt.default,
        })
    }));

    lines.extend(
        result
            .diagnostics
            .iter()
            .map(|diagnostic| with_type(hen_diagnostic_json(diagnostic), "diagnostic")),
    );

    render_ndjson(lines)
}

pub fn inspection_result_ndjson(result: &automation::InspectionResult) -> String {
    render_ndjson(vec![with_type(inspection_result_json(result), "inspect")])
}

pub fn hen_error_ndjson(error: &HenError) -> String {
    render_ndjson(vec![with_type(hen_error_json(error), "error")])
}

fn render_ndjson(lines: Vec<Value>) -> String {
    lines
        .into_iter()
        .map(|line| serde_json::to_string(&line).expect("ndjson line should serialize"))
        .collect::<Vec<_>>()
        .join("\n")
}