use crate::config::{CallConfig, E2eConfig};
use crate::escape::{escape_c, sanitize_filename, sanitize_ident};
use crate::field_access::FieldResolver;
use crate::fixture::{Assertion, Fixture, FixtureGroup};
use alef_core::backend::GeneratedFile;
use alef_core::config::ResolvedCrateConfig;
use alef_core::hash::{self, CommentStyle};
use anyhow::Result;
use heck::{ToPascalCase, ToSnakeCase};
use std::collections::HashMap;
use std::fmt::Write as FmtWrite;
use std::path::PathBuf;
use super::E2eCodegen;
pub struct CCodegen;
fn is_primitive_c_type(t: &str) -> bool {
matches!(
t,
"uint8_t"
| "uint16_t"
| "uint32_t"
| "uint64_t"
| "int8_t"
| "int16_t"
| "int32_t"
| "int64_t"
| "uintptr_t"
| "intptr_t"
| "size_t"
| "ssize_t"
| "double"
| "float"
| "bool"
)
}
impl E2eCodegen for CCodegen {
fn generate(
&self,
groups: &[FixtureGroup],
e2e_config: &E2eConfig,
config: &ResolvedCrateConfig,
) -> Result<Vec<GeneratedFile>> {
let lang = self.language_name();
let output_base = PathBuf::from(e2e_config.effective_output()).join(lang);
let mut files = Vec::new();
let call = &e2e_config.call;
let overrides = call.overrides.get(lang);
let result_var = &call.result_var;
let prefix = overrides
.and_then(|o| o.prefix.as_ref())
.cloned()
.or_else(|| config.ffi.as_ref().and_then(|ffi| ffi.prefix.as_ref()).cloned())
.unwrap_or_default();
let header = overrides
.and_then(|o| o.header.as_ref())
.cloned()
.unwrap_or_else(|| config.ffi_header_name());
let c_pkg = e2e_config.resolve_package("c");
let lib_name = c_pkg
.as_ref()
.and_then(|p| p.name.as_ref())
.cloned()
.unwrap_or_else(|| config.ffi_lib_name());
let active_groups: Vec<(&FixtureGroup, Vec<&Fixture>)> = groups
.iter()
.filter_map(|group| {
let active: Vec<&Fixture> = group
.fixtures
.iter()
.filter(|f| super::should_include_fixture(f, lang, e2e_config))
.filter(|f| f.visitor.is_none())
.collect();
if active.is_empty() { None } else { Some((group, active)) }
})
.collect();
let ffi_crate_path = c_pkg
.as_ref()
.and_then(|p| p.path.as_ref())
.cloned()
.unwrap_or_else(|| config.ffi_crate_path());
let category_names: Vec<String> = active_groups
.iter()
.map(|(g, _)| sanitize_filename(&g.category))
.collect();
files.push(GeneratedFile {
path: output_base.join("Makefile"),
content: render_makefile(&category_names, &header, &ffi_crate_path, &lib_name),
generated_header: true,
});
let github_repo = config.github_repo();
let version = config.resolved_version().unwrap_or_else(|| "0.0.0".to_string());
let ffi_pkg_name = e2e_config
.registry
.packages
.get("c")
.and_then(|p| p.name.as_ref())
.cloned()
.unwrap_or_else(|| lib_name.clone());
files.push(GeneratedFile {
path: output_base.join("download_ffi.sh"),
content: render_download_script(&github_repo, &version, &ffi_pkg_name),
generated_header: true,
});
files.push(GeneratedFile {
path: output_base.join("test_runner.h"),
content: render_test_runner_header(&active_groups),
generated_header: true,
});
files.push(GeneratedFile {
path: output_base.join("main.c"),
content: render_main_c(&active_groups),
generated_header: true,
});
let field_resolver = FieldResolver::new(
&e2e_config.fields,
&e2e_config.fields_optional,
&e2e_config.result_fields,
&e2e_config.fields_array,
&std::collections::HashSet::new(),
);
for (group, active) in &active_groups {
let filename = format!("test_{}.c", sanitize_filename(&group.category));
let content = render_test_file(
&group.category,
active,
&header,
&prefix,
result_var,
e2e_config,
lang,
&field_resolver,
);
files.push(GeneratedFile {
path: output_base.join(filename),
content,
generated_header: true,
});
}
Ok(files)
}
fn language_name(&self) -> &'static str {
"c"
}
}
struct ResolvedCallInfo {
function_name: String,
result_type_name: String,
options_type_name: String,
client_factory: Option<String>,
args: Vec<crate::config::ArgMapping>,
raw_c_result_type: Option<String>,
c_free_fn: Option<String>,
result_is_option: bool,
result_is_bytes: bool,
extra_args: Vec<String>,
}
fn resolve_call_info(call: &CallConfig, lang: &str) -> ResolvedCallInfo {
let overrides = call.overrides.get(lang);
let function_name = overrides
.and_then(|o| o.function.as_ref())
.cloned()
.unwrap_or_else(|| call.function.clone());
let result_type_name = overrides
.and_then(|o| o.result_type.as_ref())
.cloned()
.unwrap_or_else(|| call.function.to_pascal_case());
let options_type_name = overrides
.and_then(|o| o.options_type.as_deref())
.unwrap_or("ConversionOptions")
.to_string();
let client_factory = overrides.and_then(|o| o.client_factory.as_ref()).cloned();
let raw_c_result_type = overrides.and_then(|o| o.raw_c_result_type.clone());
let c_free_fn = overrides.and_then(|o| o.c_free_fn.clone());
let result_is_option = overrides
.and_then(|o| if o.result_is_option { Some(true) } else { None })
.unwrap_or(call.result_is_option);
let result_is_bytes = call.result_is_bytes || overrides.is_some_and(|o| o.result_is_bytes);
let extra_args = overrides.map(|o| o.extra_args.clone()).unwrap_or_default();
ResolvedCallInfo {
function_name,
result_type_name,
options_type_name,
client_factory,
args: call.args.clone(),
raw_c_result_type,
c_free_fn,
result_is_option,
result_is_bytes,
extra_args,
}
}
fn resolve_fixture_call_info(fixture: &Fixture, e2e_config: &E2eConfig, lang: &str) -> ResolvedCallInfo {
let call = e2e_config.resolve_call(fixture.call.as_deref());
let mut info = resolve_call_info(call, lang);
if info.client_factory.is_none() {
let default_overrides = e2e_config.call.overrides.get(lang);
if let Some(factory) = default_overrides.and_then(|o| o.client_factory.as_ref()) {
info.client_factory = Some(factory.clone());
}
}
info
}
fn render_makefile(categories: &[String], header_name: &str, ffi_crate_path: &str, lib_name: &str) -> String {
let mut out = String::new();
out.push_str(&hash::header(CommentStyle::Hash));
let _ = writeln!(out, "CC = gcc");
let _ = writeln!(out, "FFI_DIR = ffi");
let _ = writeln!(out);
let link_lib_name = lib_name.replace('-', "_");
let _ = writeln!(out, "ifneq ($(wildcard $(FFI_DIR)/include/{header_name}),)");
let _ = writeln!(out, " CFLAGS = -Wall -Wextra -I. -I$(FFI_DIR)/include");
let _ = writeln!(
out,
" LDFLAGS = -L$(FFI_DIR)/lib -l{link_lib_name} -Wl,-rpath,$(FFI_DIR)/lib"
);
let _ = writeln!(out, "else ifneq ($(wildcard {ffi_crate_path}/include/{header_name}),)");
let _ = writeln!(out, " CFLAGS = -Wall -Wextra -I. -I{ffi_crate_path}/include");
let _ = writeln!(
out,
" LDFLAGS = -L../../target/release -l{link_lib_name} -Wl,-rpath,../../target/release"
);
let _ = writeln!(out, "else");
let _ = writeln!(
out,
" CFLAGS = -Wall -Wextra -I. $(shell pkg-config --cflags {lib_name} 2>/dev/null)"
);
let _ = writeln!(out, " LDFLAGS = $(shell pkg-config --libs {lib_name} 2>/dev/null)");
let _ = writeln!(out, "endif");
let _ = writeln!(out);
let src_files: Vec<String> = categories.iter().map(|c| format!("test_{c}.c")).collect();
let srcs = src_files.join(" ");
let _ = writeln!(out, "SRCS = main.c {srcs}");
let _ = writeln!(out, "TARGET = run_tests");
let _ = writeln!(out);
let _ = writeln!(out, ".PHONY: all clean test");
let _ = writeln!(out);
let _ = writeln!(out, "all: $(TARGET)");
let _ = writeln!(out);
let _ = writeln!(out, "$(TARGET): $(SRCS)");
let _ = writeln!(out, "\t$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)");
let _ = writeln!(out);
let _ = writeln!(out, "MOCK_SERVER_BIN ?= ../rust/target/release/mock-server");
let _ = writeln!(out, "FIXTURES_DIR ?= ../../fixtures");
let _ = writeln!(out);
let _ = writeln!(out, "test: $(TARGET)");
let _ = writeln!(out, "\t@if [ -n \"$$MOCK_SERVER_URL\" ]; then \\");
let _ = writeln!(out, "\t\t./$(TARGET); \\");
let _ = writeln!(out, "\telse \\");
let _ = writeln!(out, "\t\tif [ ! -x \"$(MOCK_SERVER_BIN)\" ]; then \\");
let _ = writeln!(
out,
"\t\t\techo \"mock-server binary not found at $(MOCK_SERVER_BIN); run: cargo build --manifest-path ../rust/Cargo.toml --bin mock-server --release\" >&2; \\"
);
let _ = writeln!(out, "\t\t\texit 1; \\");
let _ = writeln!(out, "\t\tfi; \\");
let _ = writeln!(out, "\t\trm -f mock_server.stdout mock_server.stdin; \\");
let _ = writeln!(out, "\t\tmkfifo mock_server.stdin; \\");
let _ = writeln!(
out,
"\t\t\"$(MOCK_SERVER_BIN)\" \"$(FIXTURES_DIR)\" <mock_server.stdin >mock_server.stdout 2>&1 & \\"
);
let _ = writeln!(out, "\t\tMOCK_PID=$$!; \\");
let _ = writeln!(out, "\t\texec 9>mock_server.stdin; \\");
let _ = writeln!(out, "\t\tMOCK_URL=\"\"; \\");
let _ = writeln!(out, "\t\tfor _ in $$(seq 1 50); do \\");
let _ = writeln!(out, "\t\t\tif [ -s mock_server.stdout ]; then \\");
let _ = writeln!(
out,
"\t\t\t\tMOCK_URL=$$(grep -o 'MOCK_SERVER_URL=[^ ]*' mock_server.stdout | head -1 | cut -d= -f2); \\"
);
let _ = writeln!(out, "\t\t\t\tif [ -n \"$$MOCK_URL\" ]; then break; fi; \\");
let _ = writeln!(out, "\t\t\tfi; \\");
let _ = writeln!(out, "\t\t\tsleep 0.1; \\");
let _ = writeln!(out, "\t\tdone; \\");
let _ = writeln!(
out,
"\t\tif [ -z \"$$MOCK_URL\" ]; then echo 'failed to start mock-server' >&2; cat mock_server.stdout >&2; kill $$MOCK_PID 2>/dev/null || true; exit 1; fi; \\"
);
let _ = writeln!(out, "\t\tMOCK_SERVER_URL=\"$$MOCK_URL\" ./$(TARGET); STATUS=$$?; \\");
let _ = writeln!(out, "\t\texec 9>&-; \\");
let _ = writeln!(out, "\t\tkill $$MOCK_PID 2>/dev/null || true; \\");
let _ = writeln!(out, "\t\trm -f mock_server.stdout mock_server.stdin; \\");
let _ = writeln!(out, "\t\texit $$STATUS; \\");
let _ = writeln!(out, "\tfi");
let _ = writeln!(out);
let _ = writeln!(out, "clean:");
let _ = writeln!(out, "\trm -f $(TARGET) mock_server.stdout mock_server.stdin");
out
}
fn render_download_script(github_repo: &str, version: &str, ffi_pkg_name: &str) -> String {
let mut out = String::new();
let _ = writeln!(out, "#!/usr/bin/env bash");
out.push_str(&hash::header(CommentStyle::Hash));
let _ = writeln!(out, "set -euo pipefail");
let _ = writeln!(out);
let _ = writeln!(out, "REPO_URL=\"{github_repo}\"");
let _ = writeln!(out, "VERSION=\"{version}\"");
let _ = writeln!(out, "FFI_PKG_NAME=\"{ffi_pkg_name}\"");
let _ = writeln!(out, "FFI_DIR=\"ffi\"");
let _ = writeln!(out);
let _ = writeln!(out, "# Detect OS and architecture.");
let _ = writeln!(out, "OS=\"$(uname -s | tr '[:upper:]' '[:lower:]')\"");
let _ = writeln!(out, "ARCH=\"$(uname -m)\"");
let _ = writeln!(out);
let _ = writeln!(out, "case \"$ARCH\" in");
let _ = writeln!(out, "x86_64 | amd64) ARCH=\"x86_64\" ;;");
let _ = writeln!(out, "arm64 | aarch64) ARCH=\"aarch64\" ;;");
let _ = writeln!(out, "*)");
let _ = writeln!(out, " echo \"Unsupported architecture: $ARCH\" >&2");
let _ = writeln!(out, " exit 1");
let _ = writeln!(out, " ;;");
let _ = writeln!(out, "esac");
let _ = writeln!(out);
let _ = writeln!(out, "case \"$OS\" in");
let _ = writeln!(out, "linux) TRIPLE=\"${{ARCH}}-unknown-linux-gnu\" ;;");
let _ = writeln!(out, "darwin) TRIPLE=\"${{ARCH}}-apple-darwin\" ;;");
let _ = writeln!(out, "*)");
let _ = writeln!(out, " echo \"Unsupported OS: $OS\" >&2");
let _ = writeln!(out, " exit 1");
let _ = writeln!(out, " ;;");
let _ = writeln!(out, "esac");
let _ = writeln!(out);
let _ = writeln!(out, "ARCHIVE=\"${{FFI_PKG_NAME}}-${{TRIPLE}}.tar.gz\"");
let _ = writeln!(
out,
"URL=\"${{REPO_URL}}/releases/download/v${{VERSION}}/${{ARCHIVE}}\""
);
let _ = writeln!(out);
let _ = writeln!(out, "echo \"Downloading ${{ARCHIVE}} from v${{VERSION}}...\"");
let _ = writeln!(out, "mkdir -p \"$FFI_DIR\"");
let _ = writeln!(out, "curl -fSL \"$URL\" | tar xz -C \"$FFI_DIR\"");
let _ = writeln!(out, "echo \"FFI library extracted to $FFI_DIR/\"");
out
}
fn render_test_runner_header(active_groups: &[(&FixtureGroup, Vec<&Fixture>)]) -> String {
let mut out = String::new();
out.push_str(&hash::header(CommentStyle::Block));
let _ = writeln!(out, "#ifndef TEST_RUNNER_H");
let _ = writeln!(out, "#define TEST_RUNNER_H");
let _ = writeln!(out);
let _ = writeln!(out, "#include <string.h>");
let _ = writeln!(out, "#include <stdlib.h>");
let _ = writeln!(out);
let _ = writeln!(out, "/**");
let _ = writeln!(
out,
" * Compare a string against an expected value, trimming trailing whitespace."
);
let _ = writeln!(
out,
" * Returns 0 if the trimmed actual string equals the expected string."
);
let _ = writeln!(out, " */");
let _ = writeln!(
out,
"static inline int str_trim_eq(const char *actual, const char *expected) {{"
);
let _ = writeln!(
out,
" if (actual == NULL || expected == NULL) return actual != expected;"
);
let _ = writeln!(out, " size_t alen = strlen(actual);");
let _ = writeln!(
out,
" while (alen > 0 && (actual[alen-1] == ' ' || actual[alen-1] == '\\n' || actual[alen-1] == '\\r' || actual[alen-1] == '\\t')) alen--;"
);
let _ = writeln!(out, " size_t elen = strlen(expected);");
let _ = writeln!(out, " if (alen != elen) return 1;");
let _ = writeln!(out, " return memcmp(actual, expected, elen);");
let _ = writeln!(out, "}}");
let _ = writeln!(out);
let _ = writeln!(out, "/**");
let _ = writeln!(
out,
" * Extract a string value for a given key from a JSON object string."
);
let _ = writeln!(
out,
" * Returns a heap-allocated copy of the value, or NULL if not found."
);
let _ = writeln!(out, " * Caller must free() the returned string.");
let _ = writeln!(out, " */");
let _ = writeln!(
out,
"static inline char *alef_json_get_string(const char *json, const char *key) {{"
);
let _ = writeln!(out, " if (json == NULL || key == NULL) return NULL;");
let _ = writeln!(out, " /* Build search pattern: \"key\": */");
let _ = writeln!(out, " size_t key_len = strlen(key);");
let _ = writeln!(out, " char *pattern = (char *)malloc(key_len + 5);");
let _ = writeln!(out, " if (!pattern) return NULL;");
let _ = writeln!(out, " pattern[0] = '\"';");
let _ = writeln!(out, " memcpy(pattern + 1, key, key_len);");
let _ = writeln!(out, " pattern[key_len + 1] = '\"';");
let _ = writeln!(out, " pattern[key_len + 2] = ':';");
let _ = writeln!(out, " pattern[key_len + 3] = '\\0';");
let _ = writeln!(out, " const char *found = strstr(json, pattern);");
let _ = writeln!(out, " free(pattern);");
let _ = writeln!(out, " if (!found) return NULL;");
let _ = writeln!(out, " found += key_len + 3; /* skip past \"key\": */");
let _ = writeln!(out, " while (*found == ' ' || *found == '\\t') found++;");
let _ = writeln!(out, " if (*found != '\"') return NULL; /* not a string value */");
let _ = writeln!(out, " found++; /* skip opening quote */");
let _ = writeln!(out, " const char *end = found;");
let _ = writeln!(out, " while (*end && *end != '\"') {{");
let _ = writeln!(out, " if (*end == '\\\\') {{ end++; if (*end) end++; }}");
let _ = writeln!(out, " else end++;");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " size_t val_len = (size_t)(end - found);");
let _ = writeln!(out, " char *result_str = (char *)malloc(val_len + 1);");
let _ = writeln!(out, " if (!result_str) return NULL;");
let _ = writeln!(out, " memcpy(result_str, found, val_len);");
let _ = writeln!(out, " result_str[val_len] = '\\0';");
let _ = writeln!(out, " return result_str;");
let _ = writeln!(out, "}}");
let _ = writeln!(out);
let _ = writeln!(out, "/**");
let _ = writeln!(out, " * Count top-level elements in a JSON array string.");
let _ = writeln!(out, " * Returns 0 for empty arrays (\"[]\") or NULL input.");
let _ = writeln!(out, " */");
let _ = writeln!(out, "static inline int alef_json_array_count(const char *json) {{");
let _ = writeln!(out, " if (json == NULL) return 0;");
let _ = writeln!(out, " /* Skip leading whitespace */");
let _ = writeln!(
out,
" while (*json == ' ' || *json == '\\t' || *json == '\\n') json++;"
);
let _ = writeln!(out, " if (*json != '[') return 0;");
let _ = writeln!(out, " json++;");
let _ = writeln!(out, " /* Skip whitespace after '[' */");
let _ = writeln!(
out,
" while (*json == ' ' || *json == '\\t' || *json == '\\n') json++;"
);
let _ = writeln!(out, " if (*json == ']') return 0;");
let _ = writeln!(out, " int count = 1;");
let _ = writeln!(out, " int depth = 0;");
let _ = writeln!(out, " int in_string = 0;");
let _ = writeln!(
out,
" for (; *json && !(*json == ']' && depth == 0 && !in_string); json++) {{"
);
let _ = writeln!(out, " if (*json == '\\\\' && in_string) {{ json++; continue; }}");
let _ = writeln!(
out,
" if (*json == '\"') {{ in_string = !in_string; continue; }}"
);
let _ = writeln!(out, " if (in_string) continue;");
let _ = writeln!(out, " if (*json == '[' || *json == '{{') depth++;");
let _ = writeln!(out, " else if (*json == ']' || *json == '}}') depth--;");
let _ = writeln!(out, " else if (*json == ',' && depth == 0) count++;");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " return count;");
let _ = writeln!(out, "}}");
let _ = writeln!(out);
for (group, fixtures) in active_groups {
let _ = writeln!(out, "/* Tests for category: {} */", group.category);
for fixture in fixtures {
let fn_name = sanitize_ident(&fixture.id);
let _ = writeln!(out, "void test_{fn_name}(void);");
}
let _ = writeln!(out);
}
let _ = writeln!(out, "#endif /* TEST_RUNNER_H */");
out
}
fn render_main_c(active_groups: &[(&FixtureGroup, Vec<&Fixture>)]) -> String {
let mut out = String::new();
out.push_str(&hash::header(CommentStyle::Block));
let _ = writeln!(out, "#include <stdio.h>");
let _ = writeln!(out, "#include \"test_runner.h\"");
let _ = writeln!(out);
let _ = writeln!(out, "int main(void) {{");
let _ = writeln!(out, " int passed = 0;");
let _ = writeln!(out);
for (group, fixtures) in active_groups {
let _ = writeln!(out, " /* Category: {} */", group.category);
for fixture in fixtures {
let fn_name = sanitize_ident(&fixture.id);
let _ = writeln!(out, " printf(\" Running test_{fn_name}...\");");
let _ = writeln!(out, " test_{fn_name}();");
let _ = writeln!(out, " printf(\" PASSED\\n\");");
let _ = writeln!(out, " passed++;");
}
let _ = writeln!(out);
}
let _ = writeln!(out, " printf(\"\\nResults: %d passed, 0 failed\\n\", passed);");
let _ = writeln!(out, " return 0;");
let _ = writeln!(out, "}}");
out
}
#[allow(clippy::too_many_arguments)]
fn render_test_file(
category: &str,
fixtures: &[&Fixture],
header: &str,
prefix: &str,
result_var: &str,
e2e_config: &E2eConfig,
lang: &str,
field_resolver: &FieldResolver,
) -> String {
let mut out = String::new();
out.push_str(&hash::header(CommentStyle::Block));
let _ = writeln!(out, "/* E2e tests for category: {category} */");
let _ = writeln!(out);
let _ = writeln!(out, "#include <assert.h>");
let _ = writeln!(out, "#include <string.h>");
let _ = writeln!(out, "#include <stdio.h>");
let _ = writeln!(out, "#include <stdlib.h>");
let _ = writeln!(out, "#include \"{header}\"");
let _ = writeln!(out, "#include \"test_runner.h\"");
let _ = writeln!(out);
for (i, fixture) in fixtures.iter().enumerate() {
if fixture.visitor.is_some() {
panic!(
"C e2e generator: visitor pattern not supported for fixture: {}",
fixture.id
);
}
let call_info = resolve_fixture_call_info(fixture, e2e_config, lang);
render_test_function(
&mut out,
fixture,
prefix,
&call_info.function_name,
result_var,
&call_info.args,
field_resolver,
&e2e_config.fields_c_types,
&call_info.result_type_name,
&call_info.options_type_name,
call_info.client_factory.as_deref(),
call_info.raw_c_result_type.as_deref(),
call_info.c_free_fn.as_deref(),
call_info.result_is_option,
call_info.result_is_bytes,
&call_info.extra_args,
);
if i + 1 < fixtures.len() {
let _ = writeln!(out);
}
}
out
}
#[allow(clippy::too_many_arguments)]
fn render_test_function(
out: &mut String,
fixture: &Fixture,
prefix: &str,
function_name: &str,
result_var: &str,
args: &[crate::config::ArgMapping],
field_resolver: &FieldResolver,
fields_c_types: &HashMap<String, String>,
result_type_name: &str,
options_type_name: &str,
client_factory: Option<&str>,
raw_c_result_type: Option<&str>,
c_free_fn: Option<&str>,
result_is_option: bool,
result_is_bytes: bool,
extra_args: &[String],
) {
let fn_name = sanitize_ident(&fixture.id);
let description = &fixture.description;
let expects_error = fixture.assertions.iter().any(|a| a.assertion_type == "error");
let _ = writeln!(out, "void test_{fn_name}(void) {{");
let _ = writeln!(out, " /* {description} */");
let prefix_upper = prefix.to_uppercase();
if client_factory.is_some() && function_name == "chat_stream" {
render_chat_stream_test_function(out, fixture, prefix, result_var, args, options_type_name, expects_error);
return;
}
if let Some(factory) = client_factory {
if result_is_bytes {
render_bytes_test_function(
out,
fixture,
prefix,
function_name,
result_var,
args,
options_type_name,
result_type_name,
factory,
expects_error,
);
return;
}
}
if let Some(factory) = client_factory {
let mut request_handle_vars: Vec<(String, String)> = Vec::new(); let mut inline_method_args: Vec<String> = Vec::new();
for arg in args {
if arg.arg_type == "json_object" {
let request_type_pascal = if !options_type_name.is_empty() && options_type_name != "ConversionOptions" {
options_type_name.to_string()
} else if let Some(stripped) = result_type_name.strip_suffix("Response") {
format!("{}Request", stripped)
} else {
format!("{result_type_name}Request")
};
let request_type_snake = request_type_pascal.to_snake_case();
let var_name = format!("{request_type_snake}_handle");
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let json_val = if field.is_empty() || field == "input" {
Some(&fixture.input)
} else {
fixture.input.get(field)
};
if let Some(val) = json_val {
if !val.is_null() {
let normalized = super::normalize_json_keys_to_snake_case(val);
let json_str = serde_json::to_string(&normalized).unwrap_or_default();
let escaped = escape_c(&json_str);
let _ = writeln!(
out,
" {prefix_upper}{request_type_pascal}* {var_name} = \
{prefix}_{request_type_snake}_from_json(\"{escaped}\");"
);
let _ = writeln!(out, " assert({var_name} != NULL && \"failed to build request\");");
request_handle_vars.push((arg.name.clone(), var_name));
}
}
} else if arg.arg_type == "string" {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let val = fixture.input.get(field);
match val {
Some(v) if v.is_string() => {
let s = v.as_str().unwrap_or_default();
let escaped = escape_c(s);
inline_method_args.push(format!("\"{escaped}\""));
}
Some(serde_json::Value::Null) | None if arg.optional => {
inline_method_args.push("NULL".to_string());
}
None => {
inline_method_args.push("\"\"".to_string());
}
Some(other) => {
let s = serde_json::to_string(other).unwrap_or_default();
let escaped = escape_c(&s);
inline_method_args.push(format!("\"{escaped}\""));
}
}
} else if arg.optional {
inline_method_args.push("NULL".to_string());
}
}
let fixture_id = &fixture.id;
if fixture.needs_mock_server() {
let _ = writeln!(out, " const char* mock_base = getenv(\"MOCK_SERVER_URL\");");
let _ = writeln!(out, " assert(mock_base != NULL && \"MOCK_SERVER_URL must be set\");");
let _ = writeln!(out, " char base_url[1024];");
let _ = writeln!(
out,
" snprintf(base_url, sizeof(base_url), \"%s/fixtures/{fixture_id}\", mock_base);"
);
let _ = writeln!(
out,
" {prefix_upper}DefaultClient* client = {prefix}_{factory}(\"test-key\", base_url, 0, 0, NULL);"
);
} else {
let _ = writeln!(
out,
" {prefix_upper}DefaultClient* client = {prefix}_{factory}(\"test-key\", NULL, 0, 0, NULL);"
);
}
let _ = writeln!(out, " assert(client != NULL && \"failed to create client\");");
let method_args = if request_handle_vars.is_empty() && inline_method_args.is_empty() && extra_args.is_empty() {
String::new()
} else {
let handles: Vec<String> = request_handle_vars.iter().map(|(_, v)| v.clone()).collect();
let parts: Vec<String> = handles
.into_iter()
.chain(inline_method_args.iter().cloned())
.chain(extra_args.iter().cloned())
.collect();
format!(", {}", parts.join(", "))
};
let call_fn = format!("{prefix}_default_client_{function_name}");
if expects_error {
let _ = writeln!(
out,
" {prefix_upper}{result_type_name}* {result_var} = {call_fn}(client{method_args});"
);
for (_, var_name) in &request_handle_vars {
let req_snake = var_name.strip_suffix("_handle").unwrap_or(var_name);
let _ = writeln!(out, " {prefix}_{req_snake}_free({var_name});");
}
let _ = writeln!(out, " {prefix}_default_client_free(client);");
let _ = writeln!(out, " assert({result_var} == NULL && \"expected call to fail\");");
let _ = writeln!(out, "}}");
return;
}
let _ = writeln!(
out,
" {prefix_upper}{result_type_name}* {result_var} = {call_fn}(client{method_args});"
);
let _ = writeln!(out, " assert({result_var} != NULL && \"expected call to succeed\");");
let mut intermediate_handles: Vec<(String, String)> = Vec::new();
let mut accessed_fields: Vec<(String, String, bool)> = Vec::new();
let mut primitive_locals: HashMap<String, String> = HashMap::new();
for assertion in &fixture.assertions {
if let Some(f) = &assertion.field {
if !f.is_empty() && !accessed_fields.iter().any(|(k, _, _)| k == f) {
let resolved = field_resolver.resolve(f);
let local_var = f.replace(['.', '['], "_").replace(']', "");
let has_map_access = resolved.contains('[');
if resolved.contains('.') {
let leaf_primitive = emit_nested_accessor(
out,
prefix,
resolved,
&local_var,
result_var,
fields_c_types,
&mut intermediate_handles,
result_type_name,
);
if let Some(prim) = leaf_primitive {
primitive_locals.insert(local_var.clone(), prim);
}
} else {
let result_type_snake = result_type_name.to_snake_case();
let accessor_fn = format!("{prefix}_{result_type_snake}_{resolved}");
let lookup_key = format!("{result_type_snake}.{resolved}");
if let Some(t) = fields_c_types.get(&lookup_key).filter(|t| is_primitive_c_type(t)) {
let _ = writeln!(out, " {t} {local_var} = {accessor_fn}({result_var});");
primitive_locals.insert(local_var.clone(), t.clone());
} else {
let _ = writeln!(out, " char* {local_var} = {accessor_fn}({result_var});");
}
}
accessed_fields.push((f.clone(), local_var, has_map_access));
}
}
}
for assertion in &fixture.assertions {
render_assertion(
out,
assertion,
result_var,
prefix,
field_resolver,
&accessed_fields,
&primitive_locals,
);
}
for (_f, local_var, from_json) in &accessed_fields {
if primitive_locals.contains_key(local_var) {
continue;
}
if *from_json {
let _ = writeln!(out, " free({local_var});");
} else {
let _ = writeln!(out, " {prefix}_free_string({local_var});");
}
}
for (handle_var, snake_type) in intermediate_handles.iter().rev() {
if snake_type == "free_string" {
let _ = writeln!(out, " {prefix}_free_string({handle_var});");
} else {
let _ = writeln!(out, " {prefix}_{snake_type}_free({handle_var});");
}
}
let result_type_snake = result_type_name.to_snake_case();
let _ = writeln!(out, " {prefix}_{result_type_snake}_free({result_var});");
for (_, var_name) in &request_handle_vars {
let req_snake = var_name.strip_suffix("_handle").unwrap_or(var_name);
let _ = writeln!(out, " {prefix}_{req_snake}_free({var_name});");
}
let _ = writeln!(out, " {prefix}_default_client_free(client);");
let _ = writeln!(out, "}}");
return;
}
if let Some(raw_type) = raw_c_result_type {
let args_str = if args.is_empty() {
String::new()
} else {
let parts: Vec<String> = args
.iter()
.filter_map(|arg| {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let val = fixture.input.get(field);
match val {
None if arg.optional => Some("NULL".to_string()),
None => None,
Some(v) if v.is_null() && arg.optional => Some("NULL".to_string()),
Some(v) => Some(json_to_c(v)),
}
})
.collect();
parts.join(", ")
};
let _ = writeln!(out, " {raw_type} {result_var} = {function_name}({args_str});");
let has_not_error = fixture.assertions.iter().any(|a| a.assertion_type == "not_error");
if has_not_error {
match raw_type {
"char*" if !result_is_option => {
let _ = writeln!(out, " assert({result_var} != NULL && \"expected call to succeed\");");
}
"int32_t" => {
let _ = writeln!(out, " assert({result_var} >= 0 && \"expected call to succeed\");");
}
"uintptr_t" => {
let _ = writeln!(
out,
" assert({prefix}_last_error_code() == 0 && \"expected call to succeed\");"
);
}
_ => {}
}
}
for assertion in &fixture.assertions {
match assertion.assertion_type.as_str() {
"not_error" | "error" => {} "not_empty" => {
let _ = writeln!(
out,
" assert({result_var} != NULL && strlen({result_var}) > 0 && \"expected non-empty value\");"
);
}
"is_empty" => {
if result_is_option && raw_type == "char*" {
let _ = writeln!(
out,
" assert({result_var} == NULL && \"expected empty/null value\");"
);
} else {
let _ = writeln!(
out,
" assert(strlen({result_var}) == 0 && \"expected empty value\");"
);
}
}
"count_min" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
match raw_type {
"char*" => {
let _ = writeln!(out, " {{");
let _ = writeln!(
out,
" assert({result_var} != NULL && \"expected non-null JSON array\");"
);
let _ =
writeln!(out, " int elem_count = alef_json_array_count({result_var});");
let _ = writeln!(
out,
" assert(elem_count >= {n} && \"expected at least {n} elements\");"
);
let _ = writeln!(out, " }}");
}
_ => {
let _ = writeln!(
out,
" assert((size_t){result_var} >= {n} && \"expected at least {n} elements\");"
);
}
}
}
}
}
"greater_than_or_equal" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert({result_var} >= {c_val} && \"expected greater than or equal\");"
);
}
}
"contains" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert(strstr({result_var}, {c_val}) != NULL && \"expected to contain substring\");"
);
}
}
"contains_all" => {
if let Some(values) = &assertion.values {
for val in values {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert(strstr({result_var}, {c_val}) != NULL && \"expected to contain substring\");"
);
}
}
}
"equals" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
if val.is_string() {
let _ = writeln!(
out,
" assert({result_var} != NULL && str_trim_eq({result_var}, {c_val}) == 0 && \"equals assertion failed\");"
);
} else {
let _ = writeln!(
out,
" assert({result_var} == {c_val} && \"equals assertion failed\");"
);
}
}
}
"not_contains" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert(strstr({result_var}, {c_val}) == NULL && \"expected NOT to contain substring\");"
);
}
}
"starts_with" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert(strncmp({result_var}, {c_val}, strlen({c_val})) == 0 && \"expected to start with\");"
);
}
}
"is_true" => {
let _ = writeln!(out, " assert({result_var});");
}
"is_false" => {
let _ = writeln!(out, " assert(!{result_var});");
}
other => {
panic!("C e2e raw-result generator: unsupported assertion type: {other}");
}
}
}
if raw_type == "char*" {
let free_fn = c_free_fn
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{prefix}_free_string"));
if result_is_option {
let _ = writeln!(out, " if ({result_var} != NULL) {{ {free_fn}({result_var}); }}");
} else {
let _ = writeln!(out, " {free_fn}({result_var});");
}
}
let _ = writeln!(out, "}}");
return;
}
let prefixed_fn = function_name.to_string();
let mut has_options_handle = false;
for arg in args {
if arg.arg_type == "json_object" {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
if let Some(val) = fixture.input.get(field) {
if !val.is_null() {
let normalized = super::normalize_json_keys_to_snake_case(val);
let json_str = serde_json::to_string(&normalized).unwrap_or_default();
let escaped = escape_c(&json_str);
let upper = prefix.to_uppercase();
let options_type_pascal = options_type_name;
let options_type_snake = options_type_name.to_snake_case();
let _ = writeln!(
out,
" {upper}{options_type_pascal}* options_handle = {prefix}_{options_type_snake}_from_json(\"{escaped}\");"
);
has_options_handle = true;
}
}
}
}
let args_str = build_args_string_c(&fixture.input, args, has_options_handle);
if expects_error {
let _ = writeln!(
out,
" {prefix_upper}{result_type_name}* {result_var} = {prefixed_fn}({args_str});"
);
if has_options_handle {
let options_type_snake = options_type_name.to_snake_case();
let _ = writeln!(out, " {prefix}_{options_type_snake}_free(options_handle);");
}
let _ = writeln!(out, " assert({result_var} == NULL && \"expected call to fail\");");
let _ = writeln!(out, "}}");
return;
}
let _ = writeln!(
out,
" {prefix_upper}{result_type_name}* {result_var} = {prefixed_fn}({args_str});"
);
let _ = writeln!(out, " assert({result_var} != NULL && \"expected call to succeed\");");
let mut accessed_fields: Vec<(String, String, bool)> = Vec::new();
let mut intermediate_handles: Vec<(String, String)> = Vec::new();
let mut primitive_locals: HashMap<String, String> = HashMap::new();
for assertion in &fixture.assertions {
if let Some(f) = &assertion.field {
if !f.is_empty() && !accessed_fields.iter().any(|(k, _, _)| k == f) {
let resolved = field_resolver.resolve(f);
let local_var = f.replace(['.', '['], "_").replace(']', "");
let has_map_access = resolved.contains('[');
if resolved.contains('.') {
let leaf_primitive = emit_nested_accessor(
out,
prefix,
resolved,
&local_var,
result_var,
fields_c_types,
&mut intermediate_handles,
result_type_name,
);
if let Some(prim) = leaf_primitive {
primitive_locals.insert(local_var.clone(), prim);
}
} else {
let result_type_snake = result_type_name.to_snake_case();
let accessor_fn = format!("{prefix}_{result_type_snake}_{resolved}");
let lookup_key = format!("{result_type_snake}.{resolved}");
if let Some(t) = fields_c_types.get(&lookup_key).filter(|t| is_primitive_c_type(t)) {
let _ = writeln!(out, " {t} {local_var} = {accessor_fn}({result_var});");
primitive_locals.insert(local_var.clone(), t.clone());
} else {
let _ = writeln!(out, " char* {local_var} = {accessor_fn}({result_var});");
}
}
accessed_fields.push((f.clone(), local_var.clone(), has_map_access));
}
}
}
for assertion in &fixture.assertions {
render_assertion(
out,
assertion,
result_var,
prefix,
field_resolver,
&accessed_fields,
&primitive_locals,
);
}
for (_f, local_var, from_json) in &accessed_fields {
if primitive_locals.contains_key(local_var) {
continue;
}
if *from_json {
let _ = writeln!(out, " free({local_var});");
} else {
let _ = writeln!(out, " {prefix}_free_string({local_var});");
}
}
for (handle_var, snake_type) in intermediate_handles.iter().rev() {
if snake_type == "free_string" {
let _ = writeln!(out, " {prefix}_free_string({handle_var});");
} else {
let _ = writeln!(out, " {prefix}_{snake_type}_free({handle_var});");
}
}
if has_options_handle {
let options_type_snake = options_type_name.to_snake_case();
let _ = writeln!(out, " {prefix}_{options_type_snake}_free(options_handle);");
}
let result_type_snake = result_type_name.to_snake_case();
let _ = writeln!(out, " {prefix}_{result_type_snake}_free({result_var});");
let _ = writeln!(out, "}}");
}
#[allow(clippy::too_many_arguments)]
fn render_bytes_test_function(
out: &mut String,
fixture: &Fixture,
prefix: &str,
function_name: &str,
_result_var: &str,
args: &[crate::config::ArgMapping],
options_type_name: &str,
result_type_name: &str,
factory: &str,
expects_error: bool,
) {
let prefix_upper = prefix.to_uppercase();
let mut request_handle_vars: Vec<(String, String)> = Vec::new();
let mut string_arg_exprs: Vec<String> = Vec::new();
for arg in args {
match arg.arg_type.as_str() {
"json_object" => {
let request_type_pascal = if !options_type_name.is_empty() && options_type_name != "ConversionOptions" {
options_type_name.to_string()
} else if let Some(stripped) = result_type_name.strip_suffix("Response") {
format!("{}Request", stripped)
} else {
format!("{result_type_name}Request")
};
let request_type_snake = request_type_pascal.to_snake_case();
let var_name = format!("{request_type_snake}_handle");
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let json_val = if field.is_empty() || field == "input" {
Some(&fixture.input)
} else {
fixture.input.get(field)
};
if let Some(val) = json_val {
if !val.is_null() {
let normalized = super::normalize_json_keys_to_snake_case(val);
let json_str = serde_json::to_string(&normalized).unwrap_or_default();
let escaped = escape_c(&json_str);
let _ = writeln!(
out,
" {prefix_upper}{request_type_pascal}* {var_name} = \
{prefix}_{request_type_snake}_from_json(\"{escaped}\");"
);
let _ = writeln!(out, " assert({var_name} != NULL && \"failed to build request\");");
request_handle_vars.push((arg.name.clone(), var_name));
}
}
}
"string" => {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let val = fixture.input.get(field);
let expr = match val {
Some(serde_json::Value::String(s)) => format!("\"{}\"", escape_c(s)),
Some(serde_json::Value::Null) | None if arg.optional => "NULL".to_string(),
Some(v) => serde_json::to_string(v).unwrap_or_else(|_| "NULL".to_string()),
None => "NULL".to_string(),
};
string_arg_exprs.push(expr);
}
_ => {
string_arg_exprs.push("NULL".to_string());
}
}
}
let fixture_id = &fixture.id;
if fixture.needs_mock_server() {
let _ = writeln!(out, " const char* mock_base = getenv(\"MOCK_SERVER_URL\");");
let _ = writeln!(out, " assert(mock_base != NULL && \"MOCK_SERVER_URL must be set\");");
let _ = writeln!(out, " char base_url[1024];");
let _ = writeln!(
out,
" snprintf(base_url, sizeof(base_url), \"%s/fixtures/{fixture_id}\", mock_base);"
);
let _ = writeln!(
out,
" {prefix_upper}DefaultClient* client = {prefix}_{factory}(\"test-key\", base_url, 0, 0, NULL);"
);
} else {
let _ = writeln!(
out,
" {prefix_upper}DefaultClient* client = {prefix}_{factory}(\"test-key\", NULL, 0, 0, NULL);"
);
}
let _ = writeln!(out, " assert(client != NULL && \"failed to create client\");");
let _ = writeln!(out, " uint8_t* out_ptr = NULL;");
let _ = writeln!(out, " uintptr_t out_len = 0;");
let _ = writeln!(out, " uintptr_t out_cap = 0;");
let mut method_args: Vec<String> = Vec::new();
for (_, v) in &request_handle_vars {
method_args.push(v.clone());
}
method_args.extend(string_arg_exprs.iter().cloned());
let extra_args = if method_args.is_empty() {
String::new()
} else {
format!(", {}", method_args.join(", "))
};
let call_fn = format!("{prefix}_default_client_{function_name}");
let _ = writeln!(
out,
" int32_t status = {call_fn}(client{extra_args}, &out_ptr, &out_len, &out_cap);"
);
if expects_error {
for (_, var_name) in &request_handle_vars {
let req_snake = var_name.strip_suffix("_handle").unwrap_or(var_name);
let _ = writeln!(out, " {prefix}_{req_snake}_free({var_name});");
}
let _ = writeln!(out, " {prefix}_default_client_free(client);");
let _ = writeln!(out, " assert(status != 0 && \"expected call to fail\");");
let _ = writeln!(out, " {prefix}_free_bytes(out_ptr, out_len, out_cap);");
let _ = writeln!(out, "}}");
return;
}
let _ = writeln!(out, " assert(status == 0 && \"expected call to succeed\");");
let mut emitted_len_check = false;
for assertion in &fixture.assertions {
match assertion.assertion_type.as_str() {
"not_error" => {
}
"not_empty" | "not_null" => {
if !emitted_len_check {
let _ = writeln!(out, " assert(out_len > 0 && \"expected non-empty value\");");
emitted_len_check = true;
}
}
_ => {
let _ = writeln!(
out,
" /* skipped: assertion '{}' not meaningful on raw byte buffer */",
assertion.assertion_type
);
}
}
}
let _ = writeln!(out, " {prefix}_free_bytes(out_ptr, out_len, out_cap);");
for (_, var_name) in &request_handle_vars {
let req_snake = var_name.strip_suffix("_handle").unwrap_or(var_name);
let _ = writeln!(out, " {prefix}_{req_snake}_free({var_name});");
}
let _ = writeln!(out, " {prefix}_default_client_free(client);");
let _ = writeln!(out, "}}");
}
fn render_chat_stream_test_function(
out: &mut String,
fixture: &Fixture,
prefix: &str,
result_var: &str,
args: &[crate::config::ArgMapping],
options_type_name: &str,
expects_error: bool,
) {
let prefix_upper = prefix.to_uppercase();
let mut request_var: Option<String> = None;
for arg in args {
if arg.arg_type == "json_object" {
let request_type_pascal = if !options_type_name.is_empty() && options_type_name != "ConversionOptions" {
options_type_name.to_string()
} else {
"ChatCompletionRequest".to_string()
};
let request_type_snake = request_type_pascal.to_snake_case();
let var_name = format!("{request_type_snake}_handle");
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let json_val = if field.is_empty() || field == "input" {
Some(&fixture.input)
} else {
fixture.input.get(field)
};
if let Some(val) = json_val {
if !val.is_null() {
let normalized = super::normalize_json_keys_to_snake_case(val);
let json_str = serde_json::to_string(&normalized).unwrap_or_default();
let escaped = escape_c(&json_str);
let _ = writeln!(
out,
" {prefix_upper}{request_type_pascal}* {var_name} = \
{prefix}_{request_type_snake}_from_json(\"{escaped}\");"
);
let _ = writeln!(out, " assert({var_name} != NULL && \"failed to build request\");");
request_var = Some(var_name);
break;
}
}
}
}
let req_handle = request_var.clone().unwrap_or_else(|| "NULL".to_string());
let req_snake = request_var
.as_ref()
.and_then(|v| v.strip_suffix("_handle"))
.unwrap_or("chat_completion_request")
.to_string();
let fixture_id = &fixture.id;
if fixture.needs_mock_server() {
let _ = writeln!(out, " const char* mock_base = getenv(\"MOCK_SERVER_URL\");");
let _ = writeln!(out, " assert(mock_base != NULL && \"MOCK_SERVER_URL must be set\");");
let _ = writeln!(out, " char base_url[1024];");
let _ = writeln!(
out,
" snprintf(base_url, sizeof(base_url), \"%s/fixtures/{fixture_id}\", mock_base);"
);
let _ = writeln!(
out,
" {prefix_upper}DefaultClient* client = {prefix}_create_client(\"test-key\", base_url, 0, 0, NULL);"
);
} else {
let _ = writeln!(
out,
" {prefix_upper}DefaultClient* client = {prefix}_create_client(\"test-key\", NULL, 0, 0, NULL);"
);
}
let _ = writeln!(out, " assert(client != NULL && \"failed to create client\");");
let _ = writeln!(
out,
" {prefix_upper}LiterllmDefaultClientChatStreamStreamHandle* stream_handle = \
{prefix}_default_client_chat_stream_start(client, {req_handle});"
);
if expects_error {
let _ = writeln!(
out,
" assert(stream_handle == NULL && \"expected stream-start to fail\");"
);
if request_var.is_some() {
let _ = writeln!(out, " {prefix}_{req_snake}_free({req_handle});");
}
let _ = writeln!(out, " {prefix}_default_client_free(client);");
let _ = writeln!(out, "}}");
return;
}
let _ = writeln!(
out,
" assert(stream_handle != NULL && \"expected stream-start to succeed\");"
);
let _ = writeln!(out, " size_t chunks_count = 0;");
let _ = writeln!(out, " char* stream_content = (char*)malloc(1);");
let _ = writeln!(out, " assert(stream_content != NULL);");
let _ = writeln!(out, " stream_content[0] = '\\0';");
let _ = writeln!(out, " size_t stream_content_len = 0;");
let _ = writeln!(out, " int stream_complete = 0;");
let _ = writeln!(out, " int no_chunks_after_done = 1;");
let _ = writeln!(out, " char* last_choices_json = NULL;");
let _ = writeln!(out, " uint64_t total_tokens = 0;");
let _ = writeln!(out);
let _ = writeln!(out, " while (1) {{");
let _ = writeln!(
out,
" {prefix_upper}ChatCompletionChunk* {result_var} = \
{prefix}_default_client_chat_stream_next(stream_handle);"
);
let _ = writeln!(out, " if ({result_var} == NULL) {{");
let _ = writeln!(
out,
" if ({prefix}_last_error_code() == 0) {{ stream_complete = 1; }}"
);
let _ = writeln!(out, " break;");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " chunks_count++;");
let _ = writeln!(
out,
" char* choices_json = {prefix}_chat_completion_chunk_choices({result_var});"
);
let _ = writeln!(out, " if (choices_json != NULL) {{");
let _ = writeln!(
out,
" const char* d = strstr(choices_json, \"\\\"content\\\":\");"
);
let _ = writeln!(out, " if (d != NULL) {{");
let _ = writeln!(out, " d += 10;");
let _ = writeln!(out, " while (*d == ' ' || *d == '\\t') d++;");
let _ = writeln!(out, " if (*d == '\"') {{");
let _ = writeln!(out, " d++;");
let _ = writeln!(out, " const char* e = d;");
let _ = writeln!(out, " while (*e && *e != '\"') {{");
let _ = writeln!(
out,
" if (*e == '\\\\' && *(e+1)) e += 2; else e++;"
);
let _ = writeln!(out, " }}");
let _ = writeln!(out, " size_t add = (size_t)(e - d);");
let _ = writeln!(out, " if (add > 0) {{");
let _ = writeln!(
out,
" char* nc = (char*)realloc(stream_content, stream_content_len + add + 1);"
);
let _ = writeln!(out, " if (nc != NULL) {{");
let _ = writeln!(out, " stream_content = nc;");
let _ = writeln!(
out,
" memcpy(stream_content + stream_content_len, d, add);"
);
let _ = writeln!(out, " stream_content_len += add;");
let _ = writeln!(
out,
" stream_content[stream_content_len] = '\\0';"
);
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(
out,
" if (last_choices_json != NULL) {prefix}_free_string(last_choices_json);"
);
let _ = writeln!(out, " last_choices_json = choices_json;");
let _ = writeln!(out, " }}");
let _ = writeln!(
out,
" {prefix_upper}Usage* usage_handle = {prefix}_chat_completion_chunk_usage({result_var});"
);
let _ = writeln!(out, " if (usage_handle != NULL) {{");
let _ = writeln!(
out,
" total_tokens = (uint64_t){prefix}_usage_total_tokens(usage_handle);"
);
let _ = writeln!(out, " {prefix}_usage_free(usage_handle);");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " {prefix}_chat_completion_chunk_free({result_var});");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " {prefix}_default_client_chat_stream_free(stream_handle);");
let _ = writeln!(out);
let _ = writeln!(out, " char* finish_reason = NULL;");
let _ = writeln!(out, " char* tool_calls_json = NULL;");
let _ = writeln!(out, " char* tool_calls_0_function_name = NULL;");
let _ = writeln!(out, " if (last_choices_json != NULL) {{");
let _ = writeln!(
out,
" finish_reason = alef_json_get_string(last_choices_json, \"finish_reason\");"
);
let _ = writeln!(
out,
" const char* tc = strstr(last_choices_json, \"\\\"tool_calls\\\":\");"
);
let _ = writeln!(out, " if (tc != NULL) {{");
let _ = writeln!(out, " tc += 13;");
let _ = writeln!(out, " while (*tc == ' ' || *tc == '\\t') tc++;");
let _ = writeln!(out, " if (*tc == '[') {{");
let _ = writeln!(out, " int depth = 0;");
let _ = writeln!(out, " const char* end = tc;");
let _ = writeln!(out, " int in_str = 0;");
let _ = writeln!(out, " for (; *end; end++) {{");
let _ = writeln!(
out,
" if (*end == '\\\\' && in_str) {{ if (*(end+1)) end++; continue; }}"
);
let _ = writeln!(
out,
" if (*end == '\"') {{ in_str = !in_str; continue; }}"
);
let _ = writeln!(out, " if (in_str) continue;");
let _ = writeln!(out, " if (*end == '[' || *end == '{{') depth++;");
let _ = writeln!(
out,
" else if (*end == ']' || *end == '}}') {{ depth--; if (depth == 0) {{ end++; break; }} }}"
);
let _ = writeln!(out, " }}");
let _ = writeln!(out, " size_t tlen = (size_t)(end - tc);");
let _ = writeln!(out, " tool_calls_json = (char*)malloc(tlen + 1);");
let _ = writeln!(out, " if (tool_calls_json != NULL) {{");
let _ = writeln!(out, " memcpy(tool_calls_json, tc, tlen);");
let _ = writeln!(out, " tool_calls_json[tlen] = '\\0';");
let _ = writeln!(
out,
" const char* fn = strstr(tool_calls_json, \"\\\"function\\\"\");"
);
let _ = writeln!(out, " if (fn != NULL) {{");
let _ = writeln!(
out,
" const char* np = strstr(fn, \"\\\"name\\\":\");"
);
let _ = writeln!(out, " if (np != NULL) {{");
let _ = writeln!(out, " np += 7;");
let _ = writeln!(
out,
" while (*np == ' ' || *np == '\\t') np++;"
);
let _ = writeln!(out, " if (*np == '\"') {{");
let _ = writeln!(out, " np++;");
let _ = writeln!(out, " const char* ne = np;");
let _ = writeln!(
out,
" while (*ne && *ne != '\"') {{ if (*ne == '\\\\' && *(ne+1)) ne += 2; else ne++; }}"
);
let _ = writeln!(out, " size_t nlen = (size_t)(ne - np);");
let _ = writeln!(
out,
" tool_calls_0_function_name = (char*)malloc(nlen + 1);"
);
let _ = writeln!(
out,
" if (tool_calls_0_function_name != NULL) {{"
);
let _ = writeln!(
out,
" memcpy(tool_calls_0_function_name, np, nlen);"
);
let _ = writeln!(
out,
" tool_calls_0_function_name[nlen] = '\\0';"
);
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out, " }}");
let _ = writeln!(out);
for assertion in &fixture.assertions {
emit_chat_stream_assertion(out, assertion);
}
let _ = writeln!(out, " free(stream_content);");
let _ = writeln!(
out,
" if (last_choices_json != NULL) {prefix}_free_string(last_choices_json);"
);
let _ = writeln!(out, " if (finish_reason != NULL) free(finish_reason);");
let _ = writeln!(out, " if (tool_calls_json != NULL) free(tool_calls_json);");
let _ = writeln!(
out,
" if (tool_calls_0_function_name != NULL) free(tool_calls_0_function_name);"
);
if request_var.is_some() {
let _ = writeln!(out, " {prefix}_{req_snake}_free({req_handle});");
}
let _ = writeln!(out, " {prefix}_default_client_free(client);");
let _ = writeln!(
out,
" /* suppress unused */ (void)total_tokens; (void)no_chunks_after_done; \
(void)stream_complete; (void)chunks_count; (void)stream_content_len;"
);
let _ = writeln!(out, "}}");
}
fn emit_chat_stream_assertion(out: &mut String, assertion: &Assertion) {
let field = assertion.field.as_deref().unwrap_or("");
enum Kind {
IntCount,
Bool,
Str,
IntTokens,
Unsupported,
}
let (expr, kind) = match field {
"chunks" => ("chunks_count", Kind::IntCount),
"stream_content" => ("stream_content", Kind::Str),
"stream_complete" => ("stream_complete", Kind::Bool),
"no_chunks_after_done" => ("no_chunks_after_done", Kind::Bool),
"finish_reason" => ("finish_reason", Kind::Str),
"tool_calls" => ("tool_calls_json", Kind::Str),
"tool_calls[0].function.name" => ("tool_calls_0_function_name", Kind::Str),
"usage.total_tokens" => ("total_tokens", Kind::IntTokens),
_ => ("", Kind::Unsupported),
};
let atype = assertion.assertion_type.as_str();
if atype == "not_error" || atype == "error" {
return;
}
if matches!(kind, Kind::Unsupported) {
let _ = writeln!(
out,
" /* skipped: streaming assertion on unsupported field '{field}' */"
);
return;
}
match (atype, &kind) {
("count_min", Kind::IntCount) => {
if let Some(n) = assertion.value.as_ref().and_then(|v| v.as_u64()) {
let _ = writeln!(out, " assert({expr} >= {n} && \"expected at least {n} chunks\");");
}
}
("equals", Kind::Str) => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert({expr} != NULL && str_trim_eq({expr}, {c_val}) == 0 && \"streaming equals assertion failed\");"
);
}
}
("contains", Kind::Str) => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert({expr} != NULL && strstr({expr}, {c_val}) != NULL && \"streaming contains assertion failed\");"
);
}
}
("not_empty", Kind::Str) => {
let _ = writeln!(
out,
" assert({expr} != NULL && strlen({expr}) > 0 && \"expected non-empty {field}\");"
);
}
("is_true", Kind::Bool) => {
let _ = writeln!(out, " assert({expr} && \"expected {field} to be true\");");
}
("is_false", Kind::Bool) => {
let _ = writeln!(out, " assert(!{expr} && \"expected {field} to be false\");");
}
("greater_than_or_equal", Kind::IntCount) | ("greater_than_or_equal", Kind::IntTokens) => {
if let Some(n) = assertion.value.as_ref().and_then(|v| v.as_u64()) {
let _ = writeln!(out, " assert({expr} >= {n} && \"expected {expr} >= {n}\");");
}
}
("equals", Kind::IntCount) | ("equals", Kind::IntTokens) => {
if let Some(n) = assertion.value.as_ref().and_then(|v| v.as_u64()) {
let _ = writeln!(out, " assert({expr} == {n} && \"equals assertion failed\");");
}
}
_ => {
let _ = writeln!(
out,
" /* skipped: streaming assertion '{atype}' on field '{field}' not supported */"
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn emit_nested_accessor(
out: &mut String,
prefix: &str,
resolved: &str,
local_var: &str,
result_var: &str,
fields_c_types: &HashMap<String, String>,
intermediate_handles: &mut Vec<(String, String)>,
result_type_name: &str,
) -> Option<String> {
let segments: Vec<&str> = resolved.split('.').collect();
let prefix_upper = prefix.to_uppercase();
let mut current_snake_type = result_type_name.to_snake_case();
let mut current_handle = result_var.to_string();
for (i, segment) in segments.iter().enumerate() {
let is_leaf = i + 1 == segments.len();
if let Some(bracket_pos) = segment.find('[') {
let field_name = &segment[..bracket_pos];
let key = segment[bracket_pos + 1..].trim_end_matches(']');
let field_snake = field_name.to_snake_case();
let accessor_fn = format!("{prefix}_{current_snake_type}_{field_snake}");
let json_var = format!("{field_snake}_json");
if !intermediate_handles.iter().any(|(h, _)| h == &json_var) {
let _ = writeln!(out, " char* {json_var} = {accessor_fn}({current_handle});");
let _ = writeln!(out, " assert({json_var} != NULL);");
intermediate_handles.push((json_var.clone(), "free_string".to_string()));
}
let _ = writeln!(
out,
" char* {local_var} = alef_json_get_string({json_var}, \"{key}\");"
);
return None; }
let seg_snake = segment.to_snake_case();
let accessor_fn = format!("{prefix}_{current_snake_type}_{seg_snake}");
if is_leaf {
let lookup_key = format!("{current_snake_type}.{seg_snake}");
if let Some(t) = fields_c_types.get(&lookup_key).filter(|t| is_primitive_c_type(t)) {
let _ = writeln!(out, " {t} {local_var} = {accessor_fn}({current_handle});");
return Some(t.clone());
}
let _ = writeln!(out, " char* {local_var} = {accessor_fn}({current_handle});");
} else {
let lookup_key = format!("{current_snake_type}.{seg_snake}");
let return_type_pascal = match fields_c_types.get(&lookup_key) {
Some(t) => t.clone(),
None => {
segment.to_pascal_case()
}
};
let return_snake = return_type_pascal.to_snake_case();
let handle_var = format!("{seg_snake}_handle");
if !intermediate_handles.iter().any(|(h, _)| h == &handle_var) {
let _ = writeln!(
out,
" {prefix_upper}{return_type_pascal}* {handle_var} = \
{accessor_fn}({current_handle});"
);
let _ = writeln!(out, " assert({handle_var} != NULL);");
intermediate_handles.push((handle_var.clone(), return_snake.clone()));
}
current_snake_type = return_snake;
current_handle = handle_var;
}
}
None
}
fn build_args_string_c(
input: &serde_json::Value,
args: &[crate::config::ArgMapping],
has_options_handle: bool,
) -> String {
if args.is_empty() {
return json_to_c(input);
}
let parts: Vec<String> = args
.iter()
.filter_map(|arg| {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let val = input.get(field);
match val {
None if arg.optional => Some("NULL".to_string()),
None => None,
Some(v) if v.is_null() && arg.optional => Some("NULL".to_string()),
Some(v) => {
if arg.arg_type == "json_object" && has_options_handle && !v.is_null() {
Some("options_handle".to_string())
} else {
Some(json_to_c(v))
}
}
}
})
.collect();
parts.join(", ")
}
fn render_assertion(
out: &mut String,
assertion: &Assertion,
result_var: &str,
ffi_prefix: &str,
_field_resolver: &FieldResolver,
accessed_fields: &[(String, String, bool)],
primitive_locals: &HashMap<String, String>,
) {
if let Some(f) = &assertion.field {
if !f.is_empty() && !_field_resolver.is_valid_for_result(f) {
let _ = writeln!(out, " // skipped: field '{f}' not available on result type");
return;
}
}
let field_expr = match &assertion.field {
Some(f) if !f.is_empty() => {
accessed_fields
.iter()
.find(|(k, _, _)| k == f)
.map(|(_, local, _)| local.clone())
.unwrap_or_else(|| result_var.to_string())
}
_ => result_var.to_string(),
};
let field_is_primitive = primitive_locals.contains_key(&field_expr);
let field_primitive_type = primitive_locals.get(&field_expr).cloned();
let field_is_map_access = if let Some(f) = &assertion.field {
accessed_fields.iter().any(|(k, _, m)| k == f && *m)
} else {
false
};
match assertion.assertion_type.as_str() {
"equals" => {
if let Some(expected) = &assertion.value {
let c_val = json_to_c(expected);
if field_is_primitive {
let cmp_val = if field_primitive_type.as_deref() == Some("bool") {
match expected.as_bool() {
Some(true) => "1".to_string(),
Some(false) => "0".to_string(),
None => c_val,
}
} else {
c_val
};
let _ = writeln!(
out,
" assert({field_expr} == {cmp_val} && \"equals assertion failed\");"
);
} else if expected.is_string() {
let _ = writeln!(
out,
" assert(str_trim_eq({field_expr}, {c_val}) == 0 && \"equals assertion failed\");"
);
} else if field_is_map_access && expected.is_boolean() {
let lit = match expected.as_bool() {
Some(true) => "\"true\"",
_ => "\"false\"",
};
let _ = writeln!(
out,
" assert({field_expr} != NULL && strcmp({field_expr}, {lit}) == 0 && \"equals assertion failed\");"
);
} else if field_is_map_access && expected.is_number() {
if expected.is_f64() {
let _ = writeln!(
out,
" assert({field_expr} != NULL && atof({field_expr}) == {c_val} && \"equals assertion failed\");"
);
} else {
let _ = writeln!(
out,
" assert({field_expr} != NULL && atoll({field_expr}) == {c_val} && \"equals assertion failed\");"
);
}
} else {
let _ = writeln!(
out,
" assert(strcmp({field_expr}, {c_val}) == 0 && \"equals assertion failed\");"
);
}
}
}
"contains" => {
if let Some(expected) = &assertion.value {
let c_val = json_to_c(expected);
let _ = writeln!(
out,
" assert(strstr({field_expr}, {c_val}) != NULL && \"expected to contain substring\");"
);
}
}
"contains_all" => {
if let Some(values) = &assertion.values {
for val in values {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert(strstr({field_expr}, {c_val}) != NULL && \"expected to contain substring\");"
);
}
}
}
"not_contains" => {
if let Some(expected) = &assertion.value {
let c_val = json_to_c(expected);
let _ = writeln!(
out,
" assert(strstr({field_expr}, {c_val}) == NULL && \"expected NOT to contain substring\");"
);
}
}
"not_empty" => {
let _ = writeln!(
out,
" assert({field_expr} != NULL && strlen({field_expr}) > 0 && \"expected non-empty value\");"
);
}
"is_empty" => {
let _ = writeln!(
out,
" assert(strlen({field_expr}) == 0 && \"expected empty value\");"
);
}
"contains_any" => {
if let Some(values) = &assertion.values {
let _ = writeln!(out, " {{");
let _ = writeln!(out, " int found = 0;");
for val in values {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" if (strstr({field_expr}, {c_val}) != NULL) {{ found = 1; }}"
);
}
let _ = writeln!(
out,
" assert(found && \"expected to contain at least one of the specified values\");"
);
let _ = writeln!(out, " }}");
}
}
"greater_than" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
if field_is_map_access && val.is_number() && !field_is_primitive {
let _ = writeln!(
out,
" assert({field_expr} != NULL && atof({field_expr}) > {c_val} && \"expected greater than\");"
);
} else {
let _ = writeln!(out, " assert({field_expr} > {c_val} && \"expected greater than\");");
}
}
}
"less_than" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
if field_is_map_access && val.is_number() && !field_is_primitive {
let _ = writeln!(
out,
" assert({field_expr} != NULL && atof({field_expr}) < {c_val} && \"expected less than\");"
);
} else {
let _ = writeln!(out, " assert({field_expr} < {c_val} && \"expected less than\");");
}
}
}
"greater_than_or_equal" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
if field_is_map_access && val.is_number() && !field_is_primitive {
let _ = writeln!(
out,
" assert({field_expr} != NULL && atof({field_expr}) >= {c_val} && \"expected greater than or equal\");"
);
} else {
let _ = writeln!(
out,
" assert({field_expr} >= {c_val} && \"expected greater than or equal\");"
);
}
}
}
"less_than_or_equal" => {
if let Some(val) = &assertion.value {
let c_val = json_to_c(val);
if field_is_map_access && val.is_number() && !field_is_primitive {
let _ = writeln!(
out,
" assert({field_expr} != NULL && atof({field_expr}) <= {c_val} && \"expected less than or equal\");"
);
} else {
let _ = writeln!(
out,
" assert({field_expr} <= {c_val} && \"expected less than or equal\");"
);
}
}
}
"starts_with" => {
if let Some(expected) = &assertion.value {
let c_val = json_to_c(expected);
let _ = writeln!(
out,
" assert(strncmp({field_expr}, {c_val}, strlen({c_val})) == 0 && \"expected to start with\");"
);
}
}
"ends_with" => {
if let Some(expected) = &assertion.value {
let c_val = json_to_c(expected);
let _ = writeln!(out, " assert(strlen({field_expr}) >= strlen({c_val}) && ");
let _ = writeln!(
out,
" strcmp({field_expr} + strlen({field_expr}) - strlen({c_val}), {c_val}) == 0 && \"expected to end with\");"
);
}
}
"min_length" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let _ = writeln!(
out,
" assert(strlen({field_expr}) >= {n} && \"expected minimum length\");"
);
}
}
}
"max_length" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let _ = writeln!(
out,
" assert(strlen({field_expr}) <= {n} && \"expected maximum length\");"
);
}
}
}
"count_min" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let _ = writeln!(out, " {{");
let _ = writeln!(out, " /* count_min: count top-level JSON array elements */");
let _ = writeln!(
out,
" assert({field_expr} != NULL && \"expected non-null collection JSON\");"
);
let _ = writeln!(out, " int elem_count = alef_json_array_count({field_expr});");
let _ = writeln!(
out,
" assert(elem_count >= {n} && \"expected at least {n} elements\");"
);
let _ = writeln!(out, " }}");
}
}
}
"count_equals" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let _ = writeln!(out, " {{");
let _ = writeln!(out, " /* count_equals: count elements in array */");
let _ = writeln!(
out,
" assert({field_expr} != NULL && \"expected non-null collection JSON\");"
);
let _ = writeln!(out, " int elem_count = alef_json_array_count({field_expr});");
let _ = writeln!(out, " assert(elem_count == {n} && \"expected {n} elements\");");
let _ = writeln!(out, " }}");
}
}
}
"is_true" => {
let _ = writeln!(out, " assert({field_expr});");
}
"is_false" => {
let _ = writeln!(out, " assert(!{field_expr});");
}
"method_result" => {
if let Some(method_name) = &assertion.method {
render_method_result_assertion(
out,
result_var,
ffi_prefix,
method_name,
assertion.args.as_ref(),
assertion.return_type.as_deref(),
assertion.check.as_deref().unwrap_or("is_true"),
assertion.value.as_ref(),
);
} else {
panic!("C e2e generator: method_result assertion missing 'method' field");
}
}
"matches_regex" => {
if let Some(expected) = &assertion.value {
let c_val = json_to_c(expected);
let _ = writeln!(out, " {{");
let _ = writeln!(out, " regex_t _re;");
let _ = writeln!(
out,
" assert(regcomp(&_re, {c_val}, REG_EXTENDED) == 0 && \"regex compile failed\");"
);
let _ = writeln!(
out,
" assert(regexec(&_re, {field_expr}, 0, NULL, 0) == 0 && \"expected value to match regex\");"
);
let _ = writeln!(out, " regfree(&_re);");
let _ = writeln!(out, " }}");
}
}
"not_error" => {
}
"error" => {
}
other => {
panic!("C e2e generator: unsupported assertion type: {other}");
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_method_result_assertion(
out: &mut String,
result_var: &str,
ffi_prefix: &str,
method_name: &str,
args: Option<&serde_json::Value>,
return_type: Option<&str>,
check: &str,
value: Option<&serde_json::Value>,
) {
let call_expr = build_c_method_call(result_var, ffi_prefix, method_name, args);
if return_type == Some("string") {
let _ = writeln!(out, " {{");
let _ = writeln!(out, " char* _method_result = {call_expr};");
if check == "is_error" {
let _ = writeln!(
out,
" assert(_method_result == NULL && \"expected method to return error\");"
);
let _ = writeln!(out, " }}");
return;
}
let _ = writeln!(
out,
" assert(_method_result != NULL && \"method_result returned NULL\");"
);
match check {
"contains" => {
if let Some(val) = value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert(strstr(_method_result, {c_val}) != NULL && \"method_result contains assertion failed\");"
);
}
}
"equals" => {
if let Some(val) = value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert(str_trim_eq(_method_result, {c_val}) == 0 && \"method_result equals assertion failed\");"
);
}
}
"is_true" => {
let _ = writeln!(
out,
" assert(_method_result != NULL && strlen(_method_result) > 0 && \"method_result is_true assertion failed\");"
);
}
"count_min" => {
if let Some(val) = value {
let n = val.as_u64().unwrap_or(0);
let _ = writeln!(out, " int _elem_count = alef_json_array_count(_method_result);");
let _ = writeln!(
out,
" assert(_elem_count >= {n} && \"method_result count_min assertion failed\");"
);
}
}
other_check => {
panic!("C e2e generator: unsupported method_result check type for string return: {other_check}");
}
}
let _ = writeln!(out, " free(_method_result);");
let _ = writeln!(out, " }}");
return;
}
match check {
"equals" => {
if let Some(val) = value {
let c_val = json_to_c(val);
let _ = writeln!(
out,
" assert({call_expr} == {c_val} && \"method_result equals assertion failed\");"
);
}
}
"is_true" => {
let _ = writeln!(
out,
" assert({call_expr} && \"method_result is_true assertion failed\");"
);
}
"is_false" => {
let _ = writeln!(
out,
" assert(!{call_expr} && \"method_result is_false assertion failed\");"
);
}
"greater_than_or_equal" => {
if let Some(val) = value {
let n = val.as_u64().unwrap_or(0);
let _ = writeln!(
out,
" assert({call_expr} >= {n} && \"method_result >= {n} assertion failed\");"
);
}
}
"count_min" => {
if let Some(val) = value {
let n = val.as_u64().unwrap_or(0);
let _ = writeln!(
out,
" assert({call_expr} >= {n} && \"method_result count_min assertion failed\");"
);
}
}
other_check => {
panic!("C e2e generator: unsupported method_result check type: {other_check}");
}
}
}
fn build_c_method_call(
result_var: &str,
ffi_prefix: &str,
method_name: &str,
args: Option<&serde_json::Value>,
) -> String {
let extra_args = if let Some(args_val) = args {
args_val
.as_object()
.map(|obj| {
obj.values()
.map(|v| match v {
serde_json::Value::String(s) => format!("\"{}\"", escape_c(s)),
serde_json::Value::Bool(true) => "1".to_string(),
serde_json::Value::Bool(false) => "0".to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Null => "NULL".to_string(),
other => format!("\"{}\"", escape_c(&other.to_string())),
})
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_default()
} else {
String::new()
};
if extra_args.is_empty() {
format!("{ffi_prefix}_{method_name}({result_var})")
} else {
format!("{ffi_prefix}_{method_name}({result_var}, {extra_args})")
}
}
fn json_to_c(value: &serde_json::Value) -> String {
match value {
serde_json::Value::String(s) => format!("\"{}\"", escape_c(s)),
serde_json::Value::Bool(true) => "1".to_string(),
serde_json::Value::Bool(false) => "0".to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Null => "NULL".to_string(),
other => format!("\"{}\"", escape_c(&other.to_string())),
}
}