alef 0.85.15

Opinionated polyglot binding generator for Rust libraries
Documentation
use std::collections::HashSet;
use std::path::PathBuf;

use crate::core::config::e2e::{CallConfig, E2eConfig};
use crate::core::ir::{FieldDef, FunctionDef, TypeDef, TypeRef};
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::{Assertion, Fixture};

use super::go_batch::{GoBatchCase, GoBatchLayout, GoCaseOutcome, run_go_batch};
use super::test_function::{GoTestFunctionContext, render_test_function};

const MODULE: &str = "example.com/namespacepresence";

fn result_types() -> Vec<TypeDef> {
    vec![
        TypeDef {
            name: "Envelope".into(),
            fields: vec![FieldDef {
                name: "action_results".into(),
                ty: TypeRef::Vec(Box::new(TypeRef::Named("ActionResult".into()))),
                ..Default::default()
            }],
            ..Default::default()
        },
        TypeDef {
            name: "ActionResult".into(),
            fields: vec![FieldDef {
                name: "data".into(),
                ty: TypeRef::Json,
                optional: true,
                ..Default::default()
            }],
            ..Default::default()
        },
    ]
}

#[test]
fn namespace_presence_pointer_shape_matches_rendered_accessor() {
    let resolver = FieldResolver::new(
        &Default::default(),
        &Default::default(),
        &HashSet::from(["action_results".into()]),
        &Default::default(),
        &Default::default(),
    )
    .with_ir_result_fields(
        FieldResolver::ir_result_field_facts(&result_types(), "go"),
        Some("Envelope".into()),
    );
    for path in ["action_results[0].data", "interaction.action_results[0].data"] {
        assert_eq!(resolver.target_field_is_pointer(path), Some(true), "{path}");
    }
}

fn render_presence(path: &str, assertion_type: &str) -> String {
    let config = E2eConfig {
        call: CallConfig {
            function: "inspect".into(),
            module: MODULE.into(),
            returns_result: true,
            result_fields: HashSet::from(["action_results".into()]),
            ..Default::default()
        },
        fields_optional: HashSet::from(["action_results[].data".into()]),
        ..Default::default()
    };
    let fixture = Fixture {
        id: "namespace_presence".into(),
        description: "namespace presence".into(),
        assertions: vec![Assertion {
            assertion_type: assertion_type.into(),
            field: Some(path.into()),
            ..Default::default()
        }],
        ..Default::default()
    };
    let functions = [FunctionDef {
        name: "inspect".into(),
        return_type: TypeRef::Named("Envelope".into()),
        ..Default::default()
    }];
    let mut output = String::new();
    render_test_function(
        &mut output,
        &fixture,
        GoTestFunctionContext {
            import_alias: "sample",
            e2e_config: &config,
            adapters: &[],
            data_enum_names: &HashSet::new(),
            config: &Default::default(),
            type_defs: &result_types(),
            enums: &[],
            errors: &[],
            functions: &functions,
        },
    );
    output
}

#[test]
fn namespace_presence_raw_message_checks_compile_and_reject_empty_payloads() {
    let mut cases = Vec::new();
    let mut expected = Vec::new();
    for (prefix, path) in [
        ("plain", "action_results[0].data"),
        ("namespace", "interaction.action_results[0].data"),
    ] {
        for assertion in ["not_empty", "is_empty"] {
            let rendered = render_presence(path, assertion);
            for (state, setup, nonempty) in [
                ("nil", "var raw *json.RawMessage", false),
                ("empty", "value := json.RawMessage{}; raw := &value", false),
                (
                    "nonempty",
                    "value := json.RawMessage(`{\"value\":1}`); raw := &value",
                    true,
                ),
            ] {
                let name = format!("{prefix}_{assertion}_{state}");
                let source = format!(
                    "package sample\nimport \"encoding/json\"\ntype ActionResult struct {{ Data *json.RawMessage }}\ntype Envelope struct {{ ActionResults []ActionResult }}\nfunc Inspect() (*Envelope,error) {{ {setup}; return &Envelope{{ActionResults: []ActionResult{{{{Data:raw}}}}}},nil }}\n"
                );
                cases.push(GoBatchCase {
                    name: name.clone(),
                    files: vec![
                        ("sample.go".into(), source),
                        (
                            "presence_test.go".into(),
                            format!(
                                "package sample_test\nimport (\"testing\"; sample \"{MODULE}/{name}\")\n{rendered}"
                            ),
                        ),
                    ],
                });
                expected.push((
                    name,
                    if (assertion == "not_empty") == nonempty {
                        GoCaseOutcome::Passed
                    } else {
                        GoCaseOutcome::Failed
                    },
                ));
            }
        }
    }
    let layout = GoBatchLayout {
        root_files: vec![(PathBuf::from("go.mod"), format!("module {MODULE}\ngo 1.23\n"))],
        module_dir: PathBuf::new(),
        module_path: MODULE.into(),
        extra_args: vec![],
    };
    let Some(report) = run_go_batch(&layout, &cases) else {
        return;
    };
    for (name, outcome) in expected {
        report.assert_outcome(&name, outcome);
        if outcome == GoCaseOutcome::Failed {
            report.assert_output_contains(&name, "expected");
        }
    }
    assert_eq!(report.total_test_cases(), 12);
}