From cb2b5733ddd71589a00a4960906c65869303706b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Vojt=C3=A1=C5=A1ek?= Date: Wed, 13 Mar 2024 14:09:57 +0100 Subject: [PATCH] fix: fixed addGrpcMetadata option (#761) This PR should fix this issue https://github.com/stephenh/ts-proto/issues/188 I've removed `addNestjsRestParameter` parameter completely from `src/generate-services.ts` since `NestJS` seems to be handled in `generate-nestjs` file. - [x] tests --- .../custom-metadata.ts | 3 + .../default-with-custom-metadata.bin | Bin 0 -> 526 bytes .../default-with-custom-metadata.proto | 14 ++ .../default-with-custom-metadata.ts | 167 ++++++++++++++++++ .../parameters.txt | 1 + .../default-with-metadata.bin | Bin 0 -> 512 bytes .../default-with-metadata.proto | 14 ++ .../default-with-metadata.ts | 167 ++++++++++++++++++ .../default-with-metadata/parameters.txt | 1 + integration/generic-metadata/hero.ts | 24 ++- src/generate-services.ts | 17 +- 11 files changed, 397 insertions(+), 11 deletions(-) create mode 100644 integration/default-with-custom-metadata/custom-metadata.ts create mode 100644 integration/default-with-custom-metadata/default-with-custom-metadata.bin create mode 100755 integration/default-with-custom-metadata/default-with-custom-metadata.proto create mode 100644 integration/default-with-custom-metadata/default-with-custom-metadata.ts create mode 100644 integration/default-with-custom-metadata/parameters.txt create mode 100644 integration/default-with-metadata/default-with-metadata.bin create mode 100755 integration/default-with-metadata/default-with-metadata.proto create mode 100644 integration/default-with-metadata/default-with-metadata.ts create mode 100644 integration/default-with-metadata/parameters.txt diff --git a/integration/default-with-custom-metadata/custom-metadata.ts b/integration/default-with-custom-metadata/custom-metadata.ts new file mode 100644 index 000000000..2921688ac --- /dev/null +++ b/integration/default-with-custom-metadata/custom-metadata.ts @@ -0,0 +1,3 @@ +export interface Foo { + foo: "bar"; +} diff --git a/integration/default-with-custom-metadata/default-with-custom-metadata.bin b/integration/default-with-custom-metadata/default-with-custom-metadata.bin new file mode 100644 index 0000000000000000000000000000000000000000..71bf5e3afec67f9422fb195115da0df5a930ba4c GIT binary patch literal 526 zcma)(!AiqG5Qb;7n{+$f);NkSy(j@ig!G^{K@VO8!ApIDv|X`ao7!wE_y#_t@8aw> z!Gj*W&A0pSH-89bGy7a@US0k^cQ5&LQ@L`HFRUwOg)2s@b?M5X)-pX9hzvfyB>v+8 z#gn3%PqS0pySMJn&tvV3ohuk+o43?4osOh^HA1!@$ABmns-A}OGUh>-$P5@Nx#R3J!7 z-Nz0p1rd-`5uw4@L+nX6#6i{WHYAzS{h$-1TF~C<1gBbh5Ol)pbgg4xovw9E9MrXr xiFJCXOSoOfx94_=Pj&R6*CnByiEe!Z4}4{kUPJtOCOvM5b>`xFl7t_9^$mNkS}Onm literal 0 HcmV?d00001 diff --git a/integration/default-with-custom-metadata/default-with-custom-metadata.proto b/integration/default-with-custom-metadata/default-with-custom-metadata.proto new file mode 100755 index 000000000..db5c7a53d --- /dev/null +++ b/integration/default-with-custom-metadata/default-with-custom-metadata.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package basic; + +message GetBasicRequest { + string name = 1; +} + +message GetBasicResponse { + string name = 1; +} + +service BasicService { + rpc GetBasic (GetBasicRequest) returns (GetBasicResponse) {} +} diff --git a/integration/default-with-custom-metadata/default-with-custom-metadata.ts b/integration/default-with-custom-metadata/default-with-custom-metadata.ts new file mode 100644 index 000000000..e4f30f0a9 --- /dev/null +++ b/integration/default-with-custom-metadata/default-with-custom-metadata.ts @@ -0,0 +1,167 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; +import { Foo } from "./custom-metadata"; + +export const protobufPackage = "basic"; + +export interface GetBasicRequest { + name: string; +} + +export interface GetBasicResponse { + name: string; +} + +function createBaseGetBasicRequest(): GetBasicRequest { + return { name: "" }; +} + +export const GetBasicRequest = { + encode(message: GetBasicRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GetBasicRequest { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetBasicRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetBasicRequest { + return { name: isSet(object.name) ? globalThis.String(object.name) : "" }; + }, + + toJSON(message: GetBasicRequest): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + return obj; + }, + + create, I>>(base?: I): GetBasicRequest { + return GetBasicRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetBasicRequest { + const message = createBaseGetBasicRequest(); + message.name = object.name ?? ""; + return message; + }, +}; + +function createBaseGetBasicResponse(): GetBasicResponse { + return { name: "" }; +} + +export const GetBasicResponse = { + encode(message: GetBasicResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GetBasicResponse { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetBasicResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetBasicResponse { + return { name: isSet(object.name) ? globalThis.String(object.name) : "" }; + }, + + toJSON(message: GetBasicResponse): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + return obj; + }, + + create, I>>(base?: I): GetBasicResponse { + return GetBasicResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetBasicResponse { + const message = createBaseGetBasicResponse(); + message.name = object.name ?? ""; + return message; + }, +}; + +export interface BasicService { + GetBasic(request: GetBasicRequest, metadata?: Foo): Promise; +} + +export const BasicServiceServiceName = "basic.BasicService"; +export class BasicServiceClientImpl implements BasicService { + private readonly rpc: Rpc; + private readonly service: string; + constructor(rpc: Rpc, opts?: { service?: string }) { + this.service = opts?.service || BasicServiceServiceName; + this.rpc = rpc; + this.GetBasic = this.GetBasic.bind(this); + } + GetBasic(request: GetBasicRequest, metadata?: Foo): Promise { + const data = GetBasicRequest.encode(request).finish(); + const promise = this.rpc.request(this.service, "GetBasic", data, metadata); + return promise.then((data) => GetBasicResponse.decode(_m0.Reader.create(data))); + } +} + +interface Rpc { + request(service: string, method: string, data: Uint8Array, metadata?: Foo): Promise; +} + +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 isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/default-with-custom-metadata/parameters.txt b/integration/default-with-custom-metadata/parameters.txt new file mode 100644 index 000000000..a93fb309a --- /dev/null +++ b/integration/default-with-custom-metadata/parameters.txt @@ -0,0 +1 @@ +metadataType=Foo@./custom-metadata,outputServices=default,addGrpcMetadata=true \ No newline at end of file diff --git a/integration/default-with-metadata/default-with-metadata.bin b/integration/default-with-metadata/default-with-metadata.bin new file mode 100644 index 0000000000000000000000000000000000000000..e8ec18a845671b72843ca358111290cf3ca533b0 GIT binary patch literal 512 zcmaKp&q~8U5Qk^8n{+$f);NkS^`Hb45t@VE1U+~W1TXao(sspy{bRFL@C|%HpU2rv zf(OOhe4F2X^9zfIGy7C*U)<<@?w&^r>xx<7ihR9Uxz$i>neGil1|MG%|4ULlDa!dY zJHeeh>u&u#wy#@TIwDldVqpj3Se&)TVa`r5{fU+9)v~mI>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetBasicRequest { + return { name: isSet(object.name) ? globalThis.String(object.name) : "" }; + }, + + toJSON(message: GetBasicRequest): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + return obj; + }, + + create, I>>(base?: I): GetBasicRequest { + return GetBasicRequest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetBasicRequest { + const message = createBaseGetBasicRequest(); + message.name = object.name ?? ""; + return message; + }, +}; + +function createBaseGetBasicResponse(): GetBasicResponse { + return { name: "" }; +} + +export const GetBasicResponse = { + encode(message: GetBasicResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GetBasicResponse { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGetBasicResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): GetBasicResponse { + return { name: isSet(object.name) ? globalThis.String(object.name) : "" }; + }, + + toJSON(message: GetBasicResponse): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + return obj; + }, + + create, I>>(base?: I): GetBasicResponse { + return GetBasicResponse.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GetBasicResponse { + const message = createBaseGetBasicResponse(); + message.name = object.name ?? ""; + return message; + }, +}; + +export interface BasicService { + GetBasic(request: GetBasicRequest, metadata?: Metadata): Promise; +} + +export const BasicServiceServiceName = "basic.BasicService"; +export class BasicServiceClientImpl implements BasicService { + private readonly rpc: Rpc; + private readonly service: string; + constructor(rpc: Rpc, opts?: { service?: string }) { + this.service = opts?.service || BasicServiceServiceName; + this.rpc = rpc; + this.GetBasic = this.GetBasic.bind(this); + } + GetBasic(request: GetBasicRequest, metadata?: Metadata): Promise { + const data = GetBasicRequest.encode(request).finish(); + const promise = this.rpc.request(this.service, "GetBasic", data, metadata); + return promise.then((data) => GetBasicResponse.decode(_m0.Reader.create(data))); + } +} + +interface Rpc { + request(service: string, method: string, data: Uint8Array, metadata?: Metadata): Promise; +} + +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 isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/default-with-metadata/parameters.txt b/integration/default-with-metadata/parameters.txt new file mode 100644 index 000000000..df9aa9397 --- /dev/null +++ b/integration/default-with-metadata/parameters.txt @@ -0,0 +1 @@ +outputServices=default,addGrpcMetadata=true \ No newline at end of file diff --git a/integration/generic-metadata/hero.ts b/integration/generic-metadata/hero.ts index 8f4cf2943..0c15c7c8e 100644 --- a/integration/generic-metadata/hero.ts +++ b/integration/generic-metadata/hero.ts @@ -303,19 +303,19 @@ export class HeroServiceClientImpl implements HeroService { this.FindOneVillain = this.FindOneVillain.bind(this); this.FindManyVillain = this.FindManyVillain.bind(this); } - FindOneHero(request: HeroById): Promise { + FindOneHero(request: HeroById, metadata?: Foo): Promise { const data = HeroById.encode(request).finish(); const promise = this.rpc.request(this.service, "FindOneHero", data); return promise.then((data) => Hero.decode(_m0.Reader.create(data))); } - FindOneVillain(request: VillainById): Promise { + FindOneVillain(request: VillainById, metadata?: Foo): Promise { const data = VillainById.encode(request).finish(); const promise = this.rpc.request(this.service, "FindOneVillain", data); return promise.then((data) => Villain.decode(_m0.Reader.create(data))); } - FindManyVillain(request: Observable): Observable { + FindManyVillain(request: Observable, metadata?: Foo): Observable { const data = request.pipe(map((request) => VillainById.encode(request).finish())); const result = this.rpc.bidirectionalStreamingRequest(this.service, "FindManyVillain", data); return result.pipe(map((data) => Villain.decode(_m0.Reader.create(data)))); @@ -355,10 +355,20 @@ export const HeroServiceDefinition = { } as const; interface Rpc { - request(service: string, method: string, data: Uint8Array): Promise; - clientStreamingRequest(service: string, method: string, data: Observable): Promise; - serverStreamingRequest(service: string, method: string, data: Uint8Array): Observable; - bidirectionalStreamingRequest(service: string, method: string, data: Observable): Observable; + request(service: string, method: string, data: Uint8Array, metadata?: Foo): Promise; + clientStreamingRequest( + service: string, + method: string, + data: Observable, + metadata?: Foo, + ): Promise; + serverStreamingRequest(service: string, method: string, data: Uint8Array, metadata?: Foo): Observable; + bidirectionalStreamingRequest( + service: string, + method: string, + data: Observable, + metadata?: Foo, + ): Observable; } type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; diff --git a/src/generate-services.ts b/src/generate-services.ts index 3d67342fa..221d35c12 100644 --- a/src/generate-services.ts +++ b/src/generate-services.ts @@ -66,14 +66,15 @@ export function generateService( if (options.outputClientImpl === "grpc-web") { // We have to use grpc.Metadata where grpc will come from @improbable-eng params.push(code`metadata?: grpc.Metadata`); - } else if (options.addGrpcMetadata) { - const Metadata = imp("Metadata@@grpc/grpc-js"); - const q = options.addNestjsRestParameter ? "" : "?"; - params.push(code`metadata${q}: ${Metadata}`); } else if (options.metadataType) { + // custom `metadataType` has precedence over `addGrpcMetadata` that injects Metadata from grpc-js const Metadata = imp(options.metadataType); params.push(code`metadata?: ${Metadata}`); + } else if (options.addGrpcMetadata) { + const Metadata = imp("Metadata@@grpc/grpc-js"); + params.push(code`metadata?: ${Metadata}`); } + if (options.useAbortSignal) { params.push(code`abortSignal?: AbortSignal`); } @@ -112,13 +113,16 @@ function generateRegularRpcMethod(ctx: Context, methodDesc: MethodDescriptorProt const rawInputType = rawRequestType(ctx, methodDesc, { keepValueType: true }); const inputType = requestType(ctx, methodDesc); const rawOutputType = responseType(ctx, methodDesc, { keepValueType: true }); + const metadataType = options.metadataType ? imp(options.metadataType) : imp("Metadata@@grpc/grpc-js"); const params = [ ...(options.context ? [code`ctx: Context`] : []), code`request: ${inputType}`, + ...(options.metadataType || options.addGrpcMetadata ? [code`metadata?: ${metadataType}`] : []), ...(options.useAbortSignal ? [code`abortSignal?: AbortSignal`] : []), ]; const maybeCtx = options.context ? "ctx," : ""; + const maybeMetadata = options.addGrpcMetadata ? "metadata," : ""; const maybeAbortSignal = options.useAbortSignal ? "abortSignal || undefined," : ""; let errorHandler; @@ -193,6 +197,7 @@ function generateRegularRpcMethod(ctx: Context, methodDesc: MethodDescriptorProt this.service, "${methodDesc.name}", data, + ${maybeMetadata} ${maybeAbortSignal} ); return ${returnStatement}; @@ -416,8 +421,11 @@ function generateCachingRpcMethod( */ export function generateRpcType(ctx: Context, hasStreamingMethods: boolean): Code { const { options } = ctx; + const metadata = options.metadataType ? imp(options.metadataType) : imp("Metadata@@grpc/grpc-js"); + const metadataType = metadata.symbol; const maybeContext = options.context ? "" : ""; const maybeContextParam = options.context ? "ctx: Context," : ""; + const maybeMetadataParam = options.metadataType || options.addGrpcMetadata ? `metadata?: ${metadataType},` : ""; const maybeAbortSignalParam = options.useAbortSignal ? "abortSignal?: AbortSignal," : ""; const methods = [[code`request`, code`Uint8Array`, code`Promise`]]; const additionalMethods = []; @@ -456,6 +464,7 @@ export function generateRpcType(ctx: Context, hasStreamingMethods: boolean): Cod service: string, method: string, data: ${method[1]}, + ${maybeMetadataParam} ${maybeAbortSignalParam} ): ${method[2]};`); });