{"record":{"id":"865247e01bd484e6","repo":"can1357/oh-my-pi","slug":"expected-int32-got-typeof-value","errorCode":null,"errorMessage":"Expected int32, got ${typeof value}","messagePattern":"Expected int32, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/catalog/src/discovery/protobuf.ts","lineNumber":651,"sourceCode":"\nfunction requireBoolean(value: unknown): boolean {\n\tif (typeof value === \"boolean\") return value;\n\tthrow new Error(`Expected boolean, got ${typeof value}`);\n}\n\nfunction requireBytes(value: unknown): Uint8Array {\n\tif (value instanceof Uint8Array) return value;\n\tthrow new Error(\"Expected Uint8Array\");\n}\n\nfunction requireNumber(value: unknown): number {\n\tif (typeof value === \"number\" && Number.isFinite(value)) return value;\n\tthrow new Error(`Expected number, got ${typeof value}`);\n}\n\nfunction requireInt32(value: unknown): number {\n\tif (typeof value === \"number\" && Number.isInteger(value)) return value | 0;\n\tthrow new Error(`Expected int32, got ${typeof value}`);\n}\nfunction requireString(value: unknown): string {\n\tif (typeof value === \"string\") return value;\n\tthrow new Error(`Expected string, got ${typeof value}`);\n}\n\nfunction requireBigInt(value: unknown): bigint {\n\tif (typeof value === \"bigint\") return value;\n\tif (typeof value === \"number\" && Number.isInteger(value)) return BigInt(value);\n\tif (typeof value === \"string\") return BigInt(value);\n\tthrow new Error(`Expected bigint, got ${typeof value}`);\n}\n\nfunction requireUint32(value: unknown): number {\n\tif (typeof value === \"number\" && Number.isInteger(value) && value >= 0) return value >>> 0;\n\tthrow new Error(`Expected uint32, got ${typeof value}`);\n}\n","sourceCodeStart":633,"sourceCodeEnd":669,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/catalog/src/discovery/protobuf.ts#L633-L669","documentation":"Thrown by requireInt32, the validator for the `int32` and `enum` scalar kinds. It requires a JavaScript number that is an integer (`Number.isInteger`), then narrows it with `value | 0` to the signed 32-bit range on the way out. Non-integers (1.5), non-numbers (string \"2\", bigint 2n, null, undefined), and implicitly non-integer edge values (NaN) all throw with the value's `typeof`. Note the validator checks integrality, not the ±2^31 range — an out-of-range integer like 3_000_000_000 passes the check but is silently truncated by `| 0`, so range enforcement is on the caller.","triggerScenarios":"Specific: encoding an int32 or enum field with a fractional number (computed averages, divisions); passing a string enum name (\"ACTIVE\") where the descriptor expects the numeric enum value; passing a bigint from a database or 64-bit source into an int32 field; undefined for a required field; NaN from failed parsing; feeding a float-typed value into an enum field after a descriptor refactor.","commonSituations":"Real-world: enum values fetched from an API as strings; Math.mean-style results stored without rounding; DB drivers returning bigint for counts; TypeScript enum objects iterated as mixed keys/values and the string key passed through; legacy code that used numeric strings in JSON payloads.","solutions":["Map string enum names to their numeric values before encoding (keep a name->number lookup table for your enum).","Round explicitly with `Math.round`/`Math.trunc` when a computed value must be integral, and assert range `-2147483648 <= n <= 2147483647`.","Convert bigints with `Number(big)` after verifying it fits in int32; use \"int64\"/\"uint64\" kinds when it doesn't.","Parse numeric strings with `Number.parseInt(s, 10)` and validate before building the message.","Use codec.create() with the inferred TypeScript types so wrong field types fail at compile time."],"exampleFix":"// before\nconst msg = Task.create({ status: body.status }); // body.status = \"DONE\" (string)\n// after\nconst status = STATUS_BY_NAME[body.status]; // { TODO: 0, DONE: 1, ... }\nif (status === undefined) throw new TypeError(`unknown status: ${body.status}`);\nconst msg = Task.create({ status });","handlingStrategy":"validation","validationCode":"function toInt32(v: unknown): number {\n  const n = typeof v === \"string\" ? Number.parseInt(v, 10) : typeof v === \"bigint\" ? Number(v) : v;\n  if (typeof n !== \"number\" || !Number.isInteger(n) || n < -2147483648 || n > 2147483647) {\n    throw new TypeError(`int32/enum field needs an integer in [-2^31, 2^31), got ${String(v)}`);\n  }\n  return n;\n}","typeGuard":"function isInt32(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isInteger(v) && v >= -2147483648 && v <= 2147483647;\n}","tryCatchPattern":"try {\n  return MyMsg.encode(value);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Expected int32, got \")) {\n    throw new TypeError(`int32/enum field '${fieldName}' got ${err.message}; map string enum names to numbers and round computed values first`);\n  }\n  throw err;\n}","preventionTips":["Maintain name->number lookup tables for enums and never encode raw API string values.","Round or truncate computed values (Math.trunc) before assigning to int32 fields.","Check the ±2^31 range yourself — the validator's `| 0` silently truncates out-of-range integers that pass the integrality check.","Use codec.create() with inferred TypeScript enum types so wrong enum inputs fail at compile time."],"tags":["protobuf","type-validation","int32","enum","encode"],"backgroundTag":"unexpected-field-type","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}