{"record":{"id":"1d0597d74050c582","repo":"firecrawl/pdf-inspector","slug":"ctx-e","errorCode":null,"errorMessage":"{ctx}: {e}","messagePattern":"\\{ctx\\}: \\{e\\}","errorType":"exception","errorClass":"napi::Error (Status::GenericFailure)","httpStatus":null,"severity":"error","filePath":"napi/src/lib.rs","lineNumber":417,"sourceCode":"        pages_with_columns: result.pages_with_columns,\n        is_complex: result.is_complex,\n        processing_time_ms: timing_ms(result.processing_time_ms),\n        render_time_ms: timing_ms(result.render_time_ms),\n        ocr_time_ms: timing_ms(result.ocr_time_ms),\n    }\n}\n\nfn convert_item_type(t: &pdf_inspector::types::ItemType) -> (ItemType, Option<String>) {\n    match t {\n        pdf_inspector::types::ItemType::Text => (ItemType::Text, None),\n        pdf_inspector::types::ItemType::Image => (ItemType::Image, None),\n        pdf_inspector::types::ItemType::Link(url) => (ItemType::Link, Some(url.clone())),\n        pdf_inspector::types::ItemType::FormField => (ItemType::FormField, None),\n    }\n}\n\nfn to_napi_err(e: impl std::fmt::Display, ctx: &str) -> Error {\n    Error::new(Status::GenericFailure, format!(\"{ctx}: {e}\"))\n}\n\n/// Run a closure, catching any Rust panic and converting it to a NAPI error.\n/// Prevents process abort from unwind panics in the native module.\nfn catch_panic<F, T>(ctx: &str, f: F) -> Result<T>\nwhere\n    F: FnOnce() -> Result<T> + panic::UnwindSafe,\n{\n    match panic::catch_unwind(f) {\n        Ok(result) => result,\n        Err(payload) => {\n            let msg = if let Some(s) = payload.downcast_ref::<&str>() {\n                s.to_string()\n            } else if let Some(s) = payload.downcast_ref::<String>() {\n                s.clone()\n            } else {\n                \"unknown panic\".to_string()\n            };","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/firecrawl/pdf-inspector/blob/636ca1a58bdc1af4cd3fc20b8c1f549a1121cca7/napi/src/lib.rs#L399-L435","documentation":"to_napi_err in napi/src/lib.rs wraps any Rust-side failure (Display error) into a NAPI Error with Status::GenericFailure and the message \"{ctx}: {e}\". The napi bindings use it at every FFI boundary so a Rust error surfaces in Node.js as a generic JavaScript Error rather than a panic or abort. The ctx prefix tells you which binding operation (e.g. extract, detect) failed.","triggerScenarios":"Calling any pdf2md NAPI binding (extract/process/detect functions) whose underlying Rust call returns Err — e.g. invalid PDF bytes, unreadable file path, malformed options passed through to_napi_err.","commonSituations":"Passing a non-existent file path from Node.js; passing a corrupted or password-protected PDF; passing options of the wrong shape that fail Rust-side validation; running the native module on a platform where an internal syscall fails.","solutions":["Read the ctx prefix in the message to identify which binding call failed, then check the arguments passed to that call from JavaScript.","Validate inputs in JS before calling: file exists and readable (fs.accessSync), buffer is a PDF (%PDF- header), options match the documented shape.","Log the full error message — the part after the first ': ' is the original Rust error with the concrete cause.","Update the napi package and native binary to matching versions, since a mismatched .node binary can cause argument conversion failures."],"exampleFix":"// before\nconst md = pdf2md.processFileSync('/tmp/doc.pdf');\n// after\nif (!fs.existsSync('/tmp/doc.pdf')) throw new Error('file missing');\nif (!fs.readFileSync('/tmp/doc.pdf').subarray(0,5).toString().startsWith('%PDF-')) throw new Error('not a PDF');\nconst md = pdf2md.processFileSync('/tmp/doc.pdf');","handlingStrategy":"try-catch","validationCode":"const fs = require('fs');\nfunction assertPdfInput(path, options) {\n  fs.accessSync(path, fs.constants.R_OK);\n  const head = fs.readFileSync(path).subarray(0, 5).toString();\n  if (!head.startsWith('%PDF-')) throw new Error('not a PDF: ' + path);\n  if (options != null && typeof options !== 'object') throw new Error('options must be an object');\n}","typeGuard":"function isNapiError(e) {\n  return e instanceof Error && typeof e.message === 'string' && !e.message.includes('Rust panic:');\n}","tryCatchPattern":"try {\n  const md = pdf2md.processFileSync(path, opts);\n} catch (e) {\n  const [ctx, cause] = String(e.message).split(/: (.+)/);\n  console.error(`binding ${ctx} failed:`, cause ?? e.message);\n  // fall back or rethrow based on ctx\n}","preventionTips":["Always validate file existence and PDF magic bytes before calling bindings.","Keep the napi JS package and the compiled .node binary versions in sync.","Match options objects against the documented TypeScript definitions (strict typing / zod schema).","Include the full error message in logs — the ctx prefix pinpoints the failing operation."],"tags":["napi","nodejs","native-module","error-wrapping"],"backgroundTag":"generic-failure-napi-error","analyzedSha":"636ca1a58bdc1af4cd3fc20b8c1f549a1121cca7","analyzedAt":"2026-09-05T08:40:31.256Z","contentChangedAt":"2026-09-05T08:40:31.256Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}