1use std::collections::HashMap;
9
10use camino::Utf8Path;
11use heck::ToUpperCamelCase;
12use serde::{Deserialize, Serialize};
13use weaveffi_core::abi;
14use weaveffi_core::backend::{LanguageBackend, OutputFile};
15use weaveffi_core::capabilities::TargetCapabilities;
16use weaveffi_core::codegen::common::{emit_doc as common_emit_doc, DocCommentStyle};
17use weaveffi_core::model::{
18 BindingModel, CallbackBinding, FnBinding, ListenerBinding, ParamBinding, StructBinding,
19};
20use weaveffi_core::pkg::{self, ResolvedPackage};
21use weaveffi_core::utils::{
22 c_abi_struct_name, local_type_name, render_json_prelude, render_prelude, render_trailer,
23 wrapper_name, CommentStyle,
24};
25use weaveffi_ir::ir::{Api, TypeRef};
26
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29#[serde(default)]
30pub struct NodeConfig {
31 pub package_name: Option<String>,
33 pub strip_module_prefix: bool,
36 pub prefix: Option<String>,
40 #[serde(skip)]
42 pub input_basename: Option<String>,
43}
44
45impl NodeConfig {
46 pub fn package_name(&self) -> &str {
47 self.package_name.as_deref().unwrap_or("weaveffi")
48 }
49
50 pub fn prefix(&self) -> &str {
51 self.prefix.as_deref().unwrap_or("weaveffi")
52 }
53
54 pub fn input_basename(&self) -> &str {
55 self.input_basename.as_deref().unwrap_or("weaveffi.yml")
56 }
57}
58
59pub struct NodeGenerator;
60
61impl LanguageBackend for NodeGenerator {
62 type Config = NodeConfig;
63
64 fn name(&self) -> &'static str {
65 "node"
66 }
67
68 fn capabilities(&self) -> TargetCapabilities {
69 TargetCapabilities::full()
70 }
71
72 fn prefix<'a>(&self, config: &'a Self::Config) -> &'a str {
73 config.prefix()
74 }
75
76 fn files(
77 &self,
78 api: &Api,
79 _model: &BindingModel,
80 out_dir: &Utf8Path,
81 config: &Self::Config,
82 ) -> Vec<OutputFile> {
83 let dir = out_dir.join("node");
84 let input_basename = config.input_basename();
85 let prefix = config.prefix();
86 let strip = config.strip_module_prefix;
87 let dbl = CommentStyle::DoubleSlash;
88 vec![
89 OutputFile::new(
90 dir.join("index.js"),
91 format!(
92 "{}// Prefer the default node-gyp output path; fall back to a\n\
93 // prebuilt index.node placed next to this file.\n\
94 let addon;\n\
95 try {{\n addon = require('./build/Release/weaveffi.node');\n}} catch (e) {{\n addon = require('./index.node');\n}}\n\
96 module.exports = addon;\n\n{}",
97 render_prelude(dbl, input_basename),
98 render_trailer(dbl, "index.js"),
99 ),
100 ),
101 OutputFile::new(
102 dir.join("types.d.ts"),
103 render_node_dts(api, prefix, strip, input_basename),
104 ),
105 OutputFile::new(
106 dir.join("package.json"),
107 render_package_json(
108 &pkg::resolve(
109 api,
110 config.package_name.as_deref(),
111 config.input_basename.as_deref(),
112 ),
113 input_basename,
114 ),
115 ),
116 OutputFile::new(dir.join("binding.gyp"), render_binding_gyp(input_basename)),
117 OutputFile::new(
118 dir.join("weaveffi_addon.c"),
119 render_addon_c(api, prefix, strip, input_basename),
120 ),
121 ]
122 }
123}
124
125weaveffi_core::impl_generator_via_backend!(NodeGenerator);
126
127fn render_package_json(package: &ResolvedPackage, input_basename: &str) -> String {
128 let prelude = render_json_prelude(input_basename);
129 let name = &package.name;
130 let version = &package.version;
131 let description = package.description_or_default();
132 let mut optional = String::new();
133 if let Some(license) = &package.license {
134 optional.push_str(&format!(" \"license\": \"{license}\",\n"));
135 }
136 if let Some(author) = package.authors.first() {
137 optional.push_str(&format!(" \"author\": \"{author}\",\n"));
138 }
139 if let Some(homepage) = &package.homepage {
140 optional.push_str(&format!(" \"homepage\": \"{homepage}\",\n"));
141 }
142 if let Some(repository) = &package.repository {
143 optional.push_str(&format!(
144 " \"repository\": {{ \"type\": \"git\", \"url\": \"{repository}\" }},\n"
145 ));
146 }
147 format!(
148 "{{\n{prelude} \"name\": \"{name}\",\n \"version\": \"{version}\",\n \"description\": \"{description}\",\n{optional} \"main\": \"index.js\",\n \"types\": \"types.d.ts\",\n \"gypfile\": true,\n \"scripts\": {{\n \"install\": \"node-gyp rebuild\"\n }}\n}}\n"
149 )
150}
151
152fn render_binding_gyp(input_basename: &str) -> String {
153 let prelude = render_prelude(CommentStyle::Hash, input_basename);
154 let trailer = render_trailer(CommentStyle::Hash, "binding.gyp");
155 format!(
156 "{prelude}{{\n \"targets\": [\n {{\n \"target_name\": \"weaveffi\",\n \"sources\": [\"weaveffi_addon.c\"],\n \"include_dirs\": [\"../c\"],\n \"libraries\": [\"-lweaveffi\"]\n }}\n ]\n}}\n\n{trailer}"
157 )
158}
159
160fn is_c_ptr_type(ty: &TypeRef) -> bool {
161 matches!(
162 ty,
163 TypeRef::StringUtf8
164 | TypeRef::Bytes
165 | TypeRef::Struct(_)
166 | TypeRef::List(_)
167 | TypeRef::Map(_, _)
168 | TypeRef::Iterator(_)
169 )
170}
171
172fn c_elem_type(ty: &TypeRef, module: &str, prefix: &str) -> String {
173 match ty {
174 TypeRef::I32 => "int32_t".into(),
175 TypeRef::U32 => "uint32_t".into(),
176 TypeRef::I64 => "int64_t".into(),
177 TypeRef::F64 => "double".into(),
178 TypeRef::Bool => "bool".into(),
179 TypeRef::Handle => "weaveffi_handle_t".into(),
183 TypeRef::TypedHandle(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
184 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "const char*".into(),
185 TypeRef::Bytes | TypeRef::BorrowedBytes => "const uint8_t*".into(),
186 TypeRef::Struct(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
187 TypeRef::Enum(e) => format!("{prefix}_{module}_{e}"),
188 TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
189 c_elem_type(inner, module, prefix)
190 }
191 TypeRef::Map(_, _) => "void*".into(),
192 }
193}
194
195fn c_ret_type_str(ty: &TypeRef, module: &str, prefix: &str) -> String {
196 match ty {
197 TypeRef::I32 => "int32_t".into(),
198 TypeRef::U32 => "uint32_t".into(),
199 TypeRef::I64 => "int64_t".into(),
200 TypeRef::F64 => "double".into(),
201 TypeRef::Bool => "bool".into(),
202 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "const char*".into(),
203 TypeRef::Bytes | TypeRef::BorrowedBytes => "const uint8_t*".into(),
204 TypeRef::Handle => "weaveffi_handle_t".into(),
205 TypeRef::TypedHandle(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
206 TypeRef::Struct(s) => format!("{}*", c_abi_struct_name(s, module, prefix)),
207 TypeRef::Enum(e) => format!("{prefix}_{module}_{e}"),
208 TypeRef::Optional(inner) => {
209 if is_c_ptr_type(inner) {
210 c_ret_type_str(inner, module, prefix)
211 } else {
212 format!("{}*", c_elem_type(inner, module, prefix))
213 }
214 }
215 TypeRef::List(inner) => format!("{}*", c_elem_type(inner, module, prefix)),
216 TypeRef::Map(_, _) => "void".into(),
217 TypeRef::Iterator(_) => "void*".into(),
218 }
219}
220
221fn napi_getter(ty: &TypeRef) -> &'static str {
222 match ty {
223 TypeRef::I32 | TypeRef::Enum(_) => "napi_get_value_int32",
224 TypeRef::U32 => "napi_get_value_uint32",
225 TypeRef::I64 | TypeRef::Handle | TypeRef::TypedHandle(_) | TypeRef::Struct(_) => {
226 "napi_get_value_int64"
227 }
228 TypeRef::F64 => "napi_get_value_double",
229 TypeRef::Bool => "napi_get_value_bool",
230 _ => "napi_get_value_int64",
231 }
232}
233
234fn render_addon_c(
235 api: &Api,
236 prefix: &str,
237 strip_module_prefix: bool,
238 input_basename: &str,
239) -> String {
240 let mut out = render_prelude(CommentStyle::DoubleSlash, input_basename);
241 out.push_str(&format!(
242 "#include <node_api.h>\n#include \"{prefix}.h\"\n#include <stdlib.h>\n#include <string.h>\n\n"
243 ));
244
245 let model = BindingModel::build(api, prefix);
246 let mut all_exports: Vec<(String, String)> = Vec::new();
247 let structs = struct_registry(&model);
248
249 let has_listeners = model.modules.iter().any(|m| !m.listeners.is_empty());
250 if has_listeners {
251 render_listener_support_c(&mut out, prefix);
252 }
253
254 for m in &model.modules {
255 let used_callbacks: Vec<&CallbackBinding> = m
258 .listeners
259 .iter()
260 .filter_map(|l| m.callback(&l.event_callback))
261 .collect();
262 for cb in &used_callbacks {
263 render_cb_payload_struct(&mut out, cb, prefix);
264 render_cb_tramp(&mut out, cb, prefix);
265 render_cb_calljs(&mut out, cb, prefix);
266 }
267 for l in &m.listeners {
268 let Some(cb) = m.callback(&l.event_callback) else {
269 unreachable!("validation guarantees the listener's callback exists");
270 };
271 render_listener_napi_fns(&mut out, l, cb, prefix);
272 all_exports.push((
273 wrapper_name(
274 &m.path,
275 &format!("register_{}", l.name),
276 strip_module_prefix,
277 ),
278 format!("Napi_{}", l.register_symbol),
279 ));
280 all_exports.push((
281 wrapper_name(
282 &m.path,
283 &format!("unregister_{}", l.name),
284 strip_module_prefix,
285 ),
286 format!("Napi_{}", l.unregister_symbol),
287 ));
288 }
289 for f in &m.functions {
290 let c_name = &f.c_base;
291 let napi_name = format!("Napi_{c_name}");
292 let js_name = wrapper_name(&m.path, &f.name, strip_module_prefix);
293 all_exports.push((js_name, napi_name.clone()));
294
295 if f.is_async {
296 render_async_machinery(&mut out, f, c_name, &m.path, prefix, &structs);
297 }
298
299 out.push_str(&format!(
300 "static napi_value {napi_name}(napi_env env, napi_callback_info info) {{\n"
301 ));
302 if f.is_async {
303 render_async_napi_body(&mut out, f, c_name, &m.path, prefix);
304 } else {
305 render_napi_body(&mut out, f, c_name, &m.path, prefix, &structs);
306 }
307 out.push_str("}\n\n");
308 }
309 }
310
311 out.push_str("static napi_value Init(napi_env env, napi_value exports) {\n");
312 if !all_exports.is_empty() {
313 out.push_str(" napi_property_descriptor props[] = {\n");
314 for (js_name, napi_fn) in &all_exports {
315 out.push_str(&format!(
316 " {{ \"{js_name}\", NULL, {napi_fn}, NULL, NULL, NULL, napi_default, NULL }},\n"
317 ));
318 }
319 out.push_str(" };\n");
320 out.push_str(&format!(
321 " napi_define_properties(env, exports, {}, props);\n",
322 all_exports.len()
323 ));
324 }
325 out.push_str(" return exports;\n");
326 out.push_str("}\n\n");
327 out.push_str("NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)\n\n");
328 out.push_str(&render_trailer(
329 CommentStyle::DoubleSlash,
330 "weaveffi_addon.c",
331 ));
332 out
333}
334
335fn render_listener_support_c(out: &mut String, prefix: &str) {
339 out.push_str(&format!("typedef struct {prefix}_napi_listener_ctx {{\n"));
340 out.push_str(" napi_threadsafe_function tsfn;\n");
341 out.push_str(" uint64_t id;\n");
342 out.push_str(&format!(" struct {prefix}_napi_listener_ctx* next;\n"));
343 out.push_str(&format!("}} {prefix}_napi_listener_ctx;\n\n"));
344 out.push_str(&format!(
345 "static {prefix}_napi_listener_ctx* {prefix}_napi_listeners = NULL;\n\n"
346 ));
347}
348
349fn cb_payload_name(cb: &CallbackBinding) -> String {
350 format!("{}_payload", cb.c_fn_type)
351}
352
353fn cb_slot_decls(cb: &CallbackBinding, prefix: &str) -> Vec<String> {
355 cb.params
356 .iter()
357 .flat_map(|p| abi::lower_param(&p.name, &p.ty, "", false))
358 .map(|slot| format!("{} {}", slot.ty.render_c(prefix), slot.name))
359 .collect()
360}
361
362fn render_cb_payload_struct(out: &mut String, cb: &CallbackBinding, prefix: &str) {
367 out.push_str("typedef struct {\n");
368 for p in &cb.params {
369 let slots = abi::lower_param(&p.name, &p.ty, "", false);
370 let n0 = &slots[0].name;
371 match &p.ty {
372 TypeRef::I32
373 | TypeRef::U32
374 | TypeRef::I64
375 | TypeRef::F64
376 | TypeRef::Bool
377 | TypeRef::Handle
378 | TypeRef::Enum(_) => {
379 out.push_str(&format!(" {} {n0};\n", slots[0].ty.render_c(prefix)));
380 }
381 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
382 out.push_str(&format!(" char* {n0};\n"));
383 }
384 TypeRef::Bytes | TypeRef::BorrowedBytes => {
385 out.push_str(&format!(" uint8_t* {n0};\n"));
386 out.push_str(&format!(" size_t {};\n", slots[1].name));
387 }
388 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
389 out.push_str(&format!(" void* {n0};\n"));
390 }
391 TypeRef::Optional(inner) => match inner.as_ref() {
392 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
393 out.push_str(&format!(" char* {n0};\n"));
394 }
395 TypeRef::Bytes | TypeRef::BorrowedBytes => {
396 out.push_str(&format!(" int {n0}_has;\n"));
397 out.push_str(&format!(" uint8_t* {n0};\n"));
398 out.push_str(&format!(" size_t {};\n", slots[1].name));
399 }
400 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
401 out.push_str(&format!(" void* {n0};\n"));
402 }
403 other => {
404 out.push_str(&format!(" int {n0}_has;\n"));
405 out.push_str(&format!(
406 " {} {n0};\n",
407 abi::element_ctype(other, "").render_c(prefix)
408 ));
409 }
410 },
411 TypeRef::List(inner) => {
412 let elem = elem_payload_ctype(inner, prefix);
413 out.push_str(&format!(" {elem}* {n0};\n"));
414 out.push_str(&format!(" size_t {};\n", slots[1].name));
415 }
416 TypeRef::Map(k, v) => {
417 let kt = elem_payload_ctype(k, prefix);
418 let vt = elem_payload_ctype(v, prefix);
419 out.push_str(&format!(" {kt}* {n0};\n"));
420 out.push_str(&format!(" {vt}* {};\n", slots[1].name));
421 out.push_str(&format!(" size_t {};\n", slots[2].name));
422 }
423 TypeRef::Iterator(_) => unreachable!("validated: iterator not a callback param"),
424 }
425 }
426 out.push_str(&format!("}} {};\n\n", cb_payload_name(cb)));
427}
428
429fn elem_payload_ctype(ty: &TypeRef, prefix: &str) -> String {
432 match ty {
433 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "char*".into(),
434 other => abi::element_ctype(other, "").render_c(prefix),
435 }
436}
437
438fn render_cb_tramp(out: &mut String, cb: &CallbackBinding, prefix: &str) {
442 let payload = cb_payload_name(cb);
443 let mut decls = cb_slot_decls(cb, prefix);
444 decls.push("void* context".into());
445 out.push_str(&format!(
446 "static void {}_napi_tramp({}) {{\n",
447 cb.c_fn_type,
448 decls.join(", ")
449 ));
450 out.push_str(&format!(
451 " {prefix}_napi_listener_ctx* ctx = ({prefix}_napi_listener_ctx*)context;\n"
452 ));
453 out.push_str(&format!(
454 " {payload}* p = ({payload}*)calloc(1, sizeof({payload}));\n"
455 ));
456 for p in &cb.params {
457 let slots = abi::lower_param(&p.name, &p.ty, "", false);
458 let n0 = &slots[0].name;
459 match &p.ty {
460 TypeRef::I32
461 | TypeRef::U32
462 | TypeRef::I64
463 | TypeRef::F64
464 | TypeRef::Bool
465 | TypeRef::Handle
466 | TypeRef::Enum(_) => {
467 out.push_str(&format!(" p->{n0} = {n0};\n"));
468 }
469 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
470 out.push_str(&format!(" p->{n0} = {n0} ? strdup({n0}) : NULL;\n"));
471 }
472 TypeRef::Bytes | TypeRef::BorrowedBytes => {
473 let n1 = &slots[1].name;
474 out.push_str(&format!(" p->{n1} = {n1};\n"));
475 out.push_str(&format!(
476 " if ({n0} != NULL && {n1} > 0) {{ p->{n0} = (uint8_t*)malloc({n1}); memcpy(p->{n0}, {n0}, {n1}); }}\n"
477 ));
478 }
479 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
480 out.push_str(&format!(" p->{n0} = (void*){n0};\n"));
481 }
482 TypeRef::Optional(inner) => match inner.as_ref() {
483 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
484 out.push_str(&format!(" p->{n0} = {n0} ? strdup({n0}) : NULL;\n"));
485 }
486 TypeRef::Bytes | TypeRef::BorrowedBytes => {
487 let n1 = &slots[1].name;
488 out.push_str(&format!(" p->{n0}_has = {n0} != NULL;\n"));
489 out.push_str(&format!(" p->{n1} = {n1};\n"));
490 out.push_str(&format!(
491 " if ({n0} != NULL && {n1} > 0) {{ p->{n0} = (uint8_t*)malloc({n1}); memcpy(p->{n0}, {n0}, {n1}); }}\n"
492 ));
493 }
494 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
495 out.push_str(&format!(" p->{n0} = (void*){n0};\n"));
496 }
497 _ => {
498 out.push_str(&format!(" p->{n0}_has = {n0} != NULL;\n"));
499 out.push_str(&format!(" if ({n0} != NULL) p->{n0} = *{n0};\n"));
500 }
501 },
502 TypeRef::List(inner) => {
503 let n1 = &slots[1].name;
504 out.push_str(&format!(" p->{n1} = {n1};\n"));
505 match inner.as_ref() {
506 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
507 out.push_str(&format!(
508 " if ({n0} != NULL && {n1} > 0) {{\n p->{n0} = (char**)calloc({n1}, sizeof(char*));\n for (size_t i = 0; i < {n1}; i++) p->{n0}[i] = {n0}[i] ? strdup({n0}[i]) : NULL;\n }}\n"
509 ));
510 }
511 _ => {
512 out.push_str(&format!(
513 " if ({n0} != NULL && {n1} > 0) {{ p->{n0} = malloc({n1} * sizeof(*p->{n0})); memcpy(p->{n0}, {n0}, {n1} * sizeof(*p->{n0})); }}\n"
514 ));
515 }
516 }
517 }
518 TypeRef::Map(k, v) => {
519 let keys = n0;
520 let vals = &slots[1].name;
521 let len = &slots[2].name;
522 out.push_str(&format!(" p->{len} = {len};\n"));
523 for (base, ty) in [(keys, k), (vals, v)] {
524 match ty.as_ref() {
525 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
526 out.push_str(&format!(
527 " if ({base} != NULL && {len} > 0) {{\n p->{base} = (char**)calloc({len}, sizeof(char*));\n for (size_t i = 0; i < {len}; i++) p->{base}[i] = {base}[i] ? strdup({base}[i]) : NULL;\n }}\n"
528 ));
529 }
530 _ => {
531 out.push_str(&format!(
532 " if ({base} != NULL && {len} > 0) {{ p->{base} = malloc({len} * sizeof(*p->{base})); memcpy(p->{base}, {base}, {len} * sizeof(*p->{base})); }}\n"
533 ));
534 }
535 }
536 }
537 }
538 TypeRef::Iterator(_) => unreachable!("validated: iterator not a callback param"),
539 }
540 }
541 out.push_str(" napi_call_threadsafe_function(ctx->tsfn, p, napi_tsfn_nonblocking);\n");
542 out.push_str("}\n\n");
543}
544
545fn emit_payload_to_napi(out: &mut String, p: &ParamBinding, idx: usize, prefix: &str) {
547 let slots = abi::lower_param(&p.name, &p.ty, "", false);
548 let n0 = &slots[0].name;
549 let target = format!("argv[{idx}]");
550 let _ = prefix;
551 match &p.ty {
552 TypeRef::I32 => out.push_str(&format!(
553 " napi_create_int32(env, p->{n0}, &{target});\n"
554 )),
555 TypeRef::U32 => out.push_str(&format!(
556 " napi_create_uint32(env, p->{n0}, &{target});\n"
557 )),
558 TypeRef::I64 => out.push_str(&format!(
559 " napi_create_int64(env, p->{n0}, &{target});\n"
560 )),
561 TypeRef::F64 => out.push_str(&format!(
562 " napi_create_double(env, p->{n0}, &{target});\n"
563 )),
564 TypeRef::Bool => out.push_str(&format!(
565 " napi_get_boolean(env, p->{n0}, &{target});\n"
566 )),
567 TypeRef::Handle => out.push_str(&format!(
568 " napi_create_int64(env, (int64_t)p->{n0}, &{target});\n"
569 )),
570 TypeRef::Enum(_) => out.push_str(&format!(
571 " napi_create_int32(env, (int32_t)p->{n0}, &{target});\n"
572 )),
573 TypeRef::StringUtf8 | TypeRef::BorrowedStr => out.push_str(&format!(
574 " napi_create_string_utf8(env, p->{n0} ? p->{n0} : \"\", NAPI_AUTO_LENGTH, &{target});\n"
575 )),
576 TypeRef::Bytes | TypeRef::BorrowedBytes => {
577 let n1 = &slots[1].name;
578 out.push_str(&format!(
579 " napi_create_buffer_copy(env, p->{n1}, p->{n0} ? (const void*)p->{n0} : (const void*)\"\", NULL, &{target});\n"
580 ));
581 }
582 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => out.push_str(&format!(
583 " napi_create_int64(env, (int64_t)(intptr_t)p->{n0}, &{target});\n"
584 )),
585 TypeRef::Optional(inner) => match inner.as_ref() {
586 TypeRef::StringUtf8 | TypeRef::BorrowedStr => out.push_str(&format!(
587 " if (p->{n0}) napi_create_string_utf8(env, p->{n0}, NAPI_AUTO_LENGTH, &{target}); else napi_get_null(env, &{target});\n"
588 )),
589 TypeRef::Bytes | TypeRef::BorrowedBytes => {
590 let n1 = &slots[1].name;
591 out.push_str(&format!(
592 " if (p->{n0}_has) napi_create_buffer_copy(env, p->{n1}, p->{n0} ? (const void*)p->{n0} : (const void*)\"\", NULL, &{target}); else napi_get_null(env, &{target});\n"
593 ));
594 }
595 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => out.push_str(&format!(
596 " if (p->{n0}) napi_create_int64(env, (int64_t)(intptr_t)p->{n0}, &{target}); else napi_get_null(env, &{target});\n"
597 )),
598 other => {
599 let leaf = payload_leaf_to_napi(other, &format!("p->{n0}"), &target);
600 out.push_str(&format!(
601 " if (p->{n0}_has) {{ {leaf} }} else napi_get_null(env, &{target});\n"
602 ));
603 }
604 },
605 TypeRef::List(inner) => {
606 let n1 = &slots[1].name;
607 out.push_str(&format!(" napi_create_array(env, &{target});\n"));
608 out.push_str(&format!(
609 " for (size_t i = 0; p->{n0} != NULL && i < p->{n1}; i++) {{\n"
610 ));
611 out.push_str(" napi_value elem;\n");
612 let leaf = payload_elem_to_napi(inner, &format!("p->{n0}[i]"), "elem");
613 out.push_str(&format!(" {leaf}\n"));
614 out.push_str(&format!(
615 " napi_set_element(env, {target}, (uint32_t)i, elem);\n"
616 ));
617 out.push_str(" }\n");
618 }
619 TypeRef::Map(k, v) => {
620 let keys = n0;
621 let vals = &slots[1].name;
622 let len = &slots[2].name;
623 out.push_str(&format!(" napi_create_object(env, &{target});\n"));
624 out.push_str(&format!(
625 " for (size_t i = 0; p->{keys} != NULL && p->{vals} != NULL && i < p->{len}; i++) {{\n"
626 ));
627 out.push_str(" napi_value mk; napi_value mv;\n");
628 let kc = payload_elem_to_napi(k, &format!("p->{keys}[i]"), "mk");
629 let vc = payload_elem_to_napi(v, &format!("p->{vals}[i]"), "mv");
630 out.push_str(&format!(" {kc}\n"));
631 out.push_str(&format!(" {vc}\n"));
632 out.push_str(&format!(
633 " napi_set_property(env, {target}, mk, mv);\n"
634 ));
635 out.push_str(" }\n");
636 }
637 TypeRef::Iterator(_) => unreachable!("validated: iterator not a callback param"),
638 }
639}
640
641fn payload_leaf_to_napi(ty: &TypeRef, expr: &str, target: &str) -> String {
643 match ty {
644 TypeRef::I32 => format!("napi_create_int32(env, {expr}, &{target});"),
645 TypeRef::U32 => format!("napi_create_uint32(env, {expr}, &{target});"),
646 TypeRef::I64 => format!("napi_create_int64(env, {expr}, &{target});"),
647 TypeRef::F64 => format!("napi_create_double(env, {expr}, &{target});"),
648 TypeRef::Bool => format!("napi_get_boolean(env, {expr}, &{target});"),
649 TypeRef::Handle => format!("napi_create_int64(env, (int64_t){expr}, &{target});"),
650 TypeRef::Enum(_) => format!("napi_create_int32(env, (int32_t){expr}, &{target});"),
651 _ => format!("napi_get_null(env, &{target});"),
652 }
653}
654
655fn payload_elem_to_napi(ty: &TypeRef, expr: &str, target: &str) -> String {
657 match ty {
658 TypeRef::StringUtf8 | TypeRef::BorrowedStr => format!(
659 "napi_create_string_utf8(env, {expr} ? {expr} : \"\", NAPI_AUTO_LENGTH, &{target});"
660 ),
661 other => payload_leaf_to_napi(other, expr, target),
662 }
663}
664
665fn emit_payload_free(out: &mut String, p: &ParamBinding) {
667 let slots = abi::lower_param(&p.name, &p.ty, "", false);
668 let n0 = &slots[0].name;
669 match &p.ty {
670 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
671 out.push_str(&format!(" free(p->{n0});\n"));
672 }
673 TypeRef::Bytes | TypeRef::BorrowedBytes => {
674 out.push_str(&format!(" free(p->{n0});\n"));
675 }
676 TypeRef::Optional(inner) => match inner.as_ref() {
677 TypeRef::StringUtf8
678 | TypeRef::BorrowedStr
679 | TypeRef::Bytes
680 | TypeRef::BorrowedBytes => {
681 out.push_str(&format!(" free(p->{n0});\n"));
682 }
683 _ => {}
684 },
685 TypeRef::List(inner) => {
686 let n1 = &slots[1].name;
687 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
688 out.push_str(&format!(
689 " for (size_t i = 0; p->{n0} != NULL && i < p->{n1}; i++) free(p->{n0}[i]);\n"
690 ));
691 }
692 out.push_str(&format!(" free(p->{n0});\n"));
693 }
694 TypeRef::Map(k, v) => {
695 let keys = n0;
696 let vals = &slots[1].name;
697 let len = &slots[2].name;
698 for (base, ty) in [(keys, k), (vals, v)] {
699 if matches!(ty.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
700 out.push_str(&format!(
701 " for (size_t i = 0; p->{base} != NULL && i < p->{len}; i++) free(p->{base}[i]);\n"
702 ));
703 }
704 out.push_str(&format!(" free(p->{base});\n"));
705 }
706 }
707 _ => {}
708 }
709}
710
711fn render_cb_calljs(out: &mut String, cb: &CallbackBinding, prefix: &str) {
714 let payload = cb_payload_name(cb);
715 out.push_str(&format!(
716 "static void {}_napi_calljs(napi_env env, napi_value js_cb, void* context, void* data) {{\n",
717 cb.c_fn_type
718 ));
719 out.push_str(" (void)context;\n");
720 out.push_str(&format!(" {payload}* p = ({payload}*)data;\n"));
721 out.push_str(" if (env != NULL) {\n");
722 out.push_str(" napi_value undefined;\n");
723 out.push_str(" napi_get_undefined(env, &undefined);\n");
724 let argc = cb.params.len();
725 if argc > 0 {
726 out.push_str(&format!(" napi_value argv[{argc}];\n"));
727 for (i, p) in cb.params.iter().enumerate() {
728 emit_payload_to_napi(out, p, i, prefix);
729 }
730 out.push_str(&format!(
731 " napi_call_function(env, undefined, js_cb, {argc}, argv, NULL);\n"
732 ));
733 } else {
734 out.push_str(" napi_call_function(env, undefined, js_cb, 0, NULL, NULL);\n");
735 }
736 out.push_str(" }\n");
737 for p in &cb.params {
738 emit_payload_free(out, p);
739 }
740 out.push_str(" free(p);\n");
741 out.push_str("}\n\n");
742}
743
744fn render_listener_napi_fns(
749 out: &mut String,
750 l: &ListenerBinding,
751 cb: &CallbackBinding,
752 prefix: &str,
753) {
754 let register_sym = &l.register_symbol;
755 let unregister_sym = &l.unregister_symbol;
756 let tramp = format!("{}_napi_tramp", cb.c_fn_type);
757 let calljs = format!("{}_napi_calljs", cb.c_fn_type);
758
759 out.push_str(&format!(
760 "static napi_value Napi_{register_sym}(napi_env env, napi_callback_info info) {{\n"
761 ));
762 out.push_str(" size_t argc = 1;\n");
763 out.push_str(" napi_value args[1];\n");
764 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
765 out.push_str(&format!(
766 " {prefix}_napi_listener_ctx* ctx = ({prefix}_napi_listener_ctx*)calloc(1, sizeof({prefix}_napi_listener_ctx));\n"
767 ));
768 out.push_str(" napi_value resource_name;\n");
769 out.push_str(&format!(
770 " napi_create_string_utf8(env, \"{register_sym}\", NAPI_AUTO_LENGTH, &resource_name);\n"
771 ));
772 out.push_str(&format!(
773 " napi_create_threadsafe_function(env, args[0], NULL, resource_name, 0, 1, NULL, NULL, NULL, {calljs}, &ctx->tsfn);\n"
774 ));
775 out.push_str(" napi_unref_threadsafe_function(env, ctx->tsfn);\n");
776 out.push_str(&format!(" uint64_t id = {register_sym}({tramp}, ctx);\n"));
777 out.push_str(" ctx->id = id;\n");
778 out.push_str(&format!(" ctx->next = {prefix}_napi_listeners;\n"));
779 out.push_str(&format!(" {prefix}_napi_listeners = ctx;\n"));
780 out.push_str(" napi_value ret;\n");
781 out.push_str(" napi_create_double(env, (double)id, &ret);\n");
782 out.push_str(" return ret;\n");
783 out.push_str("}\n\n");
784
785 out.push_str(&format!(
786 "static napi_value Napi_{unregister_sym}(napi_env env, napi_callback_info info) {{\n"
787 ));
788 out.push_str(" size_t argc = 1;\n");
789 out.push_str(" napi_value args[1];\n");
790 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
791 out.push_str(" double id_d = 0;\n");
792 out.push_str(" napi_get_value_double(env, args[0], &id_d);\n");
793 out.push_str(" uint64_t id = (uint64_t)id_d;\n");
794 out.push_str(&format!(" {unregister_sym}(id);\n"));
797 out.push_str(&format!(
798 " {prefix}_napi_listener_ctx** link = &{prefix}_napi_listeners;\n"
799 ));
800 out.push_str(" while (*link != NULL) {\n");
801 out.push_str(" if ((*link)->id == id) {\n");
802 out.push_str(&format!(
803 " {prefix}_napi_listener_ctx* found = *link;\n"
804 ));
805 out.push_str(" *link = found->next;\n");
806 out.push_str(" napi_release_threadsafe_function(found->tsfn, napi_tsfn_release);\n");
807 out.push_str(" free(found);\n");
808 out.push_str(" break;\n");
809 out.push_str(" }\n");
810 out.push_str(" link = &(*link)->next;\n");
811 out.push_str(" }\n");
812 out.push_str(" napi_value ret;\n");
813 out.push_str(" napi_get_undefined(env, &ret);\n");
814 out.push_str(" return ret;\n");
815 out.push_str("}\n\n");
816}
817
818fn async_cb_result_params_node(ret: Option<&TypeRef>, module: &str, prefix: &str) -> String {
819 match ret {
820 None => String::new(),
821 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => ", const char* result".into(),
822 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
823 ", const uint8_t* result, size_t result_len".into()
824 }
825 Some(TypeRef::List(inner)) => {
826 let et = c_elem_type(inner, module, prefix);
827 format!(", {et}* result, size_t result_len")
828 }
829 Some(TypeRef::Map(k, v)) => {
830 let kt = c_elem_type(k, module, prefix);
831 let vt = c_elem_type(v, module, prefix);
832 format!(", {kt}* result_keys, {vt}* result_values, size_t result_len")
833 }
834 Some(t) => format!(", {} result", c_ret_type_str(t, module, prefix)),
835 }
836}
837
838fn render_async_machinery(
847 out: &mut String,
848 f: &FnBinding,
849 c_name: &str,
850 module: &str,
851 prefix: &str,
852 structs: &HashMap<String, StructBinding>,
853) {
854 let actx = format!("{c_name}_napi_actx");
855 let cb_name = format!("{c_name}_napi_cb");
856 let calljs = format!("{c_name}_napi_settle");
857 let cb_result = async_cb_result_params_node(f.ret.as_ref(), module, prefix);
858
859 out.push_str("typedef struct {\n");
861 out.push_str(" napi_deferred deferred;\n");
862 out.push_str(" napi_threadsafe_function tsfn;\n");
863 out.push_str(" int32_t err_code;\n");
864 out.push_str(" char* err_msg;\n");
865 match f.ret.as_ref() {
866 None => {}
867 Some(TypeRef::I32) => out.push_str(" int32_t result;\n"),
868 Some(TypeRef::U32) => out.push_str(" uint32_t result;\n"),
869 Some(TypeRef::I64) => out.push_str(" int64_t result;\n"),
870 Some(TypeRef::F64) => out.push_str(" double result;\n"),
871 Some(TypeRef::Bool) => out.push_str(" bool result;\n"),
872 Some(TypeRef::Enum(_)) => out.push_str(" int32_t result;\n"),
873 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => {
874 out.push_str(" char* result;\n");
875 out.push_str(" int result_null;\n");
876 }
877 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
878 out.push_str(" uint8_t* result;\n");
879 out.push_str(" size_t result_len;\n");
880 }
881 Some(TypeRef::Handle) => out.push_str(" uint64_t result;\n"),
882 Some(TypeRef::TypedHandle(_) | TypeRef::Struct(_) | TypeRef::Iterator(_)) => {
883 out.push_str(" void* result;\n")
884 }
885 Some(TypeRef::Optional(inner)) => match inner.as_ref() {
886 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
887 out.push_str(" char* result;\n");
888 out.push_str(" int result_null;\n");
889 }
890 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
891 out.push_str(" void* result;\n");
892 }
893 other => {
894 out.push_str(" int result_has;\n");
895 out.push_str(&format!(
896 " {} result;\n",
897 c_elem_type(other, module, prefix)
898 ));
899 }
900 },
901 Some(TypeRef::List(inner)) => {
902 let elem = match inner.as_ref() {
903 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "char*".to_string(),
904 other => c_elem_type(other, module, prefix),
905 };
906 out.push_str(&format!(" {elem}* result;\n"));
907 out.push_str(" size_t result_len;\n");
908 }
909 Some(TypeRef::Map(k, v)) => {
910 for (field, ty) in [("result_keys", k), ("result_values", v)] {
911 let elem = match ty.as_ref() {
912 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "char*".to_string(),
913 other => c_elem_type(other, module, prefix),
914 };
915 out.push_str(&format!(" {elem}* {field};\n"));
916 }
917 out.push_str(" size_t result_len;\n");
918 }
919 }
920 out.push_str(&format!("}} {actx};\n\n"));
921
922 out.push_str(&format!(
924 "static void {cb_name}(void* context, weaveffi_error* err{cb_result}) {{\n"
925 ));
926 out.push_str(&format!(" {actx}* ctx = ({actx}*)context;\n"));
927 out.push_str(" if (err != NULL && err->code != 0) {\n");
928 out.push_str(" ctx->err_code = err->code;\n");
929 out.push_str(
930 " ctx->err_msg = err->message ? strdup(err->message) : strdup(\"unknown error\");\n",
931 );
932 out.push_str(" } else {\n");
933 match f.ret.as_ref() {
934 None => {}
935 Some(
936 TypeRef::I32
937 | TypeRef::U32
938 | TypeRef::I64
939 | TypeRef::F64
940 | TypeRef::Bool
941 | TypeRef::Handle,
942 ) => {
943 out.push_str(" ctx->result = result;\n");
944 }
945 Some(TypeRef::Enum(_)) => {
946 out.push_str(" ctx->result = (int32_t)result;\n");
947 }
948 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => {
949 out.push_str(" ctx->result_null = result == NULL;\n");
950 out.push_str(" ctx->result = result ? strdup(result) : NULL;\n");
951 }
952 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
953 out.push_str(" ctx->result_len = result_len;\n");
954 out.push_str(
955 " if (result != NULL && result_len > 0) { ctx->result = (uint8_t*)malloc(result_len); memcpy(ctx->result, result, result_len); }\n",
956 );
957 }
958 Some(TypeRef::TypedHandle(_) | TypeRef::Struct(_) | TypeRef::Iterator(_)) => {
961 out.push_str(" ctx->result = (void*)result;\n");
962 }
963 Some(TypeRef::Optional(inner)) => match inner.as_ref() {
964 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
965 out.push_str(" ctx->result_null = result == NULL;\n");
966 out.push_str(" ctx->result = result ? strdup(result) : NULL;\n");
967 }
968 TypeRef::Struct(_) | TypeRef::TypedHandle(_) => {
969 out.push_str(" ctx->result = (void*)result;\n");
970 }
971 _ => {
972 out.push_str(" ctx->result_has = result != NULL;\n");
973 out.push_str(" if (result != NULL) ctx->result = *result;\n");
974 }
975 },
976 Some(TypeRef::List(inner)) => {
977 out.push_str(" ctx->result_len = result_len;\n");
978 match inner.as_ref() {
979 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
980 out.push_str(
981 " if (result != NULL && result_len > 0) {\n ctx->result = (char**)calloc(result_len, sizeof(char*));\n for (size_t i = 0; i < result_len; i++) ctx->result[i] = result[i] ? strdup(result[i]) : NULL;\n }\n",
982 );
983 }
984 _ => {
985 out.push_str(
986 " if (result != NULL && result_len > 0) { ctx->result = malloc(result_len * sizeof(*ctx->result)); memcpy(ctx->result, result, result_len * sizeof(*ctx->result)); }\n",
987 );
988 }
989 }
990 }
991 Some(TypeRef::Map(k, v)) => {
992 out.push_str(" ctx->result_len = result_len;\n");
993 for (field, src, ty) in [
994 ("result_keys", "result_keys", k),
995 ("result_values", "result_values", v),
996 ] {
997 match ty.as_ref() {
998 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
999 out.push_str(&format!(
1000 " if ({src} != NULL && result_len > 0) {{\n ctx->{field} = (char**)calloc(result_len, sizeof(char*));\n for (size_t i = 0; i < result_len; i++) ctx->{field}[i] = {src}[i] ? strdup({src}[i]) : NULL;\n }}\n"
1001 ));
1002 }
1003 _ => {
1004 out.push_str(&format!(
1005 " if ({src} != NULL && result_len > 0) {{ ctx->{field} = malloc(result_len * sizeof(*ctx->{field})); memcpy(ctx->{field}, {src}, result_len * sizeof(*ctx->{field})); }}\n"
1006 ));
1007 }
1008 }
1009 }
1010 }
1011 }
1012 out.push_str(" }\n");
1013 out.push_str(" napi_call_threadsafe_function(ctx->tsfn, ctx, napi_tsfn_blocking);\n");
1014 out.push_str("}\n\n");
1015
1016 out.push_str(&format!(
1018 "static void {calljs}(napi_env env, napi_value js_cb, void* context, void* data) {{\n"
1019 ));
1020 out.push_str(" (void)js_cb;\n");
1021 out.push_str(" (void)context;\n");
1022 out.push_str(&format!(" {actx}* ctx = ({actx}*)data;\n"));
1023 out.push_str(" if (env != NULL) {\n");
1024 out.push_str(" if (ctx->err_code != 0) {\n");
1025 out.push_str(" napi_value err_msg;\n");
1026 out.push_str(
1027 " napi_create_string_utf8(env, ctx->err_msg ? ctx->err_msg : \"\", NAPI_AUTO_LENGTH, &err_msg);\n",
1028 );
1029 out.push_str(" napi_value err_obj;\n");
1030 out.push_str(" napi_create_error(env, NULL, err_msg, &err_obj);\n");
1031 out.push_str(" napi_value err_code;\n");
1032 out.push_str(" napi_create_int32(env, ctx->err_code, &err_code);\n");
1033 out.push_str(" napi_set_named_property(env, err_obj, \"code\", err_code);\n");
1034 out.push_str(" napi_reject_deferred(env, ctx->deferred, err_obj);\n");
1035 out.push_str(" } else {\n");
1036 out.push_str(" napi_value val;\n");
1037 match f.ret.as_ref() {
1038 None => out.push_str(" napi_get_undefined(env, &val);\n"),
1039 Some(TypeRef::I32) => out.push_str(" napi_create_int32(env, ctx->result, &val);\n"),
1040 Some(TypeRef::U32) => out.push_str(" napi_create_uint32(env, ctx->result, &val);\n"),
1041 Some(TypeRef::I64) => out.push_str(" napi_create_int64(env, ctx->result, &val);\n"),
1042 Some(TypeRef::F64) => out.push_str(" napi_create_double(env, ctx->result, &val);\n"),
1043 Some(TypeRef::Bool) => out.push_str(" napi_get_boolean(env, ctx->result, &val);\n"),
1044 Some(TypeRef::Enum(_)) => {
1045 out.push_str(" napi_create_int32(env, ctx->result, &val);\n");
1046 }
1047 Some(TypeRef::StringUtf8 | TypeRef::BorrowedStr) => {
1048 out.push_str(
1049 " if (ctx->result_null) napi_get_null(env, &val); else napi_create_string_utf8(env, ctx->result ? ctx->result : \"\", NAPI_AUTO_LENGTH, &val);\n",
1050 );
1051 }
1052 Some(TypeRef::Bytes | TypeRef::BorrowedBytes) => {
1053 out.push_str(
1054 " napi_create_buffer_copy(env, ctx->result_len, ctx->result ? (const void*)ctx->result : (const void*)\"\", NULL, &val);\n",
1055 );
1056 }
1057 Some(TypeRef::Handle) => {
1058 out.push_str(" napi_create_int64(env, (int64_t)ctx->result, &val);\n");
1059 }
1060 Some(TypeRef::TypedHandle(_) | TypeRef::Iterator(_)) => {
1061 out.push_str(" napi_create_int64(env, (int64_t)(intptr_t)ctx->result, &val);\n");
1062 }
1063 Some(TypeRef::Struct(name)) => {
1064 emit_struct_to_object(
1065 out,
1066 "env",
1067 name,
1068 "ctx->result",
1069 "val",
1070 module,
1071 prefix,
1072 structs,
1073 " ",
1074 true,
1075 );
1076 }
1077 Some(TypeRef::Optional(inner)) => match inner.as_ref() {
1078 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1079 out.push_str(
1080 " if (ctx->result_null) napi_get_null(env, &val); else napi_create_string_utf8(env, ctx->result ? ctx->result : \"\", NAPI_AUTO_LENGTH, &val);\n",
1081 );
1082 }
1083 TypeRef::Struct(name) => {
1084 out.push_str(
1085 " if (ctx->result == NULL) { napi_get_null(env, &val); } else {\n",
1086 );
1087 emit_struct_to_object(
1088 out,
1089 "env",
1090 name,
1091 "ctx->result",
1092 "val",
1093 module,
1094 prefix,
1095 structs,
1096 " ",
1097 true,
1098 );
1099 out.push_str(" }\n");
1100 }
1101 TypeRef::TypedHandle(_) => {
1102 out.push_str(
1103 " if (ctx->result == NULL) napi_get_null(env, &val); else napi_create_int64(env, (int64_t)(intptr_t)ctx->result, &val);\n",
1104 );
1105 }
1106 other => {
1107 let leaf = payload_leaf_to_napi(other, "ctx->result", "val");
1108 out.push_str(&format!(
1109 " if (ctx->result_has) {{ {leaf} }} else napi_get_null(env, &val);\n"
1110 ));
1111 }
1112 },
1113 Some(TypeRef::List(inner)) => {
1114 out.push_str(" napi_create_array(env, &val);\n");
1115 out.push_str(
1116 " for (size_t i = 0; ctx->result != NULL && i < ctx->result_len; i++) {\n",
1117 );
1118 out.push_str(" napi_value elem;\n");
1119 let leaf = payload_elem_to_napi(inner, "ctx->result[i]", "elem");
1120 out.push_str(&format!(" {leaf}\n"));
1121 out.push_str(" napi_set_element(env, val, (uint32_t)i, elem);\n");
1122 out.push_str(" }\n");
1123 }
1124 Some(TypeRef::Map(k, v)) => {
1125 out.push_str(" napi_create_object(env, &val);\n");
1126 out.push_str(
1127 " for (size_t i = 0; ctx->result_keys != NULL && ctx->result_values != NULL && i < ctx->result_len; i++) {\n",
1128 );
1129 out.push_str(" napi_value mk; napi_value mv;\n");
1130 let kc = payload_elem_to_napi(k, "ctx->result_keys[i]", "mk");
1131 let vc = payload_elem_to_napi(v, "ctx->result_values[i]", "mv");
1132 out.push_str(&format!(" {kc}\n"));
1133 out.push_str(&format!(" {vc}\n"));
1134 out.push_str(" napi_set_property(env, val, mk, mv);\n");
1135 out.push_str(" }\n");
1136 }
1137 }
1138 out.push_str(" napi_resolve_deferred(env, ctx->deferred, val);\n");
1139 out.push_str(" }\n");
1140 out.push_str(" }\n");
1141 out.push_str(" free(ctx->err_msg);\n");
1142 match f.ret.as_ref() {
1143 Some(
1144 TypeRef::StringUtf8 | TypeRef::BorrowedStr | TypeRef::Bytes | TypeRef::BorrowedBytes,
1145 ) => {
1146 out.push_str(" free(ctx->result);\n");
1147 }
1148 Some(TypeRef::Optional(inner))
1149 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) =>
1150 {
1151 out.push_str(" free(ctx->result);\n");
1152 }
1153 Some(TypeRef::List(inner)) => {
1154 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
1155 out.push_str(
1156 " for (size_t i = 0; ctx->result != NULL && i < ctx->result_len; i++) free(ctx->result[i]);\n",
1157 );
1158 }
1159 out.push_str(" free(ctx->result);\n");
1160 }
1161 Some(TypeRef::Map(k, v)) => {
1162 for (field, ty) in [("result_keys", k), ("result_values", v)] {
1163 if matches!(ty.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) {
1164 out.push_str(&format!(
1165 " for (size_t i = 0; ctx->{field} != NULL && i < ctx->result_len; i++) free(ctx->{field}[i]);\n"
1166 ));
1167 }
1168 out.push_str(&format!(" free(ctx->{field});\n"));
1169 }
1170 }
1171 _ => {}
1172 }
1173 out.push_str(" napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release);\n");
1174 out.push_str(" free(ctx);\n");
1175 out.push_str("}\n\n");
1176}
1177
1178fn render_async_napi_body(
1179 out: &mut String,
1180 f: &FnBinding,
1181 c_name: &str,
1182 module: &str,
1183 prefix: &str,
1184) {
1185 let n = f.params.len();
1186 if n > 0 {
1187 out.push_str(&format!(" size_t argc = {n};\n"));
1188 out.push_str(&format!(" napi_value args[{n}];\n"));
1189 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
1190 } else {
1191 out.push_str(" size_t argc = 0;\n");
1192 out.push_str(" napi_get_cb_info(env, info, &argc, NULL, NULL, NULL);\n");
1193 }
1194
1195 let mut c_args: Vec<String> = Vec::new();
1196 let mut cleanups: Vec<String> = Vec::new();
1197 for (i, p) in f.params.iter().enumerate() {
1198 emit_param(
1199 out,
1200 &mut c_args,
1201 &mut cleanups,
1202 &p.ty,
1203 &p.name,
1204 i,
1205 module,
1206 prefix,
1207 );
1208 }
1209
1210 let actx = format!("{c_name}_napi_actx");
1211 out.push_str(&format!(
1212 " {actx}* ctx = ({actx}*)calloc(1, sizeof({actx}));\n"
1213 ));
1214 out.push_str(" napi_value promise;\n");
1215 out.push_str(" napi_create_promise(env, &ctx->deferred, &promise);\n");
1216 out.push_str(" napi_value resource_name;\n");
1217 out.push_str(&format!(
1218 " napi_create_string_utf8(env, \"{c_name}\", NAPI_AUTO_LENGTH, &resource_name);\n"
1219 ));
1220 out.push_str(&format!(
1222 " napi_create_threadsafe_function(env, NULL, NULL, resource_name, 0, 1, NULL, NULL, NULL, {c_name}_napi_settle, &ctx->tsfn);\n"
1223 ));
1224
1225 if f.cancellable {
1226 c_args.push("NULL".into());
1227 }
1228
1229 let cb_name = format!("{c_name}_napi_cb");
1230 c_args.push(cb_name);
1231 c_args.push("ctx".into());
1232 let args_str = c_args.join(", ");
1233 out.push_str(&format!(" {c_name}_async({args_str});\n"));
1234
1235 for cleanup in &cleanups {
1236 out.push_str(cleanup);
1237 }
1238
1239 out.push_str(" return promise;\n");
1240}
1241
1242fn render_napi_body(
1243 out: &mut String,
1244 f: &FnBinding,
1245 c_name: &str,
1246 module: &str,
1247 prefix: &str,
1248 structs: &HashMap<String, StructBinding>,
1249) {
1250 let n = f.params.len();
1251 if n > 0 {
1252 out.push_str(&format!(" size_t argc = {n};\n"));
1253 out.push_str(&format!(" napi_value args[{n}];\n"));
1254 out.push_str(" napi_get_cb_info(env, info, &argc, args, NULL, NULL);\n");
1255 } else {
1256 out.push_str(" size_t argc = 0;\n");
1257 out.push_str(" napi_get_cb_info(env, info, &argc, NULL, NULL, NULL);\n");
1258 }
1259
1260 let mut c_args: Vec<String> = Vec::new();
1261 let mut cleanups: Vec<String> = Vec::new();
1262 for (i, p) in f.params.iter().enumerate() {
1263 emit_param(
1264 out,
1265 &mut c_args,
1266 &mut cleanups,
1267 &p.ty,
1268 &p.name,
1269 i,
1270 module,
1271 prefix,
1272 );
1273 }
1274
1275 out.push_str(" weaveffi_error err = {0};\n");
1276
1277 if let Some(ret) = &f.ret {
1278 emit_ret_out_params(out, &mut c_args, ret, module, prefix);
1279 }
1280 c_args.push("&err".to_string());
1281
1282 let args_str = c_args.join(", ");
1283 let ret_type = f.ret.as_ref().map(|r| c_ret_type_str(r, module, prefix));
1284 match &ret_type {
1285 Some(rt) if rt != "void" => {
1286 out.push_str(&format!(" {rt} result = {c_name}({args_str});\n"));
1287 }
1288 _ => {
1289 out.push_str(&format!(" {c_name}({args_str});\n"));
1290 }
1291 }
1292
1293 for cleanup in &cleanups {
1294 out.push_str(cleanup);
1295 }
1296
1297 out.push_str(" if (err.code != 0) {\n");
1298 out.push_str(" napi_throw_error(env, NULL, err.message);\n");
1299 out.push_str(" weaveffi_error_clear(&err);\n");
1300 out.push_str(" return NULL;\n");
1301 out.push_str(" }\n");
1302
1303 match &f.ret {
1304 Some(ret) => emit_ret_to_napi(out, ret, module, prefix, &f.name, structs),
1305 None => {
1306 out.push_str(" napi_value ret;\n");
1307 out.push_str(" napi_get_undefined(env, &ret);\n");
1308 out.push_str(" return ret;\n");
1309 }
1310 }
1311}
1312
1313#[allow(clippy::too_many_arguments)]
1314fn emit_param(
1315 out: &mut String,
1316 c_args: &mut Vec<String>,
1317 cleanups: &mut Vec<String>,
1318 ty: &TypeRef,
1319 name: &str,
1320 idx: usize,
1321 module: &str,
1322 prefix: &str,
1323) {
1324 match ty {
1325 TypeRef::I32 | TypeRef::U32 | TypeRef::I64 | TypeRef::F64 | TypeRef::Bool => {
1326 let ct = c_elem_type(ty, module, prefix);
1327 let getter = napi_getter(ty);
1328 out.push_str(&format!(" {ct} {name};\n"));
1329 out.push_str(&format!(" {getter}(env, args[{idx}], &{name});\n"));
1330 c_args.push(name.into());
1331 }
1332 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1333 out.push_str(&format!(" size_t {name}_len;\n"));
1334 out.push_str(&format!(
1335 " napi_get_value_string_utf8(env, args[{idx}], NULL, 0, &{name}_len);\n"
1336 ));
1337 out.push_str(&format!(
1338 " char* {name} = (char*)malloc({name}_len + 1);\n"
1339 ));
1340 out.push_str(&format!(
1341 " napi_get_value_string_utf8(env, args[{idx}], {name}, {name}_len + 1, &{name}_len);\n"
1342 ));
1343 c_args.push(name.into());
1344 cleanups.push(format!(" free({name});\n"));
1345 }
1346 TypeRef::Handle => {
1347 out.push_str(&format!(" int64_t {name}_raw;\n"));
1348 out.push_str(&format!(
1349 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1350 ));
1351 c_args.push(format!("(weaveffi_handle_t){name}_raw"));
1352 }
1353 TypeRef::TypedHandle(s) => {
1354 let abi = c_abi_struct_name(s, module, prefix);
1355 out.push_str(&format!(" int64_t {name}_raw;\n"));
1356 out.push_str(&format!(
1357 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1358 ));
1359 c_args.push(format!("({abi}*)(intptr_t){name}_raw"));
1360 }
1361 TypeRef::Enum(e) => {
1362 out.push_str(&format!(" int32_t {name};\n"));
1363 out.push_str(&format!(
1364 " napi_get_value_int32(env, args[{idx}], &{name});\n"
1365 ));
1366 c_args.push(format!("({prefix}_{module}_{e}){name}"));
1367 }
1368 TypeRef::Struct(s) => {
1369 let abi = c_abi_struct_name(s, module, prefix);
1370 out.push_str(&format!(" int64_t {name}_raw;\n"));
1371 out.push_str(&format!(
1372 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1373 ));
1374 c_args.push(format!("(const {abi}*)(intptr_t){name}_raw"));
1375 }
1376 TypeRef::Optional(inner) => {
1377 out.push_str(&format!(" napi_valuetype {name}_type;\n"));
1378 out.push_str(&format!(" napi_typeof(env, args[{idx}], &{name}_type);\n"));
1379 emit_optional_param(out, c_args, cleanups, inner, name, idx, module, prefix);
1380 }
1381 TypeRef::List(inner) => {
1382 emit_list_param(out, c_args, cleanups, inner, name, idx, module, prefix);
1383 }
1384 TypeRef::Bytes | TypeRef::BorrowedBytes => {
1385 out.push_str(&format!(" void* {name}_raw;\n"));
1386 out.push_str(&format!(" size_t {name}_len;\n"));
1387 out.push_str(&format!(
1388 " napi_get_buffer_info(env, args[{idx}], &{name}_raw, &{name}_len);\n"
1389 ));
1390 c_args.push(format!("(const uint8_t*){name}_raw"));
1391 c_args.push(format!("{name}_len"));
1392 }
1393 TypeRef::Map(k, v) => {
1394 emit_map_param(out, c_args, cleanups, k, v, name, idx, module, prefix);
1395 }
1396 TypeRef::Iterator(_) => unreachable!("iterator not valid as parameter"),
1397 }
1398}
1399
1400fn emit_opt_val(
1401 out: &mut String,
1402 c_args: &mut Vec<String>,
1403 c_type: &str,
1404 napi_fn: &str,
1405 name: &str,
1406 idx: usize,
1407) {
1408 out.push_str(&format!(" {c_type} {name}_val;\n"));
1409 out.push_str(&format!(" const {c_type}* {name}_ptr = NULL;\n"));
1410 out.push_str(&format!(
1411 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1412 ));
1413 out.push_str(&format!(" {napi_fn}(env, args[{idx}], &{name}_val);\n"));
1414 out.push_str(&format!(" {name}_ptr = &{name}_val;\n"));
1415 out.push_str(" }\n");
1416 c_args.push(format!("{name}_ptr"));
1417}
1418
1419#[allow(clippy::too_many_arguments)]
1420fn emit_optional_param(
1421 out: &mut String,
1422 c_args: &mut Vec<String>,
1423 cleanups: &mut Vec<String>,
1424 inner: &TypeRef,
1425 name: &str,
1426 idx: usize,
1427 module: &str,
1428 prefix: &str,
1429) {
1430 match inner {
1431 TypeRef::I32 => {
1432 emit_opt_val(out, c_args, "int32_t", "napi_get_value_int32", name, idx);
1433 }
1434 TypeRef::U32 => {
1435 emit_opt_val(out, c_args, "uint32_t", "napi_get_value_uint32", name, idx);
1436 }
1437 TypeRef::I64 => {
1438 emit_opt_val(out, c_args, "int64_t", "napi_get_value_int64", name, idx);
1439 }
1440 TypeRef::F64 => {
1441 emit_opt_val(out, c_args, "double", "napi_get_value_double", name, idx);
1442 }
1443 TypeRef::Bool => {
1444 emit_opt_val(out, c_args, "bool", "napi_get_value_bool", name, idx);
1445 }
1446 TypeRef::Handle => {
1447 out.push_str(&format!(" int64_t {name}_raw = 0;\n"));
1448 out.push_str(&format!(" weaveffi_handle_t {name}_val;\n"));
1449 out.push_str(&format!(" const weaveffi_handle_t* {name}_ptr = NULL;\n"));
1450 out.push_str(&format!(
1451 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1452 ));
1453 out.push_str(&format!(
1454 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1455 ));
1456 out.push_str(&format!(
1457 " {name}_val = (weaveffi_handle_t){name}_raw;\n"
1458 ));
1459 out.push_str(&format!(" {name}_ptr = &{name}_val;\n"));
1460 out.push_str(" }\n");
1461 c_args.push(format!("{name}_ptr"));
1462 }
1463 TypeRef::TypedHandle(s) => {
1466 let abi = c_abi_struct_name(s, module, prefix);
1467 out.push_str(&format!(" int64_t {name}_raw = 0;\n"));
1468 out.push_str(&format!(
1469 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1470 ));
1471 out.push_str(&format!(
1472 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1473 ));
1474 out.push_str(" }\n");
1475 c_args.push(format!("{name}_raw ? ({abi}*)(intptr_t){name}_raw : NULL"));
1476 }
1477 TypeRef::Enum(e) => {
1478 let etype = format!("{prefix}_{module}_{e}");
1479 out.push_str(&format!(" int32_t {name}_raw;\n"));
1480 out.push_str(&format!(" {etype} {name}_val;\n"));
1481 out.push_str(&format!(" const {etype}* {name}_ptr = NULL;\n"));
1482 out.push_str(&format!(
1483 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1484 ));
1485 out.push_str(&format!(
1486 " napi_get_value_int32(env, args[{idx}], &{name}_raw);\n"
1487 ));
1488 out.push_str(&format!(" {name}_val = ({etype}){name}_raw;\n"));
1489 out.push_str(&format!(" {name}_ptr = &{name}_val;\n"));
1490 out.push_str(" }\n");
1491 c_args.push(format!("{name}_ptr"));
1492 }
1493 TypeRef::StringUtf8 => {
1494 out.push_str(&format!(" char* {name} = NULL;\n"));
1495 out.push_str(&format!(
1496 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1497 ));
1498 out.push_str(&format!(" size_t {name}_len;\n"));
1499 out.push_str(&format!(
1500 " napi_get_value_string_utf8(env, args[{idx}], NULL, 0, &{name}_len);\n"
1501 ));
1502 out.push_str(&format!(" {name} = (char*)malloc({name}_len + 1);\n"));
1503 out.push_str(&format!(
1504 " napi_get_value_string_utf8(env, args[{idx}], {name}, {name}_len + 1, &{name}_len);\n"
1505 ));
1506 out.push_str(" }\n");
1507 c_args.push(name.into());
1508 cleanups.push(format!(" free({name});\n"));
1509 }
1510 TypeRef::Struct(s) => {
1511 let abi = c_abi_struct_name(s, module, prefix);
1512 out.push_str(&format!(" int64_t {name}_raw = 0;\n"));
1513 out.push_str(&format!(
1514 " if ({name}_type != napi_null && {name}_type != napi_undefined) {{\n"
1515 ));
1516 out.push_str(&format!(
1517 " napi_get_value_int64(env, args[{idx}], &{name}_raw);\n"
1518 ));
1519 out.push_str(" }\n");
1520 c_args.push(format!(
1521 "{name}_raw ? (const {abi}*)(intptr_t){name}_raw : NULL"
1522 ));
1523 }
1524 _ => {
1525 emit_param(out, c_args, cleanups, inner, name, idx, module, prefix);
1526 }
1527 }
1528}
1529
1530#[allow(clippy::too_many_arguments)]
1531fn emit_list_param(
1532 out: &mut String,
1533 c_args: &mut Vec<String>,
1534 cleanups: &mut Vec<String>,
1535 inner: &TypeRef,
1536 name: &str,
1537 idx: usize,
1538 module: &str,
1539 prefix: &str,
1540) {
1541 let et = c_elem_type(inner, module, prefix);
1542 out.push_str(&format!(" uint32_t {name}_count;\n"));
1543 out.push_str(&format!(
1544 " napi_get_array_length(env, args[{idx}], &{name}_count);\n"
1545 ));
1546 out.push_str(&format!(
1547 " {et}* {name}_arr = ({et}*)malloc(sizeof({et}) * ({name}_count + 1));\n"
1548 ));
1549 out.push_str(&format!(
1550 " for (uint32_t {name}_i = 0; {name}_i < {name}_count; {name}_i++) {{\n"
1551 ));
1552 out.push_str(&format!(" napi_value {name}_el;\n"));
1553 out.push_str(&format!(
1554 " napi_get_element(env, args[{idx}], {name}_i, &{name}_el);\n"
1555 ));
1556
1557 match inner {
1558 TypeRef::I32 | TypeRef::U32 | TypeRef::I64 | TypeRef::F64 | TypeRef::Bool => {
1559 let getter = napi_getter(inner);
1560 out.push_str(&format!(
1561 " {getter}(env, {name}_el, &{name}_arr[{name}_i]);\n"
1562 ));
1563 }
1564 TypeRef::Handle => {
1565 out.push_str(&format!(" int64_t {name}_h;\n"));
1566 out.push_str(&format!(
1567 " napi_get_value_int64(env, {name}_el, &{name}_h);\n"
1568 ));
1569 out.push_str(&format!(
1570 " {name}_arr[{name}_i] = (weaveffi_handle_t){name}_h;\n"
1571 ));
1572 }
1573 TypeRef::TypedHandle(s) => {
1574 let abi = c_abi_struct_name(s, module, prefix);
1575 out.push_str(&format!(" int64_t {name}_h;\n"));
1576 out.push_str(&format!(
1577 " napi_get_value_int64(env, {name}_el, &{name}_h);\n"
1578 ));
1579 out.push_str(&format!(
1580 " {name}_arr[{name}_i] = ({abi}*)(intptr_t){name}_h;\n"
1581 ));
1582 }
1583 TypeRef::Enum(_) => {
1584 out.push_str(&format!(" int32_t {name}_ev;\n"));
1585 out.push_str(&format!(
1586 " napi_get_value_int32(env, {name}_el, &{name}_ev);\n"
1587 ));
1588 out.push_str(&format!(" {name}_arr[{name}_i] = ({et}){name}_ev;\n"));
1589 }
1590 TypeRef::StringUtf8 => {
1591 out.push_str(&format!(" size_t {name}_sl;\n"));
1592 out.push_str(&format!(
1593 " napi_get_value_string_utf8(env, {name}_el, NULL, 0, &{name}_sl);\n"
1594 ));
1595 out.push_str(&format!(
1596 " char* {name}_s = (char*)malloc({name}_sl + 1);\n"
1597 ));
1598 out.push_str(&format!(
1599 " napi_get_value_string_utf8(env, {name}_el, {name}_s, {name}_sl + 1, &{name}_sl);\n"
1600 ));
1601 out.push_str(&format!(" {name}_arr[{name}_i] = {name}_s;\n"));
1602 }
1603 TypeRef::Struct(_) => {
1604 out.push_str(&format!(" int64_t {name}_sp;\n"));
1605 out.push_str(&format!(
1606 " napi_get_value_int64(env, {name}_el, &{name}_sp);\n"
1607 ));
1608 out.push_str(&format!(
1609 " {name}_arr[{name}_i] = ({et})(intptr_t){name}_sp;\n"
1610 ));
1611 }
1612 _ => {
1613 let getter = napi_getter(inner);
1614 out.push_str(&format!(
1615 " {getter}(env, {name}_el, &{name}_arr[{name}_i]);\n"
1616 ));
1617 }
1618 }
1619
1620 out.push_str(" }\n");
1621 c_args.push(format!("{name}_arr"));
1622 c_args.push(format!("(size_t){name}_count"));
1623
1624 if matches!(inner, TypeRef::StringUtf8) {
1625 cleanups.push(format!(
1626 " for (uint32_t {name}_j = 0; {name}_j < {name}_count; {name}_j++) free((void*){name}_arr[{name}_j]);\n"
1627 ));
1628 }
1629 cleanups.push(format!(" free({name}_arr);\n"));
1630}
1631
1632#[allow(clippy::too_many_arguments)]
1633fn emit_map_param(
1634 out: &mut String,
1635 c_args: &mut Vec<String>,
1636 cleanups: &mut Vec<String>,
1637 k: &TypeRef,
1638 v: &TypeRef,
1639 name: &str,
1640 idx: usize,
1641 module: &str,
1642 prefix: &str,
1643) {
1644 let kt = c_elem_type(k, module, prefix);
1645 let vt = c_elem_type(v, module, prefix);
1646 out.push_str(&format!(" napi_value {name}_keys_napi;\n"));
1647 out.push_str(&format!(
1648 " napi_get_property_names(env, args[{idx}], &{name}_keys_napi);\n"
1649 ));
1650 out.push_str(&format!(" uint32_t {name}_count;\n"));
1651 out.push_str(&format!(
1652 " napi_get_array_length(env, {name}_keys_napi, &{name}_count);\n"
1653 ));
1654 out.push_str(&format!(
1655 " {kt}* {name}_keys = ({kt}*)malloc(sizeof({kt}) * ({name}_count + 1));\n"
1656 ));
1657 out.push_str(&format!(
1658 " {vt}* {name}_values = ({vt}*)malloc(sizeof({vt}) * ({name}_count + 1));\n"
1659 ));
1660 out.push_str(&format!(
1661 " for (uint32_t {name}_i = 0; {name}_i < {name}_count; {name}_i++) {{\n"
1662 ));
1663 out.push_str(&format!(" napi_value {name}_k;\n"));
1664 out.push_str(&format!(
1665 " napi_get_element(env, {name}_keys_napi, {name}_i, &{name}_k);\n"
1666 ));
1667
1668 if matches!(k, TypeRef::StringUtf8) {
1669 out.push_str(&format!(" size_t {name}_kl;\n"));
1670 out.push_str(&format!(
1671 " napi_get_value_string_utf8(env, {name}_k, NULL, 0, &{name}_kl);\n"
1672 ));
1673 out.push_str(&format!(
1674 " char* {name}_ks = (char*)malloc({name}_kl + 1);\n"
1675 ));
1676 out.push_str(&format!(
1677 " napi_get_value_string_utf8(env, {name}_k, {name}_ks, {name}_kl + 1, &{name}_kl);\n"
1678 ));
1679 out.push_str(&format!(" {name}_keys[{name}_i] = {name}_ks;\n"));
1680 } else {
1681 out.push_str(&format!(" napi_value {name}_kn;\n"));
1682 out.push_str(&format!(
1683 " napi_coerce_to_number(env, {name}_k, &{name}_kn);\n"
1684 ));
1685 let kgetter = napi_getter(k);
1686 out.push_str(&format!(
1687 " {kgetter}(env, {name}_kn, &{name}_keys[{name}_i]);\n"
1688 ));
1689 }
1690
1691 out.push_str(&format!(" napi_value {name}_v;\n"));
1692 out.push_str(&format!(
1693 " napi_get_property(env, args[{idx}], {name}_k, &{name}_v);\n"
1694 ));
1695
1696 if matches!(v, TypeRef::StringUtf8) {
1697 out.push_str(&format!(" size_t {name}_vl;\n"));
1698 out.push_str(&format!(
1699 " napi_get_value_string_utf8(env, {name}_v, NULL, 0, &{name}_vl);\n"
1700 ));
1701 out.push_str(&format!(
1702 " char* {name}_vs = (char*)malloc({name}_vl + 1);\n"
1703 ));
1704 out.push_str(&format!(
1705 " napi_get_value_string_utf8(env, {name}_v, {name}_vs, {name}_vl + 1, &{name}_vl);\n"
1706 ));
1707 out.push_str(&format!(" {name}_values[{name}_i] = {name}_vs;\n"));
1708 } else {
1709 let vgetter = napi_getter(v);
1710 out.push_str(&format!(
1711 " {vgetter}(env, {name}_v, &{name}_values[{name}_i]);\n"
1712 ));
1713 }
1714
1715 out.push_str(" }\n");
1716 c_args.push(format!("{name}_keys"));
1717 c_args.push(format!("{name}_values"));
1718 c_args.push(format!("(size_t){name}_count"));
1719
1720 if matches!(k, TypeRef::StringUtf8) {
1721 cleanups.push(format!(
1722 " for (uint32_t {name}_j = 0; {name}_j < {name}_count; {name}_j++) free((void*){name}_keys[{name}_j]);\n"
1723 ));
1724 }
1725 cleanups.push(format!(" free({name}_keys);\n"));
1726 if matches!(v, TypeRef::StringUtf8) {
1727 cleanups.push(format!(
1728 " for (uint32_t {name}_j = 0; {name}_j < {name}_count; {name}_j++) free((void*){name}_values[{name}_j]);\n"
1729 ));
1730 }
1731 cleanups.push(format!(" free({name}_values);\n"));
1732}
1733
1734fn emit_ret_out_params(
1735 out: &mut String,
1736 c_args: &mut Vec<String>,
1737 ty: &TypeRef,
1738 module: &str,
1739 prefix: &str,
1740) {
1741 match ty {
1742 TypeRef::Bytes | TypeRef::List(_) => {
1743 out.push_str(" size_t out_len;\n");
1744 c_args.push("&out_len".into());
1745 }
1746 TypeRef::Map(k, v) => {
1747 let kt = c_elem_type(k, module, prefix);
1748 let vt = c_elem_type(v, module, prefix);
1749 out.push_str(&format!(" {kt}* out_keys = NULL;\n"));
1750 out.push_str(&format!(" {vt}* out_values = NULL;\n"));
1751 out.push_str(" size_t out_len = 0;\n");
1752 c_args.push("out_keys".into());
1753 c_args.push("out_values".into());
1754 c_args.push("&out_len".into());
1755 }
1756 TypeRef::Optional(inner) if is_c_ptr_type(inner) => {
1757 emit_ret_out_params(out, c_args, inner, module, prefix);
1758 }
1759 _ => {}
1760 }
1761}
1762
1763fn struct_registry(model: &BindingModel) -> HashMap<String, StructBinding> {
1767 model
1768 .modules
1769 .iter()
1770 .flat_map(|m| m.structs.iter())
1771 .map(|s| (s.name.clone(), s.clone()))
1772 .collect()
1773}
1774
1775#[allow(clippy::too_many_arguments)]
1780fn emit_struct_to_object(
1781 out: &mut String,
1782 env: &str,
1783 struct_name: &str,
1784 ptr_expr: &str,
1785 obj_var: &str,
1786 module: &str,
1787 prefix: &str,
1788 structs: &HashMap<String, StructBinding>,
1789 indent: &str,
1790 destroy: bool,
1791) {
1792 let Some(def) = structs.get(local_type_name(struct_name)).cloned() else {
1793 out.push_str(&format!(
1795 "{indent}napi_create_int64({env}, (int64_t)(intptr_t){ptr_expr}, &{obj_var});\n"
1796 ));
1797 return;
1798 };
1799 let abi = &def.c_tag;
1800 let p = format!("{obj_var}_p");
1801 out.push_str(&format!("{indent}{{\n"));
1802 out.push_str(&format!("{indent} {abi}* {p} = ({abi}*){ptr_expr};\n"));
1803 out.push_str(&format!(
1804 "{indent} napi_create_object({env}, &{obj_var});\n"
1805 ));
1806 for field in &def.fields {
1807 let getter = &field.getter_symbol;
1808 let fv = format!("{obj_var}_{}", field.name);
1809 out.push_str(&format!("{indent} napi_value {fv};\n"));
1810 emit_struct_field_to_napi(
1811 out,
1812 env,
1813 &field.ty,
1814 getter,
1815 &p,
1816 &fv,
1817 module,
1818 prefix,
1819 structs,
1820 &format!("{indent} "),
1821 );
1822 out.push_str(&format!(
1823 "{indent} napi_set_named_property({env}, {obj_var}, \"{}\", {fv});\n",
1824 field.name
1825 ));
1826 }
1827 if destroy {
1828 out.push_str(&format!("{indent} {}({p});\n", def.destroy_symbol));
1829 }
1830 out.push_str(&format!("{indent}}}\n"));
1831}
1832
1833fn napi_create_leaf(env: &str, ty: &TypeRef, expr: &str, target: &str) -> String {
1837 match ty {
1838 TypeRef::I32 => format!("napi_create_int32({env}, {expr}, &{target});"),
1839 TypeRef::U32 => format!("napi_create_uint32({env}, {expr}, &{target});"),
1840 TypeRef::I64 => format!("napi_create_int64({env}, {expr}, &{target});"),
1841 TypeRef::F64 => format!("napi_create_double({env}, {expr}, &{target});"),
1842 TypeRef::Bool => format!("napi_get_boolean({env}, {expr}, &{target});"),
1843 TypeRef::Enum(_) => format!("napi_create_int32({env}, (int32_t)({expr}), &{target});"),
1844 TypeRef::Handle | TypeRef::TypedHandle(_) => {
1845 format!("napi_create_int64({env}, (int64_t)(intptr_t)({expr}), &{target});")
1846 }
1847 _ => format!("napi_get_null({env}, &{target});"),
1848 }
1849}
1850
1851#[allow(clippy::too_many_arguments)]
1855fn emit_elem_to_napi(
1856 out: &mut String,
1857 env: &str,
1858 ty: &TypeRef,
1859 expr: &str,
1860 target: &str,
1861 module: &str,
1862 prefix: &str,
1863 structs: &HashMap<String, StructBinding>,
1864 indent: &str,
1865) {
1866 match ty {
1867 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1868 out.push_str(&format!(
1869 "{indent}napi_create_string_utf8({env}, {expr}, NAPI_AUTO_LENGTH, &{target});\n"
1870 ));
1871 if matches!(ty, TypeRef::StringUtf8) {
1872 out.push_str(&format!("{indent}weaveffi_free_string((char*)({expr}));\n"));
1873 }
1874 }
1875 TypeRef::Struct(name) => {
1876 emit_struct_to_object(
1877 out, env, name, expr, target, module, prefix, structs, indent, false,
1878 );
1879 }
1880 _ => out.push_str(&format!(
1881 "{indent}{}\n",
1882 napi_create_leaf(env, ty, expr, target)
1883 )),
1884 }
1885}
1886
1887#[allow(clippy::too_many_arguments)]
1891fn emit_struct_field_to_napi(
1892 out: &mut String,
1893 env: &str,
1894 ty: &TypeRef,
1895 getter: &str,
1896 pv: &str,
1897 fv: &str,
1898 module: &str,
1899 prefix: &str,
1900 structs: &HashMap<String, StructBinding>,
1901 indent: &str,
1902) {
1903 match ty {
1904 TypeRef::I32 => out.push_str(&format!(
1905 "{indent}napi_create_int32({env}, {getter}({pv}), &{fv});\n"
1906 )),
1907 TypeRef::U32 => out.push_str(&format!(
1908 "{indent}napi_create_uint32({env}, {getter}({pv}), &{fv});\n"
1909 )),
1910 TypeRef::I64 => out.push_str(&format!(
1911 "{indent}napi_create_int64({env}, {getter}({pv}), &{fv});\n"
1912 )),
1913 TypeRef::F64 => out.push_str(&format!(
1914 "{indent}napi_create_double({env}, {getter}({pv}), &{fv});\n"
1915 )),
1916 TypeRef::Bool => out.push_str(&format!(
1917 "{indent}napi_get_boolean({env}, {getter}({pv}), &{fv});\n"
1918 )),
1919 TypeRef::Enum(_) => out.push_str(&format!(
1920 "{indent}napi_create_int32({env}, (int32_t){getter}({pv}), &{fv});\n"
1921 )),
1922 TypeRef::Handle | TypeRef::TypedHandle(_) => out.push_str(&format!(
1923 "{indent}napi_create_int64({env}, (int64_t)(intptr_t){getter}({pv}), &{fv});\n"
1924 )),
1925 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
1926 let owned = matches!(ty, TypeRef::StringUtf8);
1927 out.push_str(&format!("{indent}{{\n"));
1928 out.push_str(&format!(
1929 "{indent} char* {fv}_s = (char*){getter}({pv});\n"
1930 ));
1931 out.push_str(&format!(
1932 "{indent} napi_create_string_utf8({env}, {fv}_s, NAPI_AUTO_LENGTH, &{fv});\n"
1933 ));
1934 if owned {
1935 out.push_str(&format!("{indent} weaveffi_free_string({fv}_s);\n"));
1936 }
1937 out.push_str(&format!("{indent}}}\n"));
1938 }
1939 TypeRef::Struct(name) => {
1940 emit_struct_to_object(
1941 out,
1942 env,
1943 name,
1944 &format!("{getter}({pv})"),
1945 fv,
1946 module,
1947 prefix,
1948 structs,
1949 indent,
1950 true,
1951 );
1952 }
1953 TypeRef::Optional(inner)
1954 if matches!(inner.as_ref(), TypeRef::StringUtf8 | TypeRef::BorrowedStr) =>
1955 {
1956 let owned = matches!(inner.as_ref(), TypeRef::StringUtf8);
1957 out.push_str(&format!("{indent}{{\n"));
1958 out.push_str(&format!(
1959 "{indent} char* {fv}_s = (char*){getter}({pv});\n"
1960 ));
1961 out.push_str(&format!(
1962 "{indent} if ({fv}_s == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
1963 ));
1964 out.push_str(&format!(
1965 "{indent} else {{ napi_create_string_utf8({env}, {fv}_s, NAPI_AUTO_LENGTH, &{fv});"
1966 ));
1967 if owned {
1968 out.push_str(&format!(" weaveffi_free_string({fv}_s);"));
1969 }
1970 out.push_str(" }\n");
1971 out.push_str(&format!("{indent}}}\n"));
1972 }
1973 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::Struct(_)) => {
1974 let TypeRef::Struct(name) = inner.as_ref() else {
1975 unreachable!()
1976 };
1977 let abi = c_abi_struct_name(name, module, prefix);
1978 out.push_str(&format!("{indent}{{\n"));
1979 out.push_str(&format!("{indent} {abi}* {fv}_sp = {getter}({pv});\n"));
1980 out.push_str(&format!(
1981 "{indent} if ({fv}_sp == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
1982 ));
1983 out.push_str(&format!("{indent} else {{\n"));
1984 emit_struct_to_object(
1985 out,
1986 env,
1987 name,
1988 &format!("{fv}_sp"),
1989 fv,
1990 module,
1991 prefix,
1992 structs,
1993 &format!("{indent} "),
1994 true,
1995 );
1996 out.push_str(&format!("{indent} }}\n"));
1997 out.push_str(&format!("{indent}}}\n"));
1998 }
1999 TypeRef::Optional(inner) if matches!(inner.as_ref(), TypeRef::TypedHandle(_)) => {
2003 let TypeRef::TypedHandle(name) = inner.as_ref() else {
2004 unreachable!()
2005 };
2006 let abi = c_abi_struct_name(name, module, prefix);
2007 out.push_str(&format!("{indent}{{\n"));
2008 out.push_str(&format!("{indent} {abi}* {fv}_h = {getter}({pv});\n"));
2009 out.push_str(&format!(
2010 "{indent} if ({fv}_h == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2011 ));
2012 out.push_str(&format!(
2013 "{indent} else {{ napi_create_int64({env}, (int64_t)(intptr_t){fv}_h, &{fv}); }}\n"
2014 ));
2015 out.push_str(&format!("{indent}}}\n"));
2016 }
2017 TypeRef::Optional(inner) => {
2020 let ct = c_elem_type(inner, module, prefix);
2021 out.push_str(&format!("{indent}{{\n"));
2022 out.push_str(&format!("{indent} {ct}* {fv}_p = {getter}({pv});\n"));
2023 out.push_str(&format!(
2024 "{indent} if ({fv}_p == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2025 ));
2026 out.push_str(&format!(
2027 "{indent} else {{ {} }}\n",
2028 napi_create_leaf(env, inner, &format!("*{fv}_p"), fv)
2029 ));
2030 out.push_str(&format!("{indent}}}\n"));
2031 }
2032 TypeRef::Bytes | TypeRef::BorrowedBytes => {
2033 out.push_str(&format!("{indent}{{\n"));
2034 out.push_str(&format!("{indent} size_t {fv}_len;\n"));
2035 out.push_str(&format!(
2036 "{indent} const uint8_t* {fv}_data = (const uint8_t*){getter}({pv}, &{fv}_len);\n"
2037 ));
2038 out.push_str(&format!(
2039 "{indent} if ({fv}_data == NULL) {{ napi_get_null({env}, &{fv}); }}\n"
2040 ));
2041 out.push_str(&format!(
2042 "{indent} else {{ void* {fv}_buf; napi_create_buffer_copy({env}, {fv}_len, {fv}_data, &{fv}_buf, &{fv}); }}\n"
2043 ));
2044 out.push_str(&format!("{indent}}}\n"));
2045 }
2046 TypeRef::List(inner) => {
2047 let et = c_elem_type(inner, module, prefix);
2048 out.push_str(&format!("{indent}{{\n"));
2049 out.push_str(&format!("{indent} size_t {fv}_len;\n"));
2050 out.push_str(&format!(
2051 "{indent} {et}* {fv}_arr = {getter}({pv}, &{fv}_len);\n"
2052 ));
2053 out.push_str(&format!("{indent} napi_create_array({env}, &{fv});\n"));
2054 out.push_str(&format!("{indent} if ({fv}_arr != NULL) {{\n"));
2055 out.push_str(&format!(
2056 "{indent} for (size_t {fv}_i = 0; {fv}_i < {fv}_len; {fv}_i++) {{\n"
2057 ));
2058 out.push_str(&format!("{indent} napi_value {fv}_e;\n"));
2059 emit_elem_to_napi(
2060 out,
2061 env,
2062 inner,
2063 &format!("{fv}_arr[{fv}_i]"),
2064 &format!("{fv}_e"),
2065 module,
2066 prefix,
2067 structs,
2068 &format!("{indent} "),
2069 );
2070 out.push_str(&format!(
2071 "{indent} napi_set_element({env}, {fv}, (uint32_t){fv}_i, {fv}_e);\n"
2072 ));
2073 out.push_str(&format!("{indent} }}\n"));
2074 out.push_str(&format!("{indent} }}\n"));
2075 out.push_str(&format!("{indent}}}\n"));
2076 }
2077 TypeRef::Map(k, v) => {
2078 let kt = c_elem_type(k, module, prefix);
2079 let vt = c_elem_type(v, module, prefix);
2080 out.push_str(&format!("{indent}{{\n"));
2081 out.push_str(&format!("{indent} {kt}* {fv}_keys = NULL;\n"));
2082 out.push_str(&format!("{indent} {vt}* {fv}_vals = NULL;\n"));
2083 out.push_str(&format!("{indent} size_t {fv}_len;\n"));
2084 out.push_str(&format!(
2085 "{indent} {getter}({pv}, &{fv}_keys, &{fv}_vals, &{fv}_len);\n"
2086 ));
2087 out.push_str(&format!("{indent} napi_create_object({env}, &{fv});\n"));
2088 out.push_str(&format!(
2089 "{indent} if ({fv}_keys != NULL && {fv}_vals != NULL) {{\n"
2090 ));
2091 out.push_str(&format!(
2092 "{indent} for (size_t {fv}_i = 0; {fv}_i < {fv}_len; {fv}_i++) {{\n"
2093 ));
2094 out.push_str(&format!("{indent} napi_value {fv}_v;\n"));
2095 emit_elem_to_napi(
2096 out,
2097 env,
2098 v,
2099 &format!("{fv}_vals[{fv}_i]"),
2100 &format!("{fv}_v"),
2101 module,
2102 prefix,
2103 structs,
2104 &format!("{indent} "),
2105 );
2106 match k.as_ref() {
2107 TypeRef::StringUtf8 | TypeRef::BorrowedStr => {
2108 out.push_str(&format!(
2109 "{indent} napi_set_named_property({env}, {fv}, {fv}_keys[{fv}_i], {fv}_v);\n"
2110 ));
2111 if matches!(k.as_ref(), TypeRef::StringUtf8) {
2112 out.push_str(&format!(
2113 "{indent} weaveffi_free_string((char*){fv}_keys[{fv}_i]);\n"
2114 ));
2115 }
2116 }
2117 other => {
2118 out.push_str(&format!("{indent} napi_value {fv}_k;\n"));
2119 out.push_str(&format!(
2120 "{indent} {}\n",
2121 napi_create_leaf(
2122 env,
2123 other,
2124 &format!("{fv}_keys[{fv}_i]"),
2125 &format!("{fv}_k")
2126 )
2127 ));
2128 out.push_str(&format!(
2129 "{indent} napi_set_property({env}, {fv}, {fv}_k, {fv}_v);\n"
2130 ));
2131 }
2132 }
2133 out.push_str(&format!("{indent} }}\n"));
2134 out.push_str(&format!("{indent} }}\n"));
2135 out.push_str(&format!("{indent}}}\n"));
2136 }
2137 _ => out.push_str(&format!("{indent}napi_get_null({env}, &{fv});\n")),
2138 }
2139}
2140
2141fn emit_ret_to_napi(
2142 out: &mut String,
2143 ty: &TypeRef,
2144 module: &str,
2145 prefix: &str,
2146 fn_name: &str,
2147 structs: &HashMap<String, StructBinding>,
2148) {
2149 out.push_str(" napi_value ret;\n");
2150 match ty {
2151 TypeRef::I32 => out.push_str(" napi_create_int32(env, result, &ret);\n"),
2152 TypeRef::U32 => out.push_str(" napi_create_uint32(env, result, &ret);\n"),
2153 TypeRef::I64 => out.push_str(" napi_create_int64(env, result, &ret);\n"),
2154 TypeRef::F64 => out.push_str(" napi_create_double(env, result, &ret);\n"),
2155 TypeRef::Bool => out.push_str(" napi_get_boolean(env, result, &ret);\n"),
2156 TypeRef::StringUtf8 => {
2157 out.push_str(" napi_create_string_utf8(env, result, NAPI_AUTO_LENGTH, &ret);\n");
2158 out.push_str(" weaveffi_free_string(result);\n");
2159 }
2160 TypeRef::BorrowedStr => {
2161 out.push_str(" napi_create_string_utf8(env, result, NAPI_AUTO_LENGTH, &ret);\n");
2162 }
2163 TypeRef::TypedHandle(_) | TypeRef::Handle => {
2164 out.push_str(" napi_create_int64(env, (int64_t)(intptr_t)result, &ret);\n");
2165 }
2166 TypeRef::Struct(name) => {
2167 emit_struct_to_object(
2168 out, "env", name, "result", "ret", module, prefix, structs, " ", true,
2169 );
2170 }
2171 TypeRef::Enum(_) => {
2172 out.push_str(" napi_create_int32(env, (int32_t)result, &ret);\n");
2173 }
2174 TypeRef::Bytes => {
2175 out.push_str(" napi_create_buffer_copy(env, out_len, result, NULL, &ret);\n");
2176 out.push_str(" weaveffi_free_bytes((uint8_t*)result, out_len);\n");
2177 }
2178 TypeRef::BorrowedBytes => {
2179 out.push_str(" napi_create_buffer_copy(env, out_len, result, NULL, &ret);\n");
2180 }
2181 TypeRef::Optional(inner) => {
2182 out.push_str(" if (result == NULL) {\n");
2183 out.push_str(" napi_get_null(env, &ret);\n");
2184 out.push_str(" } else {\n");
2185 emit_optional_ret_inner(out, inner, module, prefix, structs);
2186 out.push_str(" }\n");
2187 }
2188 TypeRef::List(inner) => emit_list_ret(out, inner, module, prefix, " ", structs),
2189 TypeRef::Map(_, _) => {
2190 out.push_str(" napi_create_object(env, &ret);\n");
2191 }
2192 TypeRef::Iterator(inner) => {
2193 let fn_pascal = fn_name.to_upper_camel_case();
2194 let iter_type = format!("{prefix}_{module}_{fn_pascal}Iterator");
2195 let et = c_elem_type(inner, module, prefix);
2196 out.push_str(" napi_create_array(env, &ret);\n");
2197 out.push_str(" uint32_t iter_idx = 0;\n");
2198 out.push_str(&format!(" {et} iter_item;\n"));
2199 out.push_str(" weaveffi_error iter_err = {0};\n");
2203 out.push_str(&format!(
2204 " while ({iter_type}_next(result, &iter_item, &iter_err)) {{\n"
2205 ));
2206 out.push_str(" napi_value elem;\n");
2207 match inner.as_ref() {
2208 TypeRef::I32 => {
2209 out.push_str(" napi_create_int32(env, iter_item, &elem);\n");
2210 }
2211 TypeRef::U32 => {
2212 out.push_str(" napi_create_uint32(env, iter_item, &elem);\n");
2213 }
2214 TypeRef::I64 => {
2215 out.push_str(" napi_create_int64(env, iter_item, &elem);\n");
2216 }
2217 TypeRef::F64 => {
2218 out.push_str(" napi_create_double(env, iter_item, &elem);\n");
2219 }
2220 TypeRef::Bool => {
2221 out.push_str(" napi_get_boolean(env, iter_item, &elem);\n");
2222 }
2223 TypeRef::TypedHandle(_) | TypeRef::Handle => {
2224 out.push_str(
2225 " napi_create_int64(env, (int64_t)(intptr_t)iter_item, &elem);\n",
2226 );
2227 }
2228 TypeRef::StringUtf8 => {
2229 out.push_str(
2230 " napi_create_string_utf8(env, iter_item, NAPI_AUTO_LENGTH, &elem);\n",
2231 );
2232 out.push_str(" weaveffi_free_string(iter_item);\n");
2233 }
2234 TypeRef::Struct(_) | TypeRef::Enum(_) => {
2235 out.push_str(
2236 " napi_create_int64(env, (int64_t)(intptr_t)iter_item, &elem);\n",
2237 );
2238 }
2239 _ => {
2240 out.push_str(" napi_create_int64(env, (int64_t)iter_item, &elem);\n");
2241 }
2242 }
2243 out.push_str(" napi_set_element(env, ret, iter_idx++, elem);\n");
2244 out.push_str(" }\n");
2245 out.push_str(&format!(" {iter_type}_destroy(result);\n"));
2246 }
2247 }
2248 out.push_str(" return ret;\n");
2249}
2250
2251fn emit_optional_ret_inner(
2252 out: &mut String,
2253 inner: &TypeRef,
2254 module: &str,
2255 prefix: &str,
2256 structs: &HashMap<String, StructBinding>,
2257) {
2258 match inner {
2259 TypeRef::I32 => {
2260 out.push_str(" napi_create_int32(env, *result, &ret);\n");
2261 out.push_str(" free(result);\n");
2262 }
2263 TypeRef::U32 => {
2264 out.push_str(" napi_create_uint32(env, *result, &ret);\n");
2265 out.push_str(" free(result);\n");
2266 }
2267 TypeRef::I64 => {
2268 out.push_str(" napi_create_int64(env, *result, &ret);\n");
2269 out.push_str(" free(result);\n");
2270 }
2271 TypeRef::F64 => {
2272 out.push_str(" napi_create_double(env, *result, &ret);\n");
2273 out.push_str(" free(result);\n");
2274 }
2275 TypeRef::Bool => {
2276 out.push_str(" napi_get_boolean(env, *result, &ret);\n");
2277 out.push_str(" free(result);\n");
2278 }
2279 TypeRef::TypedHandle(_) | TypeRef::Handle => {
2280 out.push_str(" napi_create_int64(env, (int64_t)(intptr_t)*result, &ret);\n");
2281 out.push_str(" free(result);\n");
2282 }
2283 TypeRef::Enum(_) => {
2284 out.push_str(" napi_create_int32(env, (int32_t)*result, &ret);\n");
2285 out.push_str(" free(result);\n");
2286 }
2287 TypeRef::StringUtf8 => {
2288 out.push_str(" napi_create_string_utf8(env, result, NAPI_AUTO_LENGTH, &ret);\n");
2289 out.push_str(" weaveffi_free_string(result);\n");
2290 }
2291 TypeRef::Struct(name) => {
2292 emit_struct_to_object(
2293 out, "env", name, "result", "ret", module, prefix, structs, " ", true,
2294 );
2295 }
2296 TypeRef::List(li) => emit_list_ret(out, li, module, prefix, " ", structs),
2297 _ => out.push_str(" napi_get_null(env, &ret);\n"),
2298 }
2299}
2300
2301fn emit_list_ret(
2302 out: &mut String,
2303 inner: &TypeRef,
2304 module: &str,
2305 prefix: &str,
2306 ind: &str,
2307 structs: &HashMap<String, StructBinding>,
2308) {
2309 out.push_str(&format!(
2310 "{ind}napi_create_array_with_length(env, out_len, &ret);\n"
2311 ));
2312 out.push_str(&format!(
2313 "{ind}for (size_t ret_i = 0; ret_i < out_len; ret_i++) {{\n"
2314 ));
2315 out.push_str(&format!("{ind} napi_value elem;\n"));
2316 match inner {
2317 TypeRef::I32 => out.push_str(&format!(
2318 "{ind} napi_create_int32(env, result[ret_i], &elem);\n"
2319 )),
2320 TypeRef::U32 => out.push_str(&format!(
2321 "{ind} napi_create_uint32(env, result[ret_i], &elem);\n"
2322 )),
2323 TypeRef::I64 => out.push_str(&format!(
2324 "{ind} napi_create_int64(env, result[ret_i], &elem);\n"
2325 )),
2326 TypeRef::F64 => out.push_str(&format!(
2327 "{ind} napi_create_double(env, result[ret_i], &elem);\n"
2328 )),
2329 TypeRef::Bool => out.push_str(&format!(
2330 "{ind} napi_get_boolean(env, result[ret_i], &elem);\n"
2331 )),
2332 TypeRef::TypedHandle(_) | TypeRef::Handle => out.push_str(&format!(
2333 "{ind} napi_create_int64(env, (int64_t)(intptr_t)result[ret_i], &elem);\n"
2334 )),
2335 TypeRef::StringUtf8 => {
2336 out.push_str(&format!(
2337 "{ind} napi_create_string_utf8(env, result[ret_i], NAPI_AUTO_LENGTH, &elem);\n"
2338 ));
2339 out.push_str(&format!("{ind} weaveffi_free_string(result[ret_i]);\n"));
2340 }
2341 TypeRef::Enum(_) => out.push_str(&format!(
2342 "{ind} napi_create_int32(env, (int32_t)result[ret_i], &elem);\n"
2343 )),
2344 TypeRef::Struct(name) => {
2345 let elem_indent = format!("{ind} ");
2346 emit_struct_to_object(
2347 out,
2348 "env",
2349 name,
2350 "result[ret_i]",
2351 "elem",
2352 module,
2353 prefix,
2354 structs,
2355 &elem_indent,
2356 true,
2357 );
2358 }
2359 _ => out.push_str(&format!(
2360 "{ind} napi_create_int64(env, (int64_t)result[ret_i], &elem);\n"
2361 )),
2362 }
2363 out.push_str(&format!(
2364 "{ind} napi_set_element(env, ret, (uint32_t)ret_i, elem);\n"
2365 ));
2366 out.push_str(&format!("{ind}}}\n"));
2367 out.push_str(&format!("{ind}free(result);\n"));
2368}
2369
2370fn ts_type_for(ty: &TypeRef) -> String {
2371 match ty {
2372 TypeRef::I32 | TypeRef::U32 | TypeRef::I64 | TypeRef::F64 => "number".into(),
2373 TypeRef::Bool => "boolean".into(),
2374 TypeRef::StringUtf8 | TypeRef::BorrowedStr => "string".into(),
2375 TypeRef::Bytes | TypeRef::BorrowedBytes => "Buffer".into(),
2376 TypeRef::Handle => "bigint".into(),
2377 TypeRef::TypedHandle(name) => local_type_name(name).to_string(),
2382 TypeRef::Struct(name) => local_type_name(name).to_string(),
2383 TypeRef::Enum(name) => local_type_name(name).to_string(),
2384 TypeRef::Optional(inner) => format!("{} | null", ts_type_for(inner)),
2385 TypeRef::List(inner) => {
2386 let inner_ts = ts_type_for(inner);
2387 if matches!(inner.as_ref(), TypeRef::Optional(_)) {
2388 format!("({inner_ts})[]")
2389 } else {
2390 format!("{inner_ts}[]")
2391 }
2392 }
2393 TypeRef::Map(k, v) => format!("Record<{}, {}>", ts_type_for(k), ts_type_for(v)),
2394 TypeRef::Iterator(inner) => {
2395 let t = ts_type_for(inner);
2396 format!("{t}[]")
2397 }
2398 }
2399}
2400
2401fn emit_doc(out: &mut String, doc: &Option<String>, indent: &str) {
2404 common_emit_doc(out, doc, indent, DocCommentStyle::Javadoc);
2405}
2406
2407fn emit_fn_doc(
2410 out: &mut String,
2411 doc: &Option<String>,
2412 params: &[ParamBinding],
2413 indent: &str,
2414 extra_tags: &[String],
2415) {
2416 let has_param_docs = params.iter().any(|p| p.doc.is_some());
2417 let trimmed_doc = doc.as_ref().map(|d| d.trim()).filter(|d| !d.is_empty());
2418 if trimmed_doc.is_none() && !has_param_docs && extra_tags.is_empty() {
2419 return;
2420 }
2421 out.push_str(indent);
2422 out.push_str("/**\n");
2423 if let Some(d) = trimmed_doc {
2424 for line in d.lines() {
2425 out.push_str(indent);
2426 if line.is_empty() {
2427 out.push_str(" *\n");
2428 } else {
2429 out.push_str(" * ");
2430 out.push_str(line);
2431 out.push('\n');
2432 }
2433 }
2434 }
2435 for p in params {
2436 if let Some(pdoc) = &p.doc {
2437 let pdoc = pdoc.trim();
2438 if pdoc.is_empty() {
2439 continue;
2440 }
2441 let mut lines = pdoc.lines();
2442 if let Some(first) = lines.next() {
2443 out.push_str(indent);
2444 out.push_str(&format!(" * @param {} {}\n", p.name, first));
2445 }
2446 for line in lines {
2447 out.push_str(indent);
2448 if line.is_empty() {
2449 out.push_str(" *\n");
2450 } else {
2451 out.push_str(" * ");
2452 out.push_str(line);
2453 out.push('\n');
2454 }
2455 }
2456 }
2457 }
2458 for tag in extra_tags {
2459 out.push_str(indent);
2460 out.push_str(" * ");
2461 out.push_str(tag);
2462 out.push('\n');
2463 }
2464 out.push_str(indent);
2465 out.push_str(" */\n");
2466}
2467
2468fn render_struct_builder_dts(out: &mut String, s: &StructBinding) {
2469 let name = &s.name;
2470 emit_doc(out, &s.doc, "");
2471 out.push_str(&format!("export interface {}Builder {{\n", s.name));
2472 for field in &s.fields {
2473 let method = format!("with{}", field.name.to_upper_camel_case());
2474 let ts = ts_type_for(&field.ty);
2475 emit_doc(out, &field.doc, " ");
2476 out.push_str(&format!(" {method}(value: {ts}): {name}Builder;\n"));
2477 }
2478 out.push_str(&format!(" build(): {name};\n"));
2479 out.push_str("}\n");
2480}
2481
2482fn render_node_dts(
2483 api: &Api,
2484 prefix: &str,
2485 strip_module_prefix: bool,
2486 input_basename: &str,
2487) -> String {
2488 let model = BindingModel::build(api, prefix);
2489 let mut out = render_prelude(CommentStyle::DoubleSlash, input_basename);
2490 out.push_str("// Generated types for WeaveFFI functions\n");
2491 for m in &model.modules {
2492 for s in &m.structs {
2493 emit_doc(&mut out, &s.doc, "");
2494 out.push_str(&format!("export interface {} {{\n", s.name));
2495 for field in &s.fields {
2496 emit_doc(&mut out, &field.doc, " ");
2497 out.push_str(&format!(" {}: {};\n", field.name, ts_type_for(&field.ty)));
2498 }
2499 out.push_str("}\n");
2500 if s.builder.is_some() {
2501 render_struct_builder_dts(&mut out, s);
2502 }
2503 }
2504 for e in &m.enums {
2505 emit_doc(&mut out, &e.doc, "");
2506 out.push_str(&format!("export enum {} {{\n", e.name));
2507 for v in &e.variants {
2508 emit_doc(&mut out, &v.doc, " ");
2509 out.push_str(&format!(" {} = {},\n", v.name, v.value));
2510 }
2511 out.push_str("}\n");
2512 }
2513 out.push_str(&format!("// module {}\n", m.path));
2514 for l in &m.listeners {
2515 let Some(cb) = m.callback(&l.event_callback) else {
2516 continue;
2517 };
2518 let cb_params: Vec<String> = cb
2519 .params
2520 .iter()
2521 .map(|p| format!("{}: {}", p.name, ts_type_for(&p.ty)))
2522 .collect();
2523 let register = wrapper_name(
2524 &m.path,
2525 &format!("register_{}", l.name),
2526 strip_module_prefix,
2527 );
2528 let unregister = wrapper_name(
2529 &m.path,
2530 &format!("unregister_{}", l.name),
2531 strip_module_prefix,
2532 );
2533 emit_doc(&mut out, &l.doc, "");
2534 out.push_str(&format!(
2535 "export function {register}(callback: ({}) => void): number\n",
2536 cb_params.join(", ")
2537 ));
2538 out.push_str(&format!("export function {unregister}(id: number): void\n"));
2539 }
2540 for f in &m.functions {
2541 let params: Vec<String> = f
2542 .params
2543 .iter()
2544 .map(|p| format!("{}: {}", p.name, ts_type_for(&p.ty)))
2545 .collect();
2546 let base_ret = match &f.ret {
2547 Some(ty) => ts_type_for(ty),
2548 None => "void".into(),
2549 };
2550 let ret = if f.is_async {
2551 format!("Promise<{base_ret}>")
2552 } else {
2553 base_ret
2554 };
2555 let ts_name = wrapper_name(&m.path, &f.name, strip_module_prefix);
2556 let mut tags = vec![format!("Maps to C function: {}", f.c_base)];
2557 if let Some(msg) = &f.deprecated {
2558 tags.push(format!("@deprecated {}", msg));
2559 }
2560 emit_fn_doc(&mut out, &f.doc, &f.params, "", &tags);
2561 out.push_str(&format!(
2562 "export function {}({}): {}\n",
2563 ts_name,
2564 params.join(", "),
2565 ret
2566 ));
2567 }
2568 }
2569 out.push('\n');
2570 out.push_str(&render_trailer(CommentStyle::DoubleSlash, "types.d.ts"));
2571 out
2572}
2573
2574#[cfg(test)]
2575mod tests {
2576 use super::*;
2577 use weaveffi_core::codegen::Generator;
2578 use weaveffi_ir::ir::{EnumDef, EnumVariant, Function, Module, Param, StructDef, StructField};
2579
2580 fn make_api(modules: Vec<Module>) -> Api {
2581 Api {
2582 version: "0.3.0".into(),
2583 modules,
2584 generators: None,
2585 package: None,
2586 }
2587 }
2588
2589 fn make_module(name: &str) -> Module {
2590 Module {
2591 name: name.into(),
2592 functions: vec![],
2593 structs: vec![],
2594 enums: vec![],
2595 callbacks: vec![],
2596 listeners: vec![],
2597 errors: None,
2598 modules: vec![],
2599 }
2600 }
2601
2602 #[test]
2603 fn listeners_generate_tsfn_register_unregister() {
2604 use weaveffi_ir::ir::{CallbackDef, ListenerDef};
2605 let api = make_api(vec![Module {
2606 name: "events".into(),
2607 functions: vec![],
2608 structs: vec![],
2609 enums: vec![],
2610 callbacks: vec![CallbackDef {
2611 name: "OnMessage".into(),
2612 doc: None,
2613 params: vec![Param {
2614 name: "message".into(),
2615 ty: TypeRef::StringUtf8,
2616 mutable: false,
2617 doc: None,
2618 }],
2619 }],
2620 listeners: vec![ListenerDef {
2621 name: "message_listener".into(),
2622 event_callback: "OnMessage".into(),
2623 doc: None,
2624 }],
2625 errors: None,
2626 modules: vec![],
2627 }]);
2628 let dir = tempfile::tempdir().unwrap();
2629 let out = Utf8Path::from_path(dir.path()).unwrap();
2630 NodeGenerator
2631 .generate(&api, out, &NodeConfig::default())
2632 .unwrap();
2633 let addon = std::fs::read_to_string(dir.path().join("node/weaveffi_addon.c")).unwrap();
2634 assert!(
2635 addon.contains("napi_create_threadsafe_function"),
2636 "listeners must use threadsafe functions: {addon}"
2637 );
2638 assert!(
2639 addon.contains("Napi_weaveffi_events_register_message_listener"),
2640 "register N-API fn missing: {addon}"
2641 );
2642 assert!(
2643 addon.contains("Napi_weaveffi_events_unregister_message_listener"),
2644 "unregister N-API fn missing: {addon}"
2645 );
2646 assert!(
2647 addon.contains("napi_call_threadsafe_function(ctx->tsfn, p, napi_tsfn_nonblocking)"),
2648 "trampoline must queue payloads: {addon}"
2649 );
2650 assert!(
2651 addon.contains("napi_unref_threadsafe_function"),
2652 "tsfn must be unref'd so listeners don't pin the loop: {addon}"
2653 );
2654 let dts = std::fs::read_to_string(dir.path().join("node/types.d.ts")).unwrap();
2655 assert!(
2656 dts.contains(
2657 "export function events_register_message_listener(callback: (message: string) => void): number"
2658 ),
2659 "register dts missing: {dts}"
2660 );
2661 assert!(
2662 dts.contains("export function events_unregister_message_listener(id: number): void"),
2663 "unregister dts missing: {dts}"
2664 );
2665 }
2666
2667 #[test]
2668 fn ts_type_for_primitives() {
2669 assert_eq!(ts_type_for(&TypeRef::I32), "number");
2670 assert_eq!(ts_type_for(&TypeRef::Bool), "boolean");
2671 assert_eq!(ts_type_for(&TypeRef::StringUtf8), "string");
2672 assert_eq!(ts_type_for(&TypeRef::Bytes), "Buffer");
2673 assert_eq!(ts_type_for(&TypeRef::Handle), "bigint");
2674 }
2675
2676 #[test]
2677 fn ts_type_for_struct_and_enum() {
2678 assert_eq!(ts_type_for(&TypeRef::Struct("Contact".into())), "Contact");
2679 assert_eq!(ts_type_for(&TypeRef::Enum("Color".into())), "Color");
2680 assert_eq!(
2681 ts_type_for(&TypeRef::TypedHandle("Contact".into())),
2682 "Contact"
2683 );
2684 }
2685
2686 #[test]
2687 fn ts_type_for_cross_module_uses_local_name() {
2688 assert_eq!(
2691 ts_type_for(&TypeRef::TypedHandle("kv.Store".into())),
2692 "Store"
2693 );
2694 assert_eq!(ts_type_for(&TypeRef::Struct("kv.Store".into())), "Store");
2695 assert_eq!(ts_type_for(&TypeRef::Enum("kv.Kind".into())), "Kind");
2696 }
2697
2698 #[test]
2699 fn ts_type_for_optional() {
2700 let ty = TypeRef::Optional(Box::new(TypeRef::StringUtf8));
2701 assert_eq!(ts_type_for(&ty), "string | null");
2702 }
2703
2704 #[test]
2705 fn ts_type_for_list() {
2706 let ty = TypeRef::List(Box::new(TypeRef::I32));
2707 assert_eq!(ts_type_for(&ty), "number[]");
2708 }
2709
2710 #[test]
2711 fn ts_type_for_list_of_optional() {
2712 let ty = TypeRef::List(Box::new(TypeRef::Optional(Box::new(TypeRef::I32))));
2713 assert_eq!(ts_type_for(&ty), "(number | null)[]");
2714 }
2715
2716 #[test]
2717 fn ts_type_for_map() {
2718 let ty = TypeRef::Map(Box::new(TypeRef::StringUtf8), Box::new(TypeRef::I32));
2719 assert_eq!(ts_type_for(&ty), "Record<string, number>");
2720 }
2721
2722 #[test]
2723 fn ts_type_for_optional_list() {
2724 let ty = TypeRef::Optional(Box::new(TypeRef::List(Box::new(TypeRef::I32))));
2725 assert_eq!(ts_type_for(&ty), "number[] | null");
2726 }
2727
2728 #[test]
2729 fn generate_node_dts_with_structs() {
2730 let mut m = make_module("contacts");
2731 m.structs.push(StructDef {
2732 name: "Contact".into(),
2733 doc: None,
2734 fields: vec![
2735 StructField {
2736 name: "name".into(),
2737 ty: TypeRef::StringUtf8,
2738 doc: None,
2739 default: None,
2740 },
2741 StructField {
2742 name: "age".into(),
2743 ty: TypeRef::I32,
2744 doc: None,
2745 default: None,
2746 },
2747 StructField {
2748 name: "active".into(),
2749 ty: TypeRef::Bool,
2750 doc: None,
2751 default: None,
2752 },
2753 ],
2754 builder: false,
2755 });
2756 m.enums.push(EnumDef {
2757 name: "Color".into(),
2758 doc: None,
2759 variants: vec![
2760 EnumVariant {
2761 name: "Red".into(),
2762 value: 0,
2763 doc: None,
2764 },
2765 EnumVariant {
2766 name: "Green".into(),
2767 value: 1,
2768 doc: None,
2769 },
2770 EnumVariant {
2771 name: "Blue".into(),
2772 value: 2,
2773 doc: None,
2774 },
2775 ],
2776 });
2777 m.functions.push(Function {
2778 name: "get_contact".into(),
2779 params: vec![Param {
2780 name: "id".into(),
2781 ty: TypeRef::I32,
2782 mutable: false,
2783 doc: None,
2784 }],
2785 returns: Some(TypeRef::Optional(Box::new(TypeRef::Struct(
2786 "Contact".into(),
2787 )))),
2788 doc: None,
2789 r#async: false,
2790 cancellable: false,
2791 deprecated: None,
2792 since: None,
2793 });
2794 m.functions.push(Function {
2795 name: "list_contacts".into(),
2796 params: vec![],
2797 returns: Some(TypeRef::List(Box::new(TypeRef::Struct("Contact".into())))),
2798 doc: None,
2799 r#async: false,
2800 cancellable: false,
2801 deprecated: None,
2802 since: None,
2803 });
2804
2805 let dts = render_node_dts(&make_api(vec![m]), "weaveffi", true, "weaveffi.yml");
2806
2807 assert!(dts.contains("export interface Contact {"));
2808 assert!(dts.contains(" name: string;"));
2809 assert!(dts.contains(" age: number;"));
2810 assert!(dts.contains(" active: boolean;"));
2811 assert!(dts.contains("export enum Color {"));
2812 assert!(dts.contains(" Red = 0,"));
2813 assert!(dts.contains(" Green = 1,"));
2814 assert!(dts.contains(" Blue = 2,"));
2815 assert!(dts.contains("export function get_contact(id: number): Contact | null"));
2816 assert!(dts.contains("export function list_contacts(): Contact[]"));
2817
2818 let iface_pos = dts.find("export interface Contact").unwrap();
2819 let enum_pos = dts.find("export enum Color").unwrap();
2820 let fn_pos = dts.find("export function get_contact").unwrap();
2821 assert!(
2822 iface_pos < fn_pos,
2823 "interface should appear before functions"
2824 );
2825 assert!(enum_pos < fn_pos, "enum should appear before functions");
2826 }
2827
2828 #[test]
2829 fn node_generates_binding_gyp() {
2830 let api = make_api(vec![{
2831 let mut m = make_module("math");
2832 m.functions.push(Function {
2833 name: "add".into(),
2834 params: vec![
2835 Param {
2836 name: "a".into(),
2837 ty: TypeRef::I32,
2838 mutable: false,
2839 doc: None,
2840 },
2841 Param {
2842 name: "b".into(),
2843 ty: TypeRef::I32,
2844 mutable: false,
2845 doc: None,
2846 },
2847 ],
2848 returns: Some(TypeRef::I32),
2849 doc: None,
2850 r#async: false,
2851 cancellable: false,
2852 deprecated: None,
2853 since: None,
2854 });
2855 m
2856 }]);
2857
2858 let tmp = std::env::temp_dir().join("weaveffi_test_node_binding_gyp");
2859 let _ = std::fs::remove_dir_all(&tmp);
2860 std::fs::create_dir_all(&tmp).unwrap();
2861 let out_dir = Utf8Path::from_path(&tmp).expect("temp dir is valid UTF-8");
2862
2863 NodeGenerator
2864 .generate(&api, out_dir, &NodeConfig::default())
2865 .unwrap();
2866
2867 let gyp = std::fs::read_to_string(tmp.join("node").join("binding.gyp")).unwrap();
2868 assert!(
2869 gyp.contains("\"target_name\": \"weaveffi\""),
2870 "missing target_name: {gyp}"
2871 );
2872 assert!(
2873 gyp.contains("weaveffi_addon.c"),
2874 "missing source file: {gyp}"
2875 );
2876
2877 let addon = std::fs::read_to_string(tmp.join("node").join("weaveffi_addon.c")).unwrap();
2878 assert!(
2879 addon.contains("napi_value Init("),
2880 "missing Init function: {addon}"
2881 );
2882 assert!(
2883 addon.contains("weaveffi_math_add"),
2884 "missing C ABI call: {addon}"
2885 );
2886 assert!(
2887 addon.contains("napi_get_cb_info"),
2888 "missing napi_get_cb_info call: {addon}"
2889 );
2890
2891 let pkg = std::fs::read_to_string(tmp.join("node").join("package.json")).unwrap();
2892 assert!(pkg.contains("\"gypfile\": true"), "missing gypfile: {pkg}");
2893 assert!(
2894 pkg.contains("node-gyp rebuild"),
2895 "missing install script: {pkg}"
2896 );
2897
2898 let _ = std::fs::remove_dir_all(&tmp);
2899 }
2900
2901 #[test]
2902 fn generate_node_dts_with_structs_and_enums() {
2903 let api = make_api(vec![Module {
2904 name: "contacts".to_string(),
2905 functions: vec![
2906 Function {
2907 name: "get_contact".to_string(),
2908 params: vec![Param {
2909 name: "id".to_string(),
2910 ty: TypeRef::I32,
2911 mutable: false,
2912 doc: None,
2913 }],
2914 returns: Some(TypeRef::Optional(Box::new(TypeRef::Struct(
2915 "Contact".into(),
2916 )))),
2917 doc: None,
2918 r#async: false,
2919 cancellable: false,
2920 deprecated: None,
2921 since: None,
2922 },
2923 Function {
2924 name: "list_contacts".to_string(),
2925 params: vec![],
2926 returns: Some(TypeRef::List(Box::new(TypeRef::Struct("Contact".into())))),
2927 doc: None,
2928 r#async: false,
2929 cancellable: false,
2930 deprecated: None,
2931 since: None,
2932 },
2933 Function {
2934 name: "set_favorite_color".to_string(),
2935 params: vec![
2936 Param {
2937 name: "contact_id".to_string(),
2938 ty: TypeRef::I32,
2939 mutable: false,
2940 doc: None,
2941 },
2942 Param {
2943 name: "color".to_string(),
2944 ty: TypeRef::Optional(Box::new(TypeRef::Enum("Color".into()))),
2945 mutable: false,
2946 doc: None,
2947 },
2948 ],
2949 returns: None,
2950 doc: None,
2951 r#async: false,
2952 cancellable: false,
2953 deprecated: None,
2954 since: None,
2955 },
2956 Function {
2957 name: "get_tags".to_string(),
2958 params: vec![Param {
2959 name: "contact_id".to_string(),
2960 ty: TypeRef::I32,
2961 mutable: false,
2962 doc: None,
2963 }],
2964 returns: Some(TypeRef::List(Box::new(TypeRef::StringUtf8))),
2965 doc: None,
2966 r#async: false,
2967 cancellable: false,
2968 deprecated: None,
2969 since: None,
2970 },
2971 ],
2972 structs: vec![StructDef {
2973 name: "Contact".to_string(),
2974 doc: None,
2975 fields: vec![
2976 StructField {
2977 name: "name".to_string(),
2978 ty: TypeRef::StringUtf8,
2979 doc: None,
2980 default: None,
2981 },
2982 StructField {
2983 name: "email".to_string(),
2984 ty: TypeRef::Optional(Box::new(TypeRef::StringUtf8)),
2985 doc: None,
2986 default: None,
2987 },
2988 StructField {
2989 name: "tags".to_string(),
2990 ty: TypeRef::List(Box::new(TypeRef::StringUtf8)),
2991 doc: None,
2992 default: None,
2993 },
2994 ],
2995 builder: false,
2996 }],
2997 enums: vec![EnumDef {
2998 name: "Color".to_string(),
2999 doc: None,
3000 variants: vec![
3001 EnumVariant {
3002 name: "Red".to_string(),
3003 value: 0,
3004 doc: None,
3005 },
3006 EnumVariant {
3007 name: "Green".to_string(),
3008 value: 1,
3009 doc: None,
3010 },
3011 EnumVariant {
3012 name: "Blue".to_string(),
3013 value: 2,
3014 doc: None,
3015 },
3016 ],
3017 }],
3018 callbacks: vec![],
3019 listeners: vec![],
3020 errors: None,
3021 modules: vec![],
3022 }]);
3023
3024 let tmp = std::env::temp_dir().join("weaveffi_test_node_structs_and_enums");
3025 let _ = std::fs::remove_dir_all(&tmp);
3026 std::fs::create_dir_all(&tmp).unwrap();
3027 let out_dir = Utf8Path::from_path(&tmp).expect("temp dir is valid UTF-8");
3028
3029 NodeGenerator
3030 .generate(
3031 &api,
3032 out_dir,
3033 &NodeConfig {
3034 strip_module_prefix: true,
3035 ..NodeConfig::default()
3036 },
3037 )
3038 .unwrap();
3039
3040 let dts = std::fs::read_to_string(tmp.join("node").join("types.d.ts")).unwrap();
3041
3042 assert!(
3043 dts.contains("export interface Contact {"),
3044 "missing Contact interface: {dts}"
3045 );
3046 assert!(dts.contains(" name: string;"), "missing name field: {dts}");
3047 assert!(
3048 dts.contains(" email: string | null;"),
3049 "missing optional email field: {dts}"
3050 );
3051 assert!(
3052 dts.contains(" tags: string[];"),
3053 "missing list tags field: {dts}"
3054 );
3055
3056 assert!(
3057 dts.contains("export enum Color {"),
3058 "missing Color enum: {dts}"
3059 );
3060 assert!(dts.contains(" Red = 0,"), "missing Red variant: {dts}");
3061 assert!(dts.contains(" Green = 1,"), "missing Green variant: {dts}");
3062 assert!(dts.contains(" Blue = 2,"), "missing Blue variant: {dts}");
3063
3064 assert!(
3065 dts.contains("export function get_contact(id: number): Contact | null"),
3066 "missing get_contact with optional return: {dts}"
3067 );
3068 assert!(
3069 dts.contains("export function list_contacts(): Contact[]"),
3070 "missing list_contacts with list return: {dts}"
3071 );
3072 assert!(
3073 dts.contains(
3074 "export function set_favorite_color(contact_id: number, color: Color | null): void"
3075 ),
3076 "missing set_favorite_color with optional enum param: {dts}"
3077 );
3078 assert!(
3079 dts.contains("export function get_tags(contact_id: number): string[]"),
3080 "missing get_tags with list return: {dts}"
3081 );
3082
3083 let iface_pos = dts.find("export interface Contact").unwrap();
3084 let enum_pos = dts.find("export enum Color").unwrap();
3085 let fn_pos = dts.find("export function get_contact").unwrap();
3086 assert!(
3087 iface_pos < fn_pos,
3088 "interface should appear before functions"
3089 );
3090 assert!(enum_pos < fn_pos, "enum should appear before functions");
3091
3092 let _ = std::fs::remove_dir_all(&tmp);
3093 }
3094
3095 #[test]
3096 fn node_custom_package_name() {
3097 let api = make_api(vec![make_module("math")]);
3098
3099 let tmp = std::env::temp_dir().join("weaveffi_test_node_custom_pkg");
3100 let _ = std::fs::remove_dir_all(&tmp);
3101 std::fs::create_dir_all(&tmp).unwrap();
3102 let out_dir = Utf8Path::from_path(&tmp).expect("temp dir is valid UTF-8");
3103
3104 let config = NodeConfig {
3105 package_name: Some("@myorg/cool-lib".into()),
3106 ..NodeConfig::default()
3107 };
3108 NodeGenerator.generate(&api, out_dir, &config).unwrap();
3109
3110 let pkg = std::fs::read_to_string(tmp.join("node").join("package.json")).unwrap();
3111 assert!(
3112 pkg.contains("\"name\": \"@myorg/cool-lib\""),
3113 "package.json should use custom name: {pkg}"
3114 );
3115 assert!(
3116 !pkg.contains("\"name\": \"weaveffi\""),
3117 "package.json should not contain default name: {pkg}"
3118 );
3119
3120 let _ = std::fs::remove_dir_all(&tmp);
3121 }
3122
3123 #[test]
3124 fn node_dts_has_jsdoc() {
3125 let api = make_api(vec![{
3126 let mut m = make_module("math");
3127 m.functions.push(Function {
3128 name: "add".into(),
3129 params: vec![
3130 Param {
3131 name: "a".into(),
3132 ty: TypeRef::I32,
3133 mutable: false,
3134 doc: None,
3135 },
3136 Param {
3137 name: "b".into(),
3138 ty: TypeRef::I32,
3139 mutable: false,
3140 doc: None,
3141 },
3142 ],
3143 returns: Some(TypeRef::I32),
3144 doc: None,
3145 r#async: false,
3146 cancellable: false,
3147 deprecated: None,
3148 since: None,
3149 });
3150 m.functions.push(Function {
3151 name: "subtract".into(),
3152 params: vec![
3153 Param {
3154 name: "a".into(),
3155 ty: TypeRef::I32,
3156 mutable: false,
3157 doc: None,
3158 },
3159 Param {
3160 name: "b".into(),
3161 ty: TypeRef::I32,
3162 mutable: false,
3163 doc: None,
3164 },
3165 ],
3166 returns: Some(TypeRef::I32),
3167 doc: None,
3168 r#async: false,
3169 cancellable: false,
3170 deprecated: None,
3171 since: None,
3172 });
3173 m
3174 }]);
3175
3176 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
3177
3178 assert!(
3179 dts.contains("Maps to C function: weaveffi_math_add"),
3180 "missing JSDoc for add: {dts}"
3181 );
3182 assert!(
3183 dts.contains("Maps to C function: weaveffi_math_subtract"),
3184 "missing JSDoc for subtract: {dts}"
3185 );
3186 }
3187
3188 #[test]
3189 fn node_addon_has_no_todo() {
3190 let api = make_api(vec![{
3191 let mut m = make_module("math");
3192 m.functions.push(Function {
3193 name: "add".into(),
3194 params: vec![
3195 Param {
3196 name: "a".into(),
3197 ty: TypeRef::I32,
3198 mutable: false,
3199 doc: None,
3200 },
3201 Param {
3202 name: "b".into(),
3203 ty: TypeRef::I32,
3204 mutable: false,
3205 doc: None,
3206 },
3207 ],
3208 returns: Some(TypeRef::I32),
3209 doc: None,
3210 r#async: false,
3211 cancellable: false,
3212 deprecated: None,
3213 since: None,
3214 });
3215 m
3216 }]);
3217 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3218 assert!(
3219 !addon.contains("// TODO: implement"),
3220 "generated addon.c should not contain TODO comments: {addon}"
3221 );
3222 }
3223
3224 #[test]
3225 fn node_addon_extracts_args() {
3226 let api = make_api(vec![{
3227 let mut m = make_module("math");
3228 m.functions.push(Function {
3229 name: "add".into(),
3230 params: vec![
3231 Param {
3232 name: "a".into(),
3233 ty: TypeRef::I32,
3234 mutable: false,
3235 doc: None,
3236 },
3237 Param {
3238 name: "b".into(),
3239 ty: TypeRef::I32,
3240 mutable: false,
3241 doc: None,
3242 },
3243 ],
3244 returns: Some(TypeRef::I32),
3245 doc: None,
3246 r#async: false,
3247 cancellable: false,
3248 deprecated: None,
3249 since: None,
3250 });
3251 m
3252 }]);
3253 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3254 assert!(
3255 addon.contains("napi_get_cb_info"),
3256 "generated addon.c should call napi_get_cb_info: {addon}"
3257 );
3258 }
3259
3260 #[test]
3261 fn node_addon_frees_strings() {
3262 let api = make_api(vec![{
3263 let mut m = make_module("greet");
3264 m.functions.push(Function {
3265 name: "hello".into(),
3266 params: vec![Param {
3267 name: "name".into(),
3268 ty: TypeRef::StringUtf8,
3269 mutable: false,
3270 doc: None,
3271 }],
3272 returns: Some(TypeRef::StringUtf8),
3273 doc: None,
3274 r#async: false,
3275 cancellable: false,
3276 deprecated: None,
3277 since: None,
3278 });
3279 m
3280 }]);
3281 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3282 assert!(
3283 addon.contains("weaveffi_free_string(result)"),
3284 "generated addon should free returned strings: {addon}"
3285 );
3286 assert!(
3287 addon.contains("#include <string.h>"),
3288 "generated addon should include string.h: {addon}"
3289 );
3290 assert!(
3291 addon.contains("#include <stdlib.h>"),
3292 "generated addon should include stdlib.h: {addon}"
3293 );
3294 assert!(
3295 addon.contains("weaveffi_error_clear(&err)"),
3296 "generated addon should clear errors: {addon}"
3297 );
3298 }
3299
3300 #[test]
3301 fn node_custom_prefix_threads_to_user_symbols() {
3302 let api = make_api(vec![{
3303 let mut m = make_module("greet");
3304 m.functions.push(Function {
3305 name: "hello".into(),
3306 params: vec![Param {
3307 name: "name".into(),
3308 ty: TypeRef::StringUtf8,
3309 mutable: false,
3310 doc: None,
3311 }],
3312 returns: Some(TypeRef::StringUtf8),
3313 doc: None,
3314 r#async: false,
3315 cancellable: false,
3316 deprecated: None,
3317 since: None,
3318 });
3319 m
3320 }]);
3321
3322 let config = NodeConfig {
3323 prefix: Some("myffi".into()),
3324 ..NodeConfig::default()
3325 };
3326
3327 let tmp = std::env::temp_dir().join("weaveffi_test_node_custom_prefix");
3328 let _ = std::fs::remove_dir_all(&tmp);
3329 std::fs::create_dir_all(&tmp).unwrap();
3330 let out_dir = Utf8Path::from_path(&tmp).expect("valid UTF-8");
3331
3332 NodeGenerator.generate(&api, out_dir, &config).unwrap();
3333
3334 let addon = std::fs::read_to_string(tmp.join("node/weaveffi_addon.c")).unwrap();
3337
3338 assert!(
3340 addon.contains("myffi_greet_hello"),
3341 "addon should call the prefixed user symbol myffi_greet_hello: {addon}"
3342 );
3343 assert!(
3344 !addon.contains("weaveffi_greet_hello"),
3345 "addon must not emit the hard-coded weaveffi_ user symbol: {addon}"
3346 );
3347 assert!(
3348 addon.contains("#include \"myffi.h\""),
3349 "addon should include the prefixed header myffi.h: {addon}"
3350 );
3351
3352 assert!(
3354 addon.contains("weaveffi_error"),
3355 "runtime weaveffi_error must remain literal: {addon}"
3356 );
3357 assert!(
3358 addon.contains("weaveffi_free_string"),
3359 "runtime weaveffi_free_string must remain literal: {addon}"
3360 );
3361
3362 let _ = std::fs::remove_dir_all(&tmp);
3363 }
3364
3365 #[test]
3366 fn node_addon_checks_error() {
3367 let api = make_api(vec![{
3368 let mut m = make_module("math");
3369 m.functions.push(Function {
3370 name: "add".into(),
3371 params: vec![
3372 Param {
3373 name: "a".into(),
3374 ty: TypeRef::I32,
3375 mutable: false,
3376 doc: None,
3377 },
3378 Param {
3379 name: "b".into(),
3380 ty: TypeRef::I32,
3381 mutable: false,
3382 doc: None,
3383 },
3384 ],
3385 returns: Some(TypeRef::I32),
3386 doc: None,
3387 r#async: false,
3388 cancellable: false,
3389 deprecated: None,
3390 since: None,
3391 });
3392 m
3393 }]);
3394 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3395 assert!(
3396 addon.contains("err.code"),
3397 "generated addon.c should check err.code: {addon}"
3398 );
3399 }
3400
3401 #[test]
3402 fn node_strip_module_prefix() {
3403 let api = make_api(vec![{
3404 let mut m = make_module("contacts");
3405 m.functions.push(Function {
3406 name: "create_contact".into(),
3407 params: vec![Param {
3408 name: "name".into(),
3409 ty: TypeRef::StringUtf8,
3410 mutable: false,
3411 doc: None,
3412 }],
3413 returns: Some(TypeRef::I32),
3414 doc: None,
3415 r#async: false,
3416 cancellable: false,
3417 deprecated: None,
3418 since: None,
3419 });
3420 m
3421 }]);
3422
3423 let config = NodeConfig {
3424 strip_module_prefix: true,
3425 ..NodeConfig::default()
3426 };
3427
3428 let tmp = std::env::temp_dir().join("weaveffi_test_node_strip_prefix");
3429 let _ = std::fs::remove_dir_all(&tmp);
3430 std::fs::create_dir_all(&tmp).unwrap();
3431 let out_dir = Utf8Path::from_path(&tmp).expect("valid UTF-8");
3432
3433 NodeGenerator.generate(&api, out_dir, &config).unwrap();
3434
3435 let dts = std::fs::read_to_string(tmp.join("node/types.d.ts")).unwrap();
3436 assert!(
3437 dts.contains("export function create_contact("),
3438 "stripped name should be create_contact: {dts}"
3439 );
3440 assert!(
3441 !dts.contains("export function contacts_create_contact("),
3442 "should not contain module-prefixed name: {dts}"
3443 );
3444
3445 let addon = std::fs::read_to_string(tmp.join("node/weaveffi_addon.c")).unwrap();
3446 assert!(
3447 addon.contains("\"create_contact\""),
3448 "JS export name should be stripped: {addon}"
3449 );
3450 assert!(
3451 addon.contains("weaveffi_contacts_create_contact"),
3452 "C ABI call should still use full name: {addon}"
3453 );
3454
3455 let no_strip = NodeConfig::default();
3456 let tmp2 = std::env::temp_dir().join("weaveffi_test_node_no_strip_prefix");
3457 let _ = std::fs::remove_dir_all(&tmp2);
3458 std::fs::create_dir_all(&tmp2).unwrap();
3459 let out_dir2 = Utf8Path::from_path(&tmp2).expect("valid UTF-8");
3460
3461 NodeGenerator.generate(&api, out_dir2, &no_strip).unwrap();
3462
3463 let dts2 = std::fs::read_to_string(tmp2.join("node/types.d.ts")).unwrap();
3464 assert!(
3465 dts2.contains("export function contacts_create_contact("),
3466 "default should use module-prefixed name: {dts2}"
3467 );
3468
3469 let _ = std::fs::remove_dir_all(&tmp);
3470 let _ = std::fs::remove_dir_all(&tmp2);
3471 }
3472
3473 #[test]
3474 fn node_typed_handle_type() {
3475 let api = make_api(vec![{
3476 let mut m = make_module("contacts");
3477 m.structs.push(StructDef {
3478 name: "Contact".into(),
3479 doc: None,
3480 fields: vec![StructField {
3481 name: "name".into(),
3482 ty: TypeRef::StringUtf8,
3483 doc: None,
3484 default: None,
3485 }],
3486 builder: false,
3487 });
3488 m.functions.push(Function {
3489 name: "get_info".into(),
3490 params: vec![Param {
3491 name: "contact".into(),
3492 ty: TypeRef::TypedHandle("Contact".into()),
3493 mutable: false,
3494 doc: None,
3495 }],
3496 returns: None,
3497 doc: None,
3498 r#async: false,
3499 cancellable: false,
3500 deprecated: None,
3501 since: None,
3502 });
3503 m
3504 }]);
3505 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
3506 assert!(
3507 dts.contains("contact: Contact"),
3508 "TypedHandle should use class type not bigint: {dts}"
3509 );
3510 }
3511
3512 #[test]
3513 fn node_deeply_nested_optional() {
3514 let api = make_api(vec![Module {
3515 name: "edge".into(),
3516 functions: vec![Function {
3517 name: "process".into(),
3518 params: vec![Param {
3519 name: "data".into(),
3520 ty: TypeRef::Optional(Box::new(TypeRef::List(Box::new(TypeRef::Optional(
3521 Box::new(TypeRef::Struct("Contact".into())),
3522 ))))),
3523 mutable: false,
3524 doc: None,
3525 }],
3526 returns: None,
3527 doc: None,
3528 r#async: false,
3529 cancellable: false,
3530 deprecated: None,
3531 since: None,
3532 }],
3533 structs: vec![StructDef {
3534 name: "Contact".into(),
3535 doc: None,
3536 fields: vec![StructField {
3537 name: "name".into(),
3538 ty: TypeRef::StringUtf8,
3539 doc: None,
3540 default: None,
3541 }],
3542 builder: false,
3543 }],
3544 enums: vec![],
3545 callbacks: vec![],
3546 listeners: vec![],
3547 errors: None,
3548 modules: vec![],
3549 }]);
3550 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
3551 assert!(
3552 dts.contains("(Contact | null)[] | null"),
3553 "should contain deeply nested optional type: {dts}"
3554 );
3555 }
3556
3557 #[test]
3558 fn node_map_of_lists() {
3559 let api = make_api(vec![Module {
3560 name: "edge".into(),
3561 functions: vec![Function {
3562 name: "process".into(),
3563 params: vec![Param {
3564 name: "scores".into(),
3565 ty: TypeRef::Map(
3566 Box::new(TypeRef::StringUtf8),
3567 Box::new(TypeRef::List(Box::new(TypeRef::I32))),
3568 ),
3569 mutable: false,
3570 doc: None,
3571 }],
3572 returns: None,
3573 doc: None,
3574 r#async: false,
3575 cancellable: false,
3576 deprecated: None,
3577 since: None,
3578 }],
3579 structs: vec![],
3580 enums: vec![],
3581 callbacks: vec![],
3582 listeners: vec![],
3583 errors: None,
3584 modules: vec![],
3585 }]);
3586 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
3587 assert!(
3588 dts.contains("Record<string, number[]>"),
3589 "should contain map of lists type: {dts}"
3590 );
3591 }
3592
3593 #[test]
3594 fn node_enum_keyed_map() {
3595 let api = make_api(vec![Module {
3596 name: "edge".into(),
3597 functions: vec![Function {
3598 name: "process".into(),
3599 params: vec![Param {
3600 name: "contacts".into(),
3601 ty: TypeRef::Map(
3602 Box::new(TypeRef::Enum("Color".into())),
3603 Box::new(TypeRef::Struct("Contact".into())),
3604 ),
3605 mutable: false,
3606 doc: None,
3607 }],
3608 returns: None,
3609 doc: None,
3610 r#async: false,
3611 cancellable: false,
3612 deprecated: None,
3613 since: None,
3614 }],
3615 structs: vec![StructDef {
3616 name: "Contact".into(),
3617 doc: None,
3618 fields: vec![StructField {
3619 name: "name".into(),
3620 ty: TypeRef::StringUtf8,
3621 doc: None,
3622 default: None,
3623 }],
3624 builder: false,
3625 }],
3626 enums: vec![EnumDef {
3627 name: "Color".into(),
3628 doc: None,
3629 variants: vec![
3630 EnumVariant {
3631 name: "Red".into(),
3632 value: 0,
3633 doc: None,
3634 },
3635 EnumVariant {
3636 name: "Green".into(),
3637 value: 1,
3638 doc: None,
3639 },
3640 EnumVariant {
3641 name: "Blue".into(),
3642 value: 2,
3643 doc: None,
3644 },
3645 ],
3646 }],
3647 callbacks: vec![],
3648 listeners: vec![],
3649 errors: None,
3650 modules: vec![],
3651 }]);
3652 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
3653 assert!(
3654 dts.contains("Record<Color, Contact>"),
3655 "should contain enum-keyed map type: {dts}"
3656 );
3657 }
3658
3659 #[test]
3660 fn node_no_double_free_on_error() {
3661 let api = make_api(vec![{
3662 let mut m = make_module("contacts");
3663 m.structs.push(StructDef {
3664 name: "Contact".into(),
3665 doc: None,
3666 fields: vec![StructField {
3667 name: "name".into(),
3668 ty: TypeRef::StringUtf8,
3669 doc: None,
3670 default: None,
3671 }],
3672 builder: false,
3673 });
3674 m.functions.push(Function {
3675 name: "find_contact".into(),
3676 params: vec![Param {
3677 name: "name".into(),
3678 ty: TypeRef::StringUtf8,
3679 mutable: false,
3680 doc: None,
3681 }],
3682 returns: Some(TypeRef::Struct("Contact".into())),
3683 doc: None,
3684 r#async: false,
3685 cancellable: false,
3686 deprecated: None,
3687 since: None,
3688 });
3689 m
3690 }]);
3691 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3692 assert!(
3693 addon.contains("free(name)"),
3694 "malloc'd JS string copy should be freed after the C call: {addon}"
3695 );
3696 assert!(
3697 !addon.contains("weaveffi_free_string(name)"),
3698 "input string param must not use weaveffi_free_string: {addon}"
3699 );
3700 let free_pos = addon
3701 .find("free(name)")
3702 .expect("free(name) should be present");
3703 let err_pos = addon
3704 .find("if (err.code != 0)")
3705 .expect("err.code check should be present");
3706 assert!(
3707 free_pos < err_pos,
3708 "cleanup should run before error check: free at {free_pos}, err at {err_pos}"
3709 );
3710 let err_block_start = addon
3711 .find(" if (err.code != 0) {\n")
3712 .expect("error if block should be present");
3713 let after_err = &addon[err_block_start..];
3714 let err_block_end_rel = after_err
3715 .find(" }\n napi_value ret;")
3716 .expect("napi_value ret should follow error block");
3717 let err_block = &addon[err_block_start..err_block_start + err_block_end_rel];
3718 assert!(
3719 !err_block.contains("result"),
3720 "error path should not touch result before return NULL: {err_block}"
3721 );
3722 }
3723
3724 #[test]
3725 fn node_null_check_on_optional_return() {
3726 let api = make_api(vec![{
3727 let mut m = make_module("contacts");
3728 m.structs.push(StructDef {
3729 name: "Contact".into(),
3730 doc: None,
3731 fields: vec![StructField {
3732 name: "name".into(),
3733 ty: TypeRef::StringUtf8,
3734 doc: None,
3735 default: None,
3736 }],
3737 builder: false,
3738 });
3739 m.functions.push(Function {
3740 name: "find_contact".into(),
3741 params: vec![Param {
3742 name: "id".into(),
3743 ty: TypeRef::I32,
3744 mutable: false,
3745 doc: None,
3746 }],
3747 returns: Some(TypeRef::Optional(Box::new(TypeRef::Struct(
3748 "Contact".into(),
3749 )))),
3750 doc: None,
3751 r#async: false,
3752 cancellable: false,
3753 deprecated: None,
3754 since: None,
3755 });
3756 m
3757 }]);
3758 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3759 assert!(
3760 addon.contains("if (result == NULL)"),
3761 "optional struct return should null-check before wrapping: {addon}"
3762 );
3763 assert!(
3764 addon.contains("napi_get_null"),
3765 "optional absent should return JS null via napi_get_null: {addon}"
3766 );
3767 }
3768
3769 #[test]
3770 fn node_async_returns_promise() {
3771 let api = make_api(vec![{
3772 let mut m = make_module("tasks");
3773 m.functions.push(Function {
3774 name: "run".into(),
3775 params: vec![Param {
3776 name: "id".into(),
3777 ty: TypeRef::I32,
3778 mutable: false,
3779 doc: None,
3780 }],
3781 returns: Some(TypeRef::StringUtf8),
3782 doc: None,
3783 r#async: true,
3784 cancellable: false,
3785 deprecated: None,
3786 since: None,
3787 });
3788 m.functions.push(Function {
3789 name: "fire_and_forget".into(),
3790 params: vec![],
3791 returns: None,
3792 doc: None,
3793 r#async: true,
3794 cancellable: false,
3795 deprecated: None,
3796 since: None,
3797 });
3798 m
3799 }]);
3800 let dts = render_node_dts(&api, "weaveffi", true, "weaveffi.yml");
3801 assert!(
3802 dts.contains("Promise<"),
3803 "async function should return Promise in .d.ts: {dts}"
3804 );
3805 assert!(
3806 dts.contains("): Promise<string>"),
3807 "async string return should be Promise<string>: {dts}"
3808 );
3809 assert!(
3810 dts.contains("): Promise<void>"),
3811 "async void return should be Promise<void>: {dts}"
3812 );
3813 }
3814
3815 #[test]
3816 fn node_addon_creates_promise() {
3817 let api = make_api(vec![{
3818 let mut m = make_module("tasks");
3819 m.functions.push(Function {
3820 name: "run".into(),
3821 params: vec![Param {
3822 name: "id".into(),
3823 ty: TypeRef::I32,
3824 mutable: false,
3825 doc: None,
3826 }],
3827 returns: Some(TypeRef::I32),
3828 doc: None,
3829 r#async: true,
3830 cancellable: false,
3831 deprecated: None,
3832 since: None,
3833 });
3834 m
3835 }]);
3836 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3837 assert!(
3838 addon.contains("napi_create_promise"),
3839 "async addon should call napi_create_promise: {addon}"
3840 );
3841 assert!(
3842 addon.contains("napi_resolve_deferred"),
3843 "async callback should call napi_resolve_deferred: {addon}"
3844 );
3845 assert!(
3846 addon.contains("napi_reject_deferred"),
3847 "async callback should call napi_reject_deferred: {addon}"
3848 );
3849 assert!(
3850 addon.contains("weaveffi_tasks_run_napi_actx"),
3851 "async addon should define per-fn async context struct: {addon}"
3852 );
3853 assert!(
3854 addon.contains("weaveffi_tasks_run_async("),
3855 "async addon should call the _async C function: {addon}"
3856 );
3857 assert!(
3858 addon.contains("weaveffi_tasks_run_napi_cb"),
3859 "async addon should define the callback: {addon}"
3860 );
3861 assert!(
3864 addon.contains("napi_call_threadsafe_function(ctx->tsfn, ctx, napi_tsfn_blocking)"),
3865 "completion callback must hop to the JS thread via tsfn: {addon}"
3866 );
3867 assert!(
3868 !addon.contains("napi_resolve_deferred(ctx->env"),
3869 "deferred must never be settled from the producer thread: {addon}"
3870 );
3871 }
3872
3873 #[test]
3879 fn node_async_pins_callback_for_lifetime() {
3880 let api = make_api(vec![{
3881 let mut m = make_module("tasks");
3882 m.functions.push(Function {
3883 name: "run".into(),
3884 params: vec![Param {
3885 name: "id".into(),
3886 ty: TypeRef::I32,
3887 mutable: false,
3888 doc: None,
3889 }],
3890 returns: Some(TypeRef::I32),
3891 doc: None,
3892 r#async: true,
3893 cancellable: false,
3894 deprecated: None,
3895 since: None,
3896 });
3897 m
3898 }]);
3899 let addon = render_addon_c(&api, "weaveffi", true, "weaveffi.yml");
3900 let create_count = addon.matches("napi_create_promise").count();
3901 let resolve_count = addon.matches("napi_resolve_deferred").count();
3902 let reject_count = addon.matches("napi_reject_deferred").count();
3903 let alloc_count = addon
3904 .matches("calloc(1, sizeof(weaveffi_tasks_run_napi_actx))")
3905 .count();
3906 let free_count = addon.matches("free(ctx);").count();
3907 let release_count = addon
3908 .matches("napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release);")
3909 .count();
3910 assert_eq!(
3911 create_count, 1,
3912 "expected one napi_create_promise per async fn, got {create_count}: {addon}"
3913 );
3914 assert_eq!(
3915 resolve_count, 1,
3916 "expected one napi_resolve_deferred per async fn, got {resolve_count}: {addon}"
3917 );
3918 assert_eq!(
3919 reject_count, 1,
3920 "expected one napi_reject_deferred per async fn, got {reject_count}: {addon}"
3921 );
3922 assert_eq!(
3923 alloc_count, free_count,
3924 "ctx alloc / free must balance per async fn: alloc={alloc_count} free={free_count}: {addon}"
3925 );
3926 assert_eq!(
3927 release_count, 1,
3928 "tsfn must be released exactly once per async fn, got {release_count}: {addon}"
3929 );
3930 }
3931
3932 fn doc_module() -> Module {
3933 Module {
3934 name: "docs".into(),
3935 functions: vec![Function {
3936 name: "do_thing".into(),
3937 params: vec![Param {
3938 name: "x".into(),
3939 ty: TypeRef::I32,
3940 mutable: false,
3941 doc: Some("the input value".into()),
3942 }],
3943 returns: Some(TypeRef::I32),
3944 doc: Some("Performs a thing.".into()),
3945 r#async: false,
3946 cancellable: false,
3947 deprecated: None,
3948 since: None,
3949 }],
3950 structs: vec![StructDef {
3951 name: "Item".into(),
3952 doc: Some("An item we track.".into()),
3953 fields: vec![StructField {
3954 name: "id".into(),
3955 ty: TypeRef::I64,
3956 doc: Some("Stable id".into()),
3957 default: None,
3958 }],
3959 builder: false,
3960 }],
3961 enums: vec![EnumDef {
3962 name: "Kind".into(),
3963 doc: Some("Kind of item.".into()),
3964 variants: vec![EnumVariant {
3965 name: "Small".into(),
3966 value: 0,
3967 doc: Some("A small one".into()),
3968 }],
3969 }],
3970 callbacks: vec![],
3971 listeners: vec![],
3972 errors: None,
3973 modules: vec![],
3974 }
3975 }
3976
3977 #[test]
3978 fn node_emits_doc_on_function() {
3979 let dts = render_node_dts(
3980 &make_api(vec![doc_module()]),
3981 "weaveffi",
3982 true,
3983 "weaveffi.yml",
3984 );
3985 assert!(dts.contains("Performs a thing."), "{dts}");
3986 }
3987
3988 #[test]
3989 fn node_emits_doc_on_struct() {
3990 let dts = render_node_dts(
3991 &make_api(vec![doc_module()]),
3992 "weaveffi",
3993 true,
3994 "weaveffi.yml",
3995 );
3996 assert!(dts.contains("/** An item we track. */"), "{dts}");
3997 }
3998
3999 #[test]
4000 fn node_emits_doc_on_enum_variant() {
4001 let dts = render_node_dts(
4002 &make_api(vec![doc_module()]),
4003 "weaveffi",
4004 true,
4005 "weaveffi.yml",
4006 );
4007 assert!(dts.contains("/** Kind of item. */"), "{dts}");
4008 assert!(dts.contains("/** A small one */"), "{dts}");
4009 }
4010
4011 #[test]
4012 fn node_emits_doc_on_field() {
4013 let dts = render_node_dts(
4014 &make_api(vec![doc_module()]),
4015 "weaveffi",
4016 true,
4017 "weaveffi.yml",
4018 );
4019 assert!(dts.contains("/** Stable id */"), "{dts}");
4020 }
4021
4022 #[test]
4023 fn node_emits_doc_on_param() {
4024 let dts = render_node_dts(
4025 &make_api(vec![doc_module()]),
4026 "weaveffi",
4027 true,
4028 "weaveffi.yml",
4029 );
4030 assert!(dts.contains("@param x the input value"), "{dts}");
4031 }
4032}