build(simulator): enforce minimum node version 18.18.x
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / OCPPRequestService.ts
index ccab6468353d32e58eaf5a2317d63848ca18aee0..98c3081155bd2f350fe3a686c7909cd524166b52 100644 (file)
@@ -1,4 +1,4 @@
-import Ajv, { type JSONSchemaType } from 'ajv';
+import Ajv, { type JSONSchemaType, type ValidateFunction } from 'ajv';
 import ajvFormats from 'ajv-formats';
 
 import { OCPPConstants } from './OCPPConstants';
@@ -44,7 +44,8 @@ export abstract class OCPPRequestService {
   private readonly version: OCPPVersion;
   private readonly ajv: Ajv;
   private readonly ocppResponseService: OCPPResponseService;
-  protected abstract jsonSchemas: Map<RequestCommand, JSONSchemaType<JsonObject>>;
+  private readonly jsonValidateFunctions: Map<RequestCommand, ValidateFunction<JsonType>>;
+  protected abstract jsonSchemas: Map<RequestCommand, JSONSchemaType<JsonType>>;
 
   protected constructor(version: OCPPVersion, ocppResponseService: OCPPResponseService) {
     this.version = version;
@@ -53,6 +54,7 @@ export abstract class OCPPRequestService {
       multipleOfPrecision: 2,
     });
     ajvFormats(this.ajv);
+    this.jsonValidateFunctions = new Map<RequestCommand, ValidateFunction<JsonType>>();
     this.ocppResponseService = ocppResponseService;
     this.requestHandler = this.requestHandler.bind(this) as <
       // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -100,14 +102,14 @@ export abstract class OCPPRequestService {
       responseCallback: ResponseCallback,
       errorCallback: ErrorCallback,
     ) => string;
