alef 0.60.0

Opinionated polyglot binding generator for Rust libraries
Documentation
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{EnumDef, TypeDef};
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::Fixture;
use anyhow::{Context, Result};

pub(super) fn render_snippet_body(
    fixture: &Fixture,
    e2e_config: &E2eConfig,
    config: &ResolvedCrateConfig,
    type_defs: &[TypeDef],
    enums: &[EnumDef],
) -> Result<String> {
    if fixture.is_http_test() {
        return render_http_snippet(fixture);
    }
    let mut fixture_without_assertions = fixture.clone();
    fixture_without_assertions.assertions.clear();
    let bridge_class = config.dart_bridge_class_name();
    let first_class_map = super::values::build_dart_first_class_map(type_defs, enums, e2e_config);
    let mut test_case = String::new();
    super::test_case::render_test_case(
        &mut test_case,
        &fixture_without_assertions,
        super::test_case::DartTestCaseContext {
            e2e_config,
            lang: "dart",
            bridge_class: &bridge_class,
            dart_first_class_map: &first_class_map,
            adapters: &config.adapters,
            config,
            type_defs,
            enums,
            native_typed_dtos: true,
        },
    );
    let statements = extract_test_statements(&test_case)
        .with_context(|| format!("extracting Dart snippet body for fixture `{}`", fixture.id))?;
    let package = e2e_config
        .resolve_package("dart")
        .and_then(|value| value.name)
        .unwrap_or_else(|| config.dart_pubspec_name());
    let module = config.name.replace('-', "_");
    let needs_json = statements
        .iter()
        .any(|statement| statement.contains("jsonDecode(") || statement.contains("jsonEncode("));
    let needs_io = statements.iter().any(|statement| statement.contains("File("));
    Ok(crate::e2e::template_env::render(
        "dart/snippet_body.jinja",
        minijinja::context! {
            package => package, module => module, statements => statements, needs_json => needs_json,
            needs_io => needs_io,
        },
    ))
}

fn render_http_snippet(fixture: &Fixture) -> Result<String> {
    let http = fixture.http.as_ref().expect("HTTP fixture checked by caller");
    let plan = crate::e2e::codegen::client::http_call::plan_request(http);
    let mut headers = plan.headers;
    if let Some(content_type) = &plan.content_type
        && !headers.keys().any(|name| name.eq_ignore_ascii_case("content-type"))
    {
        headers.insert("content-type".into(), content_type.clone());
    }
    if !http.request.cookies.is_empty() {
        headers.insert(
            "cookie".into(),
            http.request
                .cookies
                .iter()
                .map(|(key, value)| format!("{key}={value}"))
                .collect::<Vec<_>>()
                .join("; "),
        );
    }
    let raw_body = plan.body.as_ref().is_some_and(|body| {
        matches!(body, serde_json::Value::String(_))
            && plan
                .content_type
                .as_deref()
                .is_some_and(crate::e2e::codegen::client::is_raw_text_content_type)
    });
    Ok(crate::e2e::template_env::render(
        "dart/http_snippet.jinja",
        minijinja::context! {
            method => http.request.method.to_uppercase(),
            path => format!("/fixtures/{}{}", fixture.id, http.request.path),
            headers => headers.iter().map(|(key, value)| minijinja::context! {
                key => super::values::escape_dart(key), value => super::values::escape_dart(value),
            }).collect::<Vec<_>>(),
            body_json => plan.body.as_ref().map(serde_json::to_string).transpose()?,
            raw_body => raw_body,
        },
    ))
}

fn extract_test_statements(rendered: &str) -> Option<Vec<String>> {
    let lines: Vec<&str> = rendered.lines().collect();
    let start = lines.iter().position(|line| line.trim_start().starts_with("test("))? + 1;
    let end = lines.iter().rposition(|line| line.trim() == "});")?;
    Some(
        lines[start..end]
            .iter()
            .map(|line| line.strip_prefix("    ").unwrap_or(line).to_string())
            .collect(),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extracts_only_the_native_test_body() {
        let rendered = "  test('sample', () async {\n    final value = await Api.load();\n  });\n";
        assert_eq!(
            extract_test_statements(rendered),
            Some(vec!["final value = await Api.load();".to_string()])
        );
    }

    #[test]
    fn renders_native_call_without_test_harness() {
        let fixture = Fixture {
            id: "sample".into(),
            description: "Sample".into(),
            ..Fixture::default()
        };
        let mut e2e = E2eConfig::default();
        e2e.call.function = "load_document".into();
        let body = render_snippet_body(&fixture, &e2e, &ResolvedCrateConfig::default(), &[], &[]).unwrap();
        assert!(body.contains("loadDocument()"));
        assert!(body.contains("Future<void> main() async"));
        assert!(!body.contains("test("));
        assert!(!body.contains("expect("));
    }

    #[test]
    fn renders_http_request_without_test_harness_assertions() {
        let fixture: Fixture = serde_json::from_value(serde_json::json!({
            "id": "create_item", "description": "Create item", "input": null,
            "http": {
                "handler": {"route": "/items", "method": "POST"},
                "request": {"method": "POST", "path": "/items", "body": {"name": "sample"}},
                "expected_response": {"status_code": 201}
            }
        }))
        .unwrap();
        let body = render_snippet_body(
            &fixture,
            &E2eConfig::default(),
            &ResolvedCrateConfig::default(),
            &[],
            &[],
        )
        .unwrap();
        assert!(body.contains("HttpClient"));
        assert!(body.contains("/fixtures/create_item/items"));
        assert!(body.contains("request.write(jsonEncode(jsonDecode"));
        assert!(!body.contains("expect("));
    }
}