From 55bc07156902469266f5c66b37f75d57ec49ad16 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 3 Apr 2026 18:33:14 +0200 Subject: [PATCH] refactor: use utility functions consistently across codebase Replace manual patterns with existing utility functions to improve DRY compliance: - Use isEmpty/isNotEmptyArray/isNotEmptyString instead of .length checks - Use convertToInt/convertToFloat instead of Number.parseInt/Number - Use roundTo instead of Math.round(x * N) / N - Use ensureError in catch blocks for proper error type narrowing - Add UNIT_DIVIDER_KILO, MILLISECONDS_PER_HOUR and DEFAULT_EXPONENTIAL_BACKOFF_JITTER_MS to Constants Note: isEmpty/isNotEmptyString apply whitespace trimming, which tightens validation for identifiers and configuration values. --- src/charging-station/ChargingStation.ts | 21 +++++----- .../UIServiceWorkerBroadcastChannel.ts | 4 +- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 2 +- .../ocpp/2.0/OCPP20CertificateManager.ts | 4 +- .../ocpp/2.0/OCPP20IncomingRequestService.ts | 39 ++++++++++--------- .../ocpp/2.0/OCPP20ServiceUtils.ts | 19 ++++----- .../ocpp/2.0/OCPP20VariableManager.ts | 4 +- .../ocpp/2.0/OCPP20VariableRegistry.ts | 15 +++++-- .../ocpp/auth/adapters/OCPP16AuthAdapter.ts | 13 ++++--- .../ocpp/auth/adapters/OCPP20AuthAdapter.ts | 4 +- .../ocpp/auth/cache/InMemoryAuthCache.ts | 4 +- .../ocpp/auth/services/OCPPAuthServiceImpl.ts | 9 +++-- .../ocpp/auth/utils/AuthHelpers.ts | 4 +- .../ocpp/auth/utils/AuthValidators.ts | 9 ++++- .../ocpp/auth/utils/ConfigValidator.ts | 4 +- src/charging-station/ui-server/UIMCPServer.ts | 4 +- .../ui-services/AbstractUIService.ts | 4 +- src/utils/Configuration.ts | 13 +++++-- src/utils/Logger.ts | 4 +- src/utils/StatisticUtils.ts | 4 +- 20 files changed, 106 insertions(+), 78 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 84277c64..fc44f304 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -275,10 +275,8 @@ export class ChargingStation extends EventEmitter { try { this.internalStopMessageSequence() } catch (error) { - logger.error( - `${this.logPrefix()} Error while stopping the internal message sequence:`, - error - ) + const e = ensureError(error) + logger.error(`${this.logPrefix()} Error while stopping the internal message sequence:`, e) } }) @@ -346,7 +344,8 @@ export class ChargingStation extends EventEmitter { try { await this.stop() } catch (error) { - logger.error(`${this.logPrefix()} Error stopping station during delete:`, error) + const e = ensureError(error) + logger.error(`${this.logPrefix()} Error stopping station during delete:`, e) } } AutomaticTransactionGenerator.deleteInstance(this) @@ -371,9 +370,10 @@ export class ChargingStation extends EventEmitter { try { rmSync(this.configurationFile, { force: true }) } catch (error) { + const e = ensureError(error) logger.error( `${this.logPrefix()} Failed to delete configuration file ${this.configurationFile}:`, - error + e ) } } @@ -957,7 +957,8 @@ export class ChargingStation extends EventEmitter { try { await this.stop(reason, graceful ? this.stationInfo?.stopTransactionsOnStopped : false) } catch (error) { - logger.error(`${this.logPrefix()} Error during reset stop phase:`, error) + const e = ensureError(error) + logger.error(`${this.logPrefix()} Error during reset stop phase:`, e) return } await sleep(this.stationInfo?.resetTime ?? 0) @@ -1058,9 +1059,10 @@ export class ChargingStation extends EventEmitter { this.restartHeartbeat() this.restartWebSocketPing() } catch (error) { + const e = ensureError(error) logger.error( `${this.logPrefix()} ${FileType.ChargingStationTemplate} file monitoring error:`, - error + e ) } } @@ -2218,8 +2220,9 @@ export class ChargingStation extends EventEmitter { } } catch (error) { if (!Array.isArray(request)) { + const e = ensureError(error) // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - logger.error(`${this.logPrefix()} Incoming message '${request}' parsing error:`, error) + logger.error(`${this.logPrefix()} Incoming message '${request}' parsing error:`, e) // OCPP 2.0.1 §4.2.3: respond with CALLERROR using messageId "-1" if (this.stationInfo?.ocppVersion !== OCPPVersion.VERSION_16) { await this.ocppRequestService diff --git a/src/charging-station/broadcast-channel/UIServiceWorkerBroadcastChannel.ts b/src/charging-station/broadcast-channel/UIServiceWorkerBroadcastChannel.ts index 8037b67b..07dc1274 100644 --- a/src/charging-station/broadcast-channel/UIServiceWorkerBroadcastChannel.ts +++ b/src/charging-station/broadcast-channel/UIServiceWorkerBroadcastChannel.ts @@ -7,7 +7,7 @@ import { type ResponsePayload, ResponseStatus, } from '../../types/index.js' -import { logger } from '../../utils/index.js' +import { isNotEmptyArray, logger } from '../../utils/index.js' import { WorkerBroadcastChannel } from './WorkerBroadcastChannel.js' const moduleName = 'UIServiceWorkerBroadcastChannel' @@ -33,7 +33,7 @@ export class UIServiceWorkerBroadcastChannel extends WorkerBroadcastChannel { private buildResponsePayload (uuid: string): ResponsePayload { const responsesArray = this.responses.get(uuid)?.responses ?? [] const responsesStatus = - responsesArray.length > 0 && + isNotEmptyArray(responsesArray) && responsesArray.every(response => response.status === ResponseStatus.SUCCESS) ? ResponseStatus.SUCCESS : ResponseStatus.FAILURE diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 2c5eb356..e3f16aa7 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -976,7 +976,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { ) } } - if (allChargingProfiles.length === 0) { + if (isEmpty(allChargingProfiles)) { return OCPP16Constants.OCPP_RESPONSE_REJECTED } const compositeSchedule = this.composeCompositeSchedule( diff --git a/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts b/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts index ea79c572..338b839e 100644 --- a/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts +++ b/src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts @@ -16,7 +16,7 @@ import { HashAlgorithmEnumType, InstallCertificateUseEnumType, } from '../../../types/index.js' -import { convertToDate, getErrorMessage, isEmpty } from '../../../utils/index.js' +import { convertToDate, getErrorMessage, isEmpty, isNotEmptyArray } from '../../../utils/index.js' /** * Interface for ChargingStation with certificate manager @@ -283,7 +283,7 @@ export class OCPP20CertificateManager { .map(dirent => dirent.name) for (const certType of certTypes) { - if (filterTypes != null && filterTypes.length > 0) { + if (filterTypes != null && isNotEmptyArray(filterTypes)) { if (!filterTypes.includes(certType as InstallCertificateUseEnumType)) { continue } diff --git a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts index eeb89f2b..f6f57e76 100644 --- a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts @@ -132,6 +132,7 @@ import { convertToIntOrNaN, generateUUID, isEmpty, + isNotEmptyArray, isNotEmptyString, logger, promiseWithTimeout, @@ -442,7 +443,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { connectorId, response.transactionId, { - ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }), + ...(isNotEmptyArray(startedMeterValues) && { meterValue: startedMeterValues }), remoteStartId: request.remoteStartId, } ).catch((error: unknown) => { @@ -984,7 +985,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { ] return order.indexOf(a.type) - order.indexOf(b.type) }) - if (entry.attributes.length > 0) { + if (isNotEmptyArray(entry.attributes)) { reportData.push({ component: entry.component, variable: entry.variable, @@ -1118,7 +1119,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { transactionId?: string ): boolean { const queue = connectorStatus.transactionEventQueue - if (queue == null || queue.length === 0) { + if (queue == null || !isNotEmptyArray(queue)) { return false } if (transactionId == null) { @@ -1719,10 +1720,10 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { const stationState = this.getStationState(chargingStation) const cached = stationState.reportDataCache.get(commandPayload.requestId) const reportData = cached ?? this.buildReportData(chargingStation, commandPayload.reportBase) - if (!cached && reportData.length > 0) { + if (!cached && isNotEmptyArray(reportData)) { stationState.reportDataCache.set(commandPayload.requestId, reportData) } - if (reportData.length === 0) { + if (!isNotEmptyArray(reportData)) { logger.info( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetBaseReport: No data available for reportBase ${commandPayload.reportBase}` ) @@ -1781,12 +1782,12 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { ) return { - certificateHashDataChain: - result.certificateHashDataChain.length > 0 ? result.certificateHashDataChain : undefined, - status: - result.certificateHashDataChain.length > 0 - ? GetInstalledCertificateStatusEnumType.Accepted - : GetInstalledCertificateStatusEnumType.NotFound, + certificateHashDataChain: isNotEmptyArray(result.certificateHashDataChain) + ? result.certificateHashDataChain + : undefined, + status: isNotEmptyArray(result.certificateHashDataChain) + ? GetInstalledCertificateStatusEnumType.Accepted + : GetInstalledCertificateStatusEnumType.NotFound, } } catch (error) { logger.error( @@ -2251,7 +2252,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { OCPP20ComponentName.OCPPCommCtrlr, OCPP20RequiredVariableName.NetworkConfigurationPriority ) - if (priorityValue.length > 0) { + if (isNotEmptyString(priorityValue)) { const priorities = priorityValue.split(',').map(Number) if (!priorities.includes(commandPayload.configurationSlot)) { logger.warn( @@ -2375,7 +2376,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { ) if ( masterPassGroupId != null && - masterPassGroupId.length > 0 && + isNotEmptyString(masterPassGroupId) && groupIdToken?.idToken === masterPassGroupId ) { logger.debug( @@ -3283,7 +3284,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { chunks.push(reportData.slice(i, i + maxItemsPerMessage)) } - if (chunks.length === 0) { + if (!isNotEmptyArray(chunks)) { chunks.push(undefined) // undefined means reportData will be omitted from the request } @@ -3296,7 +3297,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { requestId, seqNo, tbc: !isLastChunk, - ...(chunk !== undefined && chunk.length > 0 && { reportData: chunk }), + ...(chunk !== undefined && isNotEmptyArray(chunk) && { reportData: chunk }), } await chargingStation.ocppRequestService.requestHandler< @@ -3322,7 +3323,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { if ( stationState.isDrainingSecurityEvents || !chargingStation.isWebSocketConnectionOpened() || - stationState.securityEventQueue.length === 0 + !isNotEmptyArray(stationState.securityEventQueue) ) { return } @@ -3332,7 +3333,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { `${chargingStation.logPrefix()} ${moduleName}.sendQueuedSecurityEvents: Draining ${queue.length.toString()} queued security event(s)` ) const drainNextEvent = (): void => { - if (queue.length === 0 || !chargingStation.isWebSocketConnectionOpened()) { + if (!isNotEmptyArray(queue) || !chargingStation.isWebSocketConnectionOpened()) { stationState.isDrainingSecurityEvents = false return } @@ -3771,7 +3772,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { return false } - if (chargingProfile.chargingSchedule.length === 0) { + if (!isNotEmptyArray(chargingProfile.chargingSchedule)) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.validateChargingProfile: Charging profile must contain at least one charging schedule` ) @@ -3935,7 +3936,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { return false } - if (schedule.chargingSchedulePeriod.length === 0) { + if (!isNotEmptyArray(schedule.chargingSchedulePeriod)) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.validateChargingSchedule: Schedule must contain at least one charging schedule period` ) diff --git a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts index 9922cb63..0b949562 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts @@ -45,6 +45,7 @@ import { convertToIntOrNaN, formatDurationMilliSeconds, generateUUID, + isNotEmptyArray, logger, validateIdentifierString, } from '../../../utils/index.js' @@ -167,7 +168,7 @@ export class OCPP20ServiceUtils { measurandsKey, OCPP20ReadingContextEnumType.TRANSACTION_BEGIN ) as OCPP20MeterValue - return startedMeterValue.sampledValue.length > 0 ? [startedMeterValue] : [] + return isNotEmptyArray(startedMeterValue.sampledValue) ? [startedMeterValue] : [] } catch (error) { logger.warn( `${chargingStation.logPrefix()} ${moduleName}.buildTransactionStartedMeterValues: ${(error as Error).message}` @@ -686,7 +687,7 @@ export class OCPP20ServiceUtils { const connectorStatus = chargingStation.getConnectorStatus(connectorId) if ( connectorStatus?.transactionEventQueue == null || - connectorStatus.transactionEventQueue.length === 0 + !isNotEmptyArray(connectorStatus.transactionEventQueue) ) { return } @@ -838,7 +839,7 @@ export class OCPP20ServiceUtils { interval, measurandsKey ) as OCPP20MeterValue - if (meterValue.sampledValue.length > 0) { + if (isNotEmptyArray(meterValue.sampledValue)) { cs.transactionEndedMeterValues?.push(meterValue) } } @@ -875,7 +876,7 @@ export class OCPP20ServiceUtils { { idToken: idTag != null ? { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 } : undefined, - ...(startedMeterValues.length > 0 && { meterValue: startedMeterValues }), + ...(isNotEmptyArray(startedMeterValues) && { meterValue: startedMeterValues }), } ) return { @@ -1034,7 +1035,7 @@ export class OCPP20ServiceUtils { } } } - if (terminationPromises.length > 0) { + if (isNotEmptyArray(terminationPromises)) { await Promise.all(terminationPromises) } } @@ -1143,7 +1144,7 @@ export class OCPP20ServiceUtils { measurandsKey, OCPP20ReadingContextEnumType.TRANSACTION_END ) as OCPP20MeterValue - if (finalMeterValue.sampledValue.length > 0) { + if (isNotEmptyArray(finalMeterValue.sampledValue)) { return [...endedMeterValues, finalMeterValue] } } catch (error) { @@ -1151,7 +1152,7 @@ export class OCPP20ServiceUtils { `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEndedMeterValues: ${(error as Error).message}` ) } - return endedMeterValues.length > 0 ? endedMeterValues : [] + return isNotEmptyArray(endedMeterValues) ? endedMeterValues : [] } private static readVariableAsIntervalMs ( @@ -1222,7 +1223,7 @@ export class OCPP20ServiceUtils { transactionId, { evseId, - meterValue: endedMeterValues.length > 0 ? endedMeterValues : undefined, + meterValue: isNotEmptyArray(endedMeterValues) ? endedMeterValues : undefined, stoppedReason, } ) @@ -1331,7 +1332,7 @@ export function buildTransactionEvent ( transactionEventRequest.idToken = commandParams.idToken connectorStatus.transactionIdTokenSent = true } - if (commandParams.meterValue !== undefined && commandParams.meterValue.length > 0) { + if (commandParams.meterValue !== undefined && isNotEmptyArray(commandParams.meterValue)) { transactionEventRequest.meterValue = commandParams.meterValue } if (commandParams.cableMaxCurrent !== undefined) { diff --git a/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts b/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts index 2a00f672..6a20a04f 100644 --- a/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts +++ b/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts @@ -19,7 +19,7 @@ import { SetVariableStatusEnumType, type VariableType, } from '../../../types/index.js' -import { Constants, convertToIntOrNaN, logger } from '../../../utils/index.js' +import { Constants, convertToIntOrNaN, isEmpty, logger } from '../../../utils/index.js' import { type ChargingStation } from '../../ChargingStation.js' import { addConfigurationKey, @@ -479,7 +479,7 @@ export class OCPP20VariableManager { let variableValue = this.resolveVariableValue(chargingStation, component, variable) - if (variableValue.length === 0) { + if (isEmpty(variableValue)) { if ( resolvedAttributeType === AttributeEnumType.Target && variableMetadata.supportsTarget === true diff --git a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts index 1de52d7e..c2687bd4 100644 --- a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts +++ b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts @@ -22,7 +22,14 @@ import { ReasonCodeEnumType, type VariableName, } from '../../../types/index.js' -import { Constants, convertToIntOrNaN, has, isEmpty } from '../../../utils/index.js' +import { + Constants, + convertToFloat, + convertToInt, + convertToIntOrNaN, + has, + isEmpty, +} from '../../../utils/index.js' import { OCPP20Constants } from './OCPP20Constants.js' /** @@ -2597,7 +2604,7 @@ export function validateValue ( reason: ReasonCodeEnumType.InvalidValue, } } - const num = Number(rawValue) + const num = convertToFloat(rawValue) if (variableMetadata.positive && num <= 0) { return { info: 'Positive decimal > 0 required', @@ -2657,7 +2664,7 @@ export function validateValue ( reason: ReasonCodeEnumType.InvalidValue, } } - const num = Number(rawValue) + const num = convertToInt(rawValue) if (variableMetadata.allowZero && !variableMetadata.positive && num < 0) { return { info: 'Integer >= 0 required', @@ -2708,7 +2715,7 @@ export function validateValue ( } } const tokens = rawValue.split(',').map(t => t.trim()) - if (tokens.some(t => t.length === 0)) { + if (tokens.some(t => isEmpty(t))) { return { info: 'Empty list member', ok: false, reason: ReasonCodeEnumType.InvalidValue } } const seen = new Set() diff --git a/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts b/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts index 17d5d386..1e2eb685 100644 --- a/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts +++ b/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts @@ -16,7 +16,13 @@ import { RequestCommand, StandardParametersKey, } from '../../../../types/index.js' -import { convertToBoolean, getErrorMessage, logger, truncateId } from '../../../../utils/index.js' +import { + convertToBoolean, + getErrorMessage, + isEmpty, + logger, + truncateId, +} from '../../../../utils/index.js' import { AuthContext, AuthenticationMethod, @@ -300,10 +306,7 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter { } // Check length (OCPP 1.6 spec: max 20 characters) - if ( - identifier.value.length === 0 || - identifier.value.length > AuthValidators.MAX_IDTAG_LENGTH - ) { + if (isEmpty(identifier.value) || identifier.value.length > AuthValidators.MAX_IDTAG_LENGTH) { return false } diff --git a/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts b/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts index 84634789..84c07e9a 100644 --- a/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts +++ b/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts @@ -23,7 +23,7 @@ import { OCPP20RequiredVariableName, OCPPVersion, } from '../../../../types/index.js' -import { getErrorMessage, logger, truncateId } from '../../../../utils/index.js' +import { getErrorMessage, isEmpty, logger, truncateId } from '../../../../utils/index.js' import { AuthContext, AuthenticationMethod, @@ -395,7 +395,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { } // Check length (OCPP 2.0 spec: max 36 characters) - if (identifier.value.length === 0 || identifier.value.length > 36) { + if (isEmpty(identifier.value) || identifier.value.length > 36) { return false } diff --git a/src/charging-station/ocpp/auth/cache/InMemoryAuthCache.ts b/src/charging-station/ocpp/auth/cache/InMemoryAuthCache.ts index 4b46aac9..a0693cf0 100644 --- a/src/charging-station/ocpp/auth/cache/InMemoryAuthCache.ts +++ b/src/charging-station/ocpp/auth/cache/InMemoryAuthCache.ts @@ -3,7 +3,7 @@ import { secondsToMilliseconds } from 'date-fns' import type { AuthCache, CacheStats } from '../interfaces/OCPPAuthService.js' import type { AuthorizationResult } from '../types/AuthTypes.js' -import { Constants, logger, truncateId } from '../../../../utils/index.js' +import { Constants, logger, roundTo, truncateId } from '../../../../utils/index.js' import { AuthorizationStatus } from '../types/AuthTypes.js' const moduleName = 'InMemoryAuthCache' @@ -243,7 +243,7 @@ export class InMemoryAuthCache implements AuthCache { return { evictions: this.stats.evictions, expiredEntries: this.stats.expired, - hitRate: Math.round(hitRate * 100) / 100, + hitRate: roundTo(hitRate, 2), hits: this.stats.hits, memoryUsage, misses: this.stats.misses, diff --git a/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts b/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts index f320921b..33539295 100644 --- a/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts +++ b/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts @@ -11,6 +11,7 @@ import { getErrorMessage, has, logger, + roundTo, truncateId, } from '../../../../utils/index.js' import { type ChargingStation } from '../../../index.js' @@ -336,13 +337,13 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { } return { - avgResponseTime: Math.round(avgResponseTime * 100) / 100, - cacheHitRate: Math.round(cacheHitRate * 10000) / 100, + avgResponseTime: roundTo(avgResponseTime, 2), + cacheHitRate: roundTo(cacheHitRate * 100, 2), failedAuth: this.metrics.failedAuth, lastUpdatedDate: this.metrics.lastReset, - localUsageRate: Math.round(localUsageRate * 10000) / 100, + localUsageRate: roundTo(localUsageRate * 100, 2), rateLimit: rateLimitStatistics, - remoteSuccessRate: Math.round(remoteSuccessRate * 10000) / 100, + remoteSuccessRate: roundTo(remoteSuccessRate * 100, 2), successfulAuth: this.metrics.successfulAuth, totalRequests: this.metrics.totalRequests, } diff --git a/src/charging-station/ocpp/auth/utils/AuthHelpers.ts b/src/charging-station/ocpp/auth/utils/AuthHelpers.ts index f380dbe0..1579b62a 100644 --- a/src/charging-station/ocpp/auth/utils/AuthHelpers.ts +++ b/src/charging-station/ocpp/auth/utils/AuthHelpers.ts @@ -8,7 +8,7 @@ import type { Identifier, } from '../types/AuthTypes.js' -import { truncateId } from '../../../../utils/index.js' +import { isEmpty, truncateId } from '../../../../utils/index.js' import { AuthorizationStatus } from '../types/AuthTypes.js' /** @@ -169,7 +169,7 @@ function isTemporaryFailure (result: AuthorizationResult): boolean { * @returns The first ACCEPTED result, or the first result with merged metadata */ function mergeAuthResults (results: AuthorizationResult[]): AuthorizationResult | undefined { - if (results.length === 0) { + if (isEmpty(results)) { return undefined } diff --git a/src/charging-station/ocpp/auth/utils/AuthValidators.ts b/src/charging-station/ocpp/auth/utils/AuthValidators.ts index 1961bf6a..2a1f3f07 100644 --- a/src/charging-station/ocpp/auth/utils/AuthValidators.ts +++ b/src/charging-station/ocpp/auth/utils/AuthValidators.ts @@ -173,9 +173,14 @@ function validateIdentifier (identifier: unknown): boolean { case IdentifierType.MOBILE_APP: case IdentifierType.NO_AUTHORIZATION: // OCPP 2.0 types - use IdToken max length - return typedIdentifier.value.length > 0 && typedIdentifier.value.length <= MAX_IDTOKEN_LENGTH + return ( + isNotEmptyString(typedIdentifier.value) && + typedIdentifier.value.length <= MAX_IDTOKEN_LENGTH + ) case IdentifierType.ID_TAG: - return typedIdentifier.value.length > 0 && typedIdentifier.value.length <= MAX_IDTAG_LENGTH + return ( + isNotEmptyString(typedIdentifier.value) && typedIdentifier.value.length <= MAX_IDTAG_LENGTH + ) default: return false diff --git a/src/charging-station/ocpp/auth/utils/ConfigValidator.ts b/src/charging-station/ocpp/auth/utils/ConfigValidator.ts index 7f9e5bff..ed6df7a9 100644 --- a/src/charging-station/ocpp/auth/utils/ConfigValidator.ts +++ b/src/charging-station/ocpp/auth/utils/ConfigValidator.ts @@ -1,4 +1,4 @@ -import { logger } from '../../../../utils/index.js' +import { isNotEmptyArray, logger } from '../../../../utils/index.js' import { type AuthConfiguration, AuthenticationError, AuthErrorCode } from '../types/AuthTypes.js' const moduleName = 'AuthConfigValidator' @@ -27,7 +27,7 @@ function checkAuthMethodsEnabled (config: AuthConfiguration): void { if (hasCertificate) enabledMethods.push('certificate') if (hasOffline) enabledMethods.push('offline') - if (enabledMethods.length > 0) { + if (isNotEmptyArray(enabledMethods)) { logger.debug(`${moduleName}: Enabled authentication methods: ${enabledMethods.join(', ')}`) } } diff --git a/src/charging-station/ui-server/UIMCPServer.ts b/src/charging-station/ui-server/UIMCPServer.ts index 38854dcd..1045e49e 100644 --- a/src/charging-station/ui-server/UIMCPServer.ts +++ b/src/charging-station/ui-server/UIMCPServer.ts @@ -22,7 +22,7 @@ import { type UIServerConfiguration, type UUIDv4, } from '../../types/index.js' -import { generateUUID, getErrorMessage, logger } from '../../utils/index.js' +import { generateUUID, getErrorMessage, isNotEmptyArray, logger } from '../../utils/index.js' import { AbstractUIServer } from './AbstractUIServer.js' import { mcpToolSchemas, @@ -191,7 +191,7 @@ export class UIMCPServer extends AbstractUIServer { } return s.version !== OCPPVersion.VERSION_20 && s.version !== OCPPVersion.VERSION_201 }) - if (mismatched.length > 0) { + if (isNotEmptyArray(mismatched)) { const ids = mismatched.map(s => s.hashId).join(', ') const versions = [...new Set(mismatched.map(s => s.version ?? 'unknown'))].join(', ') return UIMCPServer.createToolErrorResponse( diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index 58c5a358..4d39ad4a 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -294,10 +294,10 @@ export abstract class AbstractUIService { } return { status: err != null ? ResponseStatus.FAILURE : ResponseStatus.SUCCESS, - ...(succeededStationInfos.length > 0 && { + ...(isNotEmptyArray(succeededStationInfos) && { hashIdsSucceeded: succeededStationInfos.map(stationInfo => stationInfo.hashId), }), - ...(failedStationInfos.length > 0 && { + ...(isNotEmptyArray(failedStationInfos) && { hashIdsFailed: failedStationInfos.map(stationInfo => stationInfo.hashId), }), ...(err != null && { errorMessage: err.message, errorStack: err.stack }), diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index 27f2604e..39e95297 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -35,7 +35,14 @@ import { } from './ConfigurationUtils.js' import { Constants } from './Constants.js' import { ensureError, handleFileException } from './ErrorUtils.js' -import { has, isCFEnvironment, isNotEmptyString, mergeDeepRight, once } from './Utils.js' +import { + convertToInt, + has, + isCFEnvironment, + isNotEmptyString, + mergeDeepRight, + once, +} from './Utils.js' type ConfigurationSectionType = | LogConfiguration @@ -283,7 +290,7 @@ export class Configuration { if (isCFEnvironment()) { delete uiServerConfiguration.options?.host if (uiServerConfiguration.options != null) { - uiServerConfiguration.options.port = Number.parseInt(env.PORT ?? '') + uiServerConfiguration.options.port = convertToInt(env.PORT ?? '') } } return uiServerConfiguration @@ -378,7 +385,7 @@ export class Configuration { Configuration.configurationFileReloading = false }) .catch((error: unknown) => { - throw typeof error === 'string' ? new Error(error) : error + throw ensureError(error) }) } else { Configuration.configurationFileReloading = false diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts index 8e4d9dfe..c494211e 100644 --- a/src/utils/Logger.ts +++ b/src/utils/Logger.ts @@ -11,7 +11,7 @@ import DailyRotateFile from 'winston-daily-rotate-file' import { ConfigurationSection, type LogConfiguration } from '../types/index.js' import { Configuration } from './Configuration.js' -import { insertAt, isNotEmptyString } from './Utils.js' +import { insertAt, isEmpty, isNotEmptyString } from './Utils.js' let loggerInstance: undefined | WinstonLogger @@ -79,7 +79,7 @@ const getLoggerInstance = (): WinstonLogger => { level: logConfiguration.level, silent: logConfiguration.enabled === false || - logTransports.length === 0 || + isEmpty(logTransports) || process.env.NODE_ENV === 'test', transports: logTransports, }) diff --git a/src/utils/StatisticUtils.ts b/src/utils/StatisticUtils.ts index f835e517..7e99cedb 100644 --- a/src/utils/StatisticUtils.ts +++ b/src/utils/StatisticUtils.ts @@ -1,4 +1,4 @@ -import { isNotEmptyArray } from './Utils.js' +import { isEmpty, isNotEmptyArray } from './Utils.js' export const average = (dataSet: number[]): number => { if (!isNotEmptyArray(dataSet)) { @@ -37,7 +37,7 @@ export const percentile = (dataSet: number[], percentile: number): number => { if (percentile < 0 || percentile > 100) { throw new RangeError('Percentile is not between 0 and 100') } - if (dataSet.length === 0) { + if (isEmpty(dataSet)) { return 0 } const sortedDataSet = dataSet.slice().sort((a, b) => a - b) -- 2.43.0