From d484f755404d2d85369485de394cca5530dc64ee Mon Sep 17 00:00:00 2001 From: vato007 Date: Sun, 23 Jun 2024 15:12:15 +0930 Subject: [PATCH] Add basic protobuf message parsing --- src/app/model/proto-message.model.ts | 32 ++++++++--- src/app/proto-definition.service.spec.ts | 42 ++++++++++---- src/app/proto-definition.service.ts | 72 +++++++++++++++++------- 3 files changed, 105 insertions(+), 41 deletions(-) diff --git a/src/app/model/proto-message.model.ts b/src/app/model/proto-message.model.ts index bd516c8..57e7ef6 100644 --- a/src/app/model/proto-message.model.ts +++ b/src/app/model/proto-message.model.ts @@ -1,15 +1,31 @@ -export enum MessageType { - String, - Boolean, - Numeric, - List, - Map, - Object, +export class StringMessage implements MessageConfiguration { + constructor(public maxLength?: number) {} } +export class BooleanMessage implements MessageConfiguration {} + +export class NumericMessage implements MessageConfiguration { + constructor(public min?: number, public max?: number) {} +} + +export class ListMessage implements MessageConfiguration { + constructor(readonly subConfiguration: MessageConfiguration) {} +} + +export class MapMessage implements MessageConfiguration { + constructor( + readonly keyConfiguration: MessageConfiguration, + readonly valueConfiguration: MessageConfiguration + ) {} +} + +export class ObjectMessage implements MessageConfiguration {} + +export class MessageConfiguration {} + export interface ProtoMessageField { name: string; - type: MessageType; + configuration: MessageConfiguration; value: any; } diff --git a/src/app/proto-definition.service.spec.ts b/src/app/proto-definition.service.spec.ts index 70b54b3..8a92e6c 100644 --- a/src/app/proto-definition.service.spec.ts +++ b/src/app/proto-definition.service.spec.ts @@ -1,7 +1,18 @@ import { TestBed } from '@angular/core/testing'; -import { provideExperimentalZonelessChangeDetection } from '@angular/core'; -import { MessageType, ProtoMessage } from './model/proto-message.model'; +import { + Type, + provideExperimentalZonelessChangeDetection, +} from '@angular/core'; +import { + BooleanMessage, + ListMessage, + MapMessage, + MessageConfiguration, + NumericMessage, + ProtoMessage, + StringMessage, +} from './model/proto-message.model'; import { ProtoDefinitionService } from './proto-definition.service'; let testProto = ` @@ -36,22 +47,29 @@ describe('TestService', () => { expect(converted.name).toBe('Test'); expect(converted.values.length).toBe(8); - checkNameAndType(converted, 'hello', MessageType.String); - checkNameAndType(converted, 'hello2', MessageType.Numeric); - checkNameAndType(converted, 'hello3', MessageType.Numeric); - checkNameAndType(converted, 'hello4', MessageType.Numeric); - checkNameAndType(converted, 'hello5', MessageType.Numeric); - checkNameAndType(converted, 'hello6', MessageType.Boolean); - checkNameAndType(converted, 'hello7', MessageType.List); - checkNameAndType(converted, 'hello8', MessageType.Map); + checkNameAndType(converted, 'hello', StringMessage); + checkNameAndType(converted, 'hello2', NumericMessage); + checkNameAndType(converted, 'hello3', NumericMessage); + checkNameAndType(converted, 'hello4', NumericMessage); + checkNameAndType(converted, 'hello5', NumericMessage); + checkNameAndType(converted, 'hello6', BooleanMessage); + checkNameAndType(converted, 'hello7', ListMessage); + checkNameAndType(converted, 'hello8', MapMessage); + + const listMessage = converted.values[6].configuration as ListMessage; + expect(listMessage.subConfiguration).toBeInstanceOf(StringMessage); + + const mapMessage = converted.values[7].configuration as MapMessage; + expect(mapMessage.keyConfiguration).toBeInstanceOf(StringMessage); + expect(mapMessage.valueConfiguration).toBeInstanceOf(StringMessage); }); }); const checkNameAndType = ( converted: ProtoMessage, name: string, - type: MessageType + type: Type ) => { const field = converted.values.find((value) => value.name === name); - expect(field?.type).toBe(type); + expect(field?.configuration).toBeInstanceOf(type); }; diff --git a/src/app/proto-definition.service.ts b/src/app/proto-definition.service.ts index 0b38f4c..a5a3e8d 100644 --- a/src/app/proto-definition.service.ts +++ b/src/app/proto-definition.service.ts @@ -1,6 +1,14 @@ import { Injectable } from '@angular/core'; import { FieldBase, MapField, ReflectionObject, parse } from 'protobufjs'; -import { MessageType, ProtoMessage } from './model/proto-message.model'; +import { + BooleanMessage, + ListMessage, + MapMessage, + MessageConfiguration, + NumericMessage, + ProtoMessage, + StringMessage, +} from './model/proto-message.model'; @Injectable({ providedIn: 'root', @@ -36,35 +44,57 @@ export class ProtoDefinitionService { values: [], }; const fields = messageDefinition.fieldsArray as FieldBase[]; - // TODO: Needs to be a visitor/tree search since messages/enums may be nested + // TODO: Needs to be a visitor/tree search/recursive since messages/enums may be nested for (const field of fields) { - // TODO: Better way to check this? + let type: MessageConfiguration | undefined; if ('rule' in field && field.rule === 'repeated') { - // Repeated/list field + const subType = this.getTypeFromField(field.type); + if (subType) { + type = new ListMessage(subType); + } } else if (field instanceof MapField) { // Map - } else { - switch (field.type) { - case 'string': - break; - case 'int32': - case 'int64': - case 'float': - case 'double': - break; - case 'bool': - break; - // TODO: bytes as well, though that's pretty useless (can't really represent/edit it?) + const keyConfiguration = this.getTypeFromField(field.keyType); + const valueConfiguration = this.getTypeFromField(field.type); + if (keyConfiguration && valueConfiguration) { + type = new MapMessage(keyConfiguration, valueConfiguration); } + } else { + type = this.getTypeFromField(field.type); + } + if (type) { + convertedFields.values.push({ + name: field.name, + configuration: type, + value: '', + }); + } else { + console.error(`Failed to find type ${field.type}`); } - convertedFields.values.push({ - name: field.name, - type: MessageType.String, - value: '', - }); } return convertedFields; } throw 'ReflectionObject is not a message'; } + + private getTypeFromField( + fieldType: string + ): MessageConfiguration | undefined { + switch (fieldType) { + case 'string': + return new StringMessage(); + break; + case 'int32': + case 'int64': + case 'float': + case 'double': + return new NumericMessage(); + break; + case 'bool': + return new BooleanMessage(); + break; + // TODO: bytes as well, though that's pretty useless (can't really represent/edit it?) + } + return undefined; + } }