protobuf-ts
protobuf-ts copied to clipboard
PartialMessage<T> of message with oneof field requires that oneof field be non-partial
Calling .create()
on a MessageType
instance allows partial oneof fields at runtime, but at compile-time they are required to be non-partial. TS Playground
// pulled from https://github.com/timostamm/protobuf-ts/blob/master/packages/runtime/src/message-type-contract.ts
export type PartialMessage<T extends object> = {
[K in keyof T]?: PartialField<T[K]>
}; type PartialField<T> =
T extends (Date | Uint8Array | bigint | boolean | string | number) ? T
: T extends Array<infer U> ? Array<PartialField<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<PartialField<U>>
: T extends { oneofKind: string } ? T
: T extends { oneofKind: undefined } ? T
: T extends object ? PartialMessage<T>
: T ;
// example
interface Bar {
arr: number[];
other?: number;
}
interface Foo {
args: {
oneofKind: "bar";
bar: Bar;
} | {
oneofKind: undefined;
}
}
function create(i: PartialMessage<Foo>) {}
create({
args: {
oneofKind: 'bar',
bar: { // ts-2741: Property 'arr' is missing in type '{ other: number; }' but required in type 'Bar'.
other: 1,
}
}
})
I don't think there's actually a way to get this working given the existing types. This is another instance where the updated oneof representation would make this trivial: : T extends { kind: string; value: infer U } ? { kind: T['kind']; value: PartialField<U> }
.
Good call. Would you like to open a PR against the 3.x branch?
I believe I have a solution that can work for the existing v2 representation. I know this PartialMessage
is depended upon heavily so it's possible I'm not properly covering some case.
type SetOneof = { oneofKind: string };
type SetOneofKind<T extends SetOneof> = T extends { oneofKind: string & keyof T }
? T['oneofKind']
: never;
type PartialOneofValueMap<T extends SetOneof, K extends SetOneofKind<T> = SetOneofKind<T>> = {
[k in K]: { oneofKind: k } & {
[p in k]: T extends { oneofKind: k } ? PartialField<T[k]> : never;
};
};
type PartialOneof<
T extends SetOneof,
K extends SetOneofKind<T> = SetOneofKind<T>,
M extends PartialOneofValueMap<T, K> = PartialOneofValueMap<T, K>
> = T extends { oneofKind: keyof M } ? M[keyof M] : { oneofKind: undefined };
export type PartialMessage<T extends object> = {
[K in keyof T]?: PartialField<T[K]>
}; type PartialField<T> =
T extends (Date | Uint8Array | bigint | boolean | string | number) ? T
: T extends Array<infer U> ? Array<PartialField<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<PartialField<U>>
: T extends { oneofKind: string & keyof T } ? PartialOneof<T>
: T extends { oneofKind: undefined } ? T
: T extends object ? PartialMessage<T>
: T ;