]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp): harmonize JSON schema payload validation across OCPP stacks
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 12 Mar 2026 22:31:30 +0000 (23:31 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 12 Mar 2026 22:31:30 +0000 (23:31 +0100)
- Absorb missing-validator null guard into base class methods, eliminating
  4 identical private validatePayload wrappers and their bind() calls
- Remove dead code (unreachable isValid branches in ResponseService wrappers)
- Make parseJsonSchemaFile throw on failure instead of silently returning {},
  preventing validators that accept everything as a security bypass
- Add dual-path schema resolution for production (esbuild) and test (tsx)
- Align OCPP 1.6 schema map construction with OCPP 2.0 registry pattern,
  replacing ~200 lines of verbose inline entries with declarative registries
- Collapse 4 identical payload options factory methods into 1 per version

12 files changed:
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts
src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPIncomingRequestService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPResponseService.ts
src/charging-station/ocpp/OCPPServiceUtils.ts

index 9fe6c662d3a37483f5ccab94fc00f2ebbbee75d6..d2d3a3b99bb2112301236eaf56959e33c34e24e2 100644 (file)
@@ -149,7 +149,7 @@ const moduleName = 'OCPP16IncomingRequestService'
  * 3. Request routed to appropriate handler method
  * 4. Business logic executed with charging station state management
  * 5. Response payload validated and sent back to Central System
- * @see {@link validatePayload} Request payload validation method
+ * @see {@link validateIncomingRequestPayload} Request payload validation method
  * @see {@link handleRequestRemoteStartTransaction} Example request handler
  */
 
@@ -235,7 +235,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     ])
     this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
       OCPP16ServiceUtils.createIncomingRequestPayloadConfigs(),
-      OCPP16ServiceUtils.createIncomingRequestPayloadOptions(moduleName, 'constructor'),
+      OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     // Handle incoming request events
@@ -429,7 +429,6 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         }
       }
     )
-    this.validatePayload = this.validatePayload.bind(this)
   }
 
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
@@ -468,7 +467,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
       ) {
         try {
-          this.validatePayload(chargingStation, commandName, commandPayload)
+          this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
           // Call the method to build the response
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
           const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
@@ -1660,25 +1659,4 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
     }
   }
