import protobuf from 'protobufjs' ; import { ProtobufType , MessageType , EnumType , ProtoCtx } from './protobuf' ; import { allPrimitiveTypes } from './primitiveTypes' ; import path from 'path' ; type FieldPair < T > = [ string , T ] ; type Fields < T > = Array < FieldPair < T > > ; function resolveTypeName ( resolvedField ) { return resolvedField . resolvedType ?. fullName || resolvedField . type ; } export function createMessageType ( messageType ) { const repeatedFields = [ ] ; const oneOfFields = [ ] ; const singleFields = [ ] ; const mapFields = [ ] ; messageType . fieldsArray . forEach ( ( field ) => { const resolvedField = field . resolve ( ) ; if ( resolvedField . repeated ) { repeatedFields . push ( [ resolvedField . name , resolveTypeName ( resolvedField ) ] ) ; } else if ( resolvedField . map ) { const map = field as protobuf . MapField ; mapFields . push ( [ map . name , [ map . keyType , resolveTypeName ( map ) ] ] ) ; } else { singleFields . push ( [ resolvedField . name , resolveTypeName ( resolvedField ) ] ) ; } } ) ; if ( messageType . oneofs ) { messageType . oneofsArray . forEach ( one => { const options = one . fieldsArray . map ( elt => { const typeName = resolveTypeName ( elt . resolve ( ) ) ; return [ elt . name , typeName ] ; } ) ; oneOfFields . push ( [ one . name , options ] ) ; } ) ; } const oneOfFieldNames = oneOfFields . reduce ( ( acc , [ , options ] ) => acc . concat ( options . map ( ( [ name ] ) => name ) ) , [ ] as string [ ] ) ; const realSingleFields = singleFields . filter ( ( [ name ] ) => ! oneOfFieldNames . includes ( name ) ) ; const temp = { tag : 'message' , name : messageType . fullName , singleFields : realSingleFields , repeatedFields : repeatedFields , oneOfFields : oneOfFields , mapFields : mapFields , } ; return temp ; } function createEnumType ( enumType ) { const temp = { tag : 'enum' , name : enumType . fullName , options : Object . keys ( enumType . values ) , optionValues : enumType . values , } ; return temp ; } function traverseTypes ( current ) { const types = [ ] ; if ( current instanceof protobuf . Type ) { types . push ( createMessageType ( current ) ) ; } else if ( current instanceof protobuf . Enum ) { types . push ( createEnumType ( current ) ) ; } if ( current . nestedArray ) { types . push ( ... current . nestedArray . reduce ( ( acc , nested ) => [ ... acc , ... traverseTypes ( nested ) ] , [ ] ) ) ; } return types ; } async function readProto ( root , paths ) { try { const newRoot = await root . load ( paths ) ; return [ traverseTypes ( newRoot ) , newRoot ] ; } catch ( e ) { throw new Error ( 'Protobuf cannot be read: ' + e ) ; } } export async function buildContext ( filepaths , rootPath ? ) { const baseRoot = new protobuf . Root ( ) ; baseRoot . resolvePath = ( origin , target ) : string => path . resolve ( rootPath ?? origin , target ) ; const [ types , root ] = await readProto ( baseRoot , filepaths ) ; const descriptorJson = JSON . stringify ( root . toJSON ( ) ) ; return { types : types . concat ( allPrimitiveTypes ) . reduce < ProtoCtx [ 'types' ] > ( ( acc , t ) => { acc [ t . name ] = t ; return acc ; } , { } ) , descriptorJson , } ; }