hen 0.18.0

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_error_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 {
    let mut lines = vec![json!({
        "type": "verify",
        "path": result.path.as_ref().map(|path| path.display().to_string()),
        "name": result.summary.name,
        "description": result.summary.description,
        "availableEnvironments": result.summary.available_environments,
    })];

    lines.extend(result.summary.requests.iter().map(|request| {
        json!({
            "type": "request",
            "index": request.index,
            "description": request.description,
            "method": request.method,
            "url": request.url,
        })
    }));

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

    render_ndjson(lines)
}

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")
}