From c15fae7c3dd31b480831cb9e44563ed6041faa4b Mon Sep 17 00:00:00 2001 From: KoloMl Date: Tue, 12 Nov 2024 16:19:13 +0400 Subject: [PATCH] Implemented separate class for importing/exporting the entities --- src/lib/extension/EntitiesTransporter.ts | 73 ++++++++++++++++++++ src/lib/extension/transporting/exporters.js | 26 +++++++ src/lib/extension/transporting/validators.js | 39 +++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/lib/extension/EntitiesTransporter.ts create mode 100644 src/lib/extension/transporting/exporters.js create mode 100644 src/lib/extension/transporting/validators.js diff --git a/src/lib/extension/EntitiesTransporter.ts b/src/lib/extension/EntitiesTransporter.ts new file mode 100644 index 0000000..598edbb --- /dev/null +++ b/src/lib/extension/EntitiesTransporter.ts @@ -0,0 +1,73 @@ +import {validateImportedEntity} from "$lib/extension/transporting/validators.js"; +import {exportEntityToObject} from "$lib/extension/transporting/exporters.js"; +import StorageEntity from "./base/StorageEntity.js"; +import {compressToEncodedURIComponent, decompressFromEncodedURIComponent} from "lz-string"; + +type EntityConstructor = + (new (id: string, settings: Record) => T) + & typeof StorageEntity; + +export default class EntitiesTransporter { + readonly #targetEntityConstructor: EntityConstructor; + + constructor(entityConstructor: EntityConstructor) { + this.#targetEntityConstructor = entityConstructor; + } + + importFromJSON(jsonString: string): EntityType { + const importedObject = this.#tryParsingAsJSON(jsonString); + + if (!importedObject) { + throw new Error('Invalid JSON!'); + } + + validateImportedEntity( + importedObject, + this.#targetEntityConstructor._entityName + ); + + return new this.#targetEntityConstructor( + importedObject.id, + importedObject + ); + } + + importFromCompressedJSON(compressedJsonString: string): EntityType { + return this.importFromJSON( + decompressFromEncodedURIComponent(compressedJsonString) + ) + } + + exportToJSON(entityObject: EntityType): string { + if (!(entityObject instanceof this.#targetEntityConstructor)) { + throw new TypeError('Transporter should be connected to the same entity to export!'); + } + + const exportableObject = exportEntityToObject( + entityObject, + this.#targetEntityConstructor._entityName + ); + + return JSON.stringify(exportableObject, null, 2); + } + + exportToCompressedJSON(entityObject: EntityType): string { + return compressToEncodedURIComponent(this.exportToJSON(entityObject)); + } + + #tryParsingAsJSON(jsonString: string): Record | null { + let jsonObject: Record | null = null; + + try { + jsonObject = JSON.parse(jsonString); + } catch (e) { + + } + + if (typeof jsonObject !== "object") { + throw new TypeError("Should be an object!"); + } + + return jsonObject + } +} diff --git a/src/lib/extension/transporting/exporters.js b/src/lib/extension/transporting/exporters.js new file mode 100644 index 0000000..f74e15f --- /dev/null +++ b/src/lib/extension/transporting/exporters.js @@ -0,0 +1,26 @@ +/** + * @type {Map Record)>} + */ +const entitiesExporters = new Map([ + ['profiles', /** @param {import('../entities/MaintenanceProfile.js').default} entity */entity => { + return { + v: 1, + id: entity.id, + name: entity.settings.name, + tags: entity.settings.tags, + } + }] +]) + +/** + * @param entityInstance + * @param {string} entityName + * @returns {Record} + */ +export function exportEntityToObject(entityInstance, entityName) { + if (!entitiesExporters.has(entityName)) { + throw new Error(`Missing exporter for entity: ${entityName}`); + } + + return entitiesExporters.get(entityName).call(null, entityInstance); +} diff --git a/src/lib/extension/transporting/validators.js b/src/lib/extension/transporting/validators.js new file mode 100644 index 0000000..cb41e3d --- /dev/null +++ b/src/lib/extension/transporting/validators.js @@ -0,0 +1,39 @@ +/** + * Map of validators for each entity. Function should throw the error if validation failed. + * @type {Map void)>} + */ +const entitiesValidators = new Map([ + ['profiles', importedObject => { + if (importedObject.v !== 1) { + throw new Error('Unsupported version!'); + } + + if ( + !importedObject.id + || typeof importedObject.id !== "string" + || !importedObject.name + || typeof importedObject.name !== "string" + || !importedObject.tags + || !Array.isArray(importedObject.tags) + ) { + throw new Error('Invalid profile format detected!'); + } + }] +]) + +/** + * Validate the structure of the entity. + * @param {Object} importedObject Object imported from JSON. + * @param {string} entityName Name of the entity to validate. Should be loaded from the entity class. + * @throws {Error} Error in case validation failed with the reason stored in the message. + */ +export function validateImportedEntity(importedObject, entityName) { + if (!entitiesValidators.has(entityName)) { + console.error(`Trying to validate entity without the validator present! Entity name: ${entityName}`); + return; + } + + entitiesValidators + .get(entityName) + .call(null, importedObject); +}