]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp): consolidate payload validation into shared utility
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 2 Apr 2026 15:06:12 +0000 (17:06 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 2 Apr 2026 15:06:12 +0000 (17:06 +0200)
Extract 4 near-identical validation methods into 1 shared
validatePayload function in OCPPServiceUtils. Each service
method becomes a thin wrapper delegating to the shared logic.
Preserves clone/date-conversion behavior per caller and
error message capitalization for OCPP protocol compliance.

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 57d1f5a3c5a604f51f6cbf96da4c9c4d88c9d8df..4ed9a0b7134b7d09ba1990ade890c131db422365 100644 (file)
@@ -12,9 +12,7 @@ import {
   type OCPPVersion,
 } from '../../types/index.js'
 import { isAsyncFunction, logger } from '../../utils/index.js'
-import { type Ajv, ajvErrorsToErrorType, createAjv } from './OCPPServiceUtils.js'
-
-const moduleName = 'OCPPIncomingRequestService'
+import { type Ajv, createAjv, validatePayload } from './OCPPServiceUtils.js'
 
 export abstract class OCPPIncomingRequestService extends EventEmitter {
   private static readonly instances = new Map<
@@ -177,28 +175,12 @@ export abstract class OCPPIncomingRequestService extends EventEmitter {
     commandName: IncomingRequestCommand,
     payload: T
   ): boolean {
-    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
-      return true
-    }
-    const validate = this.payloadValidatorFunctions.get(commandName)
-    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
-    )
-    throw new OCPPError(
-      ajvErrorsToErrorType(validate.errors),
-      'Incoming request PDU is invalid',
+    return validatePayload(
+      chargingStation,
       commandName,
-      JSON.stringify(validate.errors, undefined, 2)
+      payload,
+      this.payloadValidatorFunctions.get(commandName),
+      'incoming request'
     )
   }
 }
index 5385a9a1805b1a3895573f9c92083c1178245b48..1278e14ed8778da14677302af6417619eeb3657f 100644 (file)
@@ -22,7 +22,6 @@ import {
   type ResponseType,
 } from '../../types/index.js'
 import {
-  clone,
   ensureError,
   formatDurationMilliSeconds,
   getErrorMessage,
@@ -31,14 +30,7 @@ import {
   logger,
 } from '../../utils/index.js'
 import { OCPPConstants } from './OCPPConstants.js'
-import {
-  type Ajv,
-  ajvErrorsToErrorType,
-  convertDateToISOString,
-  createAjv,
-} from './OCPPServiceUtils.js'
-
-const moduleName = 'OCPPRequestService'
+import { type Ajv, createAjv, validatePayload } from './OCPPServiceUtils.js'
 
 const defaultRequestParams: RequestParams = {
   skipBufferingOnError: false,
@@ -185,33 +177,15 @@ export abstract class OCPPRequestService {
     commandName: IncomingRequestCommand | RequestCommand,
     payload: T
   ): boolean {
-    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
-      return true
-    }
-    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 false
-    }
-    payload = clone(payload)
-    convertDateToISOString(payload)
-    if (validate(payload)) {
-      return true
-    }
-    logger.error(
-      `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: Command '${commandName}' incoming request response PDU is invalid: %j`,
-      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),
-      'Incoming request response PDU is invalid',
+    return validatePayload(
+      chargingStation,
       commandName,
-      JSON.stringify(validate.errors, undefined, 2)
+      payload,
+      this.ocppResponseService.incomingRequestResponsePayloadValidateFunctions.get(
+        commandName as IncomingRequestCommand
+      ),
+      'incoming request response',
+      true
     )
   }
 
@@ -228,31 +202,13 @@ export abstract class OCPPRequestService {
     commandName: IncomingRequestCommand | RequestCommand,
     payload: T
   ): boolean {
-    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
-      return true
-    }
-    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 false
-    }
-    payload = clone(payload)
-    convertDateToISOString(payload)
-    if (validate(payload)) {
-      return true
-    }
-    logger.error(
-      `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Command '${commandName}' request PDU is invalid: %j`,
-      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),
-      'Request PDU is invalid',
+    return validatePayload(
+      chargingStation,
       commandName,
-      JSON.stringify(validate.errors, undefined, 2)
+      payload,
+      this.payloadValidatorFunctions.get(commandName as RequestCommand),
+      'request',
+      true
     )
   }
 
index 2975025a326eb4a647c2af5d28983b1a74574da1..2a276db78650c3906ccc27e974013e85ecfc3bfb 100644 (file)
@@ -12,9 +12,7 @@ import {
   type ResponseHandler,
 } from '../../types/index.js'
 import { Constants, isAsyncFunction, logger } from '../../utils/index.js'
-import { type Ajv, ajvErrorsToErrorType, createAjv } from './OCPPServiceUtils.js'
-
-const moduleName = 'OCPPResponseService'
+import { type Ajv, createAjv, validatePayload } from './OCPPServiceUtils.js'
 
 export abstract class OCPPResponseService {
   private static readonly instances = new Map<new () => OCPPResponseService, OCPPResponseService>()
@@ -157,28 +155,12 @@ export abstract class OCPPResponseService {
     commandName: RequestCommand,
     payload: T
   ): boolean {
-    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
-      return true
-    }
-    const validate = this.payloadValidatorFunctions.get(commandName)
-    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
-    )
-    throw new OCPPError(
-      ajvErrorsToErrorType(validate.errors),
-      'Response PDU is invalid',
+    return validatePayload(
+      chargingStation,
       commandName,
-      JSON.stringify(validate.errors, undefined, 2)
+      payload,
+      this.payloadValidatorFunctions.get(commandName),
+      'response'
     )
   }
 }
index fedecd9dc8134349210ef7294ffe9b93d553248e..b4826ec8cbfbfbfa0d03cba2c8e9609697d79750 100644 (file)
@@ -37,6 +37,7 @@ import {
 } from '../../types/index.js'
 import {
   ACElectricUtils,
+  clone,
   Constants,
   convertToFloat,
   convertToInt,
@@ -131,6 +132,55 @@ export const ajvErrorsToErrorType = (errors: ErrorObject[] | null | undefined):
   return ErrorType.FORMAT_VIOLATION
 }
 
+/**
+ * Validates an OCPP payload against a JSON schema validation function.
+ * Shared implementation used by request, response, and incoming request validation.
+ * @param chargingStation - The charging station instance
+ * @param commandName - OCPP command name to validate against
+ * @param payload - JSON payload to validate
+ * @param validate - Ajv validation function for the command
+ * @param context - Description of the validation context (e.g. 'request', 'response')
+ * @param clonePayload - Whether to clone payload and convert dates before validation
+ * @returns True if payload validation succeeds, false otherwise
+ */
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+export const validatePayload = <T extends JsonType>(
+  chargingStation: ChargingStation,
+  commandName: IncomingRequestCommand | RequestCommand,
+  payload: T,
+  validate: undefined | ValidateFunction<JsonType>,
+  context: string,
+  clonePayload = false
+): boolean => {
+  if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
+    return true
+  }
+  if (validate == null) {
+    logger.warn(
+      `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' ${context} PDU validation`
+    )
+    return false
+  }
+  let payloadToValidate = payload
+  if (clonePayload) {
+    payloadToValidate = clone(payload)
+    convertDateToISOString(payloadToValidate)
+  }
+  if (validate(payloadToValidate)) {
+    return true
+  }
+  logger.error(
+    `${chargingStation.logPrefix()} ${moduleName}.validatePayload: Command '${commandName}' ${context} PDU is invalid: %j`,
+    validate.errors
+  )
+  throw new OCPPError(
+    ajvErrorsToErrorType(validate.errors),
+    `${context.charAt(0).toUpperCase()}${context.slice(1)} PDU is invalid`,
+    commandName,
+    JSON.stringify(validate.errors, undefined, 2)
+  )
+}
+
 /**
  * Recursively converts Date values to ISO 8601 strings within a JSON-compatible object.
  * @param object - Object whose Date properties will be converted in-place