Add basic protobuf message parsing

This commit is contained in:
2024-06-23 15:12:15 +09:30
parent 9c2531a034
commit d484f75540
3 changed files with 105 additions and 41 deletions

View File

@@ -1,15 +1,31 @@
export enum MessageType { export class StringMessage implements MessageConfiguration {
String, constructor(public maxLength?: number) {}
Boolean,
Numeric,
List,
Map,
Object,
} }
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 { export interface ProtoMessageField {
name: string; name: string;
type: MessageType; configuration: MessageConfiguration;
value: any; value: any;
} }

View File

@@ -1,7 +1,18 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { provideExperimentalZonelessChangeDetection } from '@angular/core'; import {
import { MessageType, ProtoMessage } from './model/proto-message.model'; Type,
provideExperimentalZonelessChangeDetection,
} from '@angular/core';
import {
BooleanMessage,
ListMessage,
MapMessage,
MessageConfiguration,
NumericMessage,
ProtoMessage,
StringMessage,
} from './model/proto-message.model';
import { ProtoDefinitionService } from './proto-definition.service'; import { ProtoDefinitionService } from './proto-definition.service';
let testProto = ` let testProto = `
@@ -36,22 +47,29 @@ describe('TestService', () => {
expect(converted.name).toBe('Test'); expect(converted.name).toBe('Test');
expect(converted.values.length).toBe(8); expect(converted.values.length).toBe(8);
checkNameAndType(converted, 'hello', MessageType.String); checkNameAndType(converted, 'hello', StringMessage);
checkNameAndType(converted, 'hello2', MessageType.Numeric); checkNameAndType(converted, 'hello2', NumericMessage);
checkNameAndType(converted, 'hello3', MessageType.Numeric); checkNameAndType(converted, 'hello3', NumericMessage);
checkNameAndType(converted, 'hello4', MessageType.Numeric); checkNameAndType(converted, 'hello4', NumericMessage);
checkNameAndType(converted, 'hello5', MessageType.Numeric); checkNameAndType(converted, 'hello5', NumericMessage);
checkNameAndType(converted, 'hello6', MessageType.Boolean); checkNameAndType(converted, 'hello6', BooleanMessage);
checkNameAndType(converted, 'hello7', MessageType.List); checkNameAndType(converted, 'hello7', ListMessage);
checkNameAndType(converted, 'hello8', MessageType.Map); 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 = ( const checkNameAndType = (
converted: ProtoMessage, converted: ProtoMessage,
name: string, name: string,
type: MessageType type: Type<MessageConfiguration>
) => { ) => {
const field = converted.values.find((value) => value.name === name); const field = converted.values.find((value) => value.name === name);
expect(field?.type).toBe(type); expect(field?.configuration).toBeInstanceOf(type);
}; };

View File

@@ -1,6 +1,14 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FieldBase, MapField, ReflectionObject, parse } from 'protobufjs'; 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({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -36,35 +44,57 @@ export class ProtoDefinitionService {
values: [], values: [],
}; };
const fields = messageDefinition.fieldsArray as FieldBase[]; 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) { for (const field of fields) {
// TODO: Better way to check this? let type: MessageConfiguration | undefined;
if ('rule' in field && field.rule === 'repeated') { 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) { } else if (field instanceof MapField) {
// Map // Map
const keyConfiguration = this.getTypeFromField(field.keyType);
const valueConfiguration = this.getTypeFromField(field.type);
if (keyConfiguration && valueConfiguration) {
type = new MapMessage(keyConfiguration, valueConfiguration);
}
} else { } else {
switch (field.type) { type = this.getTypeFromField(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?)
}
} }
if (type) {
convertedFields.values.push({ convertedFields.values.push({
name: field.name, name: field.name,
type: MessageType.String, configuration: type,
value: '', value: '',
}); });
} else {
console.error(`Failed to find type ${field.type}`);
}
} }
return convertedFields; return convertedFields;
} }
throw 'ReflectionObject is not a message'; 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;
}
} }