Add enum message parsing

This commit is contained in:
2024-06-27 21:09:40 +09:30
parent da8861cde2
commit d2203b575c
3 changed files with 90 additions and 12 deletions

View File

@@ -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: '' }],

View File

@@ -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<string, string> 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');
});
});

View File

@@ -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<string, string>)
.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,