-
-  /**
-   * Validates incoming OCPP 1.6 request payload against JSON schema
-   * @param chargingStation - The charging station instance processing the request
-   * @param commandName - OCPP 1.6 command name to validate against
-   * @param commandPayload - JSON payload to validate
-   * @returns True if payload validation succeeds, false otherwise
-   */
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP16IncomingRequestCommand,
-    commandPayload: JsonType
-  ): boolean {
-    if (this.payloadValidatorFunctions.has(commandName)) {
-      return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
 }
index b0c7bc0ab5cde428c434c3617ae966135c22242d..5fe55dc83c70410a07c4be15e2e9cf6a36417ba0 100644 (file)
@@ -55,7 +55,7 @@ export class OCPP16RequestService extends OCPPRequestService {
     super(OCPPVersion.VERSION_16, ocppResponseService)
     this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
       OCPP16ServiceUtils.createRequestPayloadConfigs(),
-      OCPP16ServiceUtils.createRequestPayloadOptions(moduleName, 'constructor'),
+      OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
index 104057c4c5aaabc5b0d55fc88a0f6ac523be773d..fce62a828170562e65d2adc8e4242bec31a30d6a 100644 (file)
@@ -70,7 +70,7 @@ const moduleName = 'OCPP16ResponseService'
  * 3. Response routed to appropriate handler based on original request type
  * 4. Charging station state updated based on response content
  * 5. Any follow-up actions triggered (transactions, status changes, etc.)
- * @see {@link validatePayload} Response payload validation method
+ * @see {@link validateResponsePayload} Response payload validation method
  * @see {@link handleResponse} Response processing methods
  */
 
@@ -114,16 +114,15 @@ export class OCPP16ResponseService extends OCPPResponseService {
     ])
     this.payloadValidatorFunctions = OCPP16ServiceUtils.createPayloadValidatorMap(
       OCPP16ServiceUtils.createResponsePayloadConfigs(),
-      OCPP16ServiceUtils.createResponsePayloadOptions(moduleName, 'constructor'),
+      OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.incomingRequestResponsePayloadValidateFunctions =
       OCPP16ServiceUtils.createPayloadValidatorMap(
         OCPP16ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
-        OCPP16ServiceUtils.createIncomingRequestResponsePayloadOptions(moduleName, 'constructor'),
+        OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
         this.ajvIncomingRequest
       )
-    this.validatePayload = this.validatePayload.bind(this)
   }
 
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
@@ -145,7 +144,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
         OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
       ) {
         try {
-          this.validatePayload(chargingStation, commandName, payload)
+          this.validateResponsePayload(chargingStation, commandName, payload)
           logger.debug(
             `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handling '${commandName}' response`
           )
@@ -602,38 +601,4 @@ export class OCPP16ResponseService extends OCPPResponseService {
     resetConnectorStatus(connectorStatus)
     await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
   }
-
-  /**
-   * Validates incoming OCPP 1.6 response payload against JSON schema
-   * @param chargingStation - The charging station instance receiving the response
-   * @param commandName - OCPP 1.6 command name to validate against
-   * @param payload - JSON response payload to validate
-   * @returns True if payload validation succeeds, false otherwise
-   */
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP16RequestCommand,
-    payload: JsonType
-  ): boolean {
-    if (this.payloadValidatorFunctions.has(commandName)) {
-      logger.debug(
-        `${chargingStation.logPrefix()} ${moduleName}.validatePayload: Validating '${commandName}' response payload`
-      )
-      const isValid = this.validateResponsePayload(chargingStation, commandName, payload)
-      if (!isValid) {
-        logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.validatePayload: '${commandName}' response payload validation failed`
-        )
-      } else {
-        logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.validatePayload: '${commandName}' response payload validation successful`
-        )
-      }
-      return isValid
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
 }
index 7cde80084531256ba9eefcde91a52d4f59a12e3b..996c2f45dcc63e34cd7a73b9634a94579989d9af 100644 (file)
@@ -44,6 +44,42 @@ import { OCPP16Constants } from './OCPP16Constants.js'
 const moduleName = 'OCPP16ServiceUtils'
 
 export class OCPP16ServiceUtils extends OCPPServiceUtils {
+  private static readonly incomingRequestSchemaNames: readonly [
+    OCPP16IncomingRequestCommand,
+    string
+  ][] = [
+      [OCPP16IncomingRequestCommand.CANCEL_RESERVATION, 'CancelReservation'],
+      [OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY, 'ChangeAvailability'],
+      [OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION, 'ChangeConfiguration'],
+      [OCPP16IncomingRequestCommand.CLEAR_CACHE, 'ClearCache'],
+      [OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, 'ClearChargingProfile'],
+      [OCPP16IncomingRequestCommand.DATA_TRANSFER, 'DataTransfer'],
+      [OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, 'GetCompositeSchedule'],
+      [OCPP16IncomingRequestCommand.GET_CONFIGURATION, 'GetConfiguration'],
+      [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, 'GetDiagnostics'],
+      [OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, 'RemoteStartTransaction'],
+      [OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION, 'RemoteStopTransaction'],
+      [OCPP16IncomingRequestCommand.RESERVE_NOW, 'ReserveNow'],
+      [OCPP16IncomingRequestCommand.RESET, 'Reset'],
+      [OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, 'SetChargingProfile'],
+      [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, 'TriggerMessage'],
+      [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, 'UnlockConnector'],
+      [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, 'UpdateFirmware'],
+    ]
+
+  private static readonly outgoingRequestSchemaNames: readonly [OCPP16RequestCommand, string][] = [
+    [OCPP16RequestCommand.AUTHORIZE, 'Authorize'],
+    [OCPP16RequestCommand.BOOT_NOTIFICATION, 'BootNotification'],
+    [OCPP16RequestCommand.DATA_TRANSFER, 'DataTransfer'],
+    [OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, 'DiagnosticsStatusNotification'],
+    [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, 'FirmwareStatusNotification'],
+    [OCPP16RequestCommand.HEARTBEAT, 'Heartbeat'],
+    [OCPP16RequestCommand.METER_VALUES, 'MeterValues'],
+    [OCPP16RequestCommand.START_TRANSACTION, 'StartTransaction'],
+    [OCPP16RequestCommand.STATUS_NOTIFICATION, 'StatusNotification'],
+    [OCPP16RequestCommand.STOP_TRANSACTION, 'StopTransaction'],
+  ]
+
   public static buildTransactionBeginMeterValue (
     chargingStation: ChargingStation,
     connectorId: number,
@@ -386,87 +422,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static createIncomingRequestPayloadConfigs = (): [
     OCPP16IncomingRequestCommand,
     { schemaPath: string }
-  ][] => [
-    [
-      OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('CancelReservation.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ChangeAvailability.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ChangeConfiguration.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CLEAR_CACHE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ClearCache.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ClearChargingProfile.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.DATA_TRANSFER,
-      OCPP16ServiceUtils.PayloadValidatorConfig('DataTransfer.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('GetCompositeSchedule.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.GET_CONFIGURATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('GetConfiguration.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-      OCPP16ServiceUtils.PayloadValidatorConfig('GetDiagnostics.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('RemoteStartTransaction.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('RemoteStopTransaction.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.RESERVE_NOW,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ReserveNow.json'),
-    ],
-    [OCPP16IncomingRequestCommand.RESET, OCPP16ServiceUtils.PayloadValidatorConfig('Reset.json')],
-    [
-      OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('SetChargingProfile.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('TriggerMessage.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
-      OCPP16ServiceUtils.PayloadValidatorConfig('UnlockConnector.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('UpdateFirmware.json'),
-    ],
-  ]
-
-  /**
-   * Factory options for OCPP 1.6 Incoming Request Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 1.6 incoming request validators
-   */
-  public static createIncomingRequestPayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP16ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_16,
-      'assets/json-schemas/ocpp/1.6',
-      moduleName,
-      methodName
-    )
+  ][] =>
+    OCPP16ServiceUtils.incomingRequestSchemaNames.map(([command, schemaBase]) => [
+      command,
+      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}.json`),
+    ])
 
   /**
    * OCPP 1.6 Incoming Request Response Service validator configurations
@@ -475,87 +435,19 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static createIncomingRequestResponsePayloadConfigs = (): [
     OCPP16IncomingRequestCommand,
     { schemaPath: string }
-  ][] => [
-    [
-      OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('CancelReservationResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ChangeAvailabilityResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ChangeConfigurationResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CLEAR_CACHE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ClearCacheResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ClearChargingProfileResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.DATA_TRANSFER,
-      OCPP16ServiceUtils.PayloadValidatorConfig('DataTransferResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('GetCompositeScheduleResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.GET_CONFIGURATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('GetConfigurationResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-      OCPP16ServiceUtils.PayloadValidatorConfig('GetDiagnosticsResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('RemoteStartTransactionResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('RemoteStopTransactionResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.RESERVE_NOW,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ReserveNowResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.RESET,
-      OCPP16ServiceUtils.PayloadValidatorConfig('ResetResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('SetChargingProfileResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('TriggerMessageResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
-      OCPP16ServiceUtils.PayloadValidatorConfig('UnlockConnectorResponse.json'),
-    ],
-    [
-      OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('UpdateFirmwareResponse.json'),
-    ],
-  ]
+  ][] =>
+    OCPP16ServiceUtils.incomingRequestSchemaNames.map(([command, schemaBase]) => [
+      command,
+      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}Response.json`),
+    ])
 
   /**
-   * Factory options for OCPP 1.6 Incoming Request Response Service
+   * Factory options for OCPP 1.6 payload validators
    * @param moduleName - Name of the OCPP module
    * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 1.6 incoming request response validators
+   * @returns Factory options object for OCPP 1.6 validators
    */
-  public static createIncomingRequestResponsePayloadOptions = (
-    moduleName: string,
-    methodName: string
-  ) =>
+  public static createPayloadOptions = (moduleName: string, methodName: string) =>
     OCPP16ServiceUtils.PayloadValidatorOptions(
       OCPPVersion.VERSION_16,
       'assets/json-schemas/ocpp/1.6',
@@ -570,56 +462,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static createRequestPayloadConfigs = (): [
     OCPP16RequestCommand,
     { schemaPath: string }
-  ][] => [
-    [OCPP16RequestCommand.AUTHORIZE, OCPP16ServiceUtils.PayloadValidatorConfig('Authorize.json')],
-    [
-      OCPP16RequestCommand.BOOT_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('BootNotification.json'),
-    ],
-    [
-      OCPP16RequestCommand.DATA_TRANSFER,
-      OCPP16ServiceUtils.PayloadValidatorConfig('DataTransfer.json'),
-    ],
-    [
-      OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('DiagnosticsStatusNotification.json'),
-    ],
-    [
-      OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('FirmwareStatusNotification.json'),
-    ],
-    [OCPP16RequestCommand.HEARTBEAT, OCPP16ServiceUtils.PayloadValidatorConfig('Heartbeat.json')],
-    [
-      OCPP16RequestCommand.METER_VALUES,
-      OCPP16ServiceUtils.PayloadValidatorConfig('MeterValues.json'),
-    ],
-    [
-      OCPP16RequestCommand.START_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('StartTransaction.json'),
-    ],
-    [
-      OCPP16RequestCommand.STATUS_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('StatusNotification.json'),
-    ],
-    [
-      OCPP16RequestCommand.STOP_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('StopTransaction.json'),
-    ],
-  ]
-
-  /**
-   * Factory options for OCPP 1.6 Request Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 1.6 validators
-   */
-  public static createRequestPayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP16ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_16,
-      'assets/json-schemas/ocpp/1.6',
-      moduleName,
-      methodName
-    )
+  ][] =>
+    OCPP16ServiceUtils.outgoingRequestSchemaNames.map(([command, schemaBase]) => [
+      command,
+      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}.json`),
+    ])
 
   /**
    * OCPP 1.6 Response Service validator configurations
@@ -628,62 +475,11 @@ export class OCPP16ServiceUtils extends OCPPServiceUtils {
   public static createResponsePayloadConfigs = (): [
     OCPP16RequestCommand,
     { schemaPath: string }
-  ][] => [
-    [
-      OCPP16RequestCommand.AUTHORIZE,
-      OCPP16ServiceUtils.PayloadValidatorConfig('AuthorizeResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.BOOT_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('BootNotificationResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.DATA_TRANSFER,
-      OCPP16ServiceUtils.PayloadValidatorConfig('DataTransferResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('DiagnosticsStatusNotificationResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('FirmwareStatusNotificationResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.HEARTBEAT,
-      OCPP16ServiceUtils.PayloadValidatorConfig('HeartbeatResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.METER_VALUES,
-      OCPP16ServiceUtils.PayloadValidatorConfig('MeterValuesResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.START_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('StartTransactionResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.STATUS_NOTIFICATION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('StatusNotificationResponse.json'),
-    ],
-    [
-      OCPP16RequestCommand.STOP_TRANSACTION,
-      OCPP16ServiceUtils.PayloadValidatorConfig('StopTransactionResponse.json'),
-    ],
-  ]
-
-  /**
-   * Factory options for OCPP 1.6 Response Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 1.6 response validators
-   */
-  public static createResponsePayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP16ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_16,
-      'assets/json-schemas/ocpp/1.6',
-      moduleName,
-      methodName
-    )
+  ][] =>
+    OCPP16ServiceUtils.outgoingRequestSchemaNames.map(([command, schemaBase]) => [
+      command,
+      OCPP16ServiceUtils.PayloadValidatorConfig(`${schemaBase}Response.json`),
+    ])
 
   public static hasReservation = (
     chargingStation: ChargingStation,
index 89bc7e844306646b13becc140660eb8447f60be7..6d179205db12299e703bff98c8494c4d0d25f94c 100644 (file)
@@ -243,7 +243,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     ])
     this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
       OCPP20ServiceUtils.createIncomingRequestPayloadConfigs(),
-      OCPP20ServiceUtils.createIncomingRequestPayloadOptions(moduleName, 'constructor'),
+      OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     // Handle incoming request events
@@ -407,7 +407,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         }
       }
     )
-    this.validatePayload = this.validatePayload.bind(this)
   }
 
   public handleRequestGetVariables (
@@ -586,7 +585,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
         OCPP20ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
       ) {
         try {
-          this.validatePayload(chargingStation, commandName, commandPayload)
+          this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
           const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
           if (isAsyncFunction(incomingRequestHandler)) {
@@ -3374,27 +3373,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
     )
     return true
   }
-
-  /**
-   * Validates incoming OCPP 2.0 request payload against JSON schema
-   * @param chargingStation - The charging station instance processing the request
-   * @param commandName - OCPP 2.0 command name to validate against
-   * @param commandPayload - JSON payload to validate
-   * @returns True if payload validation succeeds, false otherwise
-   */
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP20IncomingRequestCommand,
-    commandPayload: JsonType
-  ): boolean {
-    if (this.payloadValidatorFunctions.has(commandName)) {
-      return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
 }
 
 /**
@@ -3436,7 +3414,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
  * 3. Request routed to appropriate handler method
  * 4. Business logic executed with variable model integration
  * 5. Response payload validated and sent back to CSMS
- * @see {@link validatePayload} Request payload validation method
+ * @see {@link validateIncomingRequestPayload} Request payload validation method
  * @see {@link handleRequestStartTransaction} Example OCPP 2.0+ request handler
  * @see {@link OCPP20VariableManager} Variable management integration
  */
index af2be2aabe5c0c5ff74eeebca3c083e28d581c0f..1dae156f873414275ff36ae54901a4847231000e 100644 (file)
@@ -80,7 +80,7 @@ export class OCPP20RequestService extends OCPPRequestService {
     super(OCPPVersion.VERSION_201, ocppResponseService)
     this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
       OCPP20ServiceUtils.createRequestPayloadConfigs(),
-      OCPP20ServiceUtils.createRequestPayloadOptions(moduleName, 'constructor'),
+      OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
index e64ca67d406053dae356f396a8352e6d7ecd9352..134163a8797aaa320bf197b1c55bd9af507b12f3 100644 (file)
@@ -68,7 +68,7 @@ const moduleName = 'OCPP20ResponseService'
  * 3. Response routed to appropriate handler based on original request type
  * 4. Charging station state and variable model updated based on response content
  * 5. Enhanced follow-up actions triggered based on OCPP 2.0+ capabilities
- * @see {@link validatePayload} Response payload validation method
+ * @see {@link validateResponsePayload} Response payload validation method
  * @see {@link handleResponse} Response processing methods
  * @see {@link OCPP20VariableManager} Variable management integration
  */
@@ -125,16 +125,15 @@ export class OCPP20ResponseService extends OCPPResponseService {
     ])
     this.payloadValidatorFunctions = OCPP20ServiceUtils.createPayloadValidatorMap(
       OCPP20ServiceUtils.createResponsePayloadConfigs(),
-      OCPP20ServiceUtils.createResponsePayloadOptions(moduleName, 'constructor'),
+      OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
       this.ajv
     )
     this.incomingRequestResponsePayloadValidateFunctions =
       OCPP20ServiceUtils.createPayloadValidatorMap(
         OCPP20ServiceUtils.createIncomingRequestResponsePayloadConfigs(),
-        OCPP20ServiceUtils.createIncomingRequestResponsePayloadOptions(moduleName, 'constructor'),
+        OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
         this.ajvIncomingRequest
       )
-    this.validatePayload = this.validatePayload.bind(this)
   }
 
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
@@ -156,7 +155,7 @@ export class OCPP20ResponseService extends OCPPResponseService {
         OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
       ) {
         try {
-          this.validatePayload(chargingStation, commandName, payload)
+          this.validateResponsePayload(chargingStation, commandName, payload)
           logger.debug(
             `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handling '${commandName}' response`
           )
@@ -407,38 +406,4 @@ export class OCPP20ResponseService extends OCPPResponseService {
       )
     }
   }
-
-  /**
-   * Validates incoming OCPP 2.0 response payload against JSON schema
-   * @param chargingStation - The charging station instance receiving the response
-   * @param commandName - OCPP 2.0 command name to validate against
-   * @param payload - JSON response payload to validate
-   * @returns True if payload validation succeeds, false otherwise
-   */
-  private validatePayload (
-    chargingStation: ChargingStation,
-    commandName: OCPP20RequestCommand,
-    payload: JsonType
-  ): boolean {
-    if (this.payloadValidatorFunctions.has(commandName)) {
-      logger.debug(
-        `${chargingStation.logPrefix()} ${moduleName}.validatePayload: Validating '${commandName}' response payload`
-      )
-      const isValid = this.validateResponsePayload(chargingStation, commandName, payload)
-      if (!isValid) {
-        logger.warn(
-          `${chargingStation.logPrefix()} ${moduleName}.validatePayload: '${commandName}' response payload validation failed`
-        )
-      } else {
-        logger.debug(
-          `${chargingStation.logPrefix()} ${moduleName}.validatePayload: '${commandName}' response payload validation successful`
-        )
-      }
-      return isValid
-    }
-    logger.warn(
-      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
-    )
-    return false
-  }
 }
index fba639bd959a0ff2e5b33302a09c19bbbbd2e4e4..5bcc1268ca6f813ccd5a82eedcfdb2527c497850 100644 (file)
@@ -254,20 +254,6 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
       OCPP20ServiceUtils.PayloadValidatorConfig(`${schemaBase}Request.json`),
     ])
 
-  /**
-   * Factory options for OCPP 2.0 Incoming Request Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 2.0 incoming request validators
-   */
-  public static createIncomingRequestPayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP20ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_201,
-      'assets/json-schemas/ocpp/2.0',
-      moduleName,
-      methodName
-    )
-
   /**
    * Configuration for OCPP 2.0 Incoming Request Response validators
    * @returns Array of validator configuration tuples
@@ -282,15 +268,12 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     ])
 
   /**
-   * Factory options for OCPP 2.0 Incoming Request Response Service
+   * Factory options for OCPP 2.0 payload validators
    * @param moduleName - Name of the OCPP module
    * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 2.0 incoming request response validators
+   * @returns Factory options object for OCPP 2.0 validators
    */
-  public static createIncomingRequestResponsePayloadOptions = (
-    moduleName: string,
-    methodName: string
-  ) =>
+  public static createPayloadOptions = (moduleName: string, methodName: string) =>
     OCPP20ServiceUtils.PayloadValidatorOptions(
       OCPPVersion.VERSION_201,
       'assets/json-schemas/ocpp/2.0',
@@ -311,20 +294,6 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
       OCPP20ServiceUtils.PayloadValidatorConfig(`${schemaBase}Request.json`),
     ])
 
-  /**
-   * Factory options for OCPP 2.0 Request Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 2.0 validators
-   */
-  public static createRequestPayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP20ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_201,
-      'assets/json-schemas/ocpp/2.0',
-      moduleName,
-      methodName
-    )
-
   /**
    * OCPP 2.0 Response Service validator configurations
    * @returns Array of validator configuration tuples
@@ -338,20 +307,6 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
       OCPP20ServiceUtils.PayloadValidatorConfig(`${schemaBase}Response.json`),
     ])
 
-  /**
-   * Factory options for OCPP 2.0 Response Service
-   * @param moduleName - Name of the OCPP module
-   * @param methodName - Name of the method/command
-   * @returns Factory options object for OCPP 2.0 response validators
-   */
-  public static createResponsePayloadOptions = (moduleName: string, methodName: string) =>
-    OCPP20ServiceUtils.PayloadValidatorOptions(
-      OCPPVersion.VERSION_201,
-      'assets/json-schemas/ocpp/2.0',
-      moduleName,
-      methodName
-    )
-
   public static enforceMessageLimits<
     T extends { attributeType?: unknown; component: unknown; variable: unknown }
   >(
index 3d99275e1907bd241d7319848aa9d78259c48a4d..351aef63095b3db4a3e0213e0617d010bfc44331 100644 (file)
@@ -69,18 +69,24 @@ export abstract class OCPPIncomingRequestService extends EventEmitter {
       return true
     }
     const validate = this.payloadValidatorFunctions.get(commandName)
-    if (validate?.(payload) === true) {
+    if (validate == null) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestPayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+      )
+      return false
+    }
+    if (validate(payload)) {
       return true
     }
     logger.error(
       `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestPayload: Command '${commandName}' incoming request PDU is invalid: %j`,
-      validate?.errors
+      validate.errors
     )
     throw new OCPPError(
-      ajvErrorsToErrorType(validate?.errors),
+      ajvErrorsToErrorType(validate.errors),
       'Incoming request PDU is invalid',
       commandName,
-      JSON.stringify(validate?.errors, undefined, 2)
+      JSON.stringify(validate.errors, undefined, 2)
     )
   }
 }
index ed3a56d55ea35c999f7aa64c7cd0a27cb1429171..c9762612e30532e78dbc6e75a4710069980bbd34 100644 (file)
@@ -448,34 +448,30 @@ export abstract class OCPPRequestService {
     if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
       return true
     }
-    if (
-      !this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.has(
-        commandName as IncomingRequestCommand
-      )
-    ) {
+    const validate = this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.get(
+      commandName as IncomingRequestCommand
+    )
+    if (validate == null) {
       logger.warn(
         `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
       )
       return true
     }
-    const validate = this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.get(
-      commandName as IncomingRequestCommand
-    )
     payload = clone<T>(payload)
     convertDateToISOString<T>(payload)
-    if (validate?.(payload) === true) {
+    if (validate(payload)) {
       return true
     }
     logger.error(
       `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: Command '${commandName}' incoming request response PDU is invalid: %j`,
-      validate?.errors
+      validate.errors
     )
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
-      ajvErrorsToErrorType(validate?.errors),
+      ajvErrorsToErrorType(validate.errors),
       'Incoming request response PDU is invalid',
       commandName,
-      JSON.stringify(validate?.errors, undefined, 2)
+      JSON.stringify(validate.errors, undefined, 2)
     )
   }
 
@@ -495,28 +491,28 @@ export abstract class OCPPRequestService {
     if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
       return true
     }
-    if (!this.payloadValidatorFunctions.has(commandName as RequestCommand)) {
+    const validate = this.payloadValidatorFunctions.get(commandName as RequestCommand)
+    if (validate == null) {
       logger.warn(
         `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: No JSON schema validation function found for command '${commandName}' PDU validation`
       )
       return true
     }
-    const validate = this.payloadValidatorFunctions.get(commandName as RequestCommand)
     payload = clone<T>(payload)
     convertDateToISOString<T>(payload)
-    if (validate?.(payload) === true) {
+    if (validate(payload)) {
       return true
     }
     logger.error(
       `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Command '${commandName}' request PDU is invalid: %j`,
-      validate?.errors
+      validate.errors
     )
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
-      ajvErrorsToErrorType(validate?.errors),
+      ajvErrorsToErrorType(validate.errors),
       'Request PDU is invalid',
       commandName,
-      JSON.stringify(validate?.errors, undefined, 2)
+      JSON.stringify(validate.errors, undefined, 2)
     )
   }
 }
index 3c01181c9c1224b13f4b591a964a86e3bdc700d8..d6f122e161b858fb952e12ca6a2d46c0c4ae7050 100644 (file)
@@ -79,18 +79,24 @@ export abstract class OCPPResponseService {
       return true
     }
     const validate = this.payloadValidatorFunctions.get(commandName)
-    if (validate?.(payload) === true) {
+    if (validate == null) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.validateResponsePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
+      )
+      return false
+    }
+    if (validate(payload)) {
       return true
     }
     logger.error(
       `${chargingStation.logPrefix()} ${moduleName}.validateResponsePayload: Command '${commandName}' response PDU is invalid: %j`,
-      validate?.errors
+      validate.errors
     )
     throw new OCPPError(
-      ajvErrorsToErrorType(validate?.errors),
+      ajvErrorsToErrorType(validate.errors),
       'Response PDU is invalid',
       commandName,
-      JSON.stringify(validate?.errors, undefined, 2)
+      JSON.stringify(validate.errors, undefined, 2)
     )
   }
 }
