Add map field, refine directory structure
This commit is contained in:
197
src/app/proto-definition-selector/proto-definition.service.ts
Normal file
197
src/app/proto-definition-selector/proto-definition.service.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FieldBase, MapField, ReflectionObject, parse } from 'protobufjs';
|
||||
import {
|
||||
BooleanMessage,
|
||||
EnumMessage,
|
||||
ListMessage,
|
||||
MapMessage,
|
||||
MessageConfiguration,
|
||||
MessageTypeEnum,
|
||||
NumericMessage,
|
||||
ObjectMessage,
|
||||
ProtoBase,
|
||||
ProtoEnum,
|
||||
ProtoMessage,
|
||||
StringMessage,
|
||||
UnknownProto,
|
||||
} from '../model/proto-message.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ProtoDefinitionService {
|
||||
constructor() {}
|
||||
|
||||
async parseProtoDefinition(protoContents: string): Promise<ProtoMessage[]> {
|
||||
const definition = await parse(protoContents);
|
||||
const messages = definition.root;
|
||||
if (messages) {
|
||||
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,
|
||||
standardMessages
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
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: 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;
|
||||
}
|
||||
|
||||
private populateNestedObject(
|
||||
objectMessage: ObjectMessage,
|
||||
availableMessages: ProtoMessage[]
|
||||
) {
|
||||
for (const message of availableMessages) {
|
||||
if (message.name === objectMessage.messageDefinition.name) {
|
||||
objectMessage.messageDefinition = {
|
||||
name: message.name,
|
||||
values: message.values.map((value) => {
|
||||
if (value.configuration.type === MessageTypeEnum.Object) {
|
||||
this.populateNestedObject(
|
||||
value.configuration as ObjectMessage,
|
||||
availableMessages
|
||||
);
|
||||
}
|
||||
return structuredClone(value);
|
||||
}),
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
objectMessage.messageDefinition = UnknownProto(
|
||||
objectMessage.messageDefinition.name
|
||||
);
|
||||
}
|
||||
|
||||
private convertProtoDefinitionToModel(
|
||||
messageDefinition?: ReflectionObject
|
||||
): ProtoBase {
|
||||
if (messageDefinition && 'fieldsArray' in messageDefinition) {
|
||||
const convertedFields: ProtoMessage = {
|
||||
name: messageDefinition.name,
|
||||
values: [],
|
||||
};
|
||||
const fields = messageDefinition.fieldsArray as FieldBase[];
|
||||
for (const field of fields) {
|
||||
let type: MessageConfiguration | undefined;
|
||||
if ('rule' in field && field.rule === 'repeated') {
|
||||
const subType = this.getTypeFromField(field.type);
|
||||
if (subType) {
|
||||
type = ListMessage(subType);
|
||||
}
|
||||
} else if (field instanceof MapField) {
|
||||
// Map
|
||||
const keyConfiguration = this.getTypeFromField(field.keyType);
|
||||
const valueConfiguration = this.getTypeFromField(field.type);
|
||||
if (keyConfiguration && valueConfiguration) {
|
||||
type = MapMessage(keyConfiguration, valueConfiguration);
|
||||
}
|
||||
} else {
|
||||
type = this.getTypeFromField(field.type);
|
||||
}
|
||||
if (type) {
|
||||
convertedFields.values.push({
|
||||
name: field.name,
|
||||
configuration: type,
|
||||
});
|
||||
} 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 StringMessage();
|
||||
break;
|
||||
case 'int32':
|
||||
case 'int64':
|
||||
case 'float':
|
||||
case 'double':
|
||||
return NumericMessage();
|
||||
break;
|
||||
case 'bool':
|
||||
return BooleanMessage();
|
||||
break;
|
||||
// TODO: bytes as well, though that's pretty useless (can't really represent/edit it?)
|
||||
}
|
||||
return ObjectMessage({ name: fieldType, values: [] });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user