-    this.validateRequestPayload = this.validateRequestPayload.bind(this) as <T extends JsonObject>(
+    this.validateRequestPayload = this.validateRequestPayload.bind(this) as <T extends JsonType>(
       chargingStation: ChargingStation,
       commandName: RequestCommand | IncomingRequestCommand,
       payload: T,
     ) => boolean;
     this.validateIncomingRequestResponsePayload = this.validateIncomingRequestResponsePayload.bind(
       this,
-    ) as <T extends JsonObject>(
+    ) as <T extends JsonType>(
       chargingStation: ChargingStation,
       commandName: RequestCommand | IncomingRequestCommand,
       payload: T,
@@ -196,12 +198,12 @@ export abstract class OCPPRequestService {
     }
   }
 
-  private validateRequestPayload<T extends JsonObject>(
+  private validateRequestPayload<T extends JsonType>(
     chargingStation: ChargingStation,
     commandName: RequestCommand | IncomingRequestCommand,
     payload: T,
   ): boolean {
-    if (chargingStation.getOcppStrictCompliance() === false) {
+    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
       return true;
     }
     if (this.jsonSchemas.has(commandName as RequestCommand) === false) {
@@ -210,7 +212,13 @@ export abstract class OCPPRequestService {
       );
       return true;
     }
-    const validate = this.ajv.compile(this.jsonSchemas.get(commandName as RequestCommand)!);
+    if (this.jsonValidateFunctions.has(commandName as RequestCommand) === false) {
+      this.jsonValidateFunctions.set(
+        commandName as RequestCommand,
+        this.ajv.compile<T>(this.jsonSchemas.get(commandName as RequestCommand)!).bind(this),
+      );
+    }
+    const validate = this.jsonValidateFunctions.get(commandName as RequestCommand)!;
     payload = cloneObject<T>(payload);
     OCPPServiceUtils.convertDateToISOString<T>(payload);
     if (validate(payload)) {
@@ -222,19 +230,19 @@ export abstract class OCPPRequestService {
     );
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors!),
+      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
       'Request PDU is invalid',
       commandName,
-      JSON.stringify(validate.errors, null, 2),
+      JSON.stringify(validate.errors, undefined, 2),
     );
   }
 
-  private validateIncomingRequestResponsePayload<T extends JsonObject>(
+  private validateIncomingRequestResponsePayload<T extends JsonType>(
     chargingStation: ChargingStation,
     commandName: RequestCommand | IncomingRequestCommand,
     payload: T,
   ): boolean {
-    if (chargingStation.getOcppStrictCompliance() === false) {
+    if (chargingStation.stationInfo?.ocppStrictCompliance === false) {
       return true;
     }
     if (
@@ -247,11 +255,25 @@ export abstract class OCPPRequestService {
       );
       return true;
     }
-    const validate = this.ajv.compile(
-      this.ocppResponseService.jsonIncomingRequestResponseSchemas.get(
+    if (
+      this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.has(
         commandName as IncomingRequestCommand,
-      )!,
-    );
+      ) === false
+    ) {
+      this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.set(
+        commandName as IncomingRequestCommand,
+        this.ajv
+          .compile<T>(
+            this.ocppResponseService.jsonIncomingRequestResponseSchemas.get(
+              commandName as IncomingRequestCommand,
+            )!,
+          )
+          .bind(this),
+      );
+    }
+    const validate = this.ocppResponseService.jsonIncomingRequestResponseValidateFunctions.get(
+      commandName as IncomingRequestCommand,
+    )!;
     payload = cloneObject<T>(payload);
     OCPPServiceUtils.convertDateToISOString<T>(payload);
     if (validate(payload)) {
@@ -263,10 +285,10 @@ export abstract class OCPPRequestService {
     );
     // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
     throw new OCPPError(
-      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors!),
+      OCPPServiceUtils.ajvErrorsToErrorType(validate.errors),
       'Response PDU is invalid',
       commandName,
-      JSON.stringify(validate.errors, null, 2),
+      JSON.stringify(validate.errors, undefined, 2),
     );
   }
 
@@ -285,7 +307,7 @@ export abstract class OCPPRequestService {
     if (
       (chargingStation.inUnknownState() === true &&
         commandName === RequestCommand.BOOT_NOTIFICATION) ||
-      (chargingStation.getOcppStrictCompliance() === false &&
+      (chargingStation.stationInfo?.ocppStrictCompliance === false &&
         chargingStation.inUnknownState() === true) ||
       chargingStation.inAcceptedState() === true ||
       (chargingStation.inPendingState() === true &&
@@ -303,7 +325,7 @@ export abstract class OCPPRequestService {
            * @param requestPayload -
            */
           const responseCallback = (payload: JsonType, requestPayload: JsonType): void => {
-            if (chargingStation.getEnableStatistics() === true) {
+            if (chargingStation.stationInfo?.enableStatistics === true) {
               chargingStation.performanceStatistics?.addRequestStatistic(
                 commandName,
                 MessageType.CALL_RESULT_MESSAGE,
@@ -335,7 +357,10 @@ export abstract class OCPPRequestService {
            * @param requestStatistic -
            */
           const errorCallback = (error: OCPPError, requestStatistic = true): void => {
-            if (requestStatistic === true && chargingStation.getEnableStatistics() === true) {
+            if (
+              requestStatistic === true &&
+              chargingStation.stationInfo?.enableStatistics === true
+            ) {
               chargingStation.performanceStatistics?.addRequestStatistic(
                 commandName,
                 MessageType.CALL_ERROR_MESSAGE,
@@ -352,7 +377,7 @@ export abstract class OCPPRequestService {
             reject(error);
           };
 
-          if (chargingStation.getEnableStatistics() === true) {
+          if (chargingStation.stationInfo?.enableStatistics === true) {
             chargingStation.performanceStatistics?.addRequestStatistic(commandName, messageType);
           }
           const messageToSend = this.buildMessageToSend(
@@ -397,7 +422,7 @@ export abstract class OCPPRequestService {
                 ErrorType.GENERIC_ERROR,
                 `WebSocket closed or errored for buffered message id '${messageId}' with content '${messageToSend}'`,
                 commandName,
-                (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FREEZED_OBJECT,
+                (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FROZEN_OBJECT,
               ),
             );
           } else if (wsClosedOrErrored) {
@@ -405,7 +430,7 @@ export abstract class OCPPRequestService {
               ErrorType.GENERIC_ERROR,
               `WebSocket closed or errored for non buffered message id '${messageId}' with content '${messageToSend}'`,
               commandName,
-              (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FREEZED_OBJECT,
+              (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FROZEN_OBJECT,
             );
             // Reject response
             if (messageType !== MessageType.CALL_MESSAGE) {
@@ -424,7 +449,7 @@ export abstract class OCPPRequestService {
           ErrorType.GENERIC_ERROR,
           `Timeout for message id '${messageId}'`,
           commandName,
-          (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FREEZED_OBJECT,
+          (messagePayload as JsonObject)?.details ?? Constants.EMPTY_FROZEN_OBJECT,
         ),
         () => {
           messageType === MessageType.CALL_MESSAGE && chargingStation.requests.delete(messageId);
@@ -453,7 +478,7 @@ export abstract class OCPPRequestService {
       // Request
       case MessageType.CALL_MESSAGE:
         // Build request
-        this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonObject);
+        this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType);
         chargingStation.requests.set(messageId, [
           responseCallback,
           errorCallback,
@@ -473,7 +498,7 @@ export abstract class OCPPRequestService {
         this.validateIncomingRequestResponsePayload(
           chargingStation,
           commandName,
-          messagePayload as JsonObject,
+          messagePayload as JsonType,
         );
         messageToSend = JSON.stringify([messageType, messageId, messagePayload] as Response);
         break;