index f6cc126f7245d6b1a680e66ced5c3cddae3ded37..0de76ab226562e2065869d0c757566582e0ecb14 100644 (file)
@@ -1984,7 +1984,8 @@ const getMeasurandDefaultUnit = (
  * response services) to perform common operations and validation. It acts as a shared
  * utility layer that prevents code duplication across OCPP version-specific implementations.
  * @see {@link parseJsonSchemaFile} Core JSON schema parsing functionality
- * @see {@link validatePayload} Payload validation methods in service classes
+ * @see {@link validateIncomingRequestPayload} Payload validation methods in service classes
+ * @see {@link validateResponsePayload} Payload validation methods in service classes
  */
 
 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
@@ -2145,12 +2146,13 @@ export class OCPPServiceUtils {
 
   /**
    * Parses and loads a JSON schema file for OCPP payload validation.
-   * Handles file reading, JSON parsing, and error recovery for schema validation.
+   * Handles file reading and JSON parsing for schema validation.
    * @param relativePath Path to the schema file relative to the OCPP utils directory
    * @param ocppVersion The OCPP version for error logging context
    * @param moduleName Optional module name for error logging
    * @param methodName Optional method name for error logging
-   * @returns Parsed JSON schema object or empty object if parsing fails
+   * @returns Parsed JSON schema object
+   * @throws {NodeJS.ErrnoException} If the schema file cannot be read or parsed
    */
   protected static parseJsonSchemaFile<T extends JsonType>(
     relativePath: string,
@@ -2158,18 +2160,26 @@ export class OCPPServiceUtils {
     moduleName?: string,
     methodName?: string
   ): JSONSchemaType<T> {
-    const filePath = join(dirname(fileURLToPath(import.meta.url)), relativePath)
+    const baseDir = dirname(fileURLToPath(import.meta.url))
+    // Primary: resolve from file directory (production esbuild bundle)
+    const primaryPath = join(baseDir, relativePath)
     try {
-      return JSON.parse(readFileSync(filePath, 'utf8')) as JSONSchemaType<T>
-    } catch (error) {
-      handleFileException(
-        filePath,
-        FileType.JsonSchema,
-        error as NodeJS.ErrnoException,
-        OCPPServiceUtils.logPrefix(ocppVersion, moduleName, methodName),
-        { throwError: false }
-      )
-      return {} as JSONSchemaType<T>
+      return JSON.parse(readFileSync(primaryPath, 'utf8')) as JSONSchemaType<T>
+    } catch (primaryError) {
+      // Fallback: resolve from source root (development/test with tsx)
+      const fallbackPath = join(baseDir, '..', '..', relativePath)
+      try {
+        return JSON.parse(readFileSync(fallbackPath, 'utf8')) as JSONSchemaType<T>
+      } catch {
+        handleFileException(
+          primaryPath,
+          FileType.JsonSchema,
+          primaryError as NodeJS.ErrnoException,
+          OCPPServiceUtils.logPrefix(ocppVersion, moduleName, methodName)
+        )
+        // handleFileException throws by default; this satisfies the compiler
+        throw primaryError
+      }
     }
   }