{"record":{"id":"7d986b969a83d6bd","repo":"gitbutlerapp/gitbutler","slug":"e-lib","errorCode":null,"errorMessage":"{e}","messagePattern":"\\{e\\}","errorType":"validation","errorClass":"napi::Error","httpStatus":null,"severity":"error","filePath":"crates/but-api-macros/src/lib.rs","lineNumber":1465,"sourceCode":"        let ident = &pat_ident.ident;\n\n        if let Some(context_kind) = context_param_kind(&pat_ty.ty) {\n            context_bindings.push(ContextParamBinding {\n                ident: ident.clone(),\n                kind: context_kind,\n            });\n        }\n\n        if let Some(custom_transport) = parse_param_transport_mapping(pat_ty)? {\n            let transport_ty = &custom_transport.transport_ty;\n            let ts_type_str = type_to_ts_name(transport_ty);\n            let transport_ident = format_ident!(\"__{}_transport\", ident);\n            let actual_ty = &pat_ty.ty;\n            params.push(quote! { #[napi(ts_arg_type = #ts_type_str)] #ident: ::serde_json::Value });\n            names.push(ident.to_string());\n            conversions.push(quote! {\n                let #transport_ident: #transport_ty = ::serde_json::from_value(#ident)\n                    .map_err(|e| napi::Error::new(napi::Status::InvalidArg, format!(\"{e}\")))?;\n                let #ident: #actual_ty = <#actual_ty>::from(#transport_ident);\n            });\n            call_arg_idents.push(quote! { #ident });\n            continue;\n        }\n\n        if let Some(permission_kind) = permission_param_kind(&pat_ty.ty)? {\n            let guard_ident = permission_guard_ident(ident);\n            permission_bindings.push(PermissionParamBinding {\n                ty: (*pat_ty.ty).clone(),\n                kind: permission_kind,\n                guard_ident: guard_ident.clone(),\n            });\n            call_arg_idents.push(match permission_kind {\n                PermissionParamKind::Exclusive => quote! { #guard_ident.write_permission() },\n                PermissionParamKind::Shared => quote! { #guard_ident.read_permission() },\n            });\n            continue;","sourceCodeStart":1447,"sourceCodeEnd":1483,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-api-macros/src/lib.rs#L1447-L1483","documentation":"Generated in `build_napi_params` for parameters whose type maps through a serde_json `Value` transport: `serde_json::from_value` fails while converting the JS-supplied value into the transport type, producing a napi InvalidArg error carrying serde_json's message (`{e}`). Means the JSON shape sent from JS didn't match the expected transport struct/enum.","triggerScenarios":"Calling a `but_api` function from JS passing an object with missing/extra/mistyped fields for a parameter that is deserialized from serde_json::Value (e.g. wrong nested types, string instead of number).","commonSituations":"SDK/TS type drift after a Rust struct changed; hand-built plain objects missing required fields; sending undefined/null where a field is required; camelCase vs snake_case key mismatch.","solutions":["Fix the JS argument so it matches the expected serde shape (field names and types).","Regenerate/inspect the TS definitions emitted by the macro to see the expected shape.","Log the parameter value before the call to spot mistyped fields.","Update the Rust transport struct to accept the shape callers actually send, if the new shape is intended."],"exampleFix":"// before\napi.createAssignment({ projectId: 123, commitId: \"abc\" }) // numeric id not accepted\n// after\napi.createAssignment({ projectId: \"123\", commitId: \"abc\" })","handlingStrategy":"validation","validationCode":"function assertMatchesShape(obj, requiredFields) {\n  if (obj == null || typeof obj !== 'object') throw new TypeError('expected object');\n  for (const f of requiredFields) {\n    if (obj[f] === undefined) throw new TypeError(`missing field: ${f}`);\n  }\n  return obj;\n}\n// call before the API:\nassertMatchesShape(arg, ['projectId', 'commitId']);","typeGuard":"function isPlainObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }","tryCatchPattern":"try { await api.fn(arg); }\ncatch (e) { if (e?.code === 'InvalidArg') console.error('bad JSON shape:', arg); throw e; }","preventionTips":["Use the generated TypeScript definitions instead of hand-built objects.","Keep SDK/TS types in sync with Rust transport structs after schema changes.","Avoid camelCase/snake_case drift; copy field names from the generated .d.ts."],"tags":["napi","serde","json","type-mismatch"],"backgroundTag":"json-decode-failed","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}