use minijinja::Environment;
static TEMPLATES: &[(&str, &str)] = &[
("lib_header.rs.jinja", include_str!("templates/lib_header.rs.jinja")),
(
"cfg_attribute.rs.jinja",
include_str!("templates/cfg_attribute.rs.jinja"),
),
(
"runtime_helpers.rs.jinja",
include_str!("templates/runtime_helpers.rs.jinja"),
),
(
"trait_bridge_constructor.rs.jinja",
include_str!("templates/trait_bridge_constructor.rs.jinja"),
),
(
"trait_bridge_method_body.rs.jinja",
include_str!("templates/trait_bridge_method_body.rs.jinja"),
),
(
"trait_bridge_register_shim.rs.jinja",
include_str!("templates/trait_bridge_register_shim.rs.jinja"),
),
(
"trait_unregister_shim.rs.jinja",
include_str!("templates/trait_unregister_shim.rs.jinja"),
),
(
"trait_clear_shim.rs.jinja",
include_str!("templates/trait_clear_shim.rs.jinja"),
),
(
"function_shim_open.rs.jinja",
include_str!("templates/function_shim_open.rs.jinja"),
),
(
"call_result_body.rs.jinja",
include_str!("templates/call_result_body.rs.jinja"),
),
(
"method_shim_open.rs.jinja",
include_str!("templates/method_shim_open.rs.jinja"),
),
(
"method_client_handle.rs.jinja",
include_str!("templates/method_client_handle.rs.jinja"),
),
(
"value_method_shim_open.rs.jinja",
include_str!("templates/value_method_shim_open.rs.jinja"),
),
(
"value_method_receiver.rs.jinja",
include_str!("templates/value_method_receiver.rs.jinja"),
),
(
"streaming_shims.rs.jinja",
include_str!("templates/streaming_shims.rs.jinja"),
),
(
"constructor_shim.rs.jinja",
include_str!("templates/constructor_shim.rs.jinja"),
),
(
"destructor_shim.rs.jinja",
include_str!("templates/destructor_shim.rs.jinja"),
),
(
"service_header.rs.jinja",
include_str!("templates/service_header.rs.jinja"),
),
(
"service_opaque.rs.jinja",
include_str!("templates/service_opaque.rs.jinja"),
),
(
"handler_bridge_struct.rs.jinja",
include_str!("templates/handler_bridge_struct.rs.jinja"),
),
(
"handler_bridge_impl.rs.jinja",
include_str!("templates/handler_bridge_impl.rs.jinja"),
),
(
"registration_variant.rs.jinja",
include_str!("templates/registration_variant.rs.jinja"),
),
(
"registration_function.rs.jinja",
include_str!("templates/registration_function.rs.jinja"),
),
(
"entrypoint_run.rs.jinja",
include_str!("templates/entrypoint_run.rs.jinja"),
),
(
"entrypoint_finalize.rs.jinja",
include_str!("templates/entrypoint_finalize.rs.jinja"),
),
("param_decl.rs.jinja", include_str!("templates/param_decl.rs.jinja")),
(
"service_param_decl.rs.jinja",
include_str!("templates/service_param_decl.rs.jinja"),
),
(
"string_unmarshal.rs.jinja",
include_str!("templates/string_unmarshal.rs.jinja"),
),
(
"byte_array_unmarshal.rs.jinja",
include_str!("templates/byte_array_unmarshal.rs.jinja"),
),
(
"base64_bytes_unmarshal.rs.jinja",
include_str!("templates/base64_bytes_unmarshal.rs.jinja"),
),
(
"opaque_handle_unmarshal.rs.jinja",
include_str!("templates/opaque_handle_unmarshal.rs.jinja"),
),
(
"complex_unmarshal.rs.jinja",
include_str!("templates/complex_unmarshal.rs.jinja"),
),
(
"request_string_unmarshal.rs.jinja",
include_str!("templates/request_string_unmarshal.rs.jinja"),
),
(
"request_map_unmarshal.rs.jinja",
include_str!("templates/request_map_unmarshal.rs.jinja"),
),
(
"request_map_param_unmarshal.rs.jinja",
include_str!("templates/request_map_param_unmarshal.rs.jinja"),
),
(
"vec_string_refs.rs.jinja",
include_str!("templates/vec_string_refs.rs.jinja"),
),
(
"path_unmarshal.rs.jinja",
include_str!("templates/path_unmarshal.rs.jinja"),
),
(
"vec_string_unmarshal.rs.jinja",
include_str!("templates/vec_string_unmarshal.rs.jinja"),
),
(
"request_string_value_unmarshal.rs.jinja",
include_str!("templates/request_string_value_unmarshal.rs.jinja"),
),
(
"json_value_unmarshal.rs.jinja",
include_str!("templates/json_value_unmarshal.rs.jinja"),
),
(
"wrapper_setup.rs.jinja",
include_str!("templates/wrapper_setup.rs.jinja"),
),
(
"stream_request_unmarshal.rs.jinja",
include_str!("templates/stream_request_unmarshal.rs.jinja"),
),
(
"stream_call_block.rs.jinja",
include_str!("templates/stream_call_block.rs.jinja"),
),
("return_bool.rs.jinja", include_str!("templates/return_bool.rs.jinja")),
(
"return_byte_array.rs.jinja",
include_str!("templates/return_byte_array.rs.jinja"),
),
(
"return_optional_byte_array.rs.jinja",
include_str!("templates/return_optional_byte_array.rs.jinja"),
),
(
"return_primitive.rs.jinja",
include_str!("templates/return_primitive.rs.jinja"),
),
(
"return_string.rs.jinja",
include_str!("templates/return_string.rs.jinja"),
),
(
"return_optional_string.rs.jinja",
include_str!("templates/return_optional_string.rs.jinja"),
),
(
"method_capsule_return.rs.jinja",
include_str!("templates/method_capsule_return.rs.jinja"),
),
("return_json.rs.jinja", include_str!("templates/return_json.rs.jinja")),
];
pub(crate) fn render(name: &str, context: minijinja::Value) -> String {
let env = make_env();
let rendered = env
.get_template(name)
.unwrap_or_else(|err| panic!("missing JNI template {name}: {err}"))
.render(context)
.unwrap_or_else(|err| panic!("failed to render JNI template {name}: {err}"));
crate::core::keep_marker::strip_keep_markers(&rendered)
}
fn make_env() -> Environment<'static> {
let mut env = Environment::new();
env.set_trim_blocks(true);
env.set_lstrip_blocks(true);
env.set_keep_trailing_newline(true);
for (name, source) in TEMPLATES {
env.add_template(name, source)
.unwrap_or_else(|err| panic!("failed to register JNI template {name}: {err}"));
}
env
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn destructor_template_uses_jni_panic_boundary() {
let output = render(
"destructor_shim.rs.jinja",
minijinja::context! { symbol => "Java_dev_sample_free", type_name => "Sample" },
);
assert!(output.contains("run_or_throw(env, |_env|"));
}
#[test]
fn crate_headers_contain_implementation_only_unsafe_lints() {
let header = render(
"lib_header.rs.jinja",
minijinja::context! { core_crate => "sample", error_class => "SampleError", crate_attributes => Vec::<String>::new() },
);
let service = render("service_header.rs.jinja", minijinja::context! {});
for output in [header, service] {
assert!(output.contains("unsafe_op_in_unsafe_fn"), "{output}");
assert!(output.contains("unsafe_attr_outside_unsafe"), "{output}");
}
}
#[test]
fn every_template_file_is_registered() {
let templates_dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/backends/jni/templates"));
let registered_contents: std::collections::HashSet<&str> =
TEMPLATES.iter().map(|(_, content)| *content).collect();
let mut unregistered = Vec::new();
collect_unregistered(templates_dir, templates_dir, ®istered_contents, &mut unregistered);
unregistered.sort();
assert!(
unregistered.is_empty(),
"found .jinja file(s) in templates/ whose content is not registered in TEMPLATES: {unregistered:?}"
);
}
fn collect_unregistered(
root: &std::path::Path,
dir: &std::path::Path,
registered_contents: &std::collections::HashSet<&str>,
unregistered: &mut Vec<String>,
) {
for entry in std::fs::read_dir(dir).expect("read templates directory") {
let entry = entry.expect("read templates directory entry");
let path = entry.path();
if path.is_dir() {
collect_unregistered(root, &path, registered_contents, unregistered);
continue;
}
if path.extension().and_then(|ext| ext.to_str()) != Some("jinja") {
continue;
}
let content = std::fs::read_to_string(&path).expect("read template file");
if !registered_contents.contains(content.as_str()) {
let relative = path
.strip_prefix(root)
.expect("template path under templates root")
.to_str()
.expect("template path is valid UTF-8")
.replace('\\', "/");
unregistered.push(relative);
}
}
}
}