swc-project/swc · error · napi::Error

GenericFailure

GenericFailure

Error message

{err:?}

What it means

swc_nodejs_common is the shared plumbing for SWC's Node.js (napi) bindings. Its `MapErr` trait converts any `anyhow::Error` into a `napi::Error` with `Status::GenericFailure`, formatting the entire anyhow error chain with `{:?}` (crates/swc_nodejs_common/src/lib.rs:12). It is the single funnel for failures crossing the Rust→JS boundary — including `get_deserialized`, which serde-deserializes a buffer passed from JavaScript — so on the JS side you only see code 'GenericFailure' plus the debug string.

Source

Thrown at crates/swc_nodejs_common/src/lib.rs:12

#![deny(warnings)]

use std::any::type_name;

use anyhow::Context;
use napi::Status;
use serde::de::DeserializeOwned;

pub trait MapErr<T>: Into<Result<T, anyhow::Error>> {
    fn convert_err(self) -> napi::Result<T> {
        self.into()
            .map_err(|err| napi::Error::new(Status::GenericFailure, format!("{err:?}")))
    }
}

impl<T> MapErr<T> for Result<T, anyhow::Error> {}

pub fn get_deserialized<T, B>(buffer: B) -> napi::Result<T>
where
    T: DeserializeOwned,
    B: AsRef<[u8]>,
{
    let mut deserializer = serde_json::Deserializer::from_slice(buffer.as_ref());
    deserializer.disable_recursion_limit();

    let v = T::deserialize(&mut deserializer)
        .with_context(|| {
            format!(
                "Failed to deserialize buffer as {}\nJSON: {}",
                type_name::<T>(),

View on GitHub (pinned to 5176682b65)

Solutions

  1. Read the {err:?} text — for serde failures it names the exact field and expected type
  2. Align the JS-side options object with the interface the installed binding version expects
  3. Ensure the native module and the JS package versions match (rebuild/reinstall node_modules)
  4. Log the exact payload right before the call and validate its shape against the binding's schema

Example fix

// before
const res = await transformFn(id, { soruceType: 'module' }); // typo → serde error → GenericFailure
// after
const res = await transformFn(id, { sourceType: 'module' });
Defensive patterns

Strategy: try-catch

Validate before calling

// JS side: validate the payload shape before calling the native binding
function validateTransformOpts(opts) {
  const known = new Set(['sourceType', 'filename', 'jsc', 'module', 'isModule', 'minify', 'sourceMaps', 'inlineSourcesContent', 'parser']);
  for (const key of Object.keys(opts || {})) if (!known.has(key)) throw new Error(`unknown option: ${key}`);
  if ('sourceType' in (opts || {}) && !['module', 'script', 'unambiguous'].includes(opts.sourceType)) throw new Error('bad sourceType');
}

Type guard

// Narrow a caught error to a napi GenericFailure from the Rust binding
function isNapiGenericFailure(e: unknown): e is Error & { code: 'GenericFailure' } {
  return e instanceof Error && (e as any).code === 'GenericFailure';
}

Try / catch

try {
  const out = await nativeTransform(id, code, opts);
} catch (e) {
  if (isNapiGenericFailure(e)) {
    // e.message is the Rust-side {err:?} debug string; parse the serde path from it
    console.error('binding failure:', e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling a Node binding whose Rust body returns Err: `get_deserialized::<T, _>(&buffer)` fails when the JS-passed payload does not deserialize into the expected type (wrong field names/types, malformed JSON), or any internal anyhow error passed through `.convert_err()` in the binding wrapper.

Common situations: Passing an options object with a typo'd or renamed field from JS (serde mismatch); version skew between the JS wrapper package and the native module; passing a string where bytes are expected; empty or truncated buffers after a failed fetch/transform.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/f91f7cf894674b2c. Report an issue: GitHub.