Skip to content

Commit

Permalink
Merge pull request grpc#1745 from murgatroid99/proto-loader_generate_…
Browse files Browse the repository at this point in the history
…service_definition

proto-loader: generator: add specific service definition interfaces
  • Loading branch information
murgatroid99 authored and flovilmart committed Apr 30, 2021
2 parents aefc9d1 + e7dccd6 commit 7c0e0ef
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 29 deletions.
98 changes: 77 additions & 21 deletions packages/proto-loader/bin/proto-loader-gen-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ 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[];
grpcLib: string;
outDir: string;
verbose?: boolean;
includeComments?: boolean;
inputAsUnsafe?: boolean;
}

class TextFormatter {
Expand Down Expand Up @@ -114,29 +116,37 @@ 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;
/* If the dependency is defined within a message, it will be generated in that
* 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');
}
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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 {
Expand All @@ -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}}`;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -541,31 +574,52 @@ 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<Protobuf.Type> = new Set<Protobuf.Type>();
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('');

generateServiceClientInterface(formatter, serviceType, options);
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);
}
Expand All @@ -577,7 +631,8 @@ function generateSingleLoadedDefinitionType(formatter: TextFormatter, nested: Pr
if (options.includeComments) {
formatComment(formatter, nested.comment);
}
formatter.writeLine(`${nested.name}: SubtypeConstructor<typeof grpc.Client, ${getTypeInterfaceName(nested)}Client> & { service: ServiceDefinition }`)
const typeInterfaceName = getTypeInterfaceName(nested);
formatter.writeLine(`${nested.name}: SubtypeConstructor<typeof grpc.Client, ${typeInterfaceName}Client> & { service: ${typeInterfaceName}Definition }`);
} else if (nested instanceof Protobuf.Enum) {
formatter.writeLine(`${nested.name}: EnumTypeDefinition`);
} else if (nested instanceof Protobuf.Type) {
Expand Down Expand Up @@ -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...')
Expand Down
8 changes: 4 additions & 4 deletions packages/proto-loader/golden-generated/echo.ts
Original file line number Diff line number Diff line change
@@ -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<Constructor extends new (...args: any) => any, Subtype> = {
new(...args: ConstructorParameters<Constructor>): Subtype;
Expand Down Expand Up @@ -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<typeof grpc.Client, _google_longrunning_OperationsClient> & { service: ServiceDefinition }
Operations: SubtypeConstructor<typeof grpc.Client, _google_longrunning_OperationsClient> & { service: _google_longrunning_OperationsDefinition }
WaitOperationRequest: MessageTypeDefinition
}
protobuf: {
Expand Down Expand Up @@ -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<typeof grpc.Client, _google_showcase_v1beta1_EchoClient> & { service: ServiceDefinition }
Echo: SubtypeConstructor<typeof grpc.Client, _google_showcase_v1beta1_EchoClient> & { service: _google_showcase_v1beta1_EchoDefinition }
EchoRequest: MessageTypeDefinition
EchoResponse: MessageTypeDefinition
ExpandRequest: MessageTypeDefinition
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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>
}
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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>
}
2 changes: 1 addition & 1 deletion packages/proto-loader/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@grpc/proto-loader",
"version": "0.6.0",
"version": "0.6.1",
"author": "Google Inc.",
"contributors": [
{
Expand Down
Loading

0 comments on commit 7c0e0ef

Please sign in to comment.