From cd61e90e59844795fb5d7d86ec99bd37d2fdf21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Ve=C4=8Derek?= Date: Tue, 10 Oct 2023 16:40:48 +0200 Subject: [PATCH] feat: add unrecognizedEnumName and unrecognizedEnumValue options (#946) --- README.markdown | 20 +++- .../default-value-test.ts | 23 ++++ .../parameters.txt | 1 + .../test.bin | Bin 0 -> 262 bytes .../test.proto | 7 ++ .../test.ts | 51 +++++++++ src/enums.ts | 103 +++++++++++++----- src/options.ts | 9 ++ tests/options-test.ts | 2 + 9 files changed, 181 insertions(+), 35 deletions(-) create mode 100644 integration/enums-with-unrecognized-name-value/default-value-test.ts create mode 100644 integration/enums-with-unrecognized-name-value/parameters.txt create mode 100644 integration/enums-with-unrecognized-name-value/test.bin create mode 100644 integration/enums-with-unrecognized-name-value/test.proto create mode 100644 integration/enums-with-unrecognized-name-value/test.ts diff --git a/README.markdown b/README.markdown index 9c7f1fcaa..77e2ddf10 100644 --- a/README.markdown +++ b/README.markdown @@ -14,15 +14,15 @@ - [Buf](#buf) - [ESM](#esm) - [Goals](#goals) + - [Non-Goals](#non-goals) - [Example Types](#example-types) - [Highlights](#highlights) - [Auto-Batching / N+1 Prevention](#auto-batching--n1-prevention) - [Usage](#usage) - - [Supported options](#supported-options) - - [Only Types](#only-types) - - [NestJS Support](#nestjs-support) - - [Watch Mode](#watch-mode) - - [Basic gRPC implementation](#basic-grpc-implementation) + - [Supported options](#supported-options) + - [NestJS Support](#nestjs-support) + - [Watch Mode](#watch-mode) + - [Basic gRPC implementation](#basic-grpc-implementation) - [Sponsors](#sponsors) - [Development](#development) - [Assumptions](#assumptions) @@ -354,7 +354,15 @@ Generated code will be placed in the Gradle build directory. See the "OneOf Handling" section. -- With `--ts_proto_opt=unrecognizedEnum=false` enums will not contain an `UNRECOGNIZED` key with value of -1. +- With `--ts_proto_opt=unrecognizedEnumName=` enums will contain a key `` with value of the `unrecognizedEnumValue` option. + + Defaults to `UNRECOGNIZED`. + +- With `--ts_proto_opt=unrecognizedEnumValue=` enums will contain a key provided by the `unrecognizedEnumName` option with value of ``. + + Defaults to `-1`. + +- With `--ts_proto_opt=unrecognizedEnum=false` enums will not contain an unrecognized enum key and value as provided by the `unrecognizedEnumName` and `unrecognizedEnumValue` options. - With `--ts_proto_opt=removeEnumPrefix=true` generated enums will have the enum name removed from members. diff --git a/integration/enums-with-unrecognized-name-value/default-value-test.ts b/integration/enums-with-unrecognized-name-value/default-value-test.ts new file mode 100644 index 000000000..b515f835e --- /dev/null +++ b/integration/enums-with-unrecognized-name-value/default-value-test.ts @@ -0,0 +1,23 @@ +import { stateEnumFromJSON, stateEnumToJSON, stateEnumToNumber, StateEnum } from "./test"; + +describe("enums-with-unrecognized-name-value", () => { + describe("stateEnumFromJSON", () => { + it("returns correct default state", () => { + expect(stateEnumFromJSON("non-existent")).toBe(StateEnum.UNKNOWN_STATE); + }); + }); + + describe("stateEnumToJSON", () => { + it("returns correct default state", () => { + // @ts-expect-error Argument of type '1' is not assignable to parameter of type 'StateEnum'. + expect(stateEnumToJSON(1)).toBe("UNKNOWN_STATE"); + }); + }); + + describe("stateEnumToNumber", () => { + it("returns correct default state", () => { + // @ts-expect-error Argument of type '1' is not assignable to parameter of type 'StateEnum'. + expect(stateEnumToNumber(1)).toBe(0); + }); + }); +}); diff --git a/integration/enums-with-unrecognized-name-value/parameters.txt b/integration/enums-with-unrecognized-name-value/parameters.txt new file mode 100644 index 000000000..67b950f46 --- /dev/null +++ b/integration/enums-with-unrecognized-name-value/parameters.txt @@ -0,0 +1 @@ +unrecognizedEnumName=UNKNOWN,unrecognizedEnumValue=0,stringEnums=true diff --git a/integration/enums-with-unrecognized-name-value/test.bin b/integration/enums-with-unrecognized-name-value/test.bin new file mode 100644 index 0000000000000000000000000000000000000000..417ca199c1f594f2420d435e3f6988ca26607e77 GIT binary patch literal 262 zcmYk%J#NB45QgE|nO%GRg@74Spp=vpq2L55>;fdaFi}cGBuai1mL~Vf@wfwPWJ{FK zyk9dw*k8iO`S%n~@7_y3nVK2>-3*?>e1WV9yRa{g*MpAn`c our enum. */ -export function generateEnumFromJson(ctx: Context, fullName: string, enumDesc: EnumDescriptorProto): Code { +export function generateEnumFromJson( + ctx: Context, + fullName: string, + enumDesc: EnumDescriptorProto, + unrecognizedEnum: UnrecognizedEnum, +): Code { const { options, utils } = ctx; const chunks: Code[] = []; @@ -92,12 +101,19 @@ export function generateEnumFromJson(ctx: Context, fullName: string, enumDesc: E } if (options.unrecognizedEnum) { - chunks.push(code` - case ${UNRECOGNIZED_ENUM_VALUE}: - case "${UNRECOGNIZED_ENUM_NAME}": - default: - return ${fullName}.${UNRECOGNIZED_ENUM_NAME}; - `); + if (!unrecognizedEnum.present) { + chunks.push(code` + case ${options.unrecognizedEnumValue}: + case "${options.unrecognizedEnumName}": + default: + return ${fullName}.${options.unrecognizedEnumName}; + `); + } else { + chunks.push(code` + default: + return ${fullName}.${unrecognizedEnum.name}; + `); + } } else { // We use globalThis to avoid conflicts on protobuf types named `Error`. chunks.push(code` @@ -112,7 +128,12 @@ export function generateEnumFromJson(ctx: Context, fullName: string, enumDesc: E } /** Generates a function with a big switch statement to encode our enum -> JSON. */ -export function generateEnumToJson(ctx: Context, fullName: string, enumDesc: EnumDescriptorProto): Code { +export function generateEnumToJson( + ctx: Context, + fullName: string, + enumDesc: EnumDescriptorProto, + unrecognizedEnum: UnrecognizedEnum, +): Code { const { options, utils } = ctx; const chunks: Code[] = []; @@ -137,18 +158,30 @@ export function generateEnumToJson(ctx: Context, fullName: string, enumDesc: Enu } if (options.unrecognizedEnum) { - chunks.push(code` - case ${fullName}.${UNRECOGNIZED_ENUM_NAME}:`); + if (!unrecognizedEnum.present) { + chunks.push(code` + case ${fullName}.${options.unrecognizedEnumName}:`); - if (ctx.options.useNumericEnumForJson) { + if (ctx.options.useNumericEnumForJson) { + chunks.push(code` + default: + return ${options.unrecognizedEnumValue}; + `); + } else { + chunks.push(code` + default: + return "${options.unrecognizedEnumName}"; + `); + } + } else if (ctx.options.useNumericEnumForJson) { chunks.push(code` - default: - return ${UNRECOGNIZED_ENUM_VALUE}; - `); + default: + return ${options.unrecognizedEnumValue}; + `); } else { chunks.push(code` default: - return "${UNRECOGNIZED_ENUM_NAME}"; + return "${unrecognizedEnum.name}"; `); } } else { @@ -165,7 +198,12 @@ export function generateEnumToJson(ctx: Context, fullName: string, enumDesc: Enu } /** Generates a function with a big switch statement to encode our string enum -> int value. */ -export function generateEnumToNumber(ctx: Context, fullName: string, enumDesc: EnumDescriptorProto): Code { +export function generateEnumToNumber( + ctx: Context, + fullName: string, + enumDesc: EnumDescriptorProto, + unrecognizedEnum: UnrecognizedEnum, +): Code { const { options, utils } = ctx; const chunks: Code[] = []; @@ -178,11 +216,18 @@ export function generateEnumToNumber(ctx: Context, fullName: string, enumDesc: E } if (options.unrecognizedEnum) { - chunks.push(code` - case ${fullName}.${UNRECOGNIZED_ENUM_NAME}: - default: - return ${UNRECOGNIZED_ENUM_VALUE}; - `); + if (!unrecognizedEnum.present) { + chunks.push(code` + case ${fullName}.${options.unrecognizedEnumName}: + default: + return ${options.unrecognizedEnumValue}; + `); + } else { + chunks.push(code` + default: + return ${options.unrecognizedEnumValue}; + `); + } } else { // We use globalThis to avoid conflicts on protobuf types named `Error`. chunks.push(code` diff --git a/src/options.ts b/src/options.ts index 8fdda8e1c..b7d4d8fe4 100644 --- a/src/options.ts +++ b/src/options.ts @@ -66,6 +66,8 @@ export type Options = { nestJs: boolean; env: EnvOption; unrecognizedEnum: boolean; + unrecognizedEnumName: string; + unrecognizedEnumValue: number; exportCommonSymbols: boolean; outputSchema: boolean; onlyTypes: boolean; @@ -119,6 +121,8 @@ export function defaultOptions(): Options { nestJs: false, env: EnvOption.BOTH, unrecognizedEnum: true, + unrecognizedEnumName: "UNRECOGNIZED", + unrecognizedEnumValue: -1, exportCommonSymbols: true, outputSchema: false, onlyTypes: false, @@ -234,6 +238,11 @@ export function optionsFromParameter(parameter: string | undefined): Options { options.exportCommonSymbols = false; } + if (options.unrecognizedEnumValue) { + // Make sure to cast number options to an actual number + options.unrecognizedEnumValue = Number(options.unrecognizedEnumValue); + } + return options; } diff --git a/tests/options-test.ts b/tests/options-test.ts index 9a45bbef9..bce760654 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -46,6 +46,8 @@ describe("options", () => { "stringEnums": false, "unknownFields": false, "unrecognizedEnum": true, + "unrecognizedEnumName": "UNRECOGNIZED", + "unrecognizedEnumValue": -1, "useAbortSignal": false, "useAsyncIterable": false, "useDate": "timestamp",