Add basic protobuf message parsing
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
const keyConfiguration = this.getTypeFromField(field.keyType);
|
||||
const valueConfiguration = this.getTypeFromField(field.type);
|
||||
if (keyConfiguration && valueConfiguration) {
|
||||
type = new MapMessage(keyConfiguration, valueConfiguration);
|
||||
}
|
||||
} 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?)
|
||||
}
|
||||
type = this.getTypeFromField(field.type);
|
||||
}
|
||||
if (type) {
|
||||
convertedFields.values.push({
|
||||
name: field.name,
|
||||
type: MessageType.String,
|
||||
configuration: type,
|
||||
value: '',
|
||||
});
|
||||
} else {
|
||||
console.error(`Failed to find type ${field.type}`);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user