From c54f06c4a7dd766abf3f91932b1e4cdf38b7f346 Mon Sep 17 00:00:00 2001 From: Luke Alvoeiro Date: Sat, 8 Jun 2024 05:36:56 +0800 Subject: [PATCH] feat: `no-file-descriptor` setting for outputSchema option (#1047) --- README.markdown | 2 +- .../schema-no-file-descriptor/const-enum.bin | Bin 0 -> 692 bytes .../const-enum.proto | 13 + .../schema-no-file-descriptor/const-enum.ts | 281 ++++++++++++++++++ .../schema-no-file-descriptor/parameters.txt | 1 + .../schema-no-file-descriptor-test.ts | 8 + src/options.ts | 2 +- src/schema.ts | 12 +- 8 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 integration/schema-no-file-descriptor/const-enum.bin create mode 100644 integration/schema-no-file-descriptor/const-enum.proto create mode 100644 integration/schema-no-file-descriptor/const-enum.ts create mode 100644 integration/schema-no-file-descriptor/parameters.txt create mode 100644 integration/schema-no-file-descriptor/schema-no-file-descriptor-test.ts diff --git a/README.markdown b/README.markdown index 24abae75f..b8b7fb353 100644 --- a/README.markdown +++ b/README.markdown @@ -434,7 +434,7 @@ Generated code will be placed in the Gradle build directory. - With `--ts_proto_opt=annotateFilesWithVersion=false`, the generated files will not contain the versions of `protoc` and `ts-proto` used to generate the file. This option is normally set to `true`, such that files list the versions used. -- With `--ts_proto_opt=outputSchema=true`, meta typings will be generated that can later be used in other code generators. +- With `--ts_proto_opt=outputSchema=true`, meta typings will be generated that can later be used in other code generators. If outputSchema is instead specified to be `no-file-descriptor` then we do not include the file descriptor in the generated schema. This is useful if you are trying to minimize the size of the generated schema. - With `--ts_proto_opt=outputTypeAnnotations=true`, each message will be given a `$type` field containing its fully-qualified name. You can use `--ts_proto_opt=outputTypeAnnotations=static-only` to omit it from the `interface` declaration, or `--ts_proto_opt=outputTypeAnnotations=optional` to make it an optional property on the `interface` definition. The latter option may be useful if you want to use the `$type` field for runtime type checking on responses from a server. diff --git a/integration/schema-no-file-descriptor/const-enum.bin b/integration/schema-no-file-descriptor/const-enum.bin new file mode 100644 index 0000000000000000000000000000000000000000..978b99505904c60903aa720123e5d8338c931cf0 GIT binary patch literal 692 zcmaKq+e*Vg5Qg`ZLnpDuZNaoClpW(f}c{}s7*B93|UbhQT!*8p~O|8RcO3mDadtGf%Vd3Js_ zxa>uN$elRGT3-JMu(e{$=imbOwPe%>LNTobodQV3xR%1LfDcMBB2u{)CW{g>S*pSY z*pvj*2rEzqCy$7gTV7{@c}uT!#FnA+EU$Hrx@G7XZ9^BR+s1T3+|Z?#j-gA`9YdF> zJE(iK7rh)*EniD8o6=^Hun~f4k#H<*hs!oZo;p|}j%}XWDH5^Wy&_?ox8HiSvk%(+ E1v4XMiU0rr literal 0 HcmV?d00001 diff --git a/integration/schema-no-file-descriptor/const-enum.proto b/integration/schema-no-file-descriptor/const-enum.proto new file mode 100644 index 000000000..ea48dbf46 --- /dev/null +++ b/integration/schema-no-file-descriptor/const-enum.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +message DividerData { + enum DividerType { + DOUBLE = 0; + SINGLE = 1; + DASHED = 2; + DOTTED = 3; + } + + DividerType type = 1; + map typeMap = 2; +} diff --git a/integration/schema-no-file-descriptor/const-enum.ts b/integration/schema-no-file-descriptor/const-enum.ts new file mode 100644 index 000000000..1d5e1071b --- /dev/null +++ b/integration/schema-no-file-descriptor/const-enum.ts @@ -0,0 +1,281 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// source: const-enum.proto + +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; + +export const protobufPackage = ""; + +export interface DividerData { + type: DividerData_DividerType; + typeMap: { [key: string]: DividerData_DividerType }; +} + +export enum DividerData_DividerType { + DOUBLE = 0, + SINGLE = 1, + DASHED = 2, + DOTTED = 3, + UNRECOGNIZED = -1, +} + +export function dividerData_DividerTypeFromJSON(object: any): DividerData_DividerType { + switch (object) { + case 0: + case "DOUBLE": + return DividerData_DividerType.DOUBLE; + case 1: + case "SINGLE": + return DividerData_DividerType.SINGLE; + case 2: + case "DASHED": + return DividerData_DividerType.DASHED; + case 3: + case "DOTTED": + return DividerData_DividerType.DOTTED; + case -1: + case "UNRECOGNIZED": + default: + return DividerData_DividerType.UNRECOGNIZED; + } +} + +export function dividerData_DividerTypeToJSON(object: DividerData_DividerType): string { + switch (object) { + case DividerData_DividerType.DOUBLE: + return "DOUBLE"; + case DividerData_DividerType.SINGLE: + return "SINGLE"; + case DividerData_DividerType.DASHED: + return "DASHED"; + case DividerData_DividerType.DOTTED: + return "DOTTED"; + case DividerData_DividerType.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + +export interface DividerData_TypeMapEntry { + key: string; + value: DividerData_DividerType; +} + +function createBaseDividerData(): DividerData { + return { type: 0, typeMap: {} }; +} + +export const DividerData = { + encode(message: DividerData, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.type !== 0) { + writer.uint32(8).int32(message.type); + } + Object.entries(message.typeMap).forEach(([key, value]) => { + DividerData_TypeMapEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).ldelim(); + }); + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): DividerData { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDividerData(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.type = reader.int32() as any; + continue; + case 2: + if (tag !== 18) { + break; + } + + const entry2 = DividerData_TypeMapEntry.decode(reader, reader.uint32()); + if (entry2.value !== undefined) { + message.typeMap[entry2.key] = entry2.value; + } + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): DividerData { + return { + type: isSet(object.type) ? dividerData_DividerTypeFromJSON(object.type) : 0, + typeMap: isObject(object.typeMap) + ? Object.entries(object.typeMap).reduce<{ [key: string]: DividerData_DividerType }>((acc, [key, value]) => { + acc[key] = dividerData_DividerTypeFromJSON(value); + return acc; + }, {}) + : {}, + }; + }, + + toJSON(message: DividerData): unknown { + const obj: any = {}; + if (message.type !== 0) { + obj.type = dividerData_DividerTypeToJSON(message.type); + } + if (message.typeMap) { + const entries = Object.entries(message.typeMap); + if (entries.length > 0) { + obj.typeMap = {}; + entries.forEach(([k, v]) => { + obj.typeMap[k] = dividerData_DividerTypeToJSON(v); + }); + } + } + return obj; + }, + + create, I>>(base?: I): DividerData { + return DividerData.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): DividerData { + const message = createBaseDividerData(); + message.type = object.type ?? 0; + message.typeMap = Object.entries(object.typeMap ?? {}).reduce<{ [key: string]: DividerData_DividerType }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value as DividerData_DividerType; + } + return acc; + }, + {}, + ); + return message; + }, +}; + +function createBaseDividerData_TypeMapEntry(): DividerData_TypeMapEntry { + return { key: "", value: 0 }; +} + +export const DividerData_TypeMapEntry = { + encode(message: DividerData_TypeMapEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== 0) { + writer.uint32(16).int32(message.value); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): DividerData_TypeMapEntry { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseDividerData_TypeMapEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.key = reader.string(); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.value = reader.int32() as any; + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): DividerData_TypeMapEntry { + return { + key: isSet(object.key) ? globalThis.String(object.key) : "", + value: isSet(object.value) ? dividerData_DividerTypeFromJSON(object.value) : 0, + }; + }, + + toJSON(message: DividerData_TypeMapEntry): unknown { + const obj: any = {}; + if (message.key !== "") { + obj.key = message.key; + } + if (message.value !== 0) { + obj.value = dividerData_DividerTypeToJSON(message.value); + } + return obj; + }, + + create, I>>(base?: I): DividerData_TypeMapEntry { + return DividerData_TypeMapEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): DividerData_TypeMapEntry { + const message = createBaseDividerData_TypeMapEntry(); + message.key = object.key ?? ""; + message.value = object.value ?? 0; + return message; + }, +}; + +type ProtoMetaMessageOptions = { + options?: { [key: string]: any }; + fields?: { [key: string]: { [key: string]: any } }; + oneof?: { [key: string]: { [key: string]: any } }; + nested?: { [key: string]: ProtoMetaMessageOptions }; +}; + +export interface ProtoMetadata { + references: { [key: string]: any }; + dependencies?: ProtoMetadata[]; + options?: { + options?: { [key: string]: any }; + services?: { + [key: string]: { options?: { [key: string]: any }; methods?: { [key: string]: { [key: string]: any } } }; + }; + messages?: { [key: string]: ProtoMetaMessageOptions }; + enums?: { [key: string]: { options?: { [key: string]: any }; values?: { [key: string]: { [key: string]: any } } } }; + }; +} + +export const protoMetadata: ProtoMetadata = { + references: { + ".DividerData": DividerData, + ".DividerData.DividerType": DividerData_DividerType, + ".DividerData.TypeMapEntry": DividerData_TypeMapEntry, + }, + dependencies: [], +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/schema-no-file-descriptor/parameters.txt b/integration/schema-no-file-descriptor/parameters.txt new file mode 100644 index 000000000..74cccdefa --- /dev/null +++ b/integration/schema-no-file-descriptor/parameters.txt @@ -0,0 +1 @@ +outputSchema=no-file-descriptor \ No newline at end of file diff --git a/integration/schema-no-file-descriptor/schema-no-file-descriptor-test.ts b/integration/schema-no-file-descriptor/schema-no-file-descriptor-test.ts new file mode 100644 index 000000000..0e638895e --- /dev/null +++ b/integration/schema-no-file-descriptor/schema-no-file-descriptor-test.ts @@ -0,0 +1,8 @@ +import { protoMetadata } from "./const-enum"; + +describe("schema-no-file-descriptor", () => { + test("the property doesn't exist", () => { + expect(protoMetadata).not.toHaveProperty("fileDescriptor"); + expect(protoMetadata).toHaveProperty("references"); + }); +}); diff --git a/src/options.ts b/src/options.ts index da8c19d20..1ca9afda1 100644 --- a/src/options.ts +++ b/src/options.ts @@ -75,7 +75,7 @@ export type Options = { unrecognizedEnumName: string; unrecognizedEnumValue: number; exportCommonSymbols: boolean; - outputSchema: boolean; + outputSchema: boolean | "no-file-descriptor"; onlyTypes: boolean; emitImportedFiles: boolean; useAbortSignal: boolean; diff --git a/src/schema.ts b/src/schema.ts index 86e08f768..f3f995547 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -36,8 +36,9 @@ export function generateSchema(ctx: Context, fileDesc: FileDescriptorProto, sour }; export interface ProtoMetadata { - fileDescriptor: ${fileDescriptorProto}; - references: { [key: string]: any }; + ${ + ctx.options.outputSchema !== "no-file-descriptor" ? code`fileDescriptor: ${fileDescriptorProto};\n` : "" + }references: { [key: string]: any }; dependencies?: ProtoMetadata[]; options?: { options?: { [key: string]: any }; @@ -181,8 +182,11 @@ export function generateSchema(ctx: Context, fileDesc: FileDescriptorProto, sour chunks.push(code` export const ${def("protoMetadata")}: ProtoMetadata = { - fileDescriptor: ${fileDescriptorProto}.fromPartial(${descriptor}), - references: { ${joinCode(references, { on: "," })} }, + ${ + ctx.options.outputSchema !== "no-file-descriptor" + ? code`fileDescriptor: ${fileDescriptorProto}.fromPartial(${descriptor}),\n` + : "" + }references: { ${joinCode(references, { on: "," })} }, dependencies: [${joinCode(dependencies, { on: "," })}], ${ fileOptions || messagesOptions.length > 0 || servicesOptions.length > 0 || enumsOptions.length > 0