fix: handle null returnType in decodeKrpcType (the actual root cause!)
The stack trace finally showed the truth: the error was in our own decodeKrpcType, not in protobufjs. The kRPC server omits the `return_type` field for procedures that have no return value (e.g. AddStream, RemoveStream, all the setters). protobufjs decodes missing message fields as null. Our cache then called `decodeKrpcType(null)`, which did `null.code` and threw. The null.code error message was a red herring all along — it looked like protobufjs was looking up an enum descriptor, but it was actually just our code accessing .code on a null parameter. The protobufjs fixes (uint32 instead of nested-enum, adding the game_scenes field) were real and needed — but they weren't the cause of THIS particular failure mode. Fix: - decodeKrpcType accepts null/undefined and returns a NONE type (code 0) in that case - Added a regression test in services.test.ts that builds a fake ServiceCache with a procedure whose returnType is null
This commit is contained in:
@@ -78,7 +78,23 @@ export interface RawKrpcTypeMessage {
|
|||||||
types: RawKrpcTypeMessage[];
|
types: RawKrpcTypeMessage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeKrpcType(raw: RawKrpcTypeMessage): KrpcType {
|
/**
|
||||||
|
* Decode a kRPC Type protobuf message into our plain KrpcType shape.
|
||||||
|
*
|
||||||
|
* Returns a NONE-type KrpcType if `raw` is null/undefined or doesn't
|
||||||
|
* have a `code` field — which happens for procedures with no return
|
||||||
|
* value (the kRPC server omits the `return_type` field). We treat that
|
||||||
|
* as the NONE type code (0) rather than throwing.
|
||||||
|
*/
|
||||||
|
export function decodeKrpcType(raw: RawKrpcTypeMessage | null | undefined): KrpcType {
|
||||||
|
if (!raw || typeof raw.code !== 'number') {
|
||||||
|
return {
|
||||||
|
code: 0 as TypeCodeValue, // NONE
|
||||||
|
service: '',
|
||||||
|
name: '',
|
||||||
|
types: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
code: raw.code as TypeCodeValue,
|
code: raw.code as TypeCodeValue,
|
||||||
service: raw.service ?? '',
|
service: raw.service ?? '',
|
||||||
|
|||||||
@@ -196,4 +196,33 @@ describe('ServiceCache', () => {
|
|||||||
expect(r.info.returnType.types[0]?.code).toBe(100);
|
expect(r.info.returnType.types[0]?.code).toBe(100);
|
||||||
expect(r.info.returnType.types[0]?.name).toBe('CelestialBody');
|
expect(r.info.returnType.types[0]?.name).toBe('CelestialBody');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles procedures with no return type (returnType is null)', () => {
|
||||||
|
// Real kRPC server omits the `return_type` field for void
|
||||||
|
// procedures (e.g. AddStream, setters). protobufjs decodes
|
||||||
|
// missing message fields as null. Our cache must not crash.
|
||||||
|
const raw = {
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: 'KRPC',
|
||||||
|
procedures: [
|
||||||
|
{
|
||||||
|
name: 'AddStream',
|
||||||
|
parameters: [],
|
||||||
|
returnType: null, // <-- the trigger
|
||||||
|
returnIsNullable: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
classes: [],
|
||||||
|
enumerations: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const cache = new ServiceCache(raw as unknown as Parameters<typeof ServiceCache>[0]);
|
||||||
|
const r = cache.lookup('KRPC', 'AddStream');
|
||||||
|
expect(r.found).toBe(true);
|
||||||
|
if (!r.found) throw new Error('unreachable');
|
||||||
|
// Missing return type becomes NONE (code 0).
|
||||||
|
expect(r.info.returnType.code).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user