protobuf-ts icon indicating copy to clipboard operation
protobuf-ts copied to clipboard

Provide access to custom `EnumOptions`, `EnumValueOptions`, and `OneofOptions` by utiliting `WeakMap`

Open jcready opened this issue 1 year ago • 0 comments

Similar to https://github.com/timostamm/protobuf-ts/issues/306#issuecomment-1131606572 it seems like it should be possible to store and retrieve custom EnumOptions and EnumValueOptions by having the generated code hold onto a reference to the custom options in a WeakMap.

// generated code
import { registerEnumOptions } from "@protobuf-ts/runtime";

export enum ExampleEnum {
    /**
     * @generated from protobuf enum value: EXAMPLE = 0;
     */
    EXAMPLE = 0,
};

registerEnumOptions(
    ExampleEnum,
    {
        options: {
            "foo.bar": {
                "baz_qux": 123,
            }
        },
        valueOptions: {
            "EXAMPLE": { // Use enum "label" instead of "value" to avoid collisions with `allow_alias`
                "my.example" {
                    "enum_value_option": true,
                },
            },
        },
    }
);
// usage
import { readEnumOption, readEnumValueOption } from "@protobuf-ts/runtime";
import { ExampleEnum } from "../generated/code";

let enumOption = readEnumOption(ExampleEnum, "foo.bar", OptionalMessageType);
//  { bazQux: 123 }
let enumValueOption = readEnumValueOption(ExampleEnum, ExampleEnum[ExampleEnum.EXAMPLE], "my.example", OptionalMessageType);
//  { enumValueOption: true }
// @protobuf-ts/runtime implementation
interface EnumOptions {
    options?: JsonObject;
    valueOptions?: Record<string, JsonObject | undefined>;
}

const enumOptionsMap = new WeakMap<object, EnumOptions>();

export function registerEnumOptions(enum: object, options: EnumOptions): void {
    enumOptionsMap.set(enum, options);
}

export function readEnumOption<T extends object>(enum: object, extensionName: string): JsonValue | undefined;
export function readEnumOption<T extends object>(enum: object, extensionName: string, extensionType: IMessageType<T>): T | undefined;
export function readEnumOption<T extends object>(enum: object, extensionName: string, extensionType?: IMessageType<T>): T | JsonValue | undefined {
    const optionVal = enumOptionsMap.get(enum)?.options?.[extensionName];
    if (optionVal === undefined) {
        return optionVal;
    }
    return extensionType ? extensionType.fromJson(optionVal) : optionVal;
}

export function readEnumValueOption<T extends object>(enum: object, enumValueName: string, extensionName: string): JsonValue | undefined;
export function readEnumValueOption<T extends object>(enum: object, enumValueName: string, extensionName: string, extensionType: IMessageType<T>): T | undefined;
export function readEnumValueOption<T extends object>(enum: object, enumValueName: string, extensionName: string, extensionType?: IMessageType<T>): T | JsonValue | undefined {
    const optionVal = enumOptionsMap.get(enum)?.valueOptions?.[extensionName];
    if (optionVal === undefined) {
        return optionVal;
    }
    return extensionType ? extensionType.fromJson(optionVal) : optionVal;
}

This same method could be used to provide access to OneofOptions as well:

// generated code
registerOneofOptions(ExampleMessageType, 'my_oneof_name', { ...JsonObjectOptions });

// usage
const myOneofOptions = readOneofOption(ExampleMessageType, 'my_oneof_name', OptionalMessageType);

jcready avatar May 17 '23 12:05 jcready