diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 66a6fec7a..0c253068c 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -25,6 +25,7 @@ import * as yargs from 'yargs'; import camelCase = require('lodash.camelcase'); import { loadProtosWithOptions, addCommonProtos } from '../src/util'; +import { options } from 'yargs'; type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeDirs?: string[]; @@ -32,6 +33,7 @@ type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { outDir: string; verbose?: boolean; includeComments?: boolean; + inputAsUnsafe?: boolean; } class TextFormatter { @@ -114,7 +116,7 @@ function getTypeInterfaceName(type: Protobuf.Type | Protobuf.Enum | Protobuf.Ser return type.fullName.replace(/\./g, '_'); } -function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Service, from?: Protobuf.Type | Protobuf.Service) { +function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Service, from: Protobuf.Type | Protobuf.Service | undefined = undefined, options: GeneratorOptions) { const filePath = from === undefined ? './' + getImportPath(dependency) : getRelativeImportPath(from, dependency); const typeInterfaceName = getTypeInterfaceName(dependency); let importedTypes: string; @@ -122,21 +124,29 @@ function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Serv * message's file and exported using its typeInterfaceName. */ if (dependency.parent instanceof Protobuf.Type) { if (dependency instanceof Protobuf.Type) { - importedTypes = `${typeInterfaceName}, ${typeInterfaceName}__Output`; + if (options.inputAsUnsafe) { + importedTypes = `${typeInterfaceName}__Input, ${typeInterfaceName}`; + } else { + importedTypes = `${typeInterfaceName}, ${typeInterfaceName}__Output`; + } } else if (dependency instanceof Protobuf.Enum) { importedTypes = `${typeInterfaceName}`; } else if (dependency instanceof Protobuf.Service) { - importedTypes = `${typeInterfaceName}Client`; + importedTypes = `${typeInterfaceName}Client, ${typeInterfaceName}Definition`; } else { throw new Error('Invalid object passed to getImportLine'); } } else { if (dependency instanceof Protobuf.Type) { - importedTypes = `${dependency.name} as ${typeInterfaceName}, ${dependency.name}__Output as ${typeInterfaceName}__Output`; + if (options.inputAsUnsafe) { + importedTypes = `${dependency.name}__Input as ${typeInterfaceName}__Input, ${dependency.name} as ${typeInterfaceName}`; + } else { + importedTypes = `${dependency.name} as ${typeInterfaceName}, ${dependency.name}__Output as ${typeInterfaceName}__Output`; + } } else if (dependency instanceof Protobuf.Enum) { importedTypes = `${dependency.name} as ${typeInterfaceName}`; } else if (dependency instanceof Protobuf.Service) { - importedTypes = `${dependency.name}Client as ${typeInterfaceName}Client`; + importedTypes = `${dependency.name}Client as ${typeInterfaceName}Client, ${dependency.name}Definition as ${typeInterfaceName}Definition`; } else { throw new Error('Invalid object passed to getImportLine'); } @@ -170,7 +180,7 @@ function formatComment(formatter: TextFormatter, comment?: string | null) { // GENERATOR FUNCTIONS -function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean): string { +function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean, options: GeneratorOptions): string { switch (fieldType) { case 'double': case 'float': @@ -199,6 +209,13 @@ function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | } const typeInterfaceName = getTypeInterfaceName(resolvedType); if (resolvedType instanceof Protobuf.Type) { + if (options.inputAsUnsafe) { + if (repeated || map) { + return `${typeInterfaceName}__Input`; + } else { + return `${typeInterfaceName}__Input | null`; + } + } if (repeated || map) { return typeInterfaceName; } else { @@ -210,8 +227,8 @@ function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | } } -function getFieldTypePermissive(field: Protobuf.FieldBase): string { - const valueType = getTypeNamePermissive(field.type, field.resolvedType, field.repeated, field.map); +function getFieldTypePermissive(field: Protobuf.FieldBase, options: GeneratorOptions): string { + const valueType = getTypeNamePermissive(field.type, field.resolvedType, field.repeated, field.map, options); if (field instanceof Protobuf.MapField) { const keyType = field.keyType === 'string' ? 'string' : 'number'; return `{[key: ${keyType}]: ${valueType}}`; @@ -233,11 +250,15 @@ function generatePermissiveMessageInterface(formatter: TextFormatter, messageTyp formatter.writeLine('}'); return; } - formatter.writeLine(`export interface ${nameOverride ?? messageType.name} {`); + if (options.inputAsUnsafe) { + formatter.writeLine(`export interface ${nameOverride ?? messageType.name}__Input {`); + } else { + formatter.writeLine(`export interface ${nameOverride ?? messageType.name} {`); + } formatter.indent(); for (const field of messageType.fieldsArray) { const repeatedString = field.repeated ? '[]' : ''; - const type: string = getFieldTypePermissive(field); + const type: string = getFieldTypePermissive(field, options); if (options.includeComments) { formatComment(formatter, field.comment); } @@ -301,6 +322,13 @@ function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | if (resolvedType instanceof Protobuf.Type) { /* null is only used to represent absent message values if the defaults * option is set, and only for non-repeated, non-map fields. */ + if (options.inputAsUnsafe) { + if (options.defaults && !repeated && !map) { + return `${typeInterfaceName} | null`; + } else { + return `${typeInterfaceName}`; + } + } if (options.defaults && !repeated && !map) { return `${typeInterfaceName}__Output | null`; } else { @@ -340,7 +368,11 @@ function generateRestrictedMessageInterface(formatter: TextFormatter, messageTyp formatter.writeLine('}'); return; } - formatter.writeLine(`export interface ${nameOverride ?? messageType.name}__Output {`); + if (options.inputAsUnsafe) { + formatter.writeLine(`export interface ${nameOverride ?? messageType.name} {`); + } else { + formatter.writeLine(`export interface ${nameOverride ?? messageType.name}__Output {`); + } formatter.indent(); for (const field of messageType.fieldsArray) { let fieldGuaranteed: boolean; @@ -389,7 +421,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob continue; } seenDeps.add(dependency.fullName); - formatter.writeLine(getImportLine(dependency, messageType)); + formatter.writeLine(getImportLine(dependency, messageType, options)); } if (field.type.indexOf('64') >= 0) { usesLong = true; @@ -404,7 +436,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob continue; } seenDeps.add(dependency.fullName); - formatter.writeLine(getImportLine(dependency, messageType)); + formatter.writeLine(getImportLine(dependency, messageType, options)); } if (field.type.indexOf('64') >= 0) { usesLong = true; @@ -466,8 +498,9 @@ function generateServiceClientInterface(formatter: TextFormatter, serviceType: P if (options.includeComments) { formatComment(formatter, method.comment); } - const requestType = getTypeInterfaceName(method.resolvedRequestType!); - const responseType = getTypeInterfaceName(method.resolvedResponseType!) + '__Output'; + console.log(method.resolvedRequestType!.fullName); + const requestType = getTypeInterfaceName(method.resolvedRequestType!) + (options.inputAsUnsafe ? '__Input' : ''); + const responseType = getTypeInterfaceName(method.resolvedResponseType!) + (options.inputAsUnsafe ? '' : '__Output'); const callbackType = `(error?: grpc.ServiceError, result?: ${responseType}) => void`; if (method.requestStream) { if (method.responseStream) { @@ -516,8 +549,8 @@ function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: if (options.includeComments) { formatComment(formatter, method.comment); } - const requestType = getTypeInterfaceName(method.resolvedRequestType!) + '__Output'; - const responseType = getTypeInterfaceName(method.resolvedResponseType!); + const requestType = getTypeInterfaceName(method.resolvedRequestType!) + (options.inputAsUnsafe ? '' : '__Output'); + const responseType = getTypeInterfaceName(method.resolvedResponseType!) + (options.inputAsUnsafe ? '__Input' : ''); if (method.requestStream) { if (method.responseStream) { // Bidi streaming @@ -541,18 +574,36 @@ function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: formatter.writeLine('}'); } +function generateServiceDefinitionInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + formatter.writeLine(`export interface ${serviceType.name}Definition {`); + formatter.indent(); + for (const methodName of Object.keys(serviceType.methods).sort()) { + const method = serviceType.methods[methodName]; + const requestType = getTypeInterfaceName(method.resolvedRequestType!); + const responseType = getTypeInterfaceName(method.resolvedResponseType!); + // if (options.inputAsUnsafe) { + // formatter.writeLine(`${methodName}: MethodDefinition<${requestType}__Input, ${responseType}__Input, ${requestType}, ${responseType}>`); + // } else { + // } + formatter.writeLine(`${methodName}: MethodDefinition<${requestType}, ${responseType}>`); + } + formatter.unindent(); + formatter.writeLine('}') +} + function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { formatter.writeLine(`// Original file: ${serviceType.filename}`); formatter.writeLine(''); const grpcImportPath = options.grpcLib.startsWith('.') ? getPathToRoot(serviceType) + options.grpcLib : options.grpcLib; formatter.writeLine(`import type * as grpc from '${grpcImportPath}'`); + formatter.writeLine(`import type { MethodDefinition } from '@grpc/proto-loader'`) const dependencies: Set = new Set(); for (const method of serviceType.methodsArray) { dependencies.add(method.resolvedRequestType!); dependencies.add(method.resolvedResponseType!); } for (const dep of Array.from(dependencies.values()).sort(compareName)) { - formatter.writeLine(getImportLine(dep, serviceType)); + formatter.writeLine(getImportLine(dep, serviceType, options)); } formatter.writeLine(''); @@ -560,12 +611,15 @@ function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protob formatter.writeLine(''); generateServiceHandlerInterface(formatter, serviceType, options); + formatter.writeLine(''); + + generateServiceDefinitionInterface(formatter, serviceType, options); } function generateServiceImports(formatter: TextFormatter, namespace: Protobuf.NamespaceBase, options: GeneratorOptions) { for (const nested of namespace.nestedArray.sort(compareName)) { if (nested instanceof Protobuf.Service) { - formatter.writeLine(getImportLine(nested)); + formatter.writeLine(getImportLine(nested, undefined, options)); } else if (isNamespaceBase(nested) && !(nested instanceof Protobuf.Type) && !(nested instanceof Protobuf.Enum)) { generateServiceImports(formatter, nested, options); } @@ -577,7 +631,8 @@ function generateSingleLoadedDefinitionType(formatter: TextFormatter, nested: Pr if (options.includeComments) { formatComment(formatter, nested.comment); } - formatter.writeLine(`${nested.name}: SubtypeConstructor & { service: ServiceDefinition }`) + const typeInterfaceName = getTypeInterfaceName(nested); + formatter.writeLine(`${nested.name}: SubtypeConstructor & { service: ${typeInterfaceName}Definition }`); } else if (nested instanceof Protobuf.Enum) { formatter.writeLine(`${nested.name}: EnumTypeDefinition`); } else if (nested instanceof Protobuf.Type) { @@ -741,7 +796,8 @@ function runScript() { includeComments: 'Generate doc comments from comments in the original files', includeDirs: 'Directories to search for included files', outDir: 'Directory in which to output files', - grpcLib: 'The gRPC implementation library that these types will be used with' + grpcLib: 'The gRPC implementation library that these types will be used with', + inputAsUnsafe: 'Add __Input on types that are unsafe' }).demandOption(['outDir', 'grpcLib']) .demand(1) .usage('$0 [options] filenames...') diff --git a/packages/proto-loader/golden-generated/echo.ts b/packages/proto-loader/golden-generated/echo.ts index 02514f053..f257a40e4 100644 --- a/packages/proto-loader/golden-generated/echo.ts +++ b/packages/proto-loader/golden-generated/echo.ts @@ -1,8 +1,8 @@ import type * as grpc from '@grpc/grpc-js'; import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; -import type { OperationsClient as _google_longrunning_OperationsClient } from './google/longrunning/Operations'; -import type { EchoClient as _google_showcase_v1beta1_EchoClient } from './google/showcase/v1beta1/Echo'; +import type { OperationsClient as _google_longrunning_OperationsClient, OperationsDefinition as _google_longrunning_OperationsDefinition } from './google/longrunning/Operations'; +import type { EchoClient as _google_showcase_v1beta1_EchoClient, EchoDefinition as _google_showcase_v1beta1_EchoDefinition } from './google/showcase/v1beta1/Echo'; type SubtypeConstructor any, Subtype> = { new(...args: ConstructorParameters): Subtype; @@ -35,7 +35,7 @@ export interface ProtoGrpcType { * returns long-running operations should implement the `Operations` interface * so developers can have a consistent client experience. */ - Operations: SubtypeConstructor & { service: ServiceDefinition } + Operations: SubtypeConstructor & { service: _google_longrunning_OperationsDefinition } WaitOperationRequest: MessageTypeDefinition } protobuf: { @@ -78,7 +78,7 @@ export interface ProtoGrpcType { * paginated calls. Set the 'showcase-trailer' metadata key on any method * to have the values echoed in the response trailers. */ - Echo: SubtypeConstructor & { service: ServiceDefinition } + Echo: SubtypeConstructor & { service: _google_showcase_v1beta1_EchoDefinition } EchoRequest: MessageTypeDefinition EchoResponse: MessageTypeDefinition ExpandRequest: MessageTypeDefinition diff --git a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts index 6aa477ada..8e5684ada 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts @@ -1,6 +1,7 @@ // Original file: deps/googleapis/google/longrunning/operations.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { CancelOperationRequest as _google_longrunning_CancelOperationRequest, CancelOperationRequest__Output as _google_longrunning_CancelOperationRequest__Output } from '../../google/longrunning/CancelOperationRequest'; import type { DeleteOperationRequest as _google_longrunning_DeleteOperationRequest, DeleteOperationRequest__Output as _google_longrunning_DeleteOperationRequest__Output } from '../../google/longrunning/DeleteOperationRequest'; import type { Empty as _google_protobuf_Empty, Empty__Output as _google_protobuf_Empty__Output } from '../../google/protobuf/Empty'; @@ -230,3 +231,11 @@ export interface OperationsHandlers extends grpc.UntypedServiceImplementation { WaitOperation: grpc.handleUnaryCall<_google_longrunning_WaitOperationRequest__Output, _google_longrunning_Operation>; } + +export interface OperationsDefinition { + CancelOperation: MethodDefinition<_google_longrunning_CancelOperationRequest, _google_protobuf_Empty, _google_longrunning_CancelOperationRequest__Output, _google_protobuf_Empty__Output> + DeleteOperation: MethodDefinition<_google_longrunning_DeleteOperationRequest, _google_protobuf_Empty, _google_longrunning_DeleteOperationRequest__Output, _google_protobuf_Empty__Output> + GetOperation: MethodDefinition<_google_longrunning_GetOperationRequest, _google_longrunning_Operation, _google_longrunning_GetOperationRequest__Output, _google_longrunning_Operation__Output> + ListOperations: MethodDefinition<_google_longrunning_ListOperationsRequest, _google_longrunning_ListOperationsResponse, _google_longrunning_ListOperationsRequest__Output, _google_longrunning_ListOperationsResponse__Output> + WaitOperation: MethodDefinition<_google_longrunning_WaitOperationRequest, _google_longrunning_Operation, _google_longrunning_WaitOperationRequest__Output, _google_longrunning_Operation__Output> +} diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts index 33fe373a8..acb911270 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts @@ -1,6 +1,7 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { BlockRequest as _google_showcase_v1beta1_BlockRequest, BlockRequest__Output as _google_showcase_v1beta1_BlockRequest__Output } from '../../../google/showcase/v1beta1/BlockRequest'; import type { BlockResponse as _google_showcase_v1beta1_BlockResponse, BlockResponse__Output as _google_showcase_v1beta1_BlockResponse__Output } from '../../../google/showcase/v1beta1/BlockResponse'; import type { EchoRequest as _google_showcase_v1beta1_EchoRequest, EchoRequest__Output as _google_showcase_v1beta1_EchoRequest__Output } from '../../../google/showcase/v1beta1/EchoRequest'; @@ -189,3 +190,13 @@ export interface EchoHandlers extends grpc.UntypedServiceImplementation { Wait: grpc.handleUnaryCall<_google_showcase_v1beta1_WaitRequest__Output, _google_longrunning_Operation>; } + +export interface EchoDefinition { + Block: MethodDefinition<_google_showcase_v1beta1_BlockRequest, _google_showcase_v1beta1_BlockResponse, _google_showcase_v1beta1_BlockRequest__Output, _google_showcase_v1beta1_BlockResponse__Output> + Chat: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> + Collect: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> + Echo: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> + Expand: MethodDefinition<_google_showcase_v1beta1_ExpandRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_ExpandRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> + PagedExpand: MethodDefinition<_google_showcase_v1beta1_PagedExpandRequest, _google_showcase_v1beta1_PagedExpandResponse, _google_showcase_v1beta1_PagedExpandRequest__Output, _google_showcase_v1beta1_PagedExpandResponse__Output> + Wait: MethodDefinition<_google_showcase_v1beta1_WaitRequest, _google_longrunning_Operation, _google_showcase_v1beta1_WaitRequest__Output, _google_longrunning_Operation__Output> +} diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index cfc767a9b..ee49abc40 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.0", + "version": "0.6.1", "author": "Google Inc.", "contributors": [ { diff --git a/packages/proto-loader/src/index.ts b/packages/proto-loader/src/index.ts index 6f76956a9..98ca97b51 100644 --- a/packages/proto-loader/src/index.ts +++ b/packages/proto-loader/src/index.ts @@ -115,14 +115,14 @@ export interface EnumTypeDefinition extends ProtobufTypeDefinition { format: 'Protocol Buffer 3 EnumDescriptorProto'; } -export interface MethodDefinition { +export interface MethodDefinition { path: string; requestStream: boolean; responseStream: boolean; requestSerialize: Serialize; responseSerialize: Serialize; - requestDeserialize: Deserialize; - responseDeserialize: Deserialize; + requestDeserialize: Deserialize; + responseDeserialize: Deserialize; originalName?: string; requestType: MessageTypeDefinition; responseType: MessageTypeDefinition;