use super::*;
impl NapiBridgeGenerator {
pub(super) fn gen_sync_on_js_thread_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
let js_method_name = to_camel_case(&method.name);
let snake_method_name = method.name.clone();
let has_error = method.error_type.is_some();
let js_args_exprs = build_napi_args(
method,
spec.bridge_config,
&self.struct_param_types,
&self.type_prefix,
&spec.trait_def.name,
&self.unsupported_args,
&self.json_safe_named_types(),
);
let (args_tuple_ty, empty_args, tuple_args) = args_tuple_pieces(&js_args_exprs);
let error_lookup =
spec.make_error("format!(\"Method '{}' not found on bridge object: {}\", self.cached_name, e)");
let error_call = spec.make_error(&format!(
"format!(\"Plugin '{{}}' method '{}' failed: {{}}\", self.cached_name, e)",
method.name
));
let has_default_impl = method.has_default_impl;
if matches!(method.return_type, TypeRef::Unit) {
crate::backends::napi::template_env::render(
"sync_method_unit_return.jinja",
crate::alef_context! {
wrapper => spec.wrapper_name(),
method_name => &js_method_name,
snake_case_method_name => &snake_method_name,
args_tuple_ty => args_tuple_ty,
has_error => has_error,
has_default_impl => has_default_impl,
empty_args => empty_args,
tuple_args => tuple_args,
error_lookup => error_lookup,
error_call => error_call,
},
)
} else {
self.gen_sync_on_js_thread_non_unit_body(
method,
spec,
&SyncOnJsThreadArgs {
js_method_name: &js_method_name,
snake_method_name: &snake_method_name,
has_error,
has_default_impl,
args_tuple_ty: &args_tuple_ty,
empty_args,
tuple_args: &tuple_args,
error_lookup: &error_lookup,
error_call: &error_call,
},
)
}
}
fn gen_sync_on_js_thread_non_unit_body(
&self,
method: &MethodDef,
spec: &TraitBridgeSpec,
args: &SyncOnJsThreadArgs<'_>,
) -> String {
let SyncOnJsThreadArgs {
js_method_name,
snake_method_name,
has_error,
has_default_impl,
args_tuple_ty,
empty_args,
tuple_args,
error_lookup,
error_call,
} = *args;
let decode = self.plan_return_decode(&method.return_type);
let reply_ty = decode.reply_ty().to_string();
let is_identity = matches!(decode, ReturnDecode::Native { .. });
let error_settle = spec.make_error(&format!(
"format!(\"Plugin '{{}}' method '{}' rejected: {{}}\", self.cached_name, e)",
method.name
));
let error_parse = spec.make_error(&format!(
"format!(\"Plugin '{{}}' failed to parse return value for method '{}': {{}}\", self.cached_name, e)",
method.name
));
let missing_arm = |infallible_warn_step: &str| -> String {
if has_default_impl {
let default_expr = if has_error {
"Ok(Default::default())"
} else {
"Default::default()"
};
format!("Err(_) => return {default_expr},")
} else if has_error {
format!("Err(e) => return Err({error_lookup}),")
} else {
format!(
"Err(e) => {{\n \
tracing::warn!(wrapper = \"{}\", method = \"{}\", error = %e, \"{infallible_warn_step}; returning default\");\n \
return Default::default();\n\
}}",
spec.wrapper_name(),
method.name,
)
}
};
let obj_missing_arm = missing_arm("bridge object unavailable");
let prop_missing_arm = missing_arm("method not found on bridge object");
let call_expr = if empty_args {
"func.call(())".to_string()
} else {
format!("func.call(napi::bindgen_prelude::FnArgs::from({tuple_args}))")
};
let convert_fail_stmt = if has_error {
format!("return Err({error_parse});")
} else {
format!(
"tracing::warn!(wrapper = \"{}\", method = \"{}\", error = %e, \"host returned an unparseable value; returning default\");\n return Default::default();",
spec.wrapper_name(),
method.name,
)
};
let convert = if is_identity {
String::new()
} else {
decode.convert_stmt(&convert_fail_stmt)
};
let result_var = if is_identity { "__decoded" } else { "__result" };
let tail = if has_error {
format!("Ok({result_var})")
} else {
result_var.to_string()
};
let call_err_arm = if has_error {
format!("Err(e) => return Err({error_call}),")
} else {
format!(
"Err(e) => {{\n \
tracing::warn!(wrapper = \"{wrapper}\", method = \"{method_name}\", error = %e, \"host callback threw; returning default\");\n \
return Default::default();\n\
}}",
wrapper = spec.wrapper_name(),
method_name = method.name,
)
};
let settle_err_arm = if has_error {
format!("Err(e) => return Err({error_settle}),")
} else {
format!(
"Err(e) => {{\n \
tracing::warn!(wrapper = \"{wrapper}\", method = \"{method_name}\", error = %e, \"host callback rejected; returning default\");\n \
return Default::default();\n\
}}",
wrapper = spec.wrapper_name(),
method_name = method.name,
)
};
format!(
"let __env = self.env();\n\
let __obj = match self.obj(&__env) {{\n Ok(o) => o,\n {obj_missing_arm}\n}};\n\
let func: napi::bindgen_prelude::Function<{args_tuple_ty}, AlefJsReply<{reply_ty}>> = \
match __obj.get_named_property(\"{js_method_name}\").or_else(|_| __obj.get_named_property(\"{snake_method_name}\")) {{\n \
Ok(f) => f,\n {prop_missing_arm}\n}};\n\
let __reply: AlefJsReply<{reply_ty}> = match {call_expr} {{\n Ok(v) => v,\n {call_err_arm}\n}};\n\
let __decoded: {reply_ty} = match __reply.settle_sync(&self.cached_name, \"{method_name}\") {{\n \
Ok(v) => v,\n {settle_err_arm}\n}};\n\
{convert}\n\
{tail}",
method_name = method.name,
)
}
pub(super) fn gen_sync_slow_path_body(&self, method: &MethodDef, spec: &TraitBridgeSpec) -> String {
let tsfn_field = Self::tsfn_field_name(method);
let has_error = method.error_type.is_some();
let has_default_impl = method.has_default_impl && self.is_forwardable(method);
let payload_expr = self.owned_payload_expr(method);
let infallible_default = "Default::default()";
let missing_tsfn_arm = if has_default_impl {
format!("let __tsfn = self.{tsfn_field}.as_ref().expect(\"presence-checked by the default-method guard\");")
} else {
format!("let __tsfn = &self.{tsfn_field};")
};
let decode = if matches!(method.return_type, TypeRef::Unit) {
None
} else {
Some(self.plan_return_decode(&method.return_type))
};
let reply_ty = decode
.as_ref()
.map(|d| d.reply_ty().to_string())
.unwrap_or_else(|| "()".to_string());
let error_parse = spec.make_error(&format!(
"format!(\"Plugin '{{}}' failed to parse return value for method '{}': {{}}\", self.cached_name, e)",
method.name
));
let convert_fail_stmt = if has_error {
format!("return Err({error_parse});")
} else {
format!(
"tracing::warn!(wrapper = \"{}\", method = \"{}\", error = %e, \"host returned an unparseable value; returning default\");\n return {infallible_default};",
spec.wrapper_name(),
method.name
)
};
let convert_and_tail = |ok_prefix: &str, ok_suffix: &str| -> String {
match &decode {
None => format!("{ok_prefix}v{ok_suffix}"),
Some(ReturnDecode::Native { .. }) => {
format!("{ok_prefix}v{ok_suffix}")
}
Some(d) => format!(
"{{ let __decoded = v; {convert} {ok_prefix}__result{ok_suffix} }}",
convert = d.convert_stmt(&convert_fail_stmt),
),
}
};
let error_settle = spec.make_error(&format!(
"format!(\"Plugin '{{}}' method '{}' rejected: {{}}\", self.cached_name, e)",
method.name
));
let recv_body = if has_error {
format!(
"let __settled: napi::Result<{reply_ty}> = match __rx.recv_timeout(std::time::Duration::from_millis(50)) {{\n\
\x20 Ok(v) => v,\n\
\x20 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {{\n\
\x20 if __tsfn.aborted() {{\n\
\x20 return Err({error_call_timeout});\n\
\x20 }}\n\
\x20 continue;\n\
\x20 }}\n\
\x20 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => return Err({error_disconnected}),\n\
}};\n\
return match __settled {{\n\
\x20 Ok(v) => {ok_arm},\n\
\x20 Err(e) => Err({error_settle}),\n\
}};",
error_call_timeout = spec.make_error(&format!(
"format!(\"Plugin '{{}}' method '{}' timed out\", self.cached_name)",
method.name
)),
error_disconnected = spec.make_error(&format!(
"format!(\"Plugin '{{}}' method '{}' channel disconnected before a reply arrived\", self.cached_name)",
method.name
)),
ok_arm = convert_and_tail("Ok(", ")"),
)
} else {
format!(
"let __settled: napi::Result<{reply_ty}> = match __rx.recv_timeout(std::time::Duration::from_millis(50)) {{\n\
\x20 Ok(v) => v,\n\
\x20 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {{\n\
\x20 if __tsfn.aborted() {{\n\
\x20 tracing::warn!(wrapper = \"{wrapper}\", method = \"{method_name}\", \"threadsafe call timed out; returning default\");\n\
\x20 return {infallible_default};\n\
\x20 }}\n\
\x20 continue;\n\
\x20 }}\n\
\x20 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {{\n\
\x20 tracing::warn!(wrapper = \"{wrapper}\", method = \"{method_name}\", \"threadsafe call channel disconnected; returning default\");\n\
\x20 return {infallible_default};\n\
\x20 }}\n\
}};\n\
return match __settled {{\n\
\x20 Ok(v) => {ok_arm},\n\
\x20 Err(e) => {{\n\
\x20 tracing::warn!(wrapper = \"{wrapper}\", method = \"{method_name}\", error = %e, \"host callback failed; returning default\");\n\
\x20 {infallible_default}\n\
\x20 }}\n\
}};",
wrapper = spec.wrapper_name(),
method_name = method.name,
ok_arm = convert_and_tail("", ""),
)
};
format!(
"{missing_tsfn_arm}\n\
let __payload = {payload_expr};\n\
let __plugin_name = self.cached_name.clone();\n\
let (__tx, __rx) = std::sync::mpsc::sync_channel::<napi::Result<{reply_ty}>>(1);\n\
let __enqueue_status = __tsfn.call_with_return_value(\n\
\x20 __payload,\n\
\x20 napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking,\n\
\x20 move |reply: napi::Result<AlefJsReply<{reply_ty}>>, _env: napi::Env| {{\n\
\x20 // Declared synchronous, so there is no executor here to await a Promise: a host\n\
\x20 // that returns one anyway is a protocol error (`settle_sync` reports it as such)\n\
\x20 // rather than being polled once and misread as immediately ready.\n\
\x20 let settled = reply.and_then(|r| r.settle_sync(&__plugin_name, \"{method_name}\"));\n\
\x20 let _ = __tx.send(settled);\n\
\x20 Ok(())\n\
\x20 }},\n\
);\n\
if __enqueue_status != napi::Status::Ok {{\n\
\x20 {enqueue_fail}\n\
}}\n\
loop {{\n{recv_body}\n}}",
method_name = method.name,
enqueue_fail = if has_error {
format!("return Err({});", spec.make_error(&format!(
"format!(\"Plugin '{{}}' method '{}' could not be enqueued: {{:?}}\", self.cached_name, __enqueue_status)",
method.name
)))
} else {
format!(
"tracing::warn!(wrapper = \"{}\", method = \"{}\", status = ?__enqueue_status, \"failed to enqueue threadsafe call; returning default\");\n return {infallible_default};",
spec.wrapper_name(),
method.name
)
}
)
}
}