From 276e05aec38f4e8b4a8d5ebd8cbbcb30592b414d Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 10 Mar 2024 17:05:34 +0100 Subject: [PATCH] feat!: handle Set at JSON serialization to string MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/Bootstrap.ts | 9 +++--- .../ui-server/UIHttpServer.ts | 5 ++-- .../ui-server/UIWebSocketServer.ts | 5 ++-- src/performance/PerformanceStatistics.ts | 5 ++-- src/performance/storage/JsonFileStorage.ts | 11 ++----- src/types/MapStringifyFormat.ts | 4 +++ src/types/SimulatorState.ts | 7 +++++ src/types/Statistics.ts | 2 +- src/types/UIProtocol.ts | 6 ---- src/types/index.ts | 8 ++--- src/utils/MessageChannelUtils.ts | 15 +--------- src/utils/Utils.ts | 29 +++++++++++-------- src/utils/index.ts | 3 +- ui/web/src/composables/UIClient.ts | 6 ++-- ui/web/start.js | 4 +-- 15 files changed, 56 insertions(+), 63 deletions(-) create mode 100644 src/types/MapStringifyFormat.ts create mode 100644 src/types/SimulatorState.ts diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index d31b1e5d..ef64c8fc 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -22,16 +22,15 @@ import { type ChargingStationWorkerMessageData, ChargingStationWorkerMessageEvents, ConfigurationSection, - type InternalTemplateStatistics, ProcedureName, type SimulatorState, type Statistics, type StorageConfiguration, + type TemplateStatistics, type UIServerConfiguration, type WorkerConfiguration } from '../types/index.js' import { - buildTemplateStatisticsPayload, Configuration, Constants, formatDurationMilliSeconds, @@ -63,7 +62,7 @@ export class Bootstrap extends EventEmitter { private workerImplementation?: WorkerAbstract private readonly uiServer: AbstractUIServer private storage?: Storage - private readonly templateStatistics: Map + private readonly templateStatistics: Map private readonly version: string = version private initializedCounters: boolean private started: boolean @@ -86,7 +85,7 @@ export class Bootstrap extends EventEmitter { this.uiServer = UIServerFactory.getUIServerImplementation( Configuration.getConfigurationSection(ConfigurationSection.uiServer) ) - this.templateStatistics = new Map() + this.templateStatistics = new Map() this.initializedCounters = false this.initializeCounters() Configuration.configurationChangeCallback = async () => { @@ -118,7 +117,7 @@ export class Bootstrap extends EventEmitter { return { version: this.version, started: this.started, - templateStatistics: buildTemplateStatisticsPayload(this.templateStatistics) + templateStatistics: this.templateStatistics } } diff --git a/src/charging-station/ui-server/UIHttpServer.ts b/src/charging-station/ui-server/UIHttpServer.ts index b5c30b31..5bcae7be 100644 --- a/src/charging-station/ui-server/UIHttpServer.ts +++ b/src/charging-station/ui-server/UIHttpServer.ts @@ -5,6 +5,7 @@ import { StatusCodes } from 'http-status-codes' import { BaseError } from '../../exception/index.js' import { ApplicationProtocolVersion, + MapStringifyFormat, type ProcedureName, type Protocol, type ProtocolRequest, @@ -18,7 +19,7 @@ import { Constants, generateUUID, isNotEmptyString, - JSONStringifyWithMapSupport, + JSONStringify, logger, logPrefix } from '../../utils/index.js' @@ -61,7 +62,7 @@ export class UIHttpServer extends AbstractUIServer { .writeHead(this.responseStatusToStatusCode(payload.status), { 'Content-Type': 'application/json' }) - .end(JSONStringifyWithMapSupport(payload)) + .end(JSONStringify(payload, undefined, MapStringifyFormat.object)) } else { logger.error( `${this.logPrefix(moduleName, 'sendResponse')} Response for unknown request id: ${uuid}` diff --git a/src/charging-station/ui-server/UIWebSocketServer.ts b/src/charging-station/ui-server/UIWebSocketServer.ts index 29a65675..bf0a6c44 100644 --- a/src/charging-station/ui-server/UIWebSocketServer.ts +++ b/src/charging-station/ui-server/UIWebSocketServer.ts @@ -5,6 +5,7 @@ import { StatusCodes } from 'http-status-codes' import { type RawData, WebSocket, WebSocketServer } from 'ws' import { + MapStringifyFormat, type ProtocolRequest, type ProtocolResponse, type UIServerConfiguration, @@ -14,7 +15,7 @@ import { Constants, getWebSocketCloseEventStatusString, isNotEmptyString, - JSONStringifyWithMapSupport, + JSONStringify, logger, logPrefix, validateUUID @@ -136,7 +137,7 @@ export class UIWebSocketServer extends AbstractUIServer { if (this.hasResponseHandler(responseId)) { const ws = this.responseHandlers.get(responseId) as WebSocket if (ws.readyState === WebSocket.OPEN) { - ws.send(JSONStringifyWithMapSupport(response)) + ws.send(JSONStringify(response, undefined, MapStringifyFormat.object)) } else { logger.error( `${this.logPrefix( diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index bf333dfd..59d697d1 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -11,6 +11,7 @@ import { ConfigurationSection, type IncomingRequestCommand, type LogConfiguration, + MapStringifyFormat, MessageType, type RequestCommand, type Statistics, @@ -27,7 +28,7 @@ import { extractTimeSeriesValues, formatDurationSeconds, generateUUID, - JSONStringifyWithMapSupport, + JSONStringify, logger, logPrefix, max, @@ -214,7 +215,7 @@ export class PerformanceStatistics { logger.info(this.logPrefix(), { ...this.statistics, statisticsData: JSON.parse( - JSONStringifyWithMapSupport(this.statistics.statisticsData) + JSONStringify(this.statistics.statisticsData, undefined, MapStringifyFormat.object) ) as Map }) } diff --git a/src/performance/storage/JsonFileStorage.ts b/src/performance/storage/JsonFileStorage.ts index 648b9c62..592504a7 100644 --- a/src/performance/storage/JsonFileStorage.ts +++ b/src/performance/storage/JsonFileStorage.ts @@ -4,13 +4,8 @@ import { closeSync, existsSync, mkdirSync, openSync, writeSync } from 'node:fs' import { dirname } from 'node:path' import { BaseError } from '../../exception/index.js' -import { FileType, type Statistics } from '../../types/index.js' -import { - AsyncLock, - AsyncLockType, - handleFileException, - JSONStringifyWithMapSupport -} from '../../utils/index.js' +import { FileType, MapStringifyFormat, type Statistics } from '../../types/index.js' +import { AsyncLock, AsyncLockType, handleFileException, JSONStringify } from '../../utils/index.js' import { Storage } from './Storage.js' export class JsonFileStorage extends Storage { @@ -28,7 +23,7 @@ export class JsonFileStorage extends Storage { writeSync( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.fd!, - JSONStringifyWithMapSupport([...this.getPerformanceStatistics()], 2), + JSONStringify([...this.getPerformanceStatistics()], 2, MapStringifyFormat.object), 0, 'utf8' ) diff --git a/src/types/MapStringifyFormat.ts b/src/types/MapStringifyFormat.ts new file mode 100644 index 00000000..8543ef27 --- /dev/null +++ b/src/types/MapStringifyFormat.ts @@ -0,0 +1,4 @@ +export enum MapStringifyFormat { + array = 'array', + object = 'object' +} diff --git a/src/types/SimulatorState.ts b/src/types/SimulatorState.ts new file mode 100644 index 00000000..d3816ede --- /dev/null +++ b/src/types/SimulatorState.ts @@ -0,0 +1,7 @@ +import type { TemplateStatistics } from './Statistics.js' + +export interface SimulatorState { + version: string + started: boolean + templateStatistics: Map +} diff --git a/src/types/Statistics.ts b/src/types/Statistics.ts index 9abd0a89..81ba1d56 100644 --- a/src/types/Statistics.ts +++ b/src/types/Statistics.ts @@ -32,7 +32,7 @@ export interface Statistics extends WorkerData { statisticsData: Map } -export interface InternalTemplateStatistics { +export interface TemplateStatistics { configured: number added: number started: number diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index f53696da..00d93931 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -86,9 +86,3 @@ export interface TemplateStatistics extends JsonObject { started: number indexes: number[] } - -export interface SimulatorState extends JsonObject { - version: string - started: boolean - templateStatistics: Record -} diff --git a/src/types/index.ts b/src/types/index.ts index 023107fd..e24d7f54 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -51,6 +51,7 @@ export type { HandleErrorParams } from './Error.js' export type { EvseStatus, EvseTemplate } from './Evse.js' export { FileType } from './FileType.js' export type { JsonObject, JsonType } from './JsonType.js' +export { MapStringifyFormat } from './MapStringifyFormat.js' export type { MeasurandPerPhaseSampledValueTemplates, SampledValueTemplate @@ -247,10 +248,11 @@ export { type StopTransactionResponse } from './ocpp/Transaction.js' export { PerformanceRecord } from './orm/entities/PerformanceRecord.js' +export type { SimulatorState } from './SimulatorState.js' export type { - InternalTemplateStatistics, Statistics, StatisticsData, + TemplateStatistics, TimestampedData } from './Statistics.js' export { DBName, StorageType } from './Storage.js' @@ -265,9 +267,7 @@ export { ProtocolVersion, type RequestPayload, type ResponsePayload, - ResponseStatus, - type SimulatorState, - type TemplateStatistics + ResponseStatus } from './UIProtocol.js' export { WebSocketCloseEventStatusCode, diff --git a/src/utils/MessageChannelUtils.ts b/src/utils/MessageChannelUtils.ts index 0306d60e..1af746da 100644 --- a/src/utils/MessageChannelUtils.ts +++ b/src/utils/MessageChannelUtils.ts @@ -3,9 +3,7 @@ import { type ChargingStationData, type ChargingStationWorkerMessage, ChargingStationWorkerMessageEvents, - type InternalTemplateStatistics, - type Statistics, - type TemplateStatistics + type Statistics } from '../types/index.js' import { buildChargingStationAutomaticTransactionGeneratorConfiguration, @@ -13,7 +11,6 @@ import { buildEvsesStatus, OutputFormat } from './ChargingStationConfigurationUtils.js' -import { clone } from './Utils.js' export const buildAddedMessage = ( chargingStation: ChargingStation @@ -89,13 +86,3 @@ export const buildChargingStationDataPayload = ( }) } } - -export const buildTemplateStatisticsPayload = ( - map: Map -): Record => { - map = clone(map) - for (const value of map.values()) { - (value as unknown as TemplateStatistics).indexes = [...value.indexes] - } - return Object.fromEntries(map.entries() as unknown as Array<[string, TemplateStatistics]>) -} diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 69f0cd42..fd3f4e2b 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -15,7 +15,8 @@ import { import { type EmptyObject, - type ProtocolResponse, + type JsonType, + MapStringifyFormat, type TimestampedData, WebSocketCloseEventStatusString } from '../types/index.js' @@ -298,22 +299,26 @@ export const secureRandom = (): number => { return getRandomValues(new Uint32Array(1))[0] / 0x100000000 } -export const JSONStringifyWithMapSupport = ( - object: - | Record - | Array> - | Map - | ProtocolResponse, - space?: string | number -): string => { +export const JSONStringify = < + T extends JsonType | Array> | Map> +>( + object: T, + space?: string | number, + mapFormat?: MapStringifyFormat + ): string => { return JSON.stringify( object, (_, value: Record) => { if (value instanceof Map) { - return { - dataType: 'Map', - value: [...value] + switch (mapFormat) { + case MapStringifyFormat.object: + return { ...Object.fromEntries>(value.entries()) } + case MapStringifyFormat.array: + default: + return [...value] } + } else if (value instanceof Set) { + return [...value] as unknown[] } return value }, diff --git a/src/utils/index.ts b/src/utils/index.ts index 81dc04d2..daa67094 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -25,7 +25,6 @@ export { buildPerformanceStatisticsMessage, buildStartedMessage, buildStoppedMessage, - buildTemplateStatisticsPayload, buildUpdatedMessage } from './MessageChannelUtils.js' export { average, median, nthPercentile, stdDeviation } from './StatisticUtils.js' @@ -52,7 +51,7 @@ export { isNotEmptyArray, isNotEmptyString, isValidDate, - JSONStringifyWithMapSupport, + JSONStringify, logPrefix, max, min, diff --git a/ui/web/src/composables/UIClient.ts b/ui/web/src/composables/UIClient.ts index 781854db..fcbf191f 100644 --- a/ui/web/src/composables/UIClient.ts +++ b/ui/web/src/composables/UIClient.ts @@ -246,13 +246,15 @@ export class UIClient { } if (!Array.isArray(response)) { - useToast().error(`Response not an array`) - console.error(`Response not an array:`, response) + useToast().error('Response not an array') + console.error('Response not an array:', response) return } const [uuid, responsePayload] = response + console.log('responsePayload', responsePayload.state?.templateStatistics) + if (this.responseHandlers.has(uuid)) { const { procedureName, resolve, reject } = this.responseHandlers.get(uuid)! switch (responsePayload.status) { diff --git a/ui/web/start.js b/ui/web/start.js index 9a325e56..46a8bb75 100644 --- a/ui/web/start.js +++ b/ui/web/start.js @@ -12,8 +12,6 @@ const uiPath = join(dirname(fileURLToPath(import.meta.url)), './dist') const serve = serveStatic(uiPath) -const server = createServer(function onRequest(req, res) { - serve(req, res, finalhandler(req, res)) -}) +const server = createServer((req, res) => serve(req, res, finalhandler(req, res))) server.listen(PORT, () => console.info(`Web UI running at: http://localhost:${PORT}`)) -- 2.34.1