Skip to content

Commit 9da4985

Browse files
committed
Implement CallError handling and add tests for error parsing and validation
1 parent 89f3566 commit 9da4985

5 files changed

Lines changed: 370 additions & 11 deletions

File tree

src/Ocpp/Exceptions/PropertyConstraintViolationError.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use SolutionForest\OcppPhp\Ocpp\Messages\CallError;
66

7-
class NotImplementedError extends CallError
7+
class PropertyConstraintViolationError extends CallError
88
{
9-
public string $errorCode = "NotImplemented";
10-
public string $errorDescription = "Request Action is recognized but not supported by the receiver";
9+
public string $errorCode = "PropertyConstraintViolation";
10+
public string $errorDescription = "Payload for Action is syntactically correct but at least one of the fields violates property constraints";
1111
}

src/Ocpp/JsonSchemaRegistry.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@
33
namespace SolutionForest\OcppPhp\Ocpp;
44

55
use SolutionForest\OcppPhp\Ocpp\Messages\Call;
6+
use SolutionForest\OcppPhp\Ocpp\Messages\CallError;
67
use SolutionForest\OcppPhp\Ocpp\Messages\CallResult;
7-
use SolutionForest\OcppPhp\Ocpp\Messages\Message;
88

99
class JsonSchemaRegistry
1010
{
1111
private $schemaDirs = ['v1.6', 'v2.0.1'];
1212

1313
/**
14-
* Create a Call or CallResult object from a raw OCPP message array.
15-
*
14+
* Create a Call, CallResult, or CallError object from a raw OCPP message array.
15+
*
1616
* For Call messages: [2, UniqueId, action, payload]
1717
* For CallResult messages: [3, UniqueId, payload] (requires action parameter)
18-
*
18+
* For CallError messages: [4, UniqueId, errorCode, errorDescription, errorDetails]
19+
*
1920
* @param array $message The raw OCPP message array
2021
* @param string $version The OCPP version (e.g., 'v1.6', 'v2.0.1')
2122
* @param string|null $action Required for CallResult messages (the action name this is a response to)
22-
* @return Call|CallResult
23+
* @return Call|CallResult|CallError
2324
* @throws \Exception
2425
*/
25-
public function createFromArray(array $message, string $version, ?string $action = null): Call|CallResult
26+
public function createFromArray(array $message, string $version, ?string $action = null): Call|CallResult|CallError
2627
{
2728
$messageType = $message[0] ?? null;
2829

@@ -37,7 +38,11 @@ public function createFromArray(array $message, string $version, ?string $action
3738
return CallResult::fromArray($message, $action, $version);
3839
}
3940

40-
throw new \Exception("Invalid message type: {$messageType}. Expected 2 (Call) or 3 (CallResult)");
41+
if ($messageType === 4) {
42+
return CallError::fromArray($message);
43+
}
44+
45+
throw new \Exception("Invalid message type: {$messageType}. Expected 2 (Call), 3 (CallResult), or 4 (CallError)");
4146
}
4247

4348
public function getSchema(Call|CallResult|array $message, string $version): array

src/Ocpp/JsonSchemaValidator.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,27 @@
1111

1212
class JsonSchemaValidator
1313
{
14-
public static function validate(Call|CallResult|array $message, string $version, bool $throwException = true): bool|CallError
14+
/**
15+
* Validate an OCPP message payload against its JSON schema.
16+
*
17+
* @param Call|CallResult|CallError|array $message The message to validate
18+
* @param string $version The OCPP version (e.g., 'v1.6', 'v2.0.1')
19+
* @param bool $throwException Whether to throw an exception on validation failure
20+
* @return bool|CallError Returns true if valid, or a CallError if invalid (when $throwException is false)
21+
*/
22+
public static function validate(Call|CallResult|CallError|array $message, string $version, bool $throwException = true): bool|CallError
1523
{
24+
// CallError messages represent protocol-level error responses (e.g., NotImplemented).
25+
// They have no payload to validate — the error code itself IS the message.
26+
if ($message instanceof CallError) {
27+
return true;
28+
}
29+
30+
// Raw array with messageTypeID=4 is also a CallError — skip validation.
31+
if (is_array($message) && ($message['messageTypeID'] ?? $message[0] ?? null) === 4) {
32+
return true;
33+
}
34+
1635
$registry = new JsonSchemaRegistry();
1736
$schema = $registry->getSchema($message, $version);
1837
$payload = is_array($message) ? $message['payload'] : $message->getPayload();

src/Ocpp/Messages/CallError.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
namespace SolutionForest\OcppPhp\Ocpp\Messages;
44

5+
use SolutionForest\OcppPhp\Ocpp\Exceptions\FormatViolationError;
6+
use SolutionForest\OcppPhp\Ocpp\Exceptions\FormationViolationError;
7+
use SolutionForest\OcppPhp\Ocpp\Exceptions\GenericError;
8+
use SolutionForest\OcppPhp\Ocpp\Exceptions\InternalError;
9+
use SolutionForest\OcppPhp\Ocpp\Exceptions\NotImplementedError;
10+
use SolutionForest\OcppPhp\Ocpp\Exceptions\NotSupportedError;
11+
use SolutionForest\OcppPhp\Ocpp\Exceptions\OccurenceConstraintViolationError;
12+
use SolutionForest\OcppPhp\Ocpp\Exceptions\OccurrenceConstraintViolationError;
13+
use SolutionForest\OcppPhp\Ocpp\Exceptions\PropertyConstraintViolationError;
14+
use SolutionForest\OcppPhp\Ocpp\Exceptions\ProtocolError;
15+
use SolutionForest\OcppPhp\Ocpp\Exceptions\SecurityError;
16+
use SolutionForest\OcppPhp\Ocpp\Exceptions\TypeConstraintViolationError;
17+
use SolutionForest\OcppPhp\Ocpp\Exceptions\UnknownCallErrorCodeError;
18+
519
abstract class CallError extends Message
620
{
721
public int $messageTypeID = 4;
@@ -12,6 +26,27 @@ abstract class CallError extends Message
1226

1327
public array $errorDetails = [];
1428

29+
/**
30+
* Map of OCPP error code strings to their corresponding CallError exception classes.
31+
*
32+
* @var array<string, class-string<CallError>>
33+
*/
34+
private const ERROR_CODE_MAP = [
35+
'NotImplemented' => NotImplementedError::class,
36+
'NotSupported' => NotSupportedError::class,
37+
'InternalError' => InternalError::class,
38+
'ProtocolError' => ProtocolError::class,
39+
'SecurityError' => SecurityError::class,
40+
'FormatViolation' => FormatViolationError::class,
41+
'FormationViolation' => FormationViolationError::class,
42+
'GenericError' => GenericError::class,
43+
'TypeConstraintViolation' => TypeConstraintViolationError::class,
44+
'OccurrenceConstraintViolation' => OccurrenceConstraintViolationError::class,
45+
'OccurenceConstraintViolation' => OccurenceConstraintViolationError::class,
46+
'PropertyConstraintViolation' => PropertyConstraintViolationError::class,
47+
];
48+
49+
1550
public function __construct(string $uniqueId, ?string $errorCode = null, ?string $errorDescription = null, ?array $errorDetails = null)
1651
{
1752
$this->uniqueId = $uniqueId;
@@ -27,6 +62,34 @@ public function __construct(string $uniqueId, ?string $errorCode = null, ?string
2762
}
2863
}
2964

65+
/**
66+
* Create a CallError object from a raw OCPP message array.
67+
*
68+
* @param array $message The raw OCPP message [4, UniqueId, errorCode, errorDescription, errorDetails]
69+
* @return static
70+
*/
71+
public static function fromArray(array $message): static
72+
{
73+
if ($message[0] !== 4) {
74+
throw new \Exception("Invalid message type for CallError, expected 4 got {$message[0]}");
75+
}
76+
77+
$uniqueId = $message[1];
78+
$errorCode = $message[2] ?? '';
79+
$errorDescription = $message[3] ?? '';
80+
$errorDetails = $message[4] ?? [];
81+
82+
$class = self::ERROR_CODE_MAP[$errorCode] ?? UnknownCallErrorCodeError::class;
83+
84+
/** @var static $instance */
85+
$instance = new $class($uniqueId);
86+
$instance->errorCode = $errorCode;
87+
$instance->errorDescription = $errorDescription;
88+
$instance->errorDetails = (array) $errorDetails;
89+
90+
return $instance;
91+
}
92+
3093
public function toArray(): array
3194
{
3295
return [

0 commit comments

Comments
 (0)