mirror of https://github.com/grpc/grpc-node.git
				
				
				
			Merge pull request #703 from murgatroid99/proto-loader_messages
Add message and enum type information to package definition output.
This commit is contained in:
		
						commit
						eb932db144
					
				|  | @ -99,7 +99,7 @@ gulp.task('native.test', 'Run tests of native code', (callback) => { | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| gulp.task('test.only', 'Run tests without rebuilding anything', | gulp.task('test.only', 'Run tests without rebuilding anything', | ||||||
|           ['js.core.test', 'native.test.only']); |           ['js.core.test', 'native.test.only', 'protobuf.test']); | ||||||
| 
 | 
 | ||||||
| gulp.task('test', 'Run all tests', (callback) => { | gulp.task('test', 'Run all tests', (callback) => { | ||||||
|   runSequence('build', 'test.only', 'internal.test.test', callback); |   runSequence('build', 'test.only', 'internal.test.test', callback); | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ import * as fs from 'fs'; | ||||||
| import * as mocha from 'gulp-mocha'; | import * as mocha from 'gulp-mocha'; | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
| import * as execa from 'execa'; | import * as execa from 'execa'; | ||||||
|  | import * as semver from 'semver'; | ||||||
| 
 | 
 | ||||||
| // gulp-help monkeypatches tasks to have an additional description parameter
 | // gulp-help monkeypatches tasks to have an additional description parameter
 | ||||||
| const gulp = help(_gulp); | const gulp = help(_gulp); | ||||||
|  | @ -54,7 +55,21 @@ gulp.task('clean', 'Deletes transpiled code.', ['install'], | ||||||
| gulp.task('clean.all', 'Deletes all files added by targets', ['clean']); | gulp.task('clean.all', 'Deletes all files added by targets', ['clean']); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Transpiles TypeScript files in src/ to JavaScript according to the settings |  * Transpiles TypeScript files in src/ and test/ to JavaScript according to the settings | ||||||
|  * found in tsconfig.json. |  * found in tsconfig.json. | ||||||
|  */ |  */ | ||||||
| gulp.task('compile', 'Transpiles src/.', () => execNpmCommand('compile')); | gulp.task('compile', 'Transpiles src/ and test/.', () => execNpmCommand('compile')); | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Transpiles src/ and test/, and then runs all tests. | ||||||
|  |  */ | ||||||
|  | gulp.task('test', 'Runs all tests.', () => { | ||||||
|  |   if (semver.satisfies(process.version, ">=6")) { | ||||||
|  |     return gulp.src(`${outDir}/test/**/*.js`) | ||||||
|  |       .pipe(mocha({reporter: 'mocha-jenkins-reporter', | ||||||
|  |                     require: ['ts-node/register']})); | ||||||
|  |   } else { | ||||||
|  |     console.log(`Skipping proto-loader tests for Node ${process.version}`); | ||||||
|  |     return Promise.resolve(null); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | @ -16,10 +16,25 @@ | ||||||
|  * |  * | ||||||
|  */ |  */ | ||||||
| import * as Protobuf from 'protobufjs'; | import * as Protobuf from 'protobufjs'; | ||||||
|  | import * as descriptor from 'protobufjs/ext/descriptor'; | ||||||
| import * as fs from 'fs'; | import * as fs from 'fs'; | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
| import camelCase = require('lodash.camelcase'); | import camelCase = require('lodash.camelcase'); | ||||||
| 
 | 
 | ||||||
|  | declare module 'protobufjs' { | ||||||
|  |   interface Type { | ||||||
|  |     toDescriptor(protoVersion: string): Protobuf.Message<descriptor.IDescriptorProto> & descriptor.IDescriptorProto; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   interface Root { | ||||||
|  |     toDescriptor(protoVersion: string): Protobuf.Message<descriptor.IFileDescriptorSet> & descriptor.IFileDescriptorSet; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   interface Enum { | ||||||
|  |     toDescriptor(protoVersion: string): Protobuf.Message<descriptor.IEnumDescriptorProto> & descriptor.IEnumDescriptorProto; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export interface Serialize<T> { | export interface Serialize<T> { | ||||||
|   (value: T): Buffer; |   (value: T): Buffer; | ||||||
| } | } | ||||||
|  | @ -28,6 +43,20 @@ export interface Deserialize<T> { | ||||||
|   (bytes: Buffer): T; |   (bytes: Buffer): T; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export interface ProtobufTypeDefinition { | ||||||
|  |   format: string; | ||||||
|  |   type: object; | ||||||
|  |   fileDescriptorProtos: Buffer[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface MessageTypeDefinition extends ProtobufTypeDefinition { | ||||||
|  |   format: 'Protocol Buffer 3 DescriptorProto'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface EnumTypeDefinition extends ProtobufTypeDefinition { | ||||||
|  |   format: 'Protocol Buffer 3 EnumDescriptorProto'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export interface MethodDefinition<RequestType, ResponseType> { | export interface MethodDefinition<RequestType, ResponseType> { | ||||||
|   path: string; |   path: string; | ||||||
|   requestStream: boolean; |   requestStream: boolean; | ||||||
|  | @ -37,20 +66,33 @@ export interface MethodDefinition<RequestType, ResponseType> { | ||||||
|   requestDeserialize: Deserialize<RequestType>; |   requestDeserialize: Deserialize<RequestType>; | ||||||
|   responseDeserialize: Deserialize<ResponseType>; |   responseDeserialize: Deserialize<ResponseType>; | ||||||
|   originalName?: string; |   originalName?: string; | ||||||
|  |   requestType: MessageTypeDefinition; | ||||||
|  |   responseType: MessageTypeDefinition; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface ServiceDefinition { | export interface ServiceDefinition { | ||||||
|   [index: string]: MethodDefinition<object, object>; |   [index: string]: MethodDefinition<object, object>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export type AnyDefinition = ServiceDefinition | MessageTypeDefinition | EnumTypeDefinition; | ||||||
|  | 
 | ||||||
| export interface PackageDefinition { | export interface PackageDefinition { | ||||||
|   [index: string]: ServiceDefinition; |   [index: string]: AnyDefinition; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type Options = Protobuf.IParseOptions & Protobuf.IConversionOptions & { | export type Options = Protobuf.IParseOptions & Protobuf.IConversionOptions & { | ||||||
|   includeDirs?: string[]; |   includeDirs?: string[]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const descriptorOptions: Protobuf.IConversionOptions = { | ||||||
|  |   longs: String, | ||||||
|  |   enums: String, | ||||||
|  |   bytes: String, | ||||||
|  |   defaults: true, | ||||||
|  |   oneofs: true, | ||||||
|  |   json: true | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| function joinName(baseName: string, name: string): string { | function joinName(baseName: string, name: string): string { | ||||||
|   if (baseName === '') { |   if (baseName === '') { | ||||||
|     return name; |     return name; | ||||||
|  | @ -59,19 +101,28 @@ function joinName(baseName: string, name: string): string { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getAllServices(obj: Protobuf.NamespaceBase, parentName: string): Array<[string, Protobuf.Service]> { | type HandledReflectionObject = Protobuf.Service | Protobuf.Type | Protobuf.Enum; | ||||||
|  | 
 | ||||||
|  | function isHandledReflectionObject(obj: Protobuf.ReflectionObject): obj is HandledReflectionObject { | ||||||
|  |   return obj instanceof Protobuf.Service || obj instanceof Protobuf.Type || obj instanceof Protobuf.Enum; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isNamespaceBase(obj: Protobuf.ReflectionObject): obj is Protobuf.NamespaceBase { | ||||||
|  |   return obj instanceof Protobuf.Namespace || obj instanceof Protobuf.Root; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getAllHandledReflectionObjects(obj: Protobuf.ReflectionObject, parentName: string): Array<[string, HandledReflectionObject]> { | ||||||
|   const objName = joinName(parentName, obj.name); |   const objName = joinName(parentName, obj.name); | ||||||
|   if (obj.hasOwnProperty('methods')) { |   if (isHandledReflectionObject(obj)) { | ||||||
|     return [[objName, obj as Protobuf.Service]]; |     return [[objName, obj]]; | ||||||
|   } else { |   } else { | ||||||
|     return obj.nestedArray.map((child) => { |     if (isNamespaceBase(obj) && typeof obj.nested !== undefined) { | ||||||
|       if (child.hasOwnProperty('nested')) { |       return Object.keys(obj.nested!).map((name) => { | ||||||
|         return getAllServices(child as Protobuf.NamespaceBase, objName); |         return getAllHandledReflectionObjects(obj.nested![name], objName); | ||||||
|       } else { |       }).reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); | ||||||
|         return []; |     } | ||||||
|       } |  | ||||||
|     }).reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); |  | ||||||
|   } |   } | ||||||
|  |   return []; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function createDeserializer(cls: Protobuf.Type, options: Options): Deserialize<object> { | function createDeserializer(cls: Protobuf.Type, options: Options): Deserialize<object> { | ||||||
|  | @ -88,16 +139,22 @@ function createSerializer(cls: Protobuf.Type): Serialize<object> { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function createMethodDefinition(method: Protobuf.Method, serviceName: string, options: Options): MethodDefinition<object, object> { | function createMethodDefinition(method: Protobuf.Method, serviceName: string, options: Options): MethodDefinition<object, object> { | ||||||
|  |   /* This is only ever called after the corresponding root.resolveAll(), so we | ||||||
|  |    * can assume that the resolved request and response types are non-null */ | ||||||
|  |   const requestType: Protobuf.Type = method.resolvedRequestType!; | ||||||
|  |   const responseType: Protobuf.Type = method.resolvedResponseType!; | ||||||
|   return { |   return { | ||||||
|     path: '/' + serviceName + '/' + method.name, |     path: '/' + serviceName + '/' + method.name, | ||||||
|     requestStream: !!method.requestStream, |     requestStream: !!method.requestStream, | ||||||
|     responseStream: !!method.responseStream, |     responseStream: !!method.responseStream, | ||||||
|     requestSerialize: createSerializer(method.resolvedRequestType as Protobuf.Type), |     requestSerialize: createSerializer(requestType), | ||||||
|     requestDeserialize: createDeserializer(method.resolvedRequestType as Protobuf.Type, options), |     requestDeserialize: createDeserializer(requestType, options), | ||||||
|     responseSerialize: createSerializer(method.resolvedResponseType as Protobuf.Type), |     responseSerialize: createSerializer(responseType), | ||||||
|     responseDeserialize: createDeserializer(method.resolvedResponseType as Protobuf.Type, options), |     responseDeserialize: createDeserializer(responseType, options), | ||||||
|     // TODO(murgatroid99): Find a better way to handle this
 |     // TODO(murgatroid99): Find a better way to handle this
 | ||||||
|     originalName: camelCase(method.name) |     originalName: camelCase(method.name), | ||||||
|  |     requestType: createMessageDefinition(requestType), | ||||||
|  |     responseType: createMessageDefinition(responseType) | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -109,10 +166,58 @@ function createServiceDefinition(service: Protobuf.Service, name: string, option | ||||||
|   return def; |   return def; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const fileDescriptorCache: Map<Protobuf.Root, Buffer[]> = new Map<Protobuf.Root, Buffer[]>(); | ||||||
|  | function getFileDescriptors(root: Protobuf.Root): Buffer[] { | ||||||
|  |   if (fileDescriptorCache.has(root)) { | ||||||
|  |     return fileDescriptorCache.get(root)!; | ||||||
|  |   } else { | ||||||
|  |     const descriptorList: descriptor.IFileDescriptorProto[] = root.toDescriptor('proto3').file; | ||||||
|  |     const bufferList: Buffer[] = descriptorList.map(value => Buffer.from(descriptor.FileDescriptorProto.encode(value).finish())); | ||||||
|  |     fileDescriptorCache.set(root, bufferList); | ||||||
|  |     return bufferList; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function createMessageDefinition(message: Protobuf.Type): MessageTypeDefinition { | ||||||
|  |   const messageDescriptor: protobuf.Message<descriptor.IDescriptorProto> = message.toDescriptor('proto3'); | ||||||
|  |   return { | ||||||
|  |     format: 'Protocol Buffer 3 DescriptorProto', | ||||||
|  |     type: messageDescriptor.$type.toObject(messageDescriptor, descriptorOptions), | ||||||
|  |     fileDescriptorProtos: getFileDescriptors(message.root) | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function createEnumDefinition(enumType: Protobuf.Enum): EnumTypeDefinition { | ||||||
|  |   const enumDescriptor: protobuf.Message<descriptor.IEnumDescriptorProto> = enumType.toDescriptor('proto3'); | ||||||
|  |   return { | ||||||
|  |     format: 'Protocol Buffer 3 EnumDescriptorProto', | ||||||
|  |     type: enumDescriptor.$type.toObject(enumDescriptor, descriptorOptions), | ||||||
|  |     fileDescriptorProtos: getFileDescriptors(enumType.root) | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * function createDefinition(obj: Protobuf.Service, name: string, options: Options): ServiceDefinition; | ||||||
|  |  * function createDefinition(obj: Protobuf.Type, name: string, options: Options): MessageTypeDefinition; | ||||||
|  |  * function createDefinition(obj: Protobuf.Enum, name: string, options: Options): EnumTypeDefinition; | ||||||
|  |  */ | ||||||
|  | function createDefinition(obj: HandledReflectionObject, name: string, options: Options): AnyDefinition { | ||||||
|  |   if (obj instanceof Protobuf.Service) { | ||||||
|  |     return createServiceDefinition(obj, name, options); | ||||||
|  |   } else if (obj instanceof Protobuf.Type) { | ||||||
|  |     return createMessageDefinition(obj); | ||||||
|  |   } else if (obj instanceof Protobuf.Enum) { | ||||||
|  |     return createEnumDefinition(obj); | ||||||
|  |   } else { | ||||||
|  |     throw new Error('Type mismatch in reflection object handling'); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function createPackageDefinition(root: Protobuf.Root, options: Options): PackageDefinition { | function createPackageDefinition(root: Protobuf.Root, options: Options): PackageDefinition { | ||||||
|   const def: PackageDefinition = {}; |   const def: PackageDefinition = {}; | ||||||
|   for (const [name, service] of getAllServices(root, '')) { |   root.resolveAll(); | ||||||
|     def[name] = createServiceDefinition(service, name, options); |   for (const [name, obj] of getAllHandledReflectionObjects(root, '')) { | ||||||
|  |     def[name] = createDefinition(obj, name, options); | ||||||
|   } |   } | ||||||
|   return def; |   return def; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | import * as assert from 'assert'; | ||||||
|  | 
 | ||||||
|  | import * as proto_loader from '../src/index'; | ||||||
|  | 
 | ||||||
|  | // Relative path from build output directory to test_protos directory
 | ||||||
|  | const TEST_PROTO_DIR = `${__dirname}/../../test_protos/`; | ||||||
|  | 
 | ||||||
|  | type TypeDefinition = proto_loader.EnumTypeDefinition | proto_loader.MessageTypeDefinition; | ||||||
|  | 
 | ||||||
|  | function isTypeObject(obj: proto_loader.AnyDefinition): obj is TypeDefinition { | ||||||
|  |   return 'format' in obj; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | describe('Descriptor types', () => { | ||||||
|  |   it('Should be output for each enum', (done) => { | ||||||
|  |     proto_loader.load(`${TEST_PROTO_DIR}/enums.proto`).then((packageDefinition) => { | ||||||
|  |       assert('Enum1' in packageDefinition); | ||||||
|  |       assert(isTypeObject(packageDefinition.Enum1)); | ||||||
|  |       // Need additional check because compiler doesn't understand asserts
 | ||||||
|  |       if(isTypeObject(packageDefinition.Enum1)) { | ||||||
|  |         const enum1Def: TypeDefinition = packageDefinition.Enum1; | ||||||
|  |         assert.strictEqual(enum1Def.format, 'Protocol Buffer 3 EnumDescriptorProto'); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       assert('Enum2' in packageDefinition); | ||||||
|  |       assert(isTypeObject(packageDefinition.Enum2)); | ||||||
|  |       // Need additional check because compiler doesn't understand asserts
 | ||||||
|  |       if(isTypeObject(packageDefinition.Enum2)) { | ||||||
|  |         const enum2Def: TypeDefinition = packageDefinition.Enum2; | ||||||
|  |         assert.strictEqual(enum2Def.format, 'Protocol Buffer 3 EnumDescriptorProto'); | ||||||
|  |       } | ||||||
|  |       done(); | ||||||
|  |     }, (error) => {done(error);}); | ||||||
|  |   }); | ||||||
|  |   it('Should be output for each message', (done) => { | ||||||
|  |     proto_loader.load(`${TEST_PROTO_DIR}/messages.proto`).then((packageDefinition) => { | ||||||
|  |       assert('LongValues' in packageDefinition); | ||||||
|  |       assert(isTypeObject(packageDefinition.LongValues)); | ||||||
|  |       if(isTypeObject(packageDefinition.LongValues)) { | ||||||
|  |         const longValuesDef: TypeDefinition = packageDefinition.LongValues; | ||||||
|  |         assert.strictEqual(longValuesDef.format, 'Protocol Buffer 3 DescriptorProto'); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       assert('SequenceValues' in packageDefinition); | ||||||
|  |       assert(isTypeObject(packageDefinition.SequenceValues)); | ||||||
|  |       if(isTypeObject(packageDefinition.SequenceValues)) { | ||||||
|  |         const sequenceValuesDef: TypeDefinition = packageDefinition.SequenceValues; | ||||||
|  |         assert.strictEqual(sequenceValuesDef.format, 'Protocol Buffer 3 DescriptorProto'); | ||||||
|  |       } | ||||||
|  |       done(); | ||||||
|  |     }, (error) => {done(error);}); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | // Copyright 2019 gRPC authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  | 
 | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | enum Enum1 { | ||||||
|  |   DEFAULT = 0; | ||||||
|  |   VALUE1 = 1; | ||||||
|  |   VALUE2 = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum Enum2 { | ||||||
|  |   DEFAULT = 0; | ||||||
|  |   ABC = 5; | ||||||
|  |   DEF = 10; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | // Copyright 2019 gRPC authors. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  | 
 | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | message LongValues { | ||||||
|  |   int64 int_64 = 1; | ||||||
|  |   uint64 uint_64 = 2; | ||||||
|  |   sint64 sint_64 = 3; | ||||||
|  |   fixed64 fixed_64 = 4; | ||||||
|  |   sfixed64 sfixed_64 = 5; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SequenceValues { | ||||||
|  |   bytes bytes_field = 1; | ||||||
|  |   repeated int32 repeated_field = 2; | ||||||
|  | } | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
|     "outDir": "build" |     "outDir": "build" | ||||||
|   }, |   }, | ||||||
|   "include": [ |   "include": [ | ||||||
|     "src/*.ts" |     "src/*.ts", | ||||||
|  |     "test/*.ts" | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue