neon/sys/bindings/
mod.rs

1//! # FFI bindings to Node-API symbols
2//!
3//! Rust types generated from [Node-API](https://nodejs.org/api/n-api.html).
4
5// These types are manually copied from bindings generated from `bindgen`. To
6// update, use the following approach:
7//
8// * Run a debug build of Neon at least once to install `nodejs-sys`
9// * Open the generated bindings at `target/debug/build/nodejs-sys-*/out/bindings.rs`
10// * Copy the types needed into `types.rs` and `functions.rs`
11// * Modify to match Rust naming conventions:
12//   - Remove `napi_` prefixes
13//   - Use `PascalCase` for types
14//   - Rename types that match a reserved word
15
16/// Constructs the name of a N-API symbol as a string from a function identifier
17/// E.g., `get_undefined` becomes `"napi_get_undefined"`
18macro_rules! napi_name {
19    // Explicitly replace identifiers that have been renamed from the N-API
20    // symbol because they would match a reserved word.
21    (typeof_value) => {
22        "napi_typeof"
23    };
24    // Default case: Stringify the identifier and prefix with `napi_`
25    ($name:ident) => {
26        concat!("napi_", stringify!($name))
27    };
28}
29
30/// Generate dynamic bindings to N-API symbols from definitions in an
31/// block `extern "C"`.
32///
33/// * A single global mutable struct holds references to the N-API functions
34/// * The global `Napi` struct  is initialized with stubs that panic if called
35/// * A `load` function is generated that loads the N-API symbols from the
36///   host process and replaces the global struct with real implementations
37/// * `load` should be called exactly once before using any N-API functions
38/// * Wrapper functions are generated to delegate to fields in the `Napi` struct
39///
40/// Sample input:
41///
42/// ```ignore
43/// extern "C" {
44///     fn get_undefined(env: Env, result: *mut Value) -> Status;
45///     /* Additional functions may be included */  
46/// }
47/// ```
48///
49/// Generated output:
50///
51/// ```ignore
52/// // Each field is a pointer to a N-API function
53/// struct Napi {
54///     get_undefined: unsafe extern "C" fn(env: Env, result: *mut Value) -> Status,
55///     /* ... repeat for each N-API function */
56/// }
57///
58/// // Defines a panic function that is called if symbols have not been loaded
59/// #[inline(never)]
60/// fn panic_load<T>() -> T {
61///     panic!("Must load N-API bindings")
62/// }
63///
64/// // Mutable global instance of the Napi struct
65/// // Initialized with stubs of N-API methods that panic
66/// static mut NAPI: Napi = {
67///     // Stubs are defined in a block to prevent naming conflicts with wrappers
68///     unsafe extern "C" fn get_undefined(_: Env, _: *mut Value) -> Status {
69///         panic_load()
70///     }
71///     /* ... repeat for each N-API function */
72///
73///     Napi {
74///         get_undefined,
75///         /* ... repeat for each N-API function */
76///     }
77/// };
78///
79/// // Load N-API symbols from the host process
80/// // # Safety: Must only be called once
81/// pub(super) unsafe fn load(
82///     host: &libloading::Library,
83///     actual_napi_version: u32,
84///     expected_napi_version: u32,
85/// ) -> Result<(), libloading::Error> {
86///     assert!(
87///         actual_napi_version >= expected_napi_version,
88///         "Minimum required N-API version {}, found {}.",
89///         expected_napi_version,
90///         actual_napi_version,
91///     );
92///
93///     NAPI = Napi {
94///         // Load each N-API symbol
95///         get_undefined: *host.get("napi_get_undefined".as_bytes())?,
96///         /* ... repeat for each N-API function */
97///     };
98///
99///     Ok(())
100/// }
101///
102/// // Each N-API function has wrapper for easy calling. These calls are optimized
103/// // to a single pointer dereference.
104/// #[inline]
105/// pub(crate) unsafe fn get_undefined(env: Env, result: *mut Value) -> Status {
106///     (NAPI.get_undefined)(env, result)
107/// }
108/// ```
109macro_rules! generate {
110    (#[$extern_attr:meta] extern "C" {
111        $($(#[$attr:meta])? fn $name:ident($($param:ident: $ptype:ty$(,)?)*)$( -> $rtype:ty)?;)+
112    }) => {
113        struct Napi {
114            $(
115                $name: unsafe extern "C" fn(
116                    $($param: $ptype,)*
117                )$( -> $rtype)?,
118            )*
119        }
120
121        #[inline(never)]
122        fn panic_load<T>() -> T {
123            panic!("Node-API symbol has not been loaded")
124        }
125
126        static mut NAPI: Napi = {
127            $(
128                unsafe extern "C" fn $name($(_: $ptype,)*) $(-> $rtype)? {
129                    panic_load()
130                }
131            )*
132
133            Napi {
134                $(
135                    $name,
136                )*
137            }
138        };
139
140        pub(super) unsafe fn load(host: &libloading::Library) {
141            let print_warn = |err| eprintln!("WARN: {}", err);
142
143            unsafe {
144                NAPI = Napi {
145                    $(
146                        $name: match host.get(napi_name!($name).as_bytes()) {
147                            Ok(f) => *f,
148                            // Node compatible runtimes may not have full coverage of Node-API
149                            // (e.g., bun). Instead of failing to start, warn on start and
150                            // panic when the API is called.
151                            // https://github.com/Jarred-Sumner/bun/issues/158
152                            Err(err) => {
153                                print_warn(err);
154                                NAPI.$name
155                            },
156                        },
157                    )*
158                };
159            }
160        }
161
162        $(
163            impl_napi_wrapper! {
164                attributes:
165                    #[$extern_attr]
166                    $(#[$attr])?
167                    #[inline]
168                    #[doc = concat!(
169                        "[`",
170                        napi_name!($name),
171                        "`](https://nodejs.org/api/n-api.html#",
172                        napi_name!($name),
173                        ")",
174                    )],
175                name: $name,
176                parameters: ($($param: $ptype,)*)
177                $(, return_type: $rtype)?
178            }
179        )*
180    };
181}
182
183macro_rules! impl_napi_wrapper {
184    {
185        attributes: $(#[$attr:meta])*,
186        name: $name:ident,
187        parameters: ($($param:ident: $ptype:ty,)*)
188    } => {
189        $(#[$attr])*
190        pub unsafe fn $name($($param: $ptype,)*) {
191            unsafe { (NAPI.$name)($($param,)*); }
192        }
193    };
194
195    {
196        attributes: $(#[$attr:meta])*,
197        name: $name:ident,
198        parameters: ($($param:ident: $ptype:ty,)*),
199        return_type:$rtype:ty
200    } => {
201        $(#[$attr])*
202        pub unsafe fn $name($($param: $ptype,)*) -> ::core::result::Result<(), $rtype> {
203            let r: $rtype = unsafe { (NAPI.$name)($($param,)*) };
204            match r {
205                <$rtype>::Ok => Ok(()),
206                status => Err(status)
207            }
208        }
209    };
210}
211
212pub use self::{functions::*, types::*};
213
214mod functions;
215mod types;