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 {
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;
}

View File

@@ -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<MessageConfiguration>
) => {
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 { 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;
}
}