diff --git a/integration/nestjs-metadata-grpc-js/hero.ts b/integration/nestjs-metadata-grpc-js/hero.ts index 54483ffd9..0d46ee1cb 100644 --- a/integration/nestjs-metadata-grpc-js/hero.ts +++ b/integration/nestjs-metadata-grpc-js/hero.ts @@ -1,6 +1,8 @@ /* eslint-disable */ -import { Metadata } from "@grpc/grpc-js"; +import { handleBidiStreamingCall, Metadata } from "@grpc/grpc-js"; +import type { handleUnaryCall, UntypedServiceImplementation } from "@grpc/grpc-js"; import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices"; +import * as _m0 from "protobufjs/minimal"; import { Observable } from "rxjs"; export const protobufPackage = "hero"; @@ -25,6 +27,170 @@ export interface Villain { export const HERO_PACKAGE_NAME = "hero"; +function createBaseHeroById(): HeroById { + return { id: 0 }; +} + +export const HeroById = { + encode(message: HeroById, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.id !== 0) { + writer.uint32(8).int32(message.id); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): HeroById { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseHeroById(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.id = reader.int32(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, +}; + +function createBaseVillainById(): VillainById { + return { id: 0 }; +} + +export const VillainById = { + encode(message: VillainById, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.id !== 0) { + writer.uint32(8).int32(message.id); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): VillainById { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVillainById(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.id = reader.int32(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, +}; + +function createBaseHero(): Hero { + return { id: 0, name: "" }; +} + +export const Hero = { + encode(message: Hero, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.id !== 0) { + writer.uint32(8).int32(message.id); + } + if (message.name !== "") { + writer.uint32(18).string(message.name); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Hero { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseHero(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.id = reader.int32(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, +}; + +function createBaseVillain(): Villain { + return { id: 0, name: "" }; +} + +export const Villain = { + encode(message: Villain, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.id !== 0) { + writer.uint32(8).int32(message.id); + } + if (message.name !== "") { + writer.uint32(18).string(message.name); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Villain { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVillain(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.id = reader.int32(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, +}; + export interface HeroServiceClient { findOneHero(request: HeroById, metadata?: Metadata): Observable; @@ -57,3 +223,40 @@ export function HeroServiceControllerMethods() { } export const HERO_SERVICE_NAME = "HeroService"; + +export type HeroServiceService = typeof HeroServiceService; +export const HeroServiceService = { + findOneHero: { + path: "/hero.HeroService/FindOneHero", + requestStream: false, + responseStream: false, + requestSerialize: (value: HeroById) => Buffer.from(HeroById.encode(value).finish()), + requestDeserialize: (value: Buffer) => HeroById.decode(value), + responseSerialize: (value: Hero) => Buffer.from(Hero.encode(value).finish()), + responseDeserialize: (value: Buffer) => Hero.decode(value), + }, + findOneVillain: { + path: "/hero.HeroService/FindOneVillain", + requestStream: false, + responseStream: false, + requestSerialize: (value: VillainById) => Buffer.from(VillainById.encode(value).finish()), + requestDeserialize: (value: Buffer) => VillainById.decode(value), + responseSerialize: (value: Villain) => Buffer.from(Villain.encode(value).finish()), + responseDeserialize: (value: Buffer) => Villain.decode(value), + }, + findManyVillain: { + path: "/hero.HeroService/FindManyVillain", + requestStream: true, + responseStream: true, + requestSerialize: (value: VillainById) => Buffer.from(VillainById.encode(value).finish()), + requestDeserialize: (value: Buffer) => VillainById.decode(value), + responseSerialize: (value: Villain) => Buffer.from(Villain.encode(value).finish()), + responseDeserialize: (value: Buffer) => Villain.decode(value), + }, +} as const; + +export interface HeroServiceServer extends UntypedServiceImplementation { + findOneHero: handleUnaryCall; + findOneVillain: handleUnaryCall; + findManyVillain: handleBidiStreamingCall; +} diff --git a/src/main.ts b/src/main.ts index 1b5108a7f..73e046383 100644 --- a/src/main.ts +++ b/src/main.ts @@ -310,52 +310,50 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri visitServices(fileDesc, sourceInfo, (serviceDesc, sInfo) => { if (options.nestJs) { - // NestJS is sufficiently different that we special case all of the client/server interfaces - + // NestJS is sufficiently different that we special case the client/server interfaces // generate nestjs grpc client interface chunks.push(generateNestjsServiceClient(ctx, fileDesc, sInfo, serviceDesc)); // and the service controller interface chunks.push(generateNestjsServiceController(ctx, fileDesc, sInfo, serviceDesc)); // generate nestjs grpc service controller decorator chunks.push(generateNestjsGrpcServiceMethodsDecorator(ctx, serviceDesc)); - let serviceConstName = `${camelToSnake(serviceDesc.name)}_NAME`; if (!serviceDesc.name.toLowerCase().endsWith("service")) { serviceConstName = `${camelToSnake(serviceDesc.name)}_SERVICE_NAME`; } - chunks.push(code`export const ${serviceConstName} = "${serviceDesc.name}";`); - } else { - const uniqueServices = [...new Set(options.outputServices)].sort(); - uniqueServices.forEach((outputService) => { - if (outputService === ServiceOption.GRPC) { - chunks.push(generateGrpcJsService(ctx, fileDesc, sInfo, serviceDesc)); - } else if (outputService === ServiceOption.NICE_GRPC) { - chunks.push(generateNiceGrpcService(ctx, fileDesc, sInfo, serviceDesc)); - } else if (outputService === ServiceOption.GENERIC) { - chunks.push(generateGenericServiceDefinition(ctx, fileDesc, sInfo, serviceDesc)); - } else if (outputService === ServiceOption.DEFAULT) { - // This service could be Twirp or grpc-web or JSON (maybe). So far all of their - // interfaces are fairly similar so we share the same service interface. - chunks.push(generateService(ctx, fileDesc, sInfo, serviceDesc)); - - if (options.outputClientImpl === true) { - chunks.push(generateServiceClientImpl(ctx, fileDesc, serviceDesc)); - } else if (options.outputClientImpl === "grpc-web") { - chunks.push(generateGrpcClientImpl(ctx, fileDesc, serviceDesc)); - chunks.push(generateGrpcServiceDesc(fileDesc, serviceDesc)); - serviceDesc.method.forEach((method) => { - if (!method.clientStreaming) { - chunks.push(generateGrpcMethodDesc(ctx, serviceDesc, method)); - } - if (method.serverStreaming) { - hasServerStreamingMethods = true; - } - }); - } - } - }); } + + const uniqueServices = [...new Set(options.outputServices)].sort(); + uniqueServices.forEach((outputService) => { + if (outputService === ServiceOption.GRPC) { + chunks.push(generateGrpcJsService(ctx, fileDesc, sInfo, serviceDesc)); + } else if (outputService === ServiceOption.NICE_GRPC) { + chunks.push(generateNiceGrpcService(ctx, fileDesc, sInfo, serviceDesc)); + } else if (outputService === ServiceOption.GENERIC) { + chunks.push(generateGenericServiceDefinition(ctx, fileDesc, sInfo, serviceDesc)); + } else if (outputService === ServiceOption.DEFAULT) { + // This service could be Twirp or grpc-web or JSON (maybe). So far all of their + // interfaces are fairly similar so we share the same service interface. + chunks.push(generateService(ctx, fileDesc, sInfo, serviceDesc)); + + if (options.outputClientImpl === true) { + chunks.push(generateServiceClientImpl(ctx, fileDesc, serviceDesc)); + } else if (options.outputClientImpl === "grpc-web") { + chunks.push(generateGrpcClientImpl(ctx, fileDesc, serviceDesc)); + chunks.push(generateGrpcServiceDesc(fileDesc, serviceDesc)); + serviceDesc.method.forEach((method) => { + if (!method.clientStreaming) { + chunks.push(generateGrpcMethodDesc(ctx, serviceDesc, method)); + } + if (method.serverStreaming) { + hasServerStreamingMethods = true; + } + }); + } + } + }); + serviceDesc.method.forEach((methodDesc, _index) => { if (methodDesc.serverStreaming || methodDesc.clientStreaming) { hasStreamingMethods = true; diff --git a/src/options.ts b/src/options.ts index 0e5661435..30c13cea2 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,5 +1,3 @@ -import { parse } from "path"; -import { Code } from "ts-poet"; import { ToStringOpts } from "ts-poet/build/Code"; export enum LongOption { @@ -202,15 +200,19 @@ export function optionsFromParameter(parameter: string | undefined): Options { if ((options.outputServices as any) === false) { options.outputServices = [ServiceOption.NONE]; } - // Existing type-coercion inside parseParameter leaves a little to be desired. if (typeof options.outputServices == "string") { options.outputServices = [options.outputServices]; } - - if (options.outputServices.length == 0) { + // Assume the user wants the default service output, unless they're using nestJs, which has + // its own controllers output (although nestjs users can ask for other services too). + if (options.outputServices.length == 0 && !options.nestJs) { options.outputServices = [ServiceOption.DEFAULT]; } + // If using nestJs + other services, add the encode methods back + if (options.nestJs && options.outputServices.length > 0) { + options.outputEncodeMethods = true; + } if ((options.useDate as any) === true) { // Treat useDate=true as DATE diff --git a/src/utils.ts b/src/utils.ts index eeb19a803..885e897f0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -208,11 +208,9 @@ export class FormattedMethodDescriptor implements MethodDescriptorProto { */ public static formatName(methodName: string, options: Options) { let result = methodName; - if (options.lowerCaseServiceMethods || options.outputServices.includes(ServiceOption.GRPC)) { if (options.snakeToCamel) result = camelCaseGrpc(result); } - return result; } } diff --git a/tests/options-test.ts b/tests/options-test.ts index fdaf2b272..6cb18944b 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -2,7 +2,6 @@ import { DateOption, optionsFromParameter, ServiceOption } from "../src/options" describe("options", () => { it("can set outputJsonMethods with nestJs=true", () => { - console.log(optionsFromParameter("nestJs=true,outputJsonMethods=true")); expect(optionsFromParameter("nestJs=true,outputJsonMethods=true")).toMatchInlineSnapshot(` { "M": {}, @@ -33,9 +32,7 @@ describe("options", () => { "outputJsonMethods": true, "outputPartialMethods": false, "outputSchema": false, - "outputServices": [ - "default", - ], + "outputServices": [], "outputTypeAnnotations": false, "outputTypeRegistry": false, "removeEnumPrefix": false, @@ -70,11 +67,12 @@ describe("options", () => { `); }); - it("can set outputJsonMethods with nestJs=true", () => { + it("can set addGrpcMetadata=false", () => { const options = optionsFromParameter("outputClientImpl=grpc-web,addGrpcMetadata=false"); expect(options).toMatchObject({ outputClientImpl: "grpc-web", addGrpcMetadata: false, + outputServices: ["default"], }); });