From: Jérôme Benoit Date: Fri, 27 Mar 2026 13:23:14 +0000 (+0100) Subject: refactor: consolidate enforceMessageLimits types with generic R and RejectionReason X-Git-Tag: ocpp-server@v4.0.0~43 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=e6bc4615407390fa47e07af12d5a4fb9f1a4c1c4;p=e-mobility-charging-stations-simulator.git refactor: consolidate enforceMessageLimits types with generic R and RejectionReason - Add generic type parameter R to enforceMessageLimits and enforcePostCalculationBytesLimit, eliminating 4 'as typeof' casts - Extract RejectionReason interface (additionalInfo + reasonCode) to replace inline anonymous types, aligned with StatusInfoType fields - Remove all enum→string→enum round-trips in callers --- diff --git a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts index a7739c36..fe1d2e2e 100644 --- a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts @@ -584,8 +584,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { (v, reason) => ({ attributeStatus: GetVariableStatusEnumType.Rejected, attributeStatusInfo: { - additionalInfo: reason.info, - + additionalInfo: reason.additionalInfo, reasonCode: reason.reasonCode, }, attributeType: v.attributeType, @@ -595,8 +594,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { logger ) if (preEnforcement.rejected) { - getVariablesResponse.getVariableResult = - preEnforcement.results as typeof getVariablesResponse.getVariableResult + getVariablesResponse.getVariableResult = preEnforcement.results return getVariablesResponse } @@ -613,16 +611,15 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { (v, reason) => ({ attributeStatus: GetVariableStatusEnumType.Rejected, attributeStatusInfo: { - additionalInfo: reason.info, - - reasonCode: ReasonCodeEnumType[reason.reasonCode as keyof typeof ReasonCodeEnumType], + additionalInfo: reason.additionalInfo, + reasonCode: reason.reasonCode, }, attributeType: v.attributeType, component: v.component, variable: v.variable, }), logger - ) as typeof getVariablesResponse.getVariableResult + ) logger.debug( `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetVariables: Processed ${commandPayload.getVariableData.length.toString()} variable requests, returning ${results.length.toString()} results` @@ -655,8 +652,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { (v, reason) => ({ attributeStatus: SetVariableStatusEnumType.Rejected, attributeStatusInfo: { - additionalInfo: reason.info, - + additionalInfo: reason.additionalInfo, reasonCode: reason.reasonCode, }, attributeType: v.attributeType ?? AttributeEnumType.Actual, @@ -666,8 +662,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { logger ) if (preEnforcement.rejected) { - setVariablesResponse.setVariableResult = - preEnforcement.results as typeof setVariablesResponse.setVariableResult + setVariablesResponse.setVariableResult = preEnforcement.results return setVariablesResponse } @@ -682,16 +677,15 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { (v, reason) => ({ attributeStatus: SetVariableStatusEnumType.Rejected, attributeStatusInfo: { - additionalInfo: reason.info, - - reasonCode: ReasonCodeEnumType[reason.reasonCode as keyof typeof ReasonCodeEnumType], + additionalInfo: reason.additionalInfo, + reasonCode: reason.reasonCode, }, attributeType: v.attributeType ?? AttributeEnumType.Actual, component: v.component, variable: v.variable, }), logger - ) as typeof setVariablesResponse.setVariableResult + ) logger.debug( `${chargingStation.logPrefix()} ${moduleName}.handleRequestSetVariables: Processed ${commandPayload.setVariableData.length.toString()} variable requests, returning ${results.length.toString()} results` diff --git a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts index a29dc1da..9070127c 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts @@ -49,6 +49,11 @@ import { OCPP20VariableManager } from './OCPP20VariableManager.js' const moduleName = 'OCPP20ServiceUtils' +export interface RejectionReason { + additionalInfo: string + reasonCode: ReasonCodeEnumType +} + export class OCPP20ServiceUtils extends OCPPServiceUtils { private static readonly incomingRequestSchemaNames: readonly [ OCPP20IncomingRequestCommand, @@ -182,7 +187,8 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils { ]) public static enforceMessageLimits< - T extends { attributeType?: unknown; component: unknown; variable: unknown } + T extends { attributeType?: unknown; component: unknown; variable: unknown }, + R >( chargingStation: { logPrefix: () => string }, moduleName: string, @@ -190,13 +196,13 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils { data: T[], itemsLimit: number, bytesLimit: number, - buildRejected: (item: T, reason: { info: string; reasonCode: ReasonCodeEnumType }) => unknown, + buildRejected: (item: T, reason: RejectionReason) => R, logger: { debug: (...args: unknown[]) => void } - ): { rejected: boolean; results: unknown[] } { + ): { rejected: boolean; results: R[] } { if (itemsLimit > 0 && data.length > itemsLimit) { const results = data.map(d => buildRejected(d, { - info: `ItemsPerMessage limit ${itemsLimit.toString()} exceeded (${data.length.toString()} requested)`, + additionalInfo: `ItemsPerMessage limit ${itemsLimit.toString()} exceeded (${data.length.toString()} requested)`, reasonCode: ReasonCodeEnumType.TooManyElements, }) ) @@ -210,7 +216,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils { if (estimatedSize > bytesLimit) { const results = data.map(d => buildRejected(d, { - info: `BytesPerMessage limit ${bytesLimit.toString()} exceeded (estimated ${estimatedSize.toString()} bytes)`, + additionalInfo: `BytesPerMessage limit ${bytesLimit.toString()} exceeded (estimated ${estimatedSize.toString()} bytes)`, reasonCode: ReasonCodeEnumType.TooLargeElement, }) ) @@ -224,24 +230,25 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils { } public static enforcePostCalculationBytesLimit< - T extends { attributeType?: unknown; component: unknown; variable: unknown } + T extends { attributeType?: unknown; component: unknown; variable: unknown }, + R >( chargingStation: { logPrefix: () => string }, moduleName: string, context: string, originalData: T[], - currentResults: unknown[], + currentResults: R[], bytesLimit: number, - buildRejected: (item: T, reason: { info: string; reasonCode: ReasonCodeEnumType }) => unknown, + buildRejected: (item: T, reason: RejectionReason) => R, logger: { debug: (...args: unknown[]) => void } - ): unknown[] { + ): R[] { if (bytesLimit > 0) { try { const actualSize = Buffer.byteLength(JSON.stringify(currentResults), 'utf8') if (actualSize > bytesLimit) { const results = originalData.map(d => buildRejected(d, { - info: `BytesPerMessage limit ${bytesLimit.toString()} exceeded (actual ${actualSize.toString()} bytes)`, + additionalInfo: `BytesPerMessage limit ${bytesLimit.toString()} exceeded (actual ${actualSize.toString()} bytes)`, reasonCode: ReasonCodeEnumType.TooLargeElement, }) ) diff --git a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts index 22650077..88a76361 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts @@ -6,7 +6,10 @@ import assert from 'node:assert/strict' import { afterEach, describe, it } from 'node:test' -import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js' +import { + OCPP20ServiceUtils, + type RejectionReason, +} from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js' import { ReasonCodeEnumType } from '../../../../src/types/index.js' import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js' @@ -16,7 +19,7 @@ interface MockLogger { } interface RejectedResult { - info: string + additionalInfo: string original: TestItem reasonCode: ReasonCodeEnumType } @@ -58,11 +61,8 @@ function makeMockStation () { /** @returns A builder function that creates rejected result objects */ function makeRejectedBuilder () { - return ( - item: TestItem, - reason: { info: string; reasonCode: ReasonCodeEnumType } - ): RejectedResult => ({ - info: reason.info, + return (item: TestItem, reason: RejectionReason): RejectedResult => ({ + additionalInfo: reason.additionalInfo, original: item, reasonCode: reason.reasonCode, }) @@ -173,9 +173,9 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { assert.strictEqual(result.rejected, true) assert.strictEqual(result.results.length, 3) - for (const r of result.results as RejectedResult[]) { + for (const r of result.results) { assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooManyElements) - assert.ok(r.info.includes('ItemsPerMessage limit 2')) + assert.ok(r.additionalInfo.includes('ItemsPerMessage limit 2')) } }) @@ -197,7 +197,7 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { assert.strictEqual(result.rejected, true) assert.strictEqual(result.results.length, 2) - for (const r of result.results as RejectedResult[]) { + for (const r of result.results) { assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooManyElements) } }) @@ -262,9 +262,9 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { assert.strictEqual(result.rejected, true) assert.strictEqual(result.results.length, 1) - const r = (result.results as RejectedResult[])[0] + const r = result.results[0] assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooLargeElement) - assert.ok(r.info.includes('BytesPerMessage limit 1')) + assert.ok(r.additionalInfo.includes('BytesPerMessage limit 1')) }) await it('should reject all items with TooLargeElement for multiple items over bytes limit', () => { @@ -285,7 +285,7 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { assert.strictEqual(result.rejected, true) assert.strictEqual(result.results.length, 2) - for (const r of result.results as RejectedResult[]) { + for (const r of result.results) { assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooLargeElement) } }) @@ -329,7 +329,7 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { ) assert.strictEqual(result.rejected, true) - for (const r of result.results as RejectedResult[]) { + for (const r of result.results) { assert.strictEqual(r.reasonCode, ReasonCodeEnumType.TooManyElements) } }) @@ -364,7 +364,7 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { const station = makeMockStation() const logger = makeMockLogger() const item = makeItem('WebSocketPingInterval', 'xyz') - const capturedReasons: { info: string; reasonCode: ReasonCodeEnumType }[] = [] + const capturedReasons: RejectionReason[] = [] OCPP20ServiceUtils.enforceMessageLimits( station, @@ -382,8 +382,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { assert.strictEqual(capturedReasons.length, 1) assert.strictEqual(capturedReasons[0].reasonCode, ReasonCodeEnumType.TooLargeElement) - assert.strictEqual(typeof capturedReasons[0].info, 'string') - assert.ok(capturedReasons[0].info.length > 0) + assert.strictEqual(typeof capturedReasons[0].additionalInfo, 'string') + assert.ok(capturedReasons[0].additionalInfo.length > 0) }) }) })