hen 0.20.2

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

use crate::{
    automation::{self, PromptRequirement},
    error::{
        HenDiagnostic, HenDiagnosticLocation, HenDiagnosticPosition,
        HenDiagnosticRange, HenDiagnosticRelatedInformation,
        HenDiagnosticSuggestion, HenDiagnosticSymbol, HenError,
    },
};

use super::{
    artifacts::{request_failure_json, run_record_json},
    common::execution_trace_json,
    redaction::OutputRedactor,
    BodyReportOptions,
};

pub fn run_outcome_json(
    outcome: &automation::RunOutcome,
    body_options: BodyReportOptions,
) -> Value {
    json!({
        "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,
            "requiredInputs": outcome.collection.required_inputs.iter().map(prompt_requirement_json).collect::<Vec<_>>(),
            "requests": outcome.collection.requests.iter().map(|request| {
                let redactor = OutputRedactor::new(&request.sensitive_values);
                json!({
                    "index": request.index,
                    "description": redactor.redact_text(&request.description),
                    "method": request.method,
                    "url": redactor.redact_text(&request.url),
                    "protocol": request.protocol,
                    "protocolContext": request.protocol_context.as_ref().map(|value| redactor.redact_json_value(value)),
                    "dependencies": request.dependencies.iter().map(|dependency| redactor.redact_text(dependency)).collect::<Vec<_>>(),
                })
            }).collect::<Vec<_>>()
        },
        "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()),
        "records": outcome.records.iter().map(|record| run_record_json(record, body_options)).collect::<Vec<_>>(),
        "failures": outcome.failures.iter().map(|failure| request_failure_json(failure, body_options)).collect::<Vec<_>>(),
        "trace": outcome.trace.iter().map(execution_trace_json).collect::<Vec<_>>(),
    })
}

pub fn verification_result_json(result: &automation::VerificationResult) -> Value {
    verification_diagnostics_result_json(&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_json(
    result: &automation::VerificationDiagnosticsResult,
) -> Value {
    let mut root = serde_json::Map::new();
    root.insert("ok".to_string(), json!(result.ok));
    root.insert(
        "path".to_string(),
        json!(result.path.as_ref().map(|path| path.display().to_string())),
    );
    root.insert(
        "summary".to_string(),
        result.summary.as_ref().map_or(Value::Null, syntax_summary_json),
    );
    root.insert(
        "requiredInputs".to_string(),
        Value::Array(
            result
                .required_inputs
                .iter()
                .map(prompt_requirement_json)
                .collect(),
        ),
    );
    root.insert(
        "diagnostics".to_string(),
        Value::Array(result.diagnostics.iter().map(hen_diagnostic_json).collect()),
    );

    if let Some(summary) = result.summary.as_ref() {
        root.insert("name".to_string(), json!(summary.name));
        root.insert("description".to_string(), json!(summary.description));
        root.insert(
            "availableEnvironments".to_string(),
            json!(summary.available_environments),
        );
        root.insert(
            "requests".to_string(),
            Value::Array(summary.requests.iter().map(syntax_request_summary_json).collect()),
        );
    }

    if let Some(error) = result.error.as_ref() {
        root.insert("error".to_string(), verification_legacy_error_json(error));
    }

    Value::Object(root)
}

pub fn inspection_result_json(result: &automation::InspectionResult) -> Value {
    json!({
        "path": result.path.as_ref().map(|path| path.display().to_string()),
        "name": result.summary.summary.name,
        "description": result.summary.summary.description,
        "availableEnvironments": result.summary.summary.available_environments,
        "requiredInputs": result.required_inputs.iter().map(prompt_requirement_json).collect::<Vec<_>>(),
        "summary": syntax_summary_json(&result.summary.summary),
        "declarations": result.summary.declarations.iter().map(syntax_declaration_summary_json).collect::<Vec<_>>(),
        "requests": result.summary.requests.iter().map(syntax_inspect_request_summary_json).collect::<Vec<_>>(),
        "symbols": syntax_symbol_table_json(&result.summary.symbols),
    })
}

pub fn hen_error_json(error: &HenError) -> Value {
    json!({
        "error": {
            "kind": error.kind().label(),
            "summary": error.summary(),
            "details": error.details(),
            "diagnostics": error.diagnostics().iter().map(hen_diagnostic_json).collect::<Vec<_>>(),
            "exitCode": error.exit_code(),
        }
    })
}

fn verification_legacy_error_json(error: &HenError) -> Value {
    json!({
        "kind": error.kind().label(),
        "summary": error.summary(),
        "details": error.details(),
        "diagnostics": error.diagnostics().iter().map(hen_diagnostic_json).collect::<Vec<_>>(),
        "exitCode": error.exit_code(),
    })
}

fn syntax_summary_json(summary: &crate::parser::SyntaxSummary) -> Value {
    json!({
        "name": summary.name,
        "description": summary.description,
        "availableEnvironments": summary.available_environments,
        "requests": summary.requests.iter().map(syntax_request_summary_json).collect::<Vec<_>>(),
    })
}

fn syntax_request_summary_json(request: &crate::parser::SyntaxRequestSummary) -> Value {
    json!({
        "index": request.index,
        "description": request.description,
        "method": request.method,
        "url": request.url,
        "protocol": request.protocol,
        "protocolContext": request.protocol_context,
    })
}

fn syntax_declaration_summary_json(
    declaration: &crate::parser::SyntaxDeclarationSummary,
) -> Value {
    json!({
        "kind": declaration.kind,
        "name": declaration.name,
        "range": syntax_range_summary_json(&declaration.range),
    })
}

fn syntax_fragment_include_summary_json(
    include: &crate::parser::SyntaxFragmentIncludeSummary,
) -> Value {
    json!({
        "path": include.path,
        "guard": include.guard,
        "assertionIndex": include.assertion_index,
        "range": syntax_range_summary_json(&include.range),
    })
}

fn syntax_inspect_request_summary_json(
    request: &crate::parser::SyntaxInspectRequestSummary,
) -> Value {
    json!({
        "index": request.index,
        "description": request.description,
        "method": request.method,
        "url": request.url,
        "protocol": request.protocol,
        "protocolContext": request.protocol_context,
        "dependencies": request.dependencies,
        "fragmentIncludes": request.fragment_includes.iter().map(syntax_fragment_include_summary_json).collect::<Vec<_>>(),
        "sessionName": request.session_name,
        "authProfile": request.auth_profile,
    })
}

fn syntax_symbol_table_json(symbols: &crate::parser::SyntaxSymbolTable) -> Value {
    json!({
        "requests": symbols.requests,
        "schemas": symbols.schemas,
        "scalars": symbols.scalars,
        "environments": symbols.environments,
        "oauthProfiles": symbols.oauth_profiles,
        "sessions": symbols.sessions,
    })
}

fn syntax_range_summary_json(range: &crate::parser::SyntaxRangeSummary) -> Value {
    json!({
        "start": syntax_position_summary_json(&range.start),
        "end": syntax_position_summary_json(&range.end),
    })
}

fn syntax_position_summary_json(position: &crate::parser::SyntaxPositionSummary) -> Value {
    json!({
        "line": position.line,
        "character": position.character,
    })
}

pub(crate) fn hen_diagnostic_json(diagnostic: &HenDiagnostic) -> Value {
    json!({
        "code": diagnostic.code,
        "severity": diagnostic.severity.label(),
        "phase": diagnostic.phase.label(),
        "message": diagnostic.message,
        "source": diagnostic.source,
        "location": hen_diagnostic_location_json(&diagnostic.location),
        "relatedInformation": diagnostic.related_information.iter().map(hen_diagnostic_related_information_json).collect::<Vec<_>>(),
        "symbol": diagnostic.symbol.as_ref().map(hen_diagnostic_symbol_json),
        "suggestions": diagnostic.suggestions.iter().map(hen_diagnostic_suggestion_json).collect::<Vec<_>>(),
        "data": diagnostic.data.clone(),
    })
}

fn hen_diagnostic_symbol_json(symbol: &HenDiagnosticSymbol) -> Value {
    json!({
        "kind": symbol.kind,
        "name": symbol.name,
        "role": symbol.role,
    })
}

fn hen_diagnostic_suggestion_json(suggestion: &HenDiagnosticSuggestion) -> Value {
    json!({
        "kind": suggestion.kind,
        "label": suggestion.label,
        "range": suggestion.range.as_ref().map(hen_diagnostic_range_json),
        "text": suggestion.text,
    })
}

fn hen_diagnostic_related_information_json(
    related: &HenDiagnosticRelatedInformation,
) -> Value {
    json!({
        "message": related.message,
        "location": hen_diagnostic_location_json(&related.location),
    })
}

fn hen_diagnostic_location_json(location: &HenDiagnosticLocation) -> Value {
    json!({
        "path": location.path,
        "range": hen_diagnostic_range_json(&location.range),
    })
}

fn hen_diagnostic_range_json(range: &HenDiagnosticRange) -> Value {
    json!({
        "start": hen_diagnostic_position_json(&range.start),
        "end": hen_diagnostic_position_json(&range.end),
    })
}

fn hen_diagnostic_position_json(position: &HenDiagnosticPosition) -> Value {
    json!({
        "line": position.line,
        "character": position.character,
    })
}

fn prompt_requirement_json(prompt: &PromptRequirement) -> Value {
    json!({
        "name": prompt.name,
        "default": prompt.default,
    })
}