diff --git a/src/app/model/proto-message.model.ts b/src/app/model/proto-message.model.ts index a039b6a..a508780 100644 --- a/src/app/model/proto-message.model.ts +++ b/src/app/model/proto-message.model.ts @@ -6,6 +6,7 @@ export enum MessageTypeEnum { Map = 'map', Object = 'object', Raw = 'raw', + Enum = 'enum', } export interface StringMessage extends MessageConfiguration { @@ -34,6 +35,10 @@ export interface ObjectMessage extends MessageConfiguration { export interface RawMessage extends MessageConfiguration {} +export interface EnumMessage extends MessageConfiguration { + options: string[]; +} + export interface MessageConfiguration { type: MessageTypeEnum; } @@ -76,17 +81,29 @@ export const ObjectMessage = (messageDefinition: ProtoMessage) => ({ export const RawMessage = (): RawMessage => ({ type: MessageTypeEnum.Raw }); +export const EnumMessage = (options: string[]) => ({ + type: MessageTypeEnum.Enum, + options, +}); + export interface ProtoMessageField { name: string; configuration: MessageConfiguration; value: any; } -export interface ProtoMessage { +export interface ProtoBase { name: string; +} + +export interface ProtoMessage extends ProtoBase { values: ProtoMessageField[]; } +export interface ProtoEnum extends ProtoBase { + options: string[]; +} + export const UnknownProto = (name: string): ProtoMessage => ({ name, values: [{ name: 'Raw JSON', configuration: RawMessage(), value: '' }], diff --git a/src/app/proto-definition.service.spec.ts b/src/app/proto-definition.service.spec.ts index 7e7b4b2..417cd48 100644 --- a/src/app/proto-definition.service.spec.ts +++ b/src/app/proto-definition.service.spec.ts @@ -2,6 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { provideExperimentalZonelessChangeDetection } from '@angular/core'; import { + EnumMessage, ListMessage, MapMessage, MessageTypeEnum, @@ -27,12 +28,17 @@ message Test { map hello8 = 8; ReferenceMessage hello9 = 9; NestedMessage hello10 = 10; + EnumTest enum_test = 11; } message ReferenceMessage { string test = 1; } +enum EnumTest { + Hello = 0; +} + `; describe('TestService', () => { @@ -51,7 +57,7 @@ describe('TestService', () => { expect(converted.name).toBe('Test'); - expect(converted.values.length).toBe(10); + expect(converted.values.length).toBe(11); checkNameAndType(converted, 'hello', MessageTypeEnum.String); checkNameAndType(converted, 'hello2', MessageTypeEnum.Numeric); checkNameAndType(converted, 'hello3', MessageTypeEnum.Numeric); @@ -62,6 +68,7 @@ describe('TestService', () => { checkNameAndType(converted, 'hello8', MessageTypeEnum.Map); checkNameAndType(converted, 'hello9', MessageTypeEnum.Object); checkNameAndType(converted, 'hello10', MessageTypeEnum.Object); + checkNameAndType(converted, 'enumTest', MessageTypeEnum.Enum); const listMessage = converted.values[6].configuration as ListMessage; expect(listMessage.subConfiguration.type).toBe(MessageTypeEnum.String); @@ -86,7 +93,9 @@ describe('TestService', () => { expect(nestedNestedMessage.configuration.type).toBe(MessageTypeEnum.String); expect(nestedNestedMessage.name).toBe('nested'); - // TODO: Enum type + const enumMessage = converted.values[10].configuration as EnumMessage; + expect(enumMessage.options.length).toBe(1); + expect(enumMessage.options[0]).toBe('Hello'); }); }); diff --git a/src/app/proto-definition.service.ts b/src/app/proto-definition.service.ts index b454915..c506f12 100644 --- a/src/app/proto-definition.service.ts +++ b/src/app/proto-definition.service.ts @@ -2,14 +2,16 @@ import { Injectable } from '@angular/core'; import { FieldBase, MapField, ReflectionObject, parse } from 'protobufjs'; import { BooleanMessage, + EnumMessage, ListMessage, MapMessage, MessageConfiguration, MessageTypeEnum, NumericMessage, ObjectMessage, + ProtoBase, + ProtoEnum, ProtoMessage, - RawMessage, StringMessage, UnknownProto, } from './model/proto-message.model'; @@ -24,35 +26,85 @@ export class ProtoDefinitionService { const definition = await parse(protoContents); const messages = definition.root; if (messages) { - const messageObjects: ProtoMessage[] = - this.parseAllNestedMessages(messages); - // Now do another pass, where we populate each of the object fields based on their definitions... need a way to detect recursion? Maybe only go up to 5 layers deep? - for (const messageObject of messageObjects) { + const messageObjects: ProtoBase[] = this.parseAllNestedMessages(messages); + const standardMessages = messageObjects + .filter((messageObject) => 'values' in messageObject) + .map((messageObject) => messageObject as ProtoMessage); + for (const messageObject of standardMessages) { for (const value of messageObject.values) { if (value.configuration.type === MessageTypeEnum.Object) { this.populateNestedObject( value.configuration as ObjectMessage, - messageObjects + standardMessages ); } } } - return messageObjects; + const enumMessages = messageObjects + .filter((messageObject) => 'options' in messageObject) + .map((messageObject) => messageObject as ProtoEnum); + for (const messageObject of standardMessages) { + for (const value of messageObject.values) { + if (value.configuration.type === MessageTypeEnum.Object) { + value.configuration = this.populateEnumMessages( + value.configuration as ObjectMessage, + enumMessages + ); + } + } + } + + return standardMessages; } return []; } + private populateEnumMessages( + objectMessage: ObjectMessage, + enumMessages: ProtoEnum[] + ): MessageConfiguration { + for (const enumMessage of enumMessages) { + if (enumMessage.name === objectMessage.messageDefinition.name) { + return EnumMessage(enumMessage.options); + } + } + objectMessage.messageDefinition.values = + objectMessage.messageDefinition.values.map((obj) => + obj.configuration.type === MessageTypeEnum.Object + ? { + ...obj, + configuration: this.populateEnumMessages( + obj.configuration as ObjectMessage, + enumMessages + ), + } + : obj + ); + return objectMessage; + } + private parseAllNestedMessages(obj: ReflectionObject) { - const messageObjects: ProtoMessage[] = []; + const messageObjects: ProtoBase[] = []; + // Nested messages/enums if ('nestedArray' in obj && obj.nestedArray) { const nestedArray = obj.nestedArray as ReflectionObject[]; for (const nestedObj of nestedArray) { messageObjects.push(...this.parseAllNestedMessages(nestedObj)); } } + // Message if ('fieldsArray' in obj) { messageObjects.push(this.convertProtoDefinitionToModel(obj)); } + // Enum + if ('values' in obj) { + messageObjects.push({ + name: obj.name, + options: Object.entries(obj.values as Record) + .sort((a, b) => Number(a[1]) - Number(b[1])) + .map(([key, _]) => key), + } as ProtoEnum); + } return messageObjects; } @@ -84,7 +136,7 @@ export class ProtoDefinitionService { private convertProtoDefinitionToModel( messageDefinition?: ReflectionObject - ): ProtoMessage { + ): ProtoBase { if (messageDefinition && 'fieldsArray' in messageDefinition) { const convertedFields: ProtoMessage = { name: messageDefinition.name,