1616 *
1717 */
1818import * as Protobuf from 'protobufjs' ;
19+ import * as descriptor from 'protobufjs/ext/descriptor' ;
1920import * as fs from 'fs' ;
2021import * as path from 'path' ;
2122import camelCase = require( 'lodash.camelcase' ) ;
2223
24+ declare module 'protobufjs' {
25+ interface Type {
26+ toDescriptor ( protoVersion : string ) : Protobuf . Message < descriptor . IDescriptorProto > & descriptor . IDescriptorProto ;
27+ }
28+
29+ interface Root {
30+ toDescriptor ( protoVersion : string ) : Protobuf . Message < descriptor . IFileDescriptorSet > & descriptor . IFileDescriptorSet ;
31+ }
32+
33+ interface Enum {
34+ toDescriptor ( protoVersion : string ) : Protobuf . Message < descriptor . IEnumDescriptorProto > & descriptor . IEnumDescriptorProto ;
35+ }
36+ }
37+
2338export interface Serialize < T > {
2439 ( value : T ) : Buffer ;
2540}
@@ -28,6 +43,20 @@ export interface Deserialize<T> {
2843 ( bytes : Buffer ) : T ;
2944}
3045
46+ export interface ProtobufTypeDefinition {
47+ format : string ;
48+ type : object ;
49+ fileDescriptorProtos : Buffer [ ] ;
50+ }
51+
52+ export interface MessageTypeDefinition extends ProtobufTypeDefinition {
53+ format : 'Protocol Buffer 3 DescriptorProto' ;
54+ }
55+
56+ export interface EnumTypeDefinition extends ProtobufTypeDefinition {
57+ format : 'Protocol Buffer 3 EnumDescriptorProto' ;
58+ }
59+
3160export interface MethodDefinition < RequestType , ResponseType > {
3261 path : string ;
3362 requestStream : boolean ;
@@ -37,20 +66,33 @@ export interface MethodDefinition<RequestType, ResponseType> {
3766 requestDeserialize : Deserialize < RequestType > ;
3867 responseDeserialize : Deserialize < ResponseType > ;
3968 originalName ?: string ;
69+ requestType : MessageTypeDefinition ;
70+ responseType : MessageTypeDefinition ;
4071}
4172
4273export interface ServiceDefinition {
4374 [ index : string ] : MethodDefinition < object , object > ;
4475}
4576
77+ export type AnyDefinition = ServiceDefinition | MessageTypeDefinition | EnumTypeDefinition ;
78+
4679export interface PackageDefinition {
47- [ index : string ] : ServiceDefinition ;
80+ [ index : string ] : AnyDefinition ;
4881}
4982
5083export type Options = Protobuf . IParseOptions & Protobuf . IConversionOptions & {
5184 includeDirs ?: string [ ] ;
5285} ;
5386
87+ const descriptorOptions : Protobuf . IConversionOptions = {
88+ longs : String ,
89+ enums : String ,
90+ bytes : String ,
91+ defaults : true ,
92+ oneofs : true ,
93+ json : true
94+ } ;
95+
5496function joinName ( baseName : string , name : string ) : string {
5597 if ( baseName === '' ) {
5698 return name ;
@@ -59,19 +101,28 @@ function joinName(baseName: string, name: string): string {
59101 }
60102}
61103
62- function getAllServices ( obj : Protobuf . NamespaceBase , parentName : string ) : Array < [ string , Protobuf . Service ] > {
104+ type HandledReflectionObject = Protobuf . Service | Protobuf . Type | Protobuf . Enum ;
105+
106+ function isHandledReflectionObject ( obj : Protobuf . ReflectionObject ) : obj is HandledReflectionObject {
107+ return obj instanceof Protobuf . Service || obj instanceof Protobuf . Type || obj instanceof Protobuf . Enum ;
108+ }
109+
110+ function isNamespaceBase ( obj : Protobuf . ReflectionObject ) : obj is Protobuf . NamespaceBase {
111+ return obj instanceof Protobuf . Namespace || obj instanceof Protobuf . Root ;
112+ }
113+
114+ function getAllHandledReflectionObjects ( obj : Protobuf . ReflectionObject , parentName : string ) : Array < [ string , HandledReflectionObject ] > {
63115 const objName = joinName ( parentName , obj . name ) ;
64- if ( obj . hasOwnProperty ( 'methods' ) ) {
65- return [ [ objName , obj as Protobuf . Service ] ] ;
116+ if ( isHandledReflectionObject ( obj ) ) {
117+ return [ [ objName , obj ] ] ;
66118 } else {
67- return obj . nestedArray . map ( ( child ) => {
68- if ( child . hasOwnProperty ( 'nested' ) ) {
69- return getAllServices ( child as Protobuf . NamespaceBase , objName ) ;
70- } else {
71- return [ ] ;
72- }
73- } ) . reduce ( ( accumulator , currentValue ) => accumulator . concat ( currentValue ) , [ ] ) ;
119+ if ( isNamespaceBase ( obj ) && typeof obj . nested !== undefined ) {
120+ return Object . keys ( obj . nested ! ) . map ( ( name ) => {
121+ return getAllHandledReflectionObjects ( obj . nested ! [ name ] , objName ) ;
122+ } ) . reduce ( ( accumulator , currentValue ) => accumulator . concat ( currentValue ) , [ ] ) ;
123+ }
74124 }
125+ return [ ] ;
75126}
76127
77128function createDeserializer ( cls : Protobuf . Type , options : Options ) : Deserialize < object > {
@@ -88,16 +139,22 @@ function createSerializer(cls: Protobuf.Type): Serialize<object> {
88139}
89140
90141function createMethodDefinition ( method : Protobuf . Method , serviceName : string , options : Options ) : MethodDefinition < object , object > {
142+ /* This is only ever called after the corresponding root.resolveAll(), so we
143+ * can assume that the resolved request and response types are non-null */
144+ const requestType : Protobuf . Type = method . resolvedRequestType ! ;
145+ const responseType : Protobuf . Type = method . resolvedResponseType ! ;
91146 return {
92147 path : '/' + serviceName + '/' + method . name ,
93148 requestStream : ! ! method . requestStream ,
94149 responseStream : ! ! method . responseStream ,
95- requestSerialize : createSerializer ( method . resolvedRequestType as Protobuf . Type ) ,
96- requestDeserialize : createDeserializer ( method . resolvedRequestType as Protobuf . Type , options ) ,
97- responseSerialize : createSerializer ( method . resolvedResponseType as Protobuf . Type ) ,
98- responseDeserialize : createDeserializer ( method . resolvedResponseType as Protobuf . Type , options ) ,
150+ requestSerialize : createSerializer ( requestType ) ,
151+ requestDeserialize : createDeserializer ( requestType , options ) ,
152+ responseSerialize : createSerializer ( responseType ) ,
153+ responseDeserialize : createDeserializer ( responseType , options ) ,
99154 // TODO(murgatroid99): Find a better way to handle this
100- originalName : camelCase ( method . name )
155+ originalName : camelCase ( method . name ) ,
156+ requestType : createMessageDefinition ( requestType ) ,
157+ responseType : createMessageDefinition ( responseType )
101158 } ;
102159}
103160
@@ -109,10 +166,58 @@ function createServiceDefinition(service: Protobuf.Service, name: string, option
109166 return def ;
110167}
111168
169+ const fileDescriptorCache : Map < Protobuf . Root , Buffer [ ] > = new Map < Protobuf . Root , Buffer [ ] > ( ) ;
170+ function getFileDescriptors ( root : Protobuf . Root ) : Buffer [ ] {
171+ if ( fileDescriptorCache . has ( root ) ) {
172+ return fileDescriptorCache . get ( root ) ! ;
173+ } else {
174+ const descriptorList : descriptor . IFileDescriptorProto [ ] = root . toDescriptor ( 'proto3' ) . file ;
175+ const bufferList : Buffer [ ] = descriptorList . map ( value => Buffer . from ( descriptor . FileDescriptorProto . encode ( value ) . finish ( ) ) ) ;
176+ fileDescriptorCache . set ( root , bufferList ) ;
177+ return bufferList ;
178+ }
179+ }
180+
181+ function createMessageDefinition ( message : Protobuf . Type ) : MessageTypeDefinition {
182+ const messageDescriptor : protobuf . Message < descriptor . IDescriptorProto > = message . toDescriptor ( 'proto3' ) ;
183+ return {
184+ format : 'Protocol Buffer 3 DescriptorProto' ,
185+ type : messageDescriptor . $type . toObject ( messageDescriptor , descriptorOptions ) ,
186+ fileDescriptorProtos : getFileDescriptors ( message . root )
187+ } ;
188+ }
189+
190+ function createEnumDefinition ( enumType : Protobuf . Enum ) : EnumTypeDefinition {
191+ const enumDescriptor : protobuf . Message < descriptor . IEnumDescriptorProto > = enumType . toDescriptor ( 'proto3' ) ;
192+ return {
193+ format : 'Protocol Buffer 3 EnumDescriptorProto' ,
194+ type : enumDescriptor . $type . toObject ( enumDescriptor , descriptorOptions ) ,
195+ fileDescriptorProtos : getFileDescriptors ( enumType . root )
196+ } ;
197+ }
198+
199+ /**
200+ * function createDefinition(obj: Protobuf.Service, name: string, options: Options): ServiceDefinition;
201+ * function createDefinition(obj: Protobuf.Type, name: string, options: Options): MessageTypeDefinition;
202+ * function createDefinition(obj: Protobuf.Enum, name: string, options: Options): EnumTypeDefinition;
203+ */
204+ function createDefinition ( obj : HandledReflectionObject , name : string , options : Options ) : AnyDefinition {
205+ if ( obj instanceof Protobuf . Service ) {
206+ return createServiceDefinition ( obj , name , options ) ;
207+ } else if ( obj instanceof Protobuf . Type ) {
208+ return createMessageDefinition ( obj ) ;
209+ } else if ( obj instanceof Protobuf . Enum ) {
210+ return createEnumDefinition ( obj ) ;
211+ } else {
212+ throw new Error ( 'Type mismatch in reflection object handling' ) ;
213+ }
214+ }
215+
112216function createPackageDefinition ( root : Protobuf . Root , options : Options ) : PackageDefinition {
113217 const def : PackageDefinition = { } ;
114- for ( const [ name , service ] of getAllServices ( root , '' ) ) {
115- def [ name ] = createServiceDefinition ( service , name , options ) ;
218+ root . resolveAll ( ) ;
219+ for ( const [ name , obj ] of getAllHandledReflectionObjects ( root , '' ) ) {
220+ def [ name ] = createDefinition ( obj , name , options ) ;
116221 }
117222 return def ;
118223}
0 commit comments