Skip to content

Commit 0fef5f7

Browse files
authored
Adds error enum templating support and adds new configuration parameters (#10)
* feat: add out_dirname_api flag This allows the caller to configure which type of api should be used to get the current directory - __dirname (classic node / commonjs) or import.meta.module (esm) * fix: add conversion between external module type -> internal module type * refactor: rename OutputModuleType -> DirnameApi * feat: add flag to allow disabling of uniffi dylib auto loading This is important for situations where a uniffi dependency should be conditionally loaded. * feat: add check to ensure uniffi native code was loaded before performing a low level ffi function call * fix: re-export uniffiLoad / uniffiUnload from -node script * fix: update docs comments for uniffiLoad / uniffiUnload * fix: add missing `type` prefixes in node script * refactor: rename node.ts template to livekit-node.ts * feat: parameterize output sys file name in templates * refactor: use Default for flag enums * feat: add out_import_extension parameter for changing import file name extension * fix: make liftError use non hard coded error type * refactor: rename template scripts to drop livekit prefix * fix: remove unused imports * docs: fix incorrect docstring * docs: update docstring * docs: add missing period
1 parent 29317d7 commit 0fef5f7

8 files changed

Lines changed: 195 additions & 48 deletions

File tree

src/bindings/filters.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use askama::Result;
2-
use heck::{ToLowerCamelCase, ToPascalCase, ToUpperCamelCase, ToSnakeCase};
3-
use uniffi_bindgen::interface::{AsType, FfiDefinition, FfiType, Type};
2+
use heck::{ToLowerCamelCase, ToPascalCase, ToUpperCamelCase};
3+
use uniffi_bindgen::interface::{AsType, FfiType, Type};
44

55
fn strip_comments(input: impl AsRef<str>) -> String {
66
input.as_ref().replace("/* ", "").replace(" */", "")

src/bindings/generator.rs

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,85 @@ use anyhow::{Context, Result};
44
use askama::Template;
55
use heck::ToKebabCase;
66

7-
use crate::bindings::filters;
7+
use crate::bindings::{filters, utils::{DirnameApi, ImportExtension}};
88

99
pub struct Bindings {
1010
pub package_json_contents: String,
11-
pub livekit_sys_template_contents: String,
11+
pub sys_template_contents: String,
1212
pub node_ts_file_contents: String,
1313
}
1414

1515
#[derive(Template)]
1616
#[template(escape = "none", path = "package.json")]
1717
struct PackageJsonTemplate<'ci> {
1818
ci: &'ci ComponentInterface,
19-
node_ts_main_file_name: String,
2019
}
2120

2221
impl<'ci> PackageJsonTemplate<'ci> {
23-
pub fn new(ci: &'ci ComponentInterface, node_ts_main_file_name: &str) -> Self {
24-
Self {
25-
ci,
26-
node_ts_main_file_name: node_ts_main_file_name.into(),
27-
}
22+
pub fn new(ci: &'ci ComponentInterface) -> Self {
23+
Self { ci }
2824
}
2925
}
3026

3127
#[derive(Template)]
32-
#[template(escape = "none", path = "livekit-sys.ts")]
33-
struct LivekitSysTemplate<'ci> {
28+
#[template(escape = "none", path = "sys.ts")]
29+
struct SysTemplate<'ci> {
3430
ci: &'ci ComponentInterface,
31+
32+
out_dirname_api: DirnameApi,
33+
out_disable_auto_loading_lib: bool,
3534
}
3635

37-
impl<'ci> LivekitSysTemplate<'ci> {
38-
pub fn new(ci: &'ci ComponentInterface) -> Self {
39-
Self { ci }
36+
impl<'ci> SysTemplate<'ci> {
37+
pub fn new(
38+
ci: &'ci ComponentInterface,
39+
out_dirname_api: DirnameApi,
40+
out_disable_auto_loading_lib: bool,
41+
) -> Self {
42+
Self { ci, out_dirname_api, out_disable_auto_loading_lib }
4043
}
4144
}
4245

4346

4447
#[derive(Template)]
4548
#[template(escape = "none", path = "node.ts")]
46-
struct NodeTs<'ci> {
49+
struct NodeTsTemplate<'ci> {
4750
ci: &'ci ComponentInterface,
51+
out_disable_auto_loading_lib: bool,
52+
sys_ts_main_file_name: String,
53+
out_import_extension: ImportExtension,
4854
}
4955

50-
impl<'ci> NodeTs<'ci> {
51-
pub fn new(ci: &'ci ComponentInterface) -> Self {
52-
Self { ci }
56+
impl<'ci> NodeTsTemplate<'ci> {
57+
pub fn new(
58+
ci: &'ci ComponentInterface,
59+
out_disable_auto_loading_lib: bool,
60+
sys_ts_main_file_name: &str,
61+
out_import_extension: ImportExtension
62+
) -> Self {
63+
Self {
64+
ci,
65+
out_disable_auto_loading_lib,
66+
sys_ts_main_file_name: sys_ts_main_file_name.to_string(),
67+
out_import_extension,
68+
}
5369
}
5470
}
5571

56-
pub fn generate_node_bindings(ci: &ComponentInterface, node_ts_main_file_name: &str) -> Result<Bindings> {
57-
let package_json_contents = PackageJsonTemplate::new(ci, node_ts_main_file_name).render().context("failed to render package.json template")?;
58-
let livekit_sys_template_contents = LivekitSysTemplate::new(ci).render().context("failed to render livekit-sys.ts template")?;
59-
let node_ts_file_contents = NodeTs::new(ci).render().context("failed to render node.ts template")?;
72+
pub fn generate_node_bindings(
73+
ci: &ComponentInterface,
74+
sys_ts_main_file_name: &str,
75+
out_dirname_api: DirnameApi,
76+
out_disable_auto_loading_lib: bool,
77+
out_import_extension: ImportExtension,
78+
) -> Result<Bindings> {
79+
let package_json_contents = PackageJsonTemplate::new(ci).render().context("failed to render package.json template")?;
80+
let sys_template_contents = SysTemplate::new(ci, out_dirname_api, out_disable_auto_loading_lib).render().context("failed to render sys.ts template")?;
81+
let node_ts_file_contents = NodeTsTemplate::new(ci, out_disable_auto_loading_lib, sys_ts_main_file_name, out_import_extension).render().context("failed to render node.ts template")?;
6082

6183
Ok(Bindings {
6284
package_json_contents,
63-
livekit_sys_template_contents,
85+
sys_template_contents,
6486
node_ts_file_contents,
6587
})
6688
}

src/bindings/mod.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@ use serde::Deserialize;
55

66
mod generator;
77
mod filters;
8+
pub mod utils;
89

9-
use crate::{bindings::generator::{Bindings, generate_node_bindings}, utils::write_with_dirs};
10+
use crate::{bindings::generator::{generate_node_bindings, Bindings}, utils::write_with_dirs};
1011

11-
pub struct NodeBindingGenerator {}
12+
pub struct NodeBindingGenerator {
13+
out_dirname_api: utils::DirnameApi,
14+
out_disable_auto_loading_lib: bool,
15+
out_import_extension: utils::ImportExtension,
16+
}
1217

1318
impl NodeBindingGenerator {
14-
pub fn new() -> Self {
15-
Self {}
19+
pub fn new(
20+
out_dirname_api: utils::DirnameApi,
21+
out_disable_auto_loading_lib: bool,
22+
out_import_extension: utils::ImportExtension,
23+
) -> Self {
24+
Self { out_dirname_api, out_disable_auto_loading_lib, out_import_extension }
1625
}
1726
}
1827

@@ -45,22 +54,28 @@ impl BindingGenerator for NodeBindingGenerator {
4554
components: &[uniffi_bindgen::Component<Self::Config>],
4655
) -> Result<()> {
4756
for uniffi_bindgen::Component { ci, config: _, .. } in components {
48-
let node_ts_main_file_name = format!("{}-node.ts", ci.namespace().to_kebab_case());
57+
let sys_ts_main_file_name = format!("{}-sys", ci.namespace().to_kebab_case());
4958

5059
let Bindings {
5160
package_json_contents,
52-
livekit_sys_template_contents,
61+
sys_template_contents,
5362
node_ts_file_contents,
54-
} = generate_node_bindings(&ci, node_ts_main_file_name.as_str())?;
63+
} = generate_node_bindings(
64+
&ci,
65+
sys_ts_main_file_name.as_str(),
66+
self.out_dirname_api.clone(),
67+
self.out_disable_auto_loading_lib,
68+
self.out_import_extension.clone(),
69+
)?;
5570

5671
let package_json_path = settings.out_dir.join("package.json");
5772
write_with_dirs(&package_json_path, package_json_contents)?;
5873

59-
let node_ts_file_path = settings.out_dir.join(node_ts_main_file_name);
74+
let node_ts_file_path = settings.out_dir.join(format!("{}-node.ts", ci.namespace().to_kebab_case()));
6075
write_with_dirs(&node_ts_file_path, node_ts_file_contents)?;
6176

62-
let livekit_sys_template_path = settings.out_dir.join(format!("{}-sys.ts", ci.namespace().to_kebab_case()));
63-
write_with_dirs(&livekit_sys_template_path, livekit_sys_template_contents)?;
77+
let sys_template_path = settings.out_dir.join(format!("{sys_ts_main_file_name}.ts"));
78+
write_with_dirs(&sys_template_path, sys_template_contents)?;
6479
}
6580

6681
Ok(())

src/lib.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ use clap::Parser;
55
mod bindings;
66
mod utils;
77

8+
#[derive(Debug, Clone, Default, clap::ValueEnum)]
9+
enum OutputDirnameApi {
10+
#[default]
11+
Dirname,
12+
ImportMetaUrl,
13+
}
14+
15+
impl Into<bindings::utils::DirnameApi> for OutputDirnameApi {
16+
fn into(self) -> bindings::utils::DirnameApi {
17+
match self {
18+
OutputDirnameApi::ImportMetaUrl => bindings::utils::DirnameApi::ImportMetaUrl,
19+
OutputDirnameApi::Dirname => bindings::utils::DirnameApi::Dirname,
20+
}
21+
}
22+
}
23+
24+
#[derive(Debug, Clone, Default, clap::ValueEnum)]
25+
enum OutputImportExtension {
26+
#[default]
27+
None,
28+
Ts,
29+
Js,
30+
}
31+
32+
impl Into<bindings::utils::ImportExtension> for OutputImportExtension {
33+
fn into(self) -> bindings::utils::ImportExtension {
34+
match self {
35+
OutputImportExtension::None => bindings::utils::ImportExtension::None,
36+
OutputImportExtension::Ts => bindings::utils::ImportExtension::Ts,
37+
OutputImportExtension::Js => bindings::utils::ImportExtension::Js,
38+
}
39+
}
40+
}
41+
842
/// UniFFI binding generator for Node.js
943
#[derive(Parser, Debug)]
1044
#[command(version, about, long_about = None)]
@@ -20,6 +54,27 @@ pub struct Args {
2054
#[arg(long, default_value = "livekit_uniffi")]
2155
crate_name: String,
2256

57+
/// The set of buildin apis which should be used to get the current
58+
/// directory - `__dirname` or `import.meta.url`.
59+
#[arg(long, value_enum, default_value_t=OutputDirnameApi::default())]
60+
out_dirname_api: OutputDirnameApi,
61+
62+
/// If specified, the dylib/so/dll native dependency won't be automatically loaded
63+
/// when the bindgen is imported. If this flag is set, explicit `uniffiLoad` / `uniffiUnload`
64+
/// will be exported from the generated package which must be called before any uniffi calls
65+
/// are made.
66+
///
67+
/// Use this if you want to only load a bindgen sometimes (ie, it is an optional dependency).
68+
#[arg(long, action)]
69+
out_disable_auto_load_lib: bool,
70+
71+
/// Changes the extension used in `import`s within the final generated output. This exists
72+
/// because depending on packaging / tsc configuration, the import path extensions may be
73+
/// expected to end in different extensions. For example, tsc often requires .js extensions
74+
/// on .ts files it imports, etc.
75+
#[arg(long, action, value_enum, default_value_t=OutputImportExtension::default())]
76+
out_import_extension: OutputImportExtension,
77+
2378
/// Config file override.
2479
#[arg(short, long)]
2580
config_override: Option<Utf8PathBuf>,
@@ -32,7 +87,11 @@ pub fn run(args: Args) -> Result<()> {
3287
let metadata = cmd.exec().context("error running cargo metadata")?;
3388
CrateConfigSupplier::from(metadata)
3489
};
35-
let node_binding_generator = bindings::NodeBindingGenerator::new();
90+
let node_binding_generator = bindings::NodeBindingGenerator::new(
91+
args.out_dirname_api.into(),
92+
args.out_disable_auto_load_lib,
93+
args.out_import_extension.into(),
94+
);
3695

3796
uniffi_bindgen::library_mode::generate_bindings(
3897
&args.lib_source,

templates/macros.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,16 @@
1212
{{ s | typescript_docstring(indent_level) }}
1313
{%- else %}
1414
{%- endmatch %}
15-
{%- endmacro %}
15+
{%- endmacro %}
16+
17+
{%- macro import_file_path(file_name) -%}
18+
{{- file_name -}}
19+
{%- match out_import_extension -%}
20+
{%- when ImportExtension::None -%}
21+
{# explicitly empty #}
22+
{%- when ImportExtension::Ts -%}
23+
.ts
24+
{%- when ImportExtension::Js -%}
25+
.js
26+
{%- endmatch -%}
27+
{%- endmacro -%}

templates/node.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
{%- match func_def.throws_type() -%}
130130
{%- when Some(err) -%}
131131
uniffiCaller.rustCallWithError(
132-
/*liftError:*/ FfiConverterTypeAccessTokenError.lift.bind(FfiConverterTypeAccessTokenError), // FIXME: where does this error type come from?
132+
/*liftError:*/ {{err | typescript_ffi_converter_name}}.lift.bind({{err | typescript_ffi_converter_name}}),
133133
/*caller:*/ (callStatus) => {
134134
{%- else -%}
135135
uniffiCaller.rustCall(
@@ -216,7 +216,7 @@ import {
216216

217217
import {
218218
DataType,
219-
JsExternal,
219+
type JsExternal,
220220
open, /* close, */
221221
define,
222222
load,
@@ -229,7 +229,7 @@ import {
229229
freePointer,
230230
isNullPointer,
231231
PointerType,
232-
FieldType,
232+
type FieldType,
233233
} from 'ffi-rs';
234234

235235

@@ -254,8 +254,11 @@ import FFI_DYNAMIC_LIB, {
254254
{%- else -%}
255255
{%- endmatch %}
256256
{%- endfor %}
257-
} from './{{ci.namespace().to_kebab_case()}}-sys';
257+
} from './{%- call ts::import_file_path(sys_ts_main_file_name) -%}';
258258

259+
{% if out_disable_auto_loading_lib %}
260+
export { uniffiLoad, uniffiUnload } from './{%- call ts::import_file_path(sys_ts_main_file_name) -%}';
261+
{% endif %}
259262

260263

261264

templates/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{
22
"name": "lib{{ ci.crate_name().to_kebab_case() }}",
33
"description": "Uniffi wrapper around the {{ ci.crate_name() }} rust crate.",
4-
"version": "0.0.1",
5-
{# "main": "{{ node_ts_main_file_name }}.js", FIXME: should this be set? But then I'd need to define a whole build system in here, and that is probably going too far... -#}
64
"dependencies": {
75
"ffi-rs": "^1.3.0",
86
"ref-napi": "^3.0.3",

0 commit comments

Comments
 (0)