govctl 0.9.1

Project governance CLI for RFC, ADR, and Work Item management
use super::super::render::{render_scalar, render_status_lines};
use super::super::support::{joined_scalar_list_text, type_mismatch};
use crate::cmd::edit::rules::{NestedNodeKind, NestedNodeRule, nested_status_list_spec};
use crate::diagnostic::DiagnosticResult;
use serde_json::Value;

pub(super) fn render_nested_node(
    node: &'static NestedNodeRule,
    value: Option<&Value>,
    id: &str,
) -> DiagnosticResult<String> {
    match node.kind {
        NestedNodeKind::Scalar => Ok(render_scalar(value)),
        NestedNodeKind::List => render_nested_list(node, value, id),
        NestedNodeKind::Object => render_nested_object(node, value, id),
    }
}

fn render_nested_list(
    node: &'static NestedNodeRule,
    value: Option<&Value>,
    id: &str,
) -> DiagnosticResult<String> {
    let Some(value) = value else {
        return Ok(String::new());
    };
    let arr = value
        .as_array()
        .ok_or_else(|| type_mismatch("Expected array value", id))?;
    let item = node
        .item
        .ok_or_else(|| type_mismatch("List node missing item rule", id))?;
    if let Some(spec) = nested_status_list_spec(node) {
        return render_status_lines(Some(value), spec.status_key, spec.text_key, id);
    }
    if item.kind == NestedNodeKind::Scalar {
        return Ok(joined_scalar_list_text(arr, "\n"));
    }
    let mut rendered = Vec::new();
    for item_value in arr {
        rendered.push(render_nested_node(item, Some(item_value), id)?);
    }
    Ok(rendered.join("\n\n"))
}

fn render_nested_object(
    node: &'static NestedNodeRule,
    value: Option<&Value>,
    id: &str,
) -> DiagnosticResult<String> {
    let Some(value) = value else {
        return Ok(String::new());
    };
    let obj = value
        .as_object()
        .ok_or_else(|| type_mismatch("Expected object value", id))?;
    let mut lines = Vec::new();
    for field in node.fields {
        if let Some(field_value) = obj.get(field.name) {
            let rendered = if field.node.kind == NestedNodeKind::List
                && field
                    .node
                    .item
                    .is_some_and(|item| item.kind == NestedNodeKind::Scalar)
            {
                let items = field_value
                    .as_array()
                    .ok_or_else(|| type_mismatch("Expected array value", id))?;
                joined_scalar_list_text(items, ", ")
            } else {
                render_nested_node(field.node, Some(field_value), id)?
            };
            lines.push(format!("{}: {}", field.name, rendered));
        }
    }
    Ok(lines.join("\n"))
}