alef 0.115.6

Opinionated polyglot binding generator for Rust libraries
Documentation
use std::collections::{HashMap, HashSet};

use crate::core::ir::ApiSurface;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Assertion;

pub(crate) fn surface() -> ApiSurface {
    let directory = tempfile::tempdir().expect("fixture directory");
    let source = directory.path().join("lib.rs");
    std::fs::write(&source, include_str!("fixture.rs")).expect("write fixture");
    crate::extract::extractor::extract(&[source.as_path()], "sample", "0.0.0", None).expect("extract fixture")
}

pub(crate) fn resolver(surface: &ApiSurface, language: &str) -> FieldResolver {
    let optional = HashSet::from(["choices[0].message.content".to_string()]);
    let arrays = HashSet::from(["choices".to_string()]);
    FieldResolver::new(&HashMap::new(), &optional, &HashSet::new(), &arrays, &HashSet::new())
        .with_ir_enum_map(
            FieldResolver::ir_enum_fields(&surface.types, &surface.enums),
            Some("Reply".to_string()),
        )
        .with_ir_result_fields(
            FieldResolver::ir_result_field_facts(&surface.types, language),
            Some("Reply".to_string()),
        )
}

pub(crate) fn assertions() -> Vec<Assertion> {
    [
        ("count_equals", "parts", serde_json::json!(2)),
        ("equals", "parts[0].text", serde_json::json!("hello")),
        ("equals", "parts[1].text", serde_json::json!(" world")),
    ]
    .into_iter()
    .map(|(kind, suffix, value)| Assertion {
        assertion_type: kind.to_string(),
        field: Some(format!("choices[0].message.content.{suffix}")),
        value: Some(value),
        ..Assertion::default()
    })
    .collect()
}

pub(crate) fn verify_native(language: &str, extension: &str, assertions: &str) {
    verify_native_payload(language, extension, assertions, cases());
}

pub(crate) fn verify_native_payload(
    language: &str,
    extension: &str,
    assertions: &str,
    cases: [(serde_json::Value, &str); 6],
) {
    let directory = tempfile::tempdir().expect("native probe directory");
    let path = std::env::var_os("PATH");
    let executable = match which::which_in(language, path, directory.path()) {
        Ok(executable) => executable,
        Err(error) => {
            assert!(
                language != "ruby" && std::env::var_os(format!("ALEF_REQUIRE_{}", language.to_uppercase())).is_none(),
                "required {language}: {error}"
            );
            tracing::info!("skipping optional {language} probe: {error}");
            return;
        }
    };
    if language == "ruby" {
        assert!(
            ruby_matchers_available(&executable, directory.path()),
            "native proof requires Ruby RSpec matchers"
        );
    }
    let template = format!("{language}/enum_collection_native_probe.{extension}.jinja");
    let source = crate::e2e::template_env::render(&template, crate::alef_context! { assertions => assertions });
    let file = directory.path().join(format!("probe.{extension}"));
    std::fs::write(&file, source).expect("write native probe");
    verify_cases(language, &executable, &file, directory.path(), cases);
}

fn cases() -> [(serde_json::Value, &'static str); 6] {
    [
        (
            serde_json::json!([{"type":"text","text":"hello"},{"type":"text","text":" world"}]),
            "PASS:3",
        ),
        (
            serde_json::json!([{"type":"text","text":"hello"}]),
            "ASSERTION_FAILURE:1:",
        ),
        (
            serde_json::json!([{"type":"text","text":"bad"},{"type":"text","text":" world"}]),
            "ASSERTION_FAILURE:2:",
        ),
        (
            serde_json::json!([{"type":"text","text":"hello"},{"type":"text","text":"bad"}]),
            "ASSERTION_FAILURE:3:",
        ),
        (serde_json::json!("ab"), "expected enum collection payload"),
        (
            serde_json::json!({"first":1,"second":2}),
            "expected enum collection payload",
        ),
    ]
}

fn verify_cases(
    language: &str,
    executable: &std::path::Path,
    file: &std::path::Path,
    directory: &std::path::Path,
    cases: [(serde_json::Value, &str); 6],
) {
    let mut checked = 0;
    for (index, (payload, marker)) in cases.into_iter().enumerate() {
        let output = std::process::Command::new(executable)
            .arg(file)
            .arg(payload.to_string())
            .current_dir(directory)
            .output()
            .expect("execute native probe");
        let evidence = format!(
            "{}{}",
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        );
        assert_eq!(
            output.status.success(),
            index == 0,
            "{language} case {index}: {evidence}"
        );
        assert!(
            evidence.contains(marker),
            "{language} case {index} expected {marker}: {evidence}"
        );
        checked += 1;
    }
    assert_eq!(checked, 6);
    tracing::info!("{language}: {checked} native cases verified (3 assertions, 5 negative controls)");
}

fn ruby_matchers_available(executable: &std::path::Path, directory: &std::path::Path) -> bool {
    std::process::Command::new(executable)
        .args(["-e", "require 'rspec/expectations'"])
        .current_dir(directory)
        .output()
        .expect("check Ruby RSpec availability")
        .status
        .success()
}