use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{FunctionDef, ParamDef, TypeRef};
use crate::e2e::codegen::c::render_c_snippet;
use crate::e2e::codegen::c::snippet_regressions::compile_snippet;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::Fixture;
fn json_arg(name: &str, field: &str) -> crate::e2e::config::ArgMapping {
crate::e2e::config::ArgMapping {
name: name.into(),
field: field.into(),
arg_type: "json_object".into(),
optional: false,
owned: true,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}
}
fn c_override() -> crate::core::config::e2e::CallOverride {
crate::core::config::e2e::CallOverride {
header: Some("sample_ffi.h".into()),
..Default::default()
}
}
fn sample_config() -> ResolvedCrateConfig {
ResolvedCrateConfig {
name: "sample".into(),
..ResolvedCrateConfig::default()
}
}
#[test]
fn a_batch_input_declared_const_char_is_passed_as_the_json_string_not_a_handle() {
let fixture = Fixture {
id: "process_batch".into(),
description: "Process a batch of items".into(),
input: serde_json::json!({"items": [{"text": "a"}, {"text": "b"}]}),
..Fixture::default()
};
let mut e2e = E2eConfig::default();
e2e.call.function = "process_batch".into();
e2e.call.args = vec![json_arg("items", "input.items")];
e2e.call.overrides.insert("c".into(), c_override());
let functions = [FunctionDef {
name: "process_batch".into(),
params: vec![ParamDef {
name: "items".into(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("ItemInput".into()))),
..ParamDef::default()
}],
return_type: TypeRef::Named("BatchReport".into()),
..FunctionDef::default()
}];
let rendered = render_c_snippet(&fixture, &e2e, &sample_config(), &[], &functions).expect("batch snippet renders");
assert!(
rendered.contains(r#"sample_process_batch("[{\"text\":\"a\"},{\"text\":\"b\"}]")"#),
"the batch input must reach the call as the serialized JSON string:\n{rendered}"
);
assert!(
!rendered.contains("items_handle"),
"a `const char *` batch parameter must not be wrapped in a handle:\n{rendered}"
);
assert!(
!rendered.contains("sample_item_input_from_json"),
"the element type's `from_json` constructor must not be called for a batch parameter:\n{rendered}"
);
compile_snippet(
&rendered,
"sample_ffi.h",
concat!(
"#include <stdint.h>\n",
"typedef uint64_t SAMPLEAlefHandle;\n",
"SAMPLEAlefHandle sample_process_batch(const char *items);\n",
"void sample_batch_report_free(SAMPLEAlefHandle result);\n",
),
);
}
#[test]
fn a_json_object_arg_declared_a_named_type_still_constructs_a_handle() {
let fixture = Fixture {
id: "convert_source".into(),
description: "Convert a source".into(),
input: serde_json::json!({"source": {"text": "a"}}),
..Fixture::default()
};
let mut e2e = E2eConfig::default();
e2e.call.function = "convert".into();
e2e.call.args = vec![json_arg("source", "input.source")];
e2e.call.overrides.insert("c".into(), c_override());
let functions = [FunctionDef {
name: "convert".into(),
params: vec![ParamDef {
name: "source".into(),
ty: TypeRef::Named("SourceInput".into()),
..ParamDef::default()
}],
return_type: TypeRef::Named("ConvertReport".into()),
..FunctionDef::default()
}];
let rendered =
render_c_snippet(&fixture, &e2e, &sample_config(), &[], &functions).expect("named-arg snippet renders");
assert!(
rendered.contains(r#"SAMPLEAlefHandle source_handle = sample_source_input_from_json("{\"text\":\"a\"}")"#),
"a handle-typed parameter must still have its handle constructed:\n{rendered}"
);
assert!(
rendered.contains("sample_convert(source_handle)"),
"the constructed handle must be what reaches the call:\n{rendered}"
);
assert!(
rendered.contains("sample_source_input_free(source_handle)"),
"the constructed handle must still be freed:\n{rendered}"
);
compile_snippet(
&rendered,
"sample_ffi.h",
concat!(
"#include <stdint.h>\n",
"typedef uint64_t SAMPLEAlefHandle;\n",
"SAMPLEAlefHandle sample_source_input_from_json(const char *json);\n",
"void sample_source_input_free(SAMPLEAlefHandle value);\n",
"SAMPLEAlefHandle sample_convert(SAMPLEAlefHandle source);\n",
"void sample_convert_report_free(SAMPLEAlefHandle result);\n",
),
);
}
#[test]
fn a_free_form_json_parameter_is_passed_as_the_json_string() {
let fixture = Fixture {
id: "annotate".into(),
description: "Annotate a payload".into(),
input: serde_json::json!({"payload": {"text": "a"}}),
..Fixture::default()
};
let mut e2e = E2eConfig::default();
e2e.call.function = "annotate".into();
e2e.call.args = vec![json_arg("payload", "input.payload")];
e2e.call.overrides.insert("c".into(), c_override());
let functions = [FunctionDef {
name: "annotate".into(),
params: vec![ParamDef {
name: "payload".into(),
ty: TypeRef::Json,
..ParamDef::default()
}],
return_type: TypeRef::Named("AnnotationReport".into()),
..FunctionDef::default()
}];
let rendered =
render_c_snippet(&fixture, &e2e, &sample_config(), &[], &functions).expect("json-param snippet renders");
assert!(
rendered.contains(r#"sample_annotate("{\"text\":\"a\"}")"#),
"a `serde_json::Value` parameter must receive the serialized JSON string:\n{rendered}"
);
assert!(
!rendered.contains("payload_handle"),
"a `const char *` JSON parameter must not be wrapped in a handle:\n{rendered}"
);
compile_snippet(
&rendered,
"sample_ffi.h",
concat!(
"#include <stdint.h>\n",
"typedef uint64_t SAMPLEAlefHandle;\n",
"SAMPLEAlefHandle sample_annotate(const char *payload);\n",
"void sample_annotation_report_free(SAMPLEAlefHandle result);\n",
),
);
}