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', Map = 'map',
Object = 'object', Object = 'object',
Raw = 'raw', Raw = 'raw',
Enum = 'enum',
} }
export interface StringMessage extends MessageConfiguration { export interface StringMessage extends MessageConfiguration {
@@ -34,6 +35,10 @@ export interface ObjectMessage extends MessageConfiguration {
export interface RawMessage extends MessageConfiguration {} export interface RawMessage extends MessageConfiguration {}
export interface EnumMessage extends MessageConfiguration {
options: string[];
}
export interface MessageConfiguration { export interface MessageConfiguration {
type: MessageTypeEnum; type: MessageTypeEnum;
} }
@@ -76,17 +81,29 @@ export const ObjectMessage = (messageDefinition: ProtoMessage) => ({
export const RawMessage = (): RawMessage => ({ type: MessageTypeEnum.Raw }); export const RawMessage = (): RawMessage => ({ type: MessageTypeEnum.Raw });
export const EnumMessage = (options: string[]) => ({
type: MessageTypeEnum.Enum,
options,
});
export interface ProtoMessageField { export interface ProtoMessageField {
name: string; name: string;
configuration: MessageConfiguration; configuration: MessageConfiguration;
value: any; value: any;
} }
export interface ProtoMessage { export interface ProtoBase {
name: string; name: string;
}
export interface ProtoMessage extends ProtoBase {
values: ProtoMessageField[]; values: ProtoMessageField[];
} }
export interface ProtoEnum extends ProtoBase {
options: string[];
}
export const UnknownProto = (name: string): ProtoMessage => ({ export const UnknownProto = (name: string): ProtoMessage => ({
name, name,
values: [{ name: 'Raw JSON', configuration: RawMessage(), value: '' }], 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 { provideExperimentalZonelessChangeDetection } from '@angular/core';
import { import {
EnumMessage,
ListMessage, ListMessage,
MapMessage, MapMessage,
MessageTypeEnum, MessageTypeEnum,
@@ -27,12 +28,17 @@ message Test {
map<string, string> hello8 = 8; map<string, string> hello8 = 8;
ReferenceMessage hello9 = 9; ReferenceMessage hello9 = 9;
NestedMessage hello10 = 10; NestedMessage hello10 = 10;
EnumTest enum_test = 11;
} }
message ReferenceMessage { message ReferenceMessage {
string test = 1; string test = 1;
} }
enum EnumTest {
Hello = 0;
}
`; `;
describe('TestService', () => { describe('TestService', () => {
@@ -51,7 +57,7 @@ describe('TestService', () => {
expect(converted.name).toBe('Test'); expect(converted.name).toBe('Test');
expect(converted.values.length).toBe(10); expect(converted.values.length).toBe(11);
checkNameAndType(converted, 'hello', MessageTypeEnum.String); checkNameAndType(converted, 'hello', MessageTypeEnum.String);
checkNameAndType(converted, 'hello2', MessageTypeEnum.Numeric); checkNameAndType(converted, 'hello2', MessageTypeEnum.Numeric);
checkNameAndType(converted, 'hello3', MessageTypeEnum.Numeric); checkNameAndType(converted, 'hello3', MessageTypeEnum.Numeric);
@@ -62,6 +68,7 @@ describe('TestService', () => {
checkNameAndType(converted, 'hello8', MessageTypeEnum.Map); checkNameAndType(converted, 'hello8', MessageTypeEnum.Map);
checkNameAndType(converted, 'hello9', MessageTypeEnum.Object); checkNameAndType(converted, 'hello9', MessageTypeEnum.Object);
checkNameAndType(converted, 'hello10', MessageTypeEnum.Object); checkNameAndType(converted, 'hello10', MessageTypeEnum.Object);
checkNameAndType(converted, 'enumTest', MessageTypeEnum.Enum);
const listMessage = converted.values[6].configuration as ListMessage; const listMessage = converted.values[6].configuration as ListMessage;
expect(listMessage.subConfiguration.type).toBe(MessageTypeEnum.String); expect(listMessage.subConfiguration.type).toBe(MessageTypeEnum.String);
@@ -86,7 +93,9 @@ describe('TestService', () => {
expect(nestedNestedMessage.configuration.type).toBe(MessageTypeEnum.String); expect(nestedNestedMessage.configuration.type).toBe(MessageTypeEnum.String);
expect(nestedNestedMessage.name).toBe('nested'); 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 { FieldBase, MapField, ReflectionObject, parse } from 'protobufjs';
import { import {
BooleanMessage, BooleanMessage,
EnumMessage,
ListMessage, ListMessage,
MapMessage, MapMessage,
MessageConfiguration, MessageConfiguration,
MessageTypeEnum, MessageTypeEnum,
NumericMessage, NumericMessage,
ObjectMessage, ObjectMessage,
ProtoBase,
ProtoEnum,
ProtoMessage, ProtoMessage,
RawMessage,
StringMessage, StringMessage,
UnknownProto, UnknownProto,
} from './model/proto-message.model'; } from './model/proto-message.model';
@@ -24,35 +26,85 @@ export class ProtoDefinitionService {
const definition = await parse(protoContents); const definition = await parse(protoContents);
const messages = definition.root; const messages = definition.root;
if (messages) { if (messages) {
const messageObjects: ProtoMessage[] = const messageObjects: ProtoBase[] = this.parseAllNestedMessages(messages);
this.parseAllNestedMessages(messages); const standardMessages = messageObjects
// 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? .filter((messageObject) => 'values' in messageObject)
for (const messageObject of messageObjects) { .map((messageObject) => messageObject as ProtoMessage);
for (const messageObject of standardMessages) {
for (const value of messageObject.values) { for (const value of messageObject.values) {
if (value.configuration.type === MessageTypeEnum.Object) { if (value.configuration.type === MessageTypeEnum.Object) {
this.populateNestedObject( this.populateNestedObject(
value.configuration as ObjectMessage, 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 []; 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) { private parseAllNestedMessages(obj: ReflectionObject) {
const messageObjects: ProtoMessage[] = []; const messageObjects: ProtoBase[] = [];
// Nested messages/enums
if ('nestedArray' in obj && obj.nestedArray) { if ('nestedArray' in obj && obj.nestedArray) {
const nestedArray = obj.nestedArray as ReflectionObject[]; const nestedArray = obj.nestedArray as ReflectionObject[];
for (const nestedObj of nestedArray) { for (const nestedObj of nestedArray) {
messageObjects.push(...this.parseAllNestedMessages(nestedObj)); messageObjects.push(...this.parseAllNestedMessages(nestedObj));
} }
} }
// Message
if ('fieldsArray' in obj) { if ('fieldsArray' in obj) {
messageObjects.push(this.convertProtoDefinitionToModel(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; return messageObjects;
} }
@@ -84,7 +136,7 @@ export class ProtoDefinitionService {
private convertProtoDefinitionToModel( private convertProtoDefinitionToModel(
messageDefinition?: ReflectionObject messageDefinition?: ReflectionObject
): ProtoMessage { ): ProtoBase {
if (messageDefinition && 'fieldsArray' in messageDefinition) { if (messageDefinition && 'fieldsArray' in messageDefinition) {
const convertedFields: ProtoMessage = { const convertedFields: ProtoMessage = {
name: messageDefinition.name, name: messageDefinition.name,