Add map field, refine directory structure

This commit is contained in:
2024-07-06 12:29:28 +09:30
parent 1a59b24f60
commit 99bc6c069b
11 changed files with 187 additions and 27 deletions

View 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: [] });
}
}