protoc-gen-grpc
Version:
Protocol Buffers Compiler (protoc) plugin for generating grpc interfaces in TypeScript..
278 lines (234 loc) • 13.6 kB
text/typescript
import {
FieldDescriptorProto,
FileDescriptorProto,
DescriptorProto,
OneofDescriptorProto
} from 'google-protobuf/google/protobuf/descriptor_pb';
import {Printer} from '../../Printer';
import {EntryMap} from '../../EntryMap';
import {Utility} from '../../Utility';
import {BYTES_TYPE, ENUM_TYPE, MESSAGE_TYPE, FieldTypes} from './FieldTypes';
import {Enum} from './Enum';
import {OneOf} from './OneOf';
import {Extension} from './Extensions';
export const OBJECT_TYPE_NAME = 'AsObject';
export namespace Message {
export interface MessageType {
messageName: string;
oneOfGroups: Array<Array<FieldDescriptorProto>>;
oneOfDeclList: Array<OneofDescriptorProto>;
fields: Array<MessageFieldType>;
nestedTypes: Array<string>;
formattedEnumListStr: Array<string>;
formattedOneOfListStr: Array<string>;
formattedExtListStr: Array<string>;
}
export const defaultMessageType = JSON.stringify({
messageName: '',
oneOfGroups: [],
oneOfDeclList: [],
fields: [],
nestedTypes: [],
formattedEnumListStr: [],
formattedOneOfListStr: [],
formattedExtListStr: [],
} as MessageType);
export interface MessageFieldType {
snakeCaseName: string;
camelCaseName: string;
camelUpperName: string;
type: FieldDescriptorProto.Type;
exportType: string;
}
export const defaultMessageFieldType = JSON.stringify({
snakeCaseName: '',
camelCaseName: '',
camelUpperName: '',
type: undefined,
exportType: '',
} as MessageFieldType);
export function hasFieldPresence(field: FieldDescriptorProto, fileDescriptor: FileDescriptorProto): boolean {
if (field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED) {
return false;
}
if (field.hasOneofIndex()) {
return true;
}
if (field.getType() === MESSAGE_TYPE) {
return true;
}
return (Utility.isProto2(fileDescriptor));
}
export function print(fileName: string, entryMap: EntryMap, descriptor: DescriptorProto, indentLevel: number, fileDescriptor: FileDescriptorProto) {
let messageData = JSON.parse(defaultMessageType) as MessageType;
messageData.messageName = descriptor.getName();
messageData.oneOfDeclList = descriptor.getOneofDeclList();
let messageOptions = descriptor.getOptions();
if (messageOptions !== undefined && messageOptions.getMapEntry()) {
// this message type is the entry tuple for a map - don't output it
return '';
}
const printer = new Printer(indentLevel);
printer.printEmptyLn();
printer.printLn(`export class ${messageData.messageName} extends jspb.Message {`);
const printerToObjectType = new Printer(indentLevel + 1);
printerToObjectType.printLn(`export type ${OBJECT_TYPE_NAME} = {`);
const oneOfGroups: Array<Array<FieldDescriptorProto>> = [];
descriptor.getFieldList().forEach(field => {
let fieldData = JSON.parse(defaultMessageFieldType) as MessageFieldType;
if (field.hasOneofIndex()) {
let oneOfIndex = field.getOneofIndex();
let existing = oneOfGroups[oneOfIndex];
if (existing === undefined) {
existing = [];
oneOfGroups[oneOfIndex] = existing;
}
existing.push(field);
}
fieldData.snakeCaseName = field.getName().toLowerCase();
fieldData.camelCaseName = Utility.snakeToCamel(fieldData.snakeCaseName);
fieldData.camelUpperName = Utility.ucFirst(fieldData.camelCaseName);
fieldData.type = field.getType();
let exportType;
let fullTypeName = field.getTypeName().slice(1);
let withinNamespace: string;
switch (fieldData.type) {
case MESSAGE_TYPE:
const fieldMessageType = entryMap.getMessageEntry(fullTypeName);
if (fieldMessageType === undefined) {
throw new Error('No message export for: ' + fullTypeName);
}
if (fieldMessageType.messageOptions !== undefined && fieldMessageType.messageOptions.getMapEntry()) {
let keyTuple = fieldMessageType.mapFieldOptions!.key;
let keyType = keyTuple[0] as FieldDescriptorProto.Type;
let keyTypeName = FieldTypes.getFieldType(keyType, keyTuple[1] as string, fileName, entryMap);
let valueTuple = fieldMessageType.mapFieldOptions!.value;
let valueType = valueTuple[0] as FieldDescriptorProto.Type;
let valueTypeName = FieldTypes.getFieldType(valueType, valueTuple[1] as string, fileName, entryMap);
if (valueType === BYTES_TYPE) {
valueTypeName = 'Uint8Array | string';
}
printer.printIndentedLn(`get${fieldData.camelUpperName}Map(): jspb.Map<${keyTypeName}, ${valueTypeName}>;`);
printer.printIndentedLn(`clear${fieldData.camelUpperName}Map(): void;`);
printerToObjectType.printIndentedLn(`${fieldData.camelCaseName}Map: Array<[${keyTypeName}${keyType === MESSAGE_TYPE ? '.AsObject' : ''}, ${valueTypeName}${valueType === MESSAGE_TYPE ? '.AsObject' : ''}]>,`);
return;
}
withinNamespace = Utility.withinNamespaceFromExportEntry(fullTypeName, fieldMessageType);
if (fieldMessageType.fileName === fileName) {
exportType = withinNamespace;
} else {
exportType = Utility.filePathToPseudoNamespace(fieldMessageType.fileName) + '.' + withinNamespace;
}
fieldData.exportType = exportType;
break;
case ENUM_TYPE:
let fieldEnumType = entryMap.getEnumEntry(fullTypeName);
if (fieldEnumType === undefined) {
throw new Error('No enum export for: ' + fullTypeName);
}
withinNamespace = Utility.withinNamespaceFromExportEntry(fullTypeName, fieldEnumType);
if (fieldEnumType.fileName === fileName) {
exportType = withinNamespace;
} else {
exportType = Utility.filePathToPseudoNamespace(fieldEnumType.fileName) + '.' + withinNamespace;
}
fieldData.exportType = exportType;
break;
default:
fieldData.exportType = FieldTypes.getTypeName(fieldData.type);
break;
}
let hasClearMethod = false;
function printClearIfNotPresent() {
if (!hasClearMethod) {
hasClearMethod = true;
printer.printIndentedLn(`clear${fieldData.camelUpperName}${field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED ? 'List' : ''}(): void;`);
}
}
function printRepeatedAddMethod(valueType: string) {
const optionalValue = field.getType() === MESSAGE_TYPE;
printer.printIndentedLn(`add${fieldData.camelUpperName}(value${optionalValue ? '?' : ''}: ${valueType}, index?: number): ${valueType};`);
}
if (Message.hasFieldPresence(field, fileDescriptor)) {
printer.printIndentedLn(`has${fieldData.camelUpperName}(): boolean;`);
printClearIfNotPresent();
}
if (field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED) {// is repeated
printClearIfNotPresent();
if (fieldData.type === BYTES_TYPE) {
printerToObjectType.printIndentedLn(`${fieldData.camelCaseName}List: Array<Uint8Array | string>,`);
printer.printIndentedLn(`get${fieldData.camelUpperName}List(): Array<Uint8Array | string>;`);
printer.printIndentedLn(`get${fieldData.camelUpperName}List_asU8(): Array<Uint8Array>;`);
printer.printIndentedLn(`get${fieldData.camelUpperName}List_asB64(): Array<string>;`);
printer.printIndentedLn(`set${fieldData.camelUpperName}List(value: Array<Uint8Array | string>): void;`);
printRepeatedAddMethod('Uint8Array | string');
} else {
printerToObjectType.printIndentedLn(`${fieldData.camelCaseName}List: Array<${fieldData.exportType}${fieldData.type === MESSAGE_TYPE ? '.AsObject' : ''}>,`);
printer.printIndentedLn(`get${fieldData.camelUpperName}List(): Array<${fieldData.exportType}>;`);
printer.printIndentedLn(`set${fieldData.camelUpperName}List(value: Array<${fieldData.exportType}>): void;`);
printRepeatedAddMethod(fieldData.exportType);
}
} else {
if (fieldData.type === BYTES_TYPE) {
printerToObjectType.printIndentedLn(`${fieldData.camelCaseName}: Uint8Array | string,`);
printer.printIndentedLn(`get${fieldData.camelUpperName}(): Uint8Array | string;`);
printer.printIndentedLn(`get${fieldData.camelUpperName}_asU8(): Uint8Array;`);
printer.printIndentedLn(`get${fieldData.camelUpperName}_asB64(): string;`);
printer.printIndentedLn(`set${fieldData.camelUpperName}(value: Uint8Array | string): void;`);
} else {
let fieldObjectType = fieldData.exportType;
let canBeUndefined = false;
if (fieldData.type === MESSAGE_TYPE) {
fieldObjectType += '.AsObject';
if (!Utility.isProto2(fileDescriptor) || (field.getLabel() === FieldDescriptorProto.Label.LABEL_OPTIONAL)) {
canBeUndefined = true;
}
} else {
if (Utility.isProto2(fileDescriptor)) {
canBeUndefined = true;
}
}
const fieldObjectName = Utility.normaliseFieldObjectName(fieldData.camelCaseName);
printerToObjectType.printIndentedLn(`${fieldObjectName}${canBeUndefined ? '?' : ''}: ${fieldObjectType},`);
printer.printIndentedLn(`get${fieldData.camelUpperName}(): ${fieldData.exportType}${canBeUndefined ? ' | undefined' : ''};`);
printer.printIndentedLn(`set${fieldData.camelUpperName}(value${fieldData.type === MESSAGE_TYPE ? '?' : ''}: ${fieldData.exportType}): void;`);
}
}
printer.printEmptyLn();
});
printerToObjectType.printLn(`}`);
descriptor.getOneofDeclList().forEach(oneOfDecl => {
printer.printIndentedLn(`get${Utility.oneOfName(oneOfDecl.getName())}Case(): ${messageData.messageName}.${Utility.oneOfName(oneOfDecl.getName())}Case;`);
});
printer.printIndentedLn(`serializeBinary(): Uint8Array;`);
printer.printIndentedLn(`toObject(includeInstance?: boolean): ${messageData.messageName}.${OBJECT_TYPE_NAME};`);
printer.printIndentedLn(`static toObject(includeInstance: boolean, msg: ${messageData.messageName}): ${messageData.messageName}.${OBJECT_TYPE_NAME};`);
printer.printIndentedLn(`static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};`);
printer.printIndentedLn(`static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};`);
printer.printIndentedLn(`static serializeBinaryToWriter(message: ${messageData.messageName}, writer: jspb.BinaryWriter): void;`);
printer.printIndentedLn(`static deserializeBinary(bytes: Uint8Array): ${messageData.messageName};`);
printer.printIndentedLn(`static deserializeBinaryFromReader(message: ${messageData.messageName}, reader: jspb.BinaryReader): ${messageData.messageName};`);
printer.printLn(`}`);
printer.printEmptyLn();
printer.printLn(`export namespace ${messageData.messageName} {`);
printer.print(printerToObjectType.getOutput());
descriptor.getNestedTypeList().forEach(nested => {
const msgOutput = Message.print(fileName, entryMap, nested, indentLevel + 1, fileDescriptor);
if (msgOutput !== '') {
// If the message class is a Map entry then it isn't output, so don't print the namespace block
printer.print(msgOutput);
}
});
descriptor.getEnumTypeList().forEach(enumType => {
printer.print(`${Enum.print(enumType, indentLevel + 1)}`);
});
descriptor.getOneofDeclList().forEach((oneOfDecl, index) => {
printer.print(`${OneOf.print(oneOfDecl, oneOfGroups[index] || [], indentLevel + 1)}`);
});
descriptor.getExtensionList().forEach(extension => {
printer.print(Extension.print(fileName, entryMap, extension, indentLevel + 1));
});
printer.printLn(`}`);
return printer.getOutput();
}
}