Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PartialMessage<T> of message with oneof field requires that oneof field be non-partial #380

Open
jcready opened this issue Oct 26, 2022 · 2 comments · May be fixed by #394
Open

PartialMessage<T> of message with oneof field requires that oneof field be non-partial #380

jcready opened this issue Oct 26, 2022 · 2 comments · May be fixed by #394

Comments

@jcready
Copy link
Contributor

jcready commented Oct 26, 2022

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> }.

@timostamm
Copy link
Owner

Good call. Would you like to open a PR against the 3.x branch?

@jcready
Copy link
Contributor Author

jcready commented Nov 11, 2022

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 ;

jcready added a commit to jcready/protobuf-ts that referenced this issue Nov 11, 2022
@jcready jcready linked a pull request Nov 11, 2022 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants