From 55c6822ab20d8fd24fe150e893b82b9588abcbdc Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Fri, 21 Nov 2025 13:32:58 +0100 Subject: [PATCH 01/22] composer dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 68bdf00..034b9ff 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "php": ">=8.4", "laravel/framework": ">=12.0", "ramsey/uuid": "^4.7", - "justinrainbow/json-schema": "^6.4" + "opis/json-schema": "^2.6" }, "require-dev": { "fakerphp/faker": "^1.24", From c3edbc6a6cf6f4e1f88182ac0ab2991eec3cadbc Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Fri, 21 Nov 2025 15:21:30 +0100 Subject: [PATCH 02/22] :sparkles: replaced our validation logic to use new lib + fixed failing tests --- .../Exceptions/FormValidationException.php | 23 ++++++++------- src/Form/Form.php | 18 +++++------- .../FormValidationExceptionTest.php | 28 +++++++++---------- tests/Form/FormTest.php | 1 + 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/Form/Exceptions/FormValidationException.php b/src/Form/Exceptions/FormValidationException.php index 4da8bce..6f01830 100644 --- a/src/Form/Exceptions/FormValidationException.php +++ b/src/Form/Exceptions/FormValidationException.php @@ -7,16 +7,21 @@ use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Support\Arr; +use Opis\JsonSchema\Errors\ErrorFormatter; +use Opis\JsonSchema\ValidationResult; use Throwable; class FormValidationException extends Exception { + private array $errors; public function __construct( + private readonly ValidationResult $validationResult, string $message = 'Form validation failed', - private readonly array $errors = [], - ?Throwable $previous = null, + ?ErrorFormatter $errorFormatter = null, ) { - parent::__construct(message: $message, previous: $previous); + parent::__construct(message: $message); + $errorFormatter ??= new ErrorFormatter(); + $this->errors = $errorFormatter->format($this->validationResult->error()); } /** @@ -29,16 +34,10 @@ public function report(): bool public function render(): JsonResponse { - $errorMapping = Arr::collapse(array_map( - fn ($error) => [ - $error['property'] => $error['message'], - ], - $this->errors, - )); - return new JsonResponse([ + return new JsonResponse(array_filter([ 'message' => $this->getMessage(), - 'errors' => $errorMapping, - ], 422); + 'errors' => $this->errors, + ]), 422); } /** diff --git a/src/Form/Form.php b/src/Form/Form.php index c944e45..466356d 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -5,9 +5,8 @@ namespace MyParcelCom\JsonSchema\FormBuilder\Form; use ArrayObject; -use JsonSchema\Constraints\Constraint; -use JsonSchema\Validator; -use MyParcelCom\JsonSchema\FormBuilder\Form\Exceptions\FormValidationException; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; +use Opis\JsonSchema\Validator; /** * @extends ArrayObject @@ -18,6 +17,7 @@ public function toJsonSchema(): array { return [ '$schema' => 'https://json-schema.org/draft/2020-12/schema', + 'type' => 'object', 'additionalProperties' => false, 'required' => $this->getRequired(), 'properties' => $this->getProperties()->toArray(), @@ -31,14 +31,10 @@ public function toJsonSchema(): array */ public function validate(array $values): void { - /** - * CHECK_MODE_TYPE_CAST: Enable fuzzy type checking for associative arrays and objects - * src: https://github.com/jsonrainbow/json-schema?tab=readme-ov-file#configuration-options - **/ - $validator = new Validator; - $validator->validate($values, $this->toJsonSchema(), Constraint::CHECK_MODE_TYPE_CAST); - if (!$validator->isValid()) { - throw new FormValidationException("Form validation failed", $validator->getErrors()); + $validator = new Validator(); + $result = $validator->validate(Helper::toJSON($values), Helper::toJSON($this->toJsonSchema())); + if (!$result->isValid()) { + throw new FormValidationException($result); } } } diff --git a/tests/Form/Exceptions/FormValidationExceptionTest.php b/tests/Form/Exceptions/FormValidationExceptionTest.php index beab83f..ef0570e 100644 --- a/tests/Form/Exceptions/FormValidationExceptionTest.php +++ b/tests/Form/Exceptions/FormValidationExceptionTest.php @@ -4,7 +4,11 @@ namespace Tests\Form\Exceptions; +use Mockery; use MyParcelCom\JsonSchema\FormBuilder\Form\Exceptions\FormValidationException; +use Opis\JsonSchema\Errors\ErrorFormatter; +use Opis\JsonSchema\Errors\ValidationError; +use Opis\JsonSchema\ValidationResult; use PHPUnit\Framework\TestCase; use function PHPUnit\Framework\assertEquals; @@ -13,25 +17,21 @@ class FormValidationExceptionTest extends TestCase { public function test_it_renders(): void { - $exception = new FormValidationException( - message: 'Form validation failed', - errors: [ - [ - 'property' => 'foo.bar', - 'message' => 'Foo is required', - ], - [ - 'property' => 'bar.foo', - 'message' => 'Bar is required', - ], - ], - ); + $resultMock = Mockery::mock(ValidationResult::class); + $errorMock = Mockery::mock(ValidationError::class); + $errorFormatterMock = Mockery::mock(ErrorFormatter::class); + + $resultMock->expects('error')->andReturn($errorMock); + + $errorFormatterMock->expects('format')->with($errorMock)->andReturn(['foo.bar' => 'Foo is required', 'bar.foo' => 'Bar is required']); + + $exception = new FormValidationException($resultMock, 'What a failure', $errorFormatterMock); $response = $exception->render(); assertEquals(422, $response->getStatusCode()); assertEquals($response->getData(true), [ - 'message' => 'Form validation failed', + 'message' => 'What a failure', 'errors' => [ 'foo.bar' => 'Foo is required', 'bar.foo' => 'Bar is required', diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index 44b58d9..80b6923 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -136,6 +136,7 @@ public function test_it_converts_to_json_schema(): void { assertEquals([ '$schema' => 'https://json-schema.org/draft/2020-12/schema', + 'type' => 'object', 'additionalProperties' => false, 'required' => ['name_2', 'name_3'], 'properties' => [ From 502d692c040753cb25f7b1431133150b481acacd Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Fri, 21 Nov 2025 16:11:56 +0100 Subject: [PATCH 03/22] Moved validation logic to dedicated module --- src/Form/Form.php | 7 +- .../Exceptions/ValidationException.php} | 6 +- src/Validation/Validator.php | 25 +++ tests/Form/FormTest.php | 69 +------- .../Exceptions/ValidationExceptionTest.php} | 8 +- tests/Validation/ValidatorTest.php | 160 ++++++++++++++++++ 6 files changed, 196 insertions(+), 79 deletions(-) rename src/{Form/Exceptions/FormValidationException.php => Validation/Exceptions/ValidationException.php} (87%) create mode 100644 src/Validation/Validator.php rename tests/{Form/Exceptions/FormValidationExceptionTest.php => Validation/Exceptions/ValidationExceptionTest.php} (79%) create mode 100644 tests/Validation/ValidatorTest.php diff --git a/src/Form/Form.php b/src/Form/Form.php index 466356d..6b14ded 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -5,7 +5,8 @@ namespace MyParcelCom\JsonSchema\FormBuilder\Form; use ArrayObject; -use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\ValidationException; +use Opis\JsonSchema\Helper; use Opis\JsonSchema\Validator; /** @@ -27,14 +28,14 @@ public function toJsonSchema(): array /** * Validate form values against the JSON Schema form * @param array $values a key value array of form values - * @throws FormValidationException + * @throws ValidationException */ public function validate(array $values): void { $validator = new Validator(); $result = $validator->validate(Helper::toJSON($values), Helper::toJSON($this->toJsonSchema())); if (!$result->isValid()) { - throw new FormValidationException($result); + throw new ValidationException($result); } } } diff --git a/src/Form/Exceptions/FormValidationException.php b/src/Validation/Exceptions/ValidationException.php similarity index 87% rename from src/Form/Exceptions/FormValidationException.php rename to src/Validation/Exceptions/ValidationException.php index 6f01830..eca8d17 100644 --- a/src/Form/Exceptions/FormValidationException.php +++ b/src/Validation/Exceptions/ValidationException.php @@ -2,16 +2,14 @@ declare(strict_types=1); -namespace MyParcelCom\JsonSchema\FormBuilder\Form\Exceptions; +namespace MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions; use Exception; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Arr; use Opis\JsonSchema\Errors\ErrorFormatter; use Opis\JsonSchema\ValidationResult; -use Throwable; -class FormValidationException extends Exception +class ValidationException extends Exception { private array $errors; public function __construct( diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php new file mode 100644 index 0000000..aa95dd0 --- /dev/null +++ b/src/Validation/Validator.php @@ -0,0 +1,25 @@ +validate(Helper::toJSON($values), Helper::toJSON($schema)); + if (!$result->isValid()) { + throw new ValidationException($result); + } + } +} diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index 80b6923..e0cb707 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -5,7 +5,6 @@ namespace Tests\Form; use MyParcelCom\JsonSchema\FormBuilder\Form\Checkbox; -use MyParcelCom\JsonSchema\FormBuilder\Form\Exceptions\FormValidationException; use MyParcelCom\JsonSchema\FormBuilder\Form\Form; use MyParcelCom\JsonSchema\FormBuilder\Form\FormElementCollection; use MyParcelCom\JsonSchema\FormBuilder\Form\Group; @@ -25,7 +24,6 @@ class FormTest extends TestCase protected function setUp(): void { parent::setUp(); - $propertyOne = new Text( name: 'name_1', label: 'Label 1', @@ -61,6 +59,7 @@ protected function setUp(): void ), ), ); + dump($this->form->toJsonSchema()); } public function test_it_gets_required(): void @@ -247,70 +246,4 @@ public function test_it_gets_values(): void ], ], $form->getValues()); } - - /** - * @throws FormValidationException - */ - public function test_it_validates(): void - { - $this->expectNotToPerformAssertions(); - $this->form->validate([ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - 'name_4' => [ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - ], - ]); - - $this->form->validate([ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - ]); - } - - public function test_it_fails_to_validate_required_missing(): void - { - $this->expectException(FormValidationException::class); - $this->form->validate([ - 'name_1' => 'value', - 'name_2' => false, - 'name_4' => [ - 'name_1' => 'value', - 'name_3' => 'a', - ], - ]); - } - - public function test_it_fails_to_validate_wrong_property_type(): void - { - $this->expectException(FormValidationException::class); - $this->form->validate([ - 'name_1' => 5, - 'name_2' => 'hello', - 'name_3' => 'a', - 'name_4' => [ - 'name_1' => 'value', - 'name_3' => 'a', - ], - ]); - } - - public function test_it_fails_to_validate_invalid_enum_value(): void - { - $this->expectException(FormValidationException::class); - $this->form->validate([ - 'name_1' => 'value', - 'name_2' => true, - 'name_3' => 'x', - 'name_4' => [ - 'name_1' => 'value', - 'name_2' => true, - 'name_3' => 'y', - ], - ]); - } } diff --git a/tests/Form/Exceptions/FormValidationExceptionTest.php b/tests/Validation/Exceptions/ValidationExceptionTest.php similarity index 79% rename from tests/Form/Exceptions/FormValidationExceptionTest.php rename to tests/Validation/Exceptions/ValidationExceptionTest.php index ef0570e..f28dc65 100644 --- a/tests/Form/Exceptions/FormValidationExceptionTest.php +++ b/tests/Validation/Exceptions/ValidationExceptionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Tests\Form\Exceptions; +namespace Tests\Validation\Exceptions; use Mockery; -use MyParcelCom\JsonSchema\FormBuilder\Form\Exceptions\FormValidationException; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\ValidationException; use Opis\JsonSchema\Errors\ErrorFormatter; use Opis\JsonSchema\Errors\ValidationError; use Opis\JsonSchema\ValidationResult; @@ -13,7 +13,7 @@ use function PHPUnit\Framework\assertEquals; -class FormValidationExceptionTest extends TestCase +class ValidationExceptionTest extends TestCase { public function test_it_renders(): void { @@ -25,7 +25,7 @@ public function test_it_renders(): void $errorFormatterMock->expects('format')->with($errorMock)->andReturn(['foo.bar' => 'Foo is required', 'bar.foo' => 'Bar is required']); - $exception = new FormValidationException($resultMock, 'What a failure', $errorFormatterMock); + $exception = new ValidationException($resultMock, 'What a failure', $errorFormatterMock); $response = $exception->render(); diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php new file mode 100644 index 0000000..d4db43c --- /dev/null +++ b/tests/Validation/ValidatorTest.php @@ -0,0 +1,160 @@ +schema = [ + '$schema' => 'https://json-schema.org/draft/2020-12/schema', + 'type' => 'object', + 'additionalProperties' => false, + 'required' => [ + 0 => 'name_2', + 1 => 'name_3', + ], + 'properties' => [ + 'name_1' => [ + 'type' => 'string', + 'description' => 'Label 1', + ], + 'name_2' => [ + 'type' => 'boolean', + 'description' => 'Label 2', + 'meta' => [ + 'help' => 'Assistance is required', + ], + ], + 'name_3' => [ + 'type' => 'string', + 'description' => 'Label 3', + 'enum' => [ + 'a', + 'b', + ], + 'meta' => [ + 'help' => 'This field has no purpose', + 'field_type' => 'radio', + 'enum_labels' => [ + 'a' => 'A', + 'b' => 'B', + ], + ], + ], + 'name_4' => [ + 'type' => 'object', + 'description' => 'Label 4', + 'required' => [ + 0 => 'name_2', + 1 => 'name_3', + ], + 'properties' => [ + 'name_1' => [ + 'type' => 'string', + 'description' => 'Label 1', + ], + 'name_2' => [ + 'type' => 'boolean', + 'description' => 'Label 2', + 'meta' => [ + 'help' => 'Assistance is required', + ], + ], + 'name_3' => [ + 'type' => 'string', + 'description' => 'Label 3', + 'enum' => [ + 'a', + 'b', + ], + 'meta' => [ + 'help' => 'This field has no purpose', + 'field_type' => 'radio', + 'enum_labels' => [ + 'a' => 'A', + 'b' => 'B', + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @throws ValidationException + */ + public function test_it_validates(): void + { + $this->expectNotToPerformAssertions(); + Validator::validate([ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + 'name_4' => [ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + ], + ], $this->schema); + + Validator::validate([ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + ], $this->schema); + } + + public function test_it_fails_to_validate_required_missing(): void + { + $this->expectException(ValidationException::class); + Validator::validate([ + 'name_1' => 'value', + 'name_2' => false, + 'name_4' => [ + 'name_1' => 'value', + 'name_3' => 'a', + ], + ], $this->schema); + } + + public function test_it_fails_to_validate_wrong_property_type(): void + { + $this->expectException(ValidationException::class); + Validator::validate([ + 'name_1' => 5, + 'name_2' => 'hello', + 'name_3' => 'a', + 'name_4' => [ + 'name_1' => 'value', + 'name_3' => 'a', + ], + ], $this->schema); + } + + public function test_it_fails_to_validate_invalid_enum_value(): void + { + $this->expectException(ValidationException::class); + Validator::validate([ + 'name_1' => 'value', + 'name_2' => true, + 'name_3' => 'x', + 'name_4' => [ + 'name_1' => 'value', + 'name_2' => true, + 'name_3' => 'y', + ], + ], $this->schema); + } + +} From d468a31aeabe841c619702cd1cc113c66d258d8b Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Mon, 24 Nov 2025 11:39:27 +0100 Subject: [PATCH 04/22] updated ->validate() --- src/Form/Form.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Form/Form.php b/src/Form/Form.php index 6b14ded..6816695 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -6,8 +6,7 @@ use ArrayObject; use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\ValidationException; -use Opis\JsonSchema\Helper; -use Opis\JsonSchema\Validator; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; /** * @extends ArrayObject @@ -32,10 +31,6 @@ public function toJsonSchema(): array */ public function validate(array $values): void { - $validator = new Validator(); - $result = $validator->validate(Helper::toJSON($values), Helper::toJSON($this->toJsonSchema())); - if (!$result->isValid()) { - throw new ValidationException($result); - } + Validator::validate($values, $this->toJsonSchema()); } } From 422c4a61c5361ce07f7c0226c81c5351ac694ec9 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 13:35:24 +0100 Subject: [PATCH 05/22] drop redundant class property --- src/Validation/Exceptions/ValidationException.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Validation/Exceptions/ValidationException.php b/src/Validation/Exceptions/ValidationException.php index eca8d17..380a95e 100644 --- a/src/Validation/Exceptions/ValidationException.php +++ b/src/Validation/Exceptions/ValidationException.php @@ -13,13 +13,13 @@ class ValidationException extends Exception { private array $errors; public function __construct( - private readonly ValidationResult $validationResult, + ValidationResult $validationResult, string $message = 'Form validation failed', ?ErrorFormatter $errorFormatter = null, ) { parent::__construct(message: $message); $errorFormatter ??= new ErrorFormatter(); - $this->errors = $errorFormatter->format($this->validationResult->error()); + $this->errors = $errorFormatter->format($validationResult->error()); } /** From b98065070cbc07eb9ed4b2f9430f8cbf50359b65 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 13:42:52 +0100 Subject: [PATCH 06/22] return $result instead of throwing exception in validator + handle exception directly in the Form class --- src/Form/Form.php | 5 ++++- src/Validation/Exceptions/ValidationException.php | 3 +++ src/Validation/Validator.php | 13 +++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Form/Form.php b/src/Form/Form.php index 6816695..f371a9d 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -31,6 +31,9 @@ public function toJsonSchema(): array */ public function validate(array $values): void { - Validator::validate($values, $this->toJsonSchema()); + $result = Validator::validate($values, $this->toJsonSchema()); + if(!$result->isValid()) { + throw new ValidationException($result); + } } } diff --git a/src/Validation/Exceptions/ValidationException.php b/src/Validation/Exceptions/ValidationException.php index 380a95e..0e71dae 100644 --- a/src/Validation/Exceptions/ValidationException.php +++ b/src/Validation/Exceptions/ValidationException.php @@ -9,6 +9,9 @@ use Opis\JsonSchema\Errors\ErrorFormatter; use Opis\JsonSchema\ValidationResult; +/** + * Exception thrown when form validation fails. + */ class ValidationException extends Exception { private array $errors; diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index aa95dd0..772a73b 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -4,22 +4,19 @@ namespace MyParcelCom\JsonSchema\FormBuilder\Validation; -use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\ValidationException; use Opis\JsonSchema\Helper; +use Opis\JsonSchema\ValidationResult; use Opis\JsonSchema\Validator as JsonSchemaValidator; class Validator { /** - * Validate form values against a JSON Schema - * @throws ValidationException + * Validate form values against a JSON Schema. Returns a ValidationResult. */ - public static function validate(array $values, array $schema): void + public static function validate(array $values, array $schema): ValidationResult { $validator = new JsonSchemaValidator(); - $result = $validator->validate(Helper::toJSON($values), Helper::toJSON($schema)); - if (!$result->isValid()) { - throw new ValidationException($result); - } + + return $validator->validate(Helper::toJSON($values), Helper::toJSON($schema)); } } From c83b0d67f01f5bc173bf575c839b45d69f731110 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 13:43:25 +0100 Subject: [PATCH 07/22] delete dump --- tests/Form/FormTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index e0cb707..3072ba6 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -59,7 +59,6 @@ protected function setUp(): void ), ), ); - dump($this->form->toJsonSchema()); } public function test_it_gets_required(): void From b9fd2336671f32eef2780d534e88ecbddc38bd4e Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 13:51:12 +0100 Subject: [PATCH 08/22] Brought back tests for the Form class validation --- src/Form/Form.php | 6 +- ...eption.php => FormValidationException.php} | 2 +- tests/Form/FormTest.php | 67 +++++++++++++++++++ .../Exceptions/ValidationExceptionTest.php | 4 +- tests/Validation/ValidatorTest.php | 39 +++++------ 5 files changed, 90 insertions(+), 28 deletions(-) rename src/Validation/Exceptions/{ValidationException.php => FormValidationException.php} (96%) diff --git a/src/Form/Form.php b/src/Form/Form.php index f371a9d..5303f0d 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -5,7 +5,7 @@ namespace MyParcelCom\JsonSchema\FormBuilder\Form; use ArrayObject; -use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\ValidationException; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; /** @@ -27,13 +27,13 @@ public function toJsonSchema(): array /** * Validate form values against the JSON Schema form * @param array $values a key value array of form values - * @throws ValidationException + * @throws FormValidationException */ public function validate(array $values): void { $result = Validator::validate($values, $this->toJsonSchema()); if(!$result->isValid()) { - throw new ValidationException($result); + throw new FormValidationException($result); } } } diff --git a/src/Validation/Exceptions/ValidationException.php b/src/Validation/Exceptions/FormValidationException.php similarity index 96% rename from src/Validation/Exceptions/ValidationException.php rename to src/Validation/Exceptions/FormValidationException.php index 0e71dae..f45a396 100644 --- a/src/Validation/Exceptions/ValidationException.php +++ b/src/Validation/Exceptions/FormValidationException.php @@ -12,7 +12,7 @@ /** * Exception thrown when form validation fails. */ -class ValidationException extends Exception +class FormValidationException extends Exception { private array $errors; public function __construct( diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index 3072ba6..4ff6992 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -12,6 +12,7 @@ use MyParcelCom\JsonSchema\FormBuilder\Form\OptionCollection; use MyParcelCom\JsonSchema\FormBuilder\Form\RadioButtons; use MyParcelCom\JsonSchema\FormBuilder\Form\Text; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use PHPUnit\Framework\TestCase; use function PHPUnit\Framework\assertEquals; @@ -245,4 +246,70 @@ public function test_it_gets_values(): void ], ], $form->getValues()); } + + /** + * @throws FormValidationException + */ + public function test_it_validates(): void + { + $this->expectNotToPerformAssertions(); + $this->form->validate([ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + 'name_4' => [ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + ], + ]); + + $this->form->validate([ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + ]); + } + + public function test_it_fails_to_validate_required_missing(): void + { + $this->expectException(FormValidationException::class); + $this->form->validate([ + 'name_1' => 'value', + 'name_2' => false, + 'name_4' => [ + 'name_1' => 'value', + 'name_3' => 'a', + ], + ]); + } + + public function test_it_fails_to_validate_wrong_property_type(): void + { + $this->expectException(FormValidationException::class); + $this->form->validate([ + 'name_1' => 5, + 'name_2' => 'hello', + 'name_3' => 'a', + 'name_4' => [ + 'name_1' => 'value', + 'name_3' => 'a', + ], + ]); + } + + public function test_it_fails_to_validate_invalid_enum_value(): void + { + $this->expectException(FormValidationException::class); + $this->form->validate([ + 'name_1' => 'value', + 'name_2' => true, + 'name_3' => 'x', + 'name_4' => [ + 'name_1' => 'value', + 'name_2' => true, + 'name_3' => 'y', + ], + ]); + } } diff --git a/tests/Validation/Exceptions/ValidationExceptionTest.php b/tests/Validation/Exceptions/ValidationExceptionTest.php index f28dc65..6a7410c 100644 --- a/tests/Validation/Exceptions/ValidationExceptionTest.php +++ b/tests/Validation/Exceptions/ValidationExceptionTest.php @@ -5,7 +5,7 @@ namespace Tests\Validation\Exceptions; use Mockery; -use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\ValidationException; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use Opis\JsonSchema\Errors\ErrorFormatter; use Opis\JsonSchema\Errors\ValidationError; use Opis\JsonSchema\ValidationResult; @@ -25,7 +25,7 @@ public function test_it_renders(): void $errorFormatterMock->expects('format')->with($errorMock)->andReturn(['foo.bar' => 'Foo is required', 'bar.foo' => 'Bar is required']); - $exception = new ValidationException($resultMock, 'What a failure', $errorFormatterMock); + $exception = new FormValidationException($resultMock, 'What a failure', $errorFormatterMock); $response = $exception->render(); diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index d4db43c..823385e 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -4,7 +4,7 @@ namespace Tests\Validation; -use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\ValidationException; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; use PHPUnit\Framework\TestCase; @@ -92,46 +92,43 @@ protected function setUp(): void } /** - * @throws ValidationException */ public function test_it_validates(): void { - $this->expectNotToPerformAssertions(); - Validator::validate([ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - 'name_4' => [ + $this->assertTrue( + Validator::validate([ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', - ], - ], $this->schema); + 'name_4' => [ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + ], + ], $this->schema)->isValid()); - Validator::validate([ + $this->assertTrue(Validator::validate([ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', - ], $this->schema); + ], $this->schema)->isValid()); } public function test_it_fails_to_validate_required_missing(): void { - $this->expectException(ValidationException::class); - Validator::validate([ + $this->assertFalse(Validator::validate([ 'name_1' => 'value', 'name_2' => false, 'name_4' => [ 'name_1' => 'value', 'name_3' => 'a', ], - ], $this->schema); + ], $this->schema)->isValid()); } public function test_it_fails_to_validate_wrong_property_type(): void { - $this->expectException(ValidationException::class); - Validator::validate([ + $this->assertFalse(Validator::validate([ 'name_1' => 5, 'name_2' => 'hello', 'name_3' => 'a', @@ -139,13 +136,12 @@ public function test_it_fails_to_validate_wrong_property_type(): void 'name_1' => 'value', 'name_3' => 'a', ], - ], $this->schema); + ], $this->schema)->isValid()); } public function test_it_fails_to_validate_invalid_enum_value(): void { - $this->expectException(ValidationException::class); - Validator::validate([ + $this->assertFalse(Validator::validate([ 'name_1' => 'value', 'name_2' => true, 'name_3' => 'x', @@ -154,7 +150,6 @@ public function test_it_fails_to_validate_invalid_enum_value(): void 'name_2' => true, 'name_3' => 'y', ], - ], $this->schema); + ], $this->schema)->isValid()); } - } From 346ee33fa476179f46f11132d1c1b82574aaa733 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 13:52:20 +0100 Subject: [PATCH 09/22] remove TODO --- tests/Validation/ValidatorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index 823385e..14634eb 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -13,7 +13,7 @@ class ValidatorTest extends TestCase protected array $schema; protected function setUp(): void { - parent::setUp(); // TODO: Change the autogenerated stub + parent::setUp(); $this->schema = [ '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'type' => 'object', From 8fd7deaae1a0cfa1d685ba366036cdbf20f3da96 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 13:52:45 +0100 Subject: [PATCH 10/22] undo new line change --- tests/Form/FormTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index 4ff6992..b4b86a3 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -25,6 +25,7 @@ class FormTest extends TestCase protected function setUp(): void { parent::setUp(); + $propertyOne = new Text( name: 'name_1', label: 'Label 1', From 736a70ae6c1d843975c49faf377d4336f2b328f1 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 15:26:21 +0100 Subject: [PATCH 11/22] Added error message assertions --- ...st.php => FormValidationExceptionTest.php} | 2 +- tests/Validation/ValidatorTest.php | 66 ++++++++++++++----- 2 files changed, 51 insertions(+), 17 deletions(-) rename tests/Validation/Exceptions/{ValidationExceptionTest.php => FormValidationExceptionTest.php} (96%) diff --git a/tests/Validation/Exceptions/ValidationExceptionTest.php b/tests/Validation/Exceptions/FormValidationExceptionTest.php similarity index 96% rename from tests/Validation/Exceptions/ValidationExceptionTest.php rename to tests/Validation/Exceptions/FormValidationExceptionTest.php index 6a7410c..a0c61e7 100644 --- a/tests/Validation/Exceptions/ValidationExceptionTest.php +++ b/tests/Validation/Exceptions/FormValidationExceptionTest.php @@ -13,7 +13,7 @@ use function PHPUnit\Framework\assertEquals; -class ValidationExceptionTest extends TestCase +class FormValidationExceptionTest extends TestCase { public function test_it_renders(): void { diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index 14634eb..1f3cada 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -6,6 +6,7 @@ use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; +use Opis\JsonSchema\Errors\ErrorFormatter; use PHPUnit\Framework\TestCase; class ValidatorTest extends TestCase @@ -95,40 +96,52 @@ protected function setUp(): void */ public function test_it_validates(): void { - $this->assertTrue( - Validator::validate([ + $validationResult = Validator::validate([ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + 'name_4' => [ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', - 'name_4' => [ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - ], - ], $this->schema)->isValid()); + ], + ], $this->schema); + + $this->assertTrue($validationResult->isValid()); + $this->assertFalse($validationResult->hasError()); + $this->assertNull($validationResult->error()); - $this->assertTrue(Validator::validate([ + $validationResult = Validator::validate([ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', - ], $this->schema)->isValid()); + ], $this->schema); + + $this->assertTrue($validationResult->isValid()); + $this->assertFalse($validationResult->hasError()); + $this->assertNull($validationResult->error()); } public function test_it_fails_to_validate_required_missing(): void { - $this->assertFalse(Validator::validate([ + $validationResult = Validator::validate([ 'name_1' => 'value', 'name_2' => false, 'name_4' => [ 'name_1' => 'value', 'name_3' => 'a', ], - ], $this->schema)->isValid()); + ], $this->schema); + + $errors = $this->formatErrorsFromValidationResult($validationResult); + + $this->assertFalse($validationResult->isValid()); + $this->assertContains('The required properties (name_3) are missing', $errors['/']); } public function test_it_fails_to_validate_wrong_property_type(): void { - $this->assertFalse(Validator::validate([ + $validationResult = Validator::validate([ 'name_1' => 5, 'name_2' => 'hello', 'name_3' => 'a', @@ -136,12 +149,17 @@ public function test_it_fails_to_validate_wrong_property_type(): void 'name_1' => 'value', 'name_3' => 'a', ], - ], $this->schema)->isValid()); + ], $this->schema); + + $errors = $this->formatErrorsFromValidationResult($validationResult); + + $this->assertFalse($validationResult->isValid()); + $this->assertContains('The data (integer) must match the type: string', $errors['/name_1']); } public function test_it_fails_to_validate_invalid_enum_value(): void { - $this->assertFalse(Validator::validate([ + $validationResult = Validator::validate([ 'name_1' => 'value', 'name_2' => true, 'name_3' => 'x', @@ -150,6 +168,22 @@ public function test_it_fails_to_validate_invalid_enum_value(): void 'name_2' => true, 'name_3' => 'y', ], - ], $this->schema)->isValid()); + ], $this->schema); + + $errors = $this->formatErrorsFromValidationResult($validationResult); + + $this->assertFalse($validationResult->isValid()); + $this->assertContains('The data should match one item from enum', $errors['/name_3']); + } + + /** + * Helper function to format errors from a ValidationResult as described in opis/json-schema (used in FormValidationException). + * @param $validationResult + * @return array + */ + private function formatErrorsFromValidationResult($validationResult): array + { + $errorFormatter ??= new ErrorFormatter(); + return $errorFormatter->format($validationResult->error()); } } From ddc87bee02fb1f635208c2426546aeac9369ae93 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 17:50:09 +0100 Subject: [PATCH 12/22] Opis json schema now gets mocked instead of called directly --- src/Validation/Validator.php | 4 +- tests/Validation/ValidatorTest.php | 94 ++++-------------------------- 2 files changed, 14 insertions(+), 84 deletions(-) diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index 772a73b..e398eac 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -13,9 +13,9 @@ class Validator /** * Validate form values against a JSON Schema. Returns a ValidationResult. */ - public static function validate(array $values, array $schema): ValidationResult + public static function validate(array $values, array $schema, ?JsonSchemaValidator $validator = null): ValidationResult { - $validator = new JsonSchemaValidator(); + $validator ??= new JsonSchemaValidator(); return $validator->validate(Helper::toJSON($values), Helper::toJSON($schema)); } diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index 1f3cada..0944370 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -4,10 +4,11 @@ namespace Tests\Validation; -use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; +use Mockery; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; -use Opis\JsonSchema\Errors\ErrorFormatter; +use Opis\JsonSchema\ValidationResult; use PHPUnit\Framework\TestCase; +use Opis\JsonSchema\Validator as JsonSchemaValidator; class ValidatorTest extends TestCase { @@ -96,94 +97,23 @@ protected function setUp(): void */ public function test_it_validates(): void { - $validationResult = Validator::validate([ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - 'name_4' => [ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - ], - ], $this->schema); + $this->expectNotToPerformAssertions(); + $validationResult = Mockery::mock(ValidationResult::class); + $validationResult->expects('isValid'); + $validationResult->expects('error'); - $this->assertTrue($validationResult->isValid()); - $this->assertFalse($validationResult->hasError()); - $this->assertNull($validationResult->error()); + $jsonSchemaValidator = Mockery::mock(JsonSchemaValidator::class); + $jsonSchemaValidator->expects('validate')->andReturn($validationResult); - $validationResult = Validator::validate([ + Validator::validate([ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', - ], $this->schema); - - $this->assertTrue($validationResult->isValid()); - $this->assertFalse($validationResult->hasError()); - $this->assertNull($validationResult->error()); - } - - public function test_it_fails_to_validate_required_missing(): void - { - $validationResult = Validator::validate([ - 'name_1' => 'value', - 'name_2' => false, - 'name_4' => [ - 'name_1' => 'value', - 'name_3' => 'a', - ], - ], $this->schema); - - $errors = $this->formatErrorsFromValidationResult($validationResult); - - $this->assertFalse($validationResult->isValid()); - $this->assertContains('The required properties (name_3) are missing', $errors['/']); - } - - public function test_it_fails_to_validate_wrong_property_type(): void - { - $validationResult = Validator::validate([ - 'name_1' => 5, - 'name_2' => 'hello', - 'name_3' => 'a', 'name_4' => [ 'name_1' => 'value', + 'name_2' => false, 'name_3' => 'a', ], - ], $this->schema); - - $errors = $this->formatErrorsFromValidationResult($validationResult); - - $this->assertFalse($validationResult->isValid()); - $this->assertContains('The data (integer) must match the type: string', $errors['/name_1']); - } - - public function test_it_fails_to_validate_invalid_enum_value(): void - { - $validationResult = Validator::validate([ - 'name_1' => 'value', - 'name_2' => true, - 'name_3' => 'x', - 'name_4' => [ - 'name_1' => 'value', - 'name_2' => true, - 'name_3' => 'y', - ], - ], $this->schema); - - $errors = $this->formatErrorsFromValidationResult($validationResult); - - $this->assertFalse($validationResult->isValid()); - $this->assertContains('The data should match one item from enum', $errors['/name_3']); - } - - /** - * Helper function to format errors from a ValidationResult as described in opis/json-schema (used in FormValidationException). - * @param $validationResult - * @return array - */ - private function formatErrorsFromValidationResult($validationResult): array - { - $errorFormatter ??= new ErrorFormatter(); - return $errorFormatter->format($validationResult->error()); + ], $this->schema, $jsonSchemaValidator); } } From 28020d9442f10741a05f5305169bea1343aa580f Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 26 Nov 2025 17:51:31 +0100 Subject: [PATCH 13/22] added assertion that returned result is the same as the mocked one --- tests/Validation/ValidatorTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index 0944370..7c9db89 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -97,7 +97,6 @@ protected function setUp(): void */ public function test_it_validates(): void { - $this->expectNotToPerformAssertions(); $validationResult = Mockery::mock(ValidationResult::class); $validationResult->expects('isValid'); $validationResult->expects('error'); @@ -105,7 +104,7 @@ public function test_it_validates(): void $jsonSchemaValidator = Mockery::mock(JsonSchemaValidator::class); $jsonSchemaValidator->expects('validate')->andReturn($validationResult); - Validator::validate([ + $result = Validator::validate([ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', @@ -115,5 +114,7 @@ public function test_it_validates(): void 'name_3' => 'a', ], ], $this->schema, $jsonSchemaValidator); + + $this->assertSame($result, $validationResult); } } From 4c147c39e6b0ce5dd05e53b21e9176afe358f34f Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Thu, 27 Nov 2025 15:26:02 +0100 Subject: [PATCH 14/22] improved tests --- src/Form/Form.php | 8 ++-- src/Validation/Validator.php | 4 +- tests/Form/FormTest.php | 66 +++++++++++++----------------- tests/Validation/ValidatorTest.php | 12 +++--- 4 files changed, 43 insertions(+), 47 deletions(-) diff --git a/src/Form/Form.php b/src/Form/Form.php index 5303f0d..5afca9a 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -7,6 +7,7 @@ use ArrayObject; use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; +use Opis\JsonSchema\Errors\ErrorFormatter; /** * @extends ArrayObject @@ -29,11 +30,12 @@ public function toJsonSchema(): array * @param array $values a key value array of form values * @throws FormValidationException */ - public function validate(array $values): void + public function validate(array $values, ?Validator $validator = null, ?ErrorFormatter $errorFormatter = null): void { - $result = Validator::validate($values, $this->toJsonSchema()); + $validator ??= new Validator(); + $result = $validator->validate($values, $this->toJsonSchema()); if(!$result->isValid()) { - throw new FormValidationException($result); + throw new FormValidationException(validationResult: $result, errorFormatter: $errorFormatter); } } } diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index e398eac..4bc2f48 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -13,9 +13,9 @@ class Validator /** * Validate form values against a JSON Schema. Returns a ValidationResult. */ - public static function validate(array $values, array $schema, ?JsonSchemaValidator $validator = null): ValidationResult + public function validate(array $values, array $schema): ValidationResult { - $validator ??= new JsonSchemaValidator(); + $validator = new JsonSchemaValidator(); return $validator->validate(Helper::toJSON($values), Helper::toJSON($schema)); } diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index b4b86a3..ce2f248 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -4,6 +4,7 @@ namespace Tests\Form; +use Mockery; use MyParcelCom\JsonSchema\FormBuilder\Form\Checkbox; use MyParcelCom\JsonSchema\FormBuilder\Form\Form; use MyParcelCom\JsonSchema\FormBuilder\Form\FormElementCollection; @@ -13,6 +14,10 @@ use MyParcelCom\JsonSchema\FormBuilder\Form\RadioButtons; use MyParcelCom\JsonSchema\FormBuilder\Form\Text; use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; +use Opis\JsonSchema\Errors\ErrorFormatter; +use Opis\JsonSchema\Errors\ValidationError; +use Opis\JsonSchema\ValidationResult; use PHPUnit\Framework\TestCase; use function PHPUnit\Framework\assertEquals; @@ -251,9 +256,16 @@ public function test_it_gets_values(): void /** * @throws FormValidationException */ - public function test_it_validates(): void + public function test_it_validates_success(): void { $this->expectNotToPerformAssertions(); + + $validationResult = Mockery::mock(ValidationResult::class); + $validationResult->expects('isValid')->andReturns(true); + + $validator = Mockery::mock(Validator::class); + $validator->expects('validate')->andReturn($validationResult); + $this->form->validate([ 'name_1' => 'value', 'name_2' => false, @@ -264,53 +276,33 @@ public function test_it_validates(): void 'name_3' => 'a', ], ]); - - $this->form->validate([ - 'name_1' => 'value', - 'name_2' => false, - 'name_3' => 'a', - ]); } - public function test_it_fails_to_validate_required_missing(): void + public function test_it_validates_failure(): void { $this->expectException(FormValidationException::class); + + $errorMock = Mockery::mock(ValidationError::class); + + $validationResult = Mockery::mock(ValidationResult::class); + $validationResult->expects('isValid')->andReturns(false); + $validationResult->expects('error')->andReturn($errorMock); + + $errorFormatterMock = Mockery::mock(ErrorFormatter::class); + $errorFormatterMock->expects('format')->with($errorMock); + + $validator = Mockery::mock(Validator::class); + $validator->expects('validate')->andReturn($validationResult); + $this->form->validate([ 'name_1' => 'value', 'name_2' => false, - 'name_4' => [ - 'name_1' => 'value', - 'name_3' => 'a', - ], - ]); - } - - public function test_it_fails_to_validate_wrong_property_type(): void - { - $this->expectException(FormValidationException::class); - $this->form->validate([ - 'name_1' => 5, - 'name_2' => 'hello', 'name_3' => 'a', 'name_4' => [ 'name_1' => 'value', + 'name_2' => false, 'name_3' => 'a', ], - ]); - } - - public function test_it_fails_to_validate_invalid_enum_value(): void - { - $this->expectException(FormValidationException::class); - $this->form->validate([ - 'name_1' => 'value', - 'name_2' => true, - 'name_3' => 'x', - 'name_4' => [ - 'name_1' => 'value', - 'name_2' => true, - 'name_3' => 'y', - ], - ]); + ], $validator, $errorFormatterMock); } } diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index 7c9db89..df3df90 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -12,10 +12,12 @@ class ValidatorTest extends TestCase { + protected Validator $validator; protected array $schema; protected function setUp(): void { parent::setUp(); + $this->validator = new Validator(); $this->schema = [ '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'type' => 'object', @@ -97,14 +99,16 @@ protected function setUp(): void */ public function test_it_validates(): void { + $this->expectNotToPerformAssertions(); + $validationResult = Mockery::mock(ValidationResult::class); $validationResult->expects('isValid'); $validationResult->expects('error'); $jsonSchemaValidator = Mockery::mock(JsonSchemaValidator::class); - $jsonSchemaValidator->expects('validate')->andReturn($validationResult); + $jsonSchemaValidator->expects('validate')->with()->andReturn($validationResult); - $result = Validator::validate([ + $this->validator->validate([ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', @@ -113,8 +117,6 @@ public function test_it_validates(): void 'name_2' => false, 'name_3' => 'a', ], - ], $this->schema, $jsonSchemaValidator); - - $this->assertSame($result, $validationResult); + ], $this->schema); } } From 2eca39fbb45908858d64289337c55038f2ce5718 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Mon, 1 Dec 2025 16:43:10 +0100 Subject: [PATCH 15/22] Refactor code for better testability --- src/Form/Form.php | 9 ++-- .../Exceptions/FormValidationException.php | 8 +-- src/Validation/Validator.php | 24 +++++++-- tests/Form/FormTest.php | 26 +++------- .../FormValidationExceptionTest.php | 13 ++--- tests/Validation/ValidatorTest.php | 49 +++++++++---------- 6 files changed, 61 insertions(+), 68 deletions(-) diff --git a/src/Form/Form.php b/src/Form/Form.php index 5afca9a..7ad6c3b 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -30,12 +30,11 @@ public function toJsonSchema(): array * @param array $values a key value array of form values * @throws FormValidationException */ - public function validate(array $values, ?Validator $validator = null, ?ErrorFormatter $errorFormatter = null): void + public function validate(array $values, ?Validator $validator = null): void { - $validator ??= new Validator(); - $result = $validator->validate($values, $this->toJsonSchema()); - if(!$result->isValid()) { - throw new FormValidationException(validationResult: $result, errorFormatter: $errorFormatter); + $validator ??= new Validator($values, $this->toJsonSchema()); + if(!$validator->isValid()) { + throw new FormValidationException(errors: $validator->getErrors()); } } } diff --git a/src/Validation/Exceptions/FormValidationException.php b/src/Validation/Exceptions/FormValidationException.php index f45a396..974cbe0 100644 --- a/src/Validation/Exceptions/FormValidationException.php +++ b/src/Validation/Exceptions/FormValidationException.php @@ -14,15 +14,11 @@ */ class FormValidationException extends Exception { - private array $errors; public function __construct( - ValidationResult $validationResult, string $message = 'Form validation failed', - ?ErrorFormatter $errorFormatter = null, + private readonly ?array $errors = null, ) { - parent::__construct(message: $message); - $errorFormatter ??= new ErrorFormatter(); - $this->errors = $errorFormatter->format($validationResult->error()); + parent::__construct($message); } /** diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index 4bc2f48..ec83d6e 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -4,19 +4,37 @@ namespace MyParcelCom\JsonSchema\FormBuilder\Validation; +use Opis\JsonSchema\Errors\ErrorFormatter; use Opis\JsonSchema\Helper; use Opis\JsonSchema\ValidationResult; use Opis\JsonSchema\Validator as JsonSchemaValidator; class Validator { + public function __construct( + private readonly array $values, + private readonly array $schema, + ) { + } + /** - * Validate form values against a JSON Schema. Returns a ValidationResult. + * Validate form the values against the JSON Schema. Returns a ValidationResult. */ - public function validate(array $values, array $schema): ValidationResult + public function isValid(): bool + { + return $this->getValidationResult()->isValid(); + } + public function getErrors(): array + { + return $this->getValidationResult()->hasError() + ? new ErrorFormatter()->format($this->getValidationResult()->error()) + : []; + } + + private function getValidationResult(): ValidationResult { $validator = new JsonSchemaValidator(); - return $validator->validate(Helper::toJSON($values), Helper::toJSON($schema)); + return $validator->validate(Helper::toJSON($this->values), Helper::toJSON($this->schema)); } } diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index ce2f248..3272c2e 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -15,9 +15,6 @@ use MyParcelCom\JsonSchema\FormBuilder\Form\Text; use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; -use Opis\JsonSchema\Errors\ErrorFormatter; -use Opis\JsonSchema\Errors\ValidationError; -use Opis\JsonSchema\ValidationResult; use PHPUnit\Framework\TestCase; use function PHPUnit\Framework\assertEquals; @@ -260,11 +257,10 @@ public function test_it_validates_success(): void { $this->expectNotToPerformAssertions(); - $validationResult = Mockery::mock(ValidationResult::class); - $validationResult->expects('isValid')->andReturns(true); - $validator = Mockery::mock(Validator::class); - $validator->expects('validate')->andReturn($validationResult); + + $validator->expects('isValid')->andReturn(true); + $validator->expects('getErrors')->never(); $this->form->validate([ 'name_1' => 'value', @@ -275,24 +271,16 @@ public function test_it_validates_success(): void 'name_2' => false, 'name_3' => 'a', ], - ]); + ], $validator); } public function test_it_validates_failure(): void { $this->expectException(FormValidationException::class); - $errorMock = Mockery::mock(ValidationError::class); - - $validationResult = Mockery::mock(ValidationResult::class); - $validationResult->expects('isValid')->andReturns(false); - $validationResult->expects('error')->andReturn($errorMock); - - $errorFormatterMock = Mockery::mock(ErrorFormatter::class); - $errorFormatterMock->expects('format')->with($errorMock); - $validator = Mockery::mock(Validator::class); - $validator->expects('validate')->andReturn($validationResult); + $validator->expects('isValid')->andReturn(false); + $validator->expects('getErrors'); $this->form->validate([ 'name_1' => 'value', @@ -303,6 +291,6 @@ public function test_it_validates_failure(): void 'name_2' => false, 'name_3' => 'a', ], - ], $validator, $errorFormatterMock); + ], $validator); } } diff --git a/tests/Validation/Exceptions/FormValidationExceptionTest.php b/tests/Validation/Exceptions/FormValidationExceptionTest.php index a0c61e7..3e7d6c1 100644 --- a/tests/Validation/Exceptions/FormValidationExceptionTest.php +++ b/tests/Validation/Exceptions/FormValidationExceptionTest.php @@ -17,15 +17,10 @@ class FormValidationExceptionTest extends TestCase { public function test_it_renders(): void { - $resultMock = Mockery::mock(ValidationResult::class); - $errorMock = Mockery::mock(ValidationError::class); - $errorFormatterMock = Mockery::mock(ErrorFormatter::class); - - $resultMock->expects('error')->andReturn($errorMock); - - $errorFormatterMock->expects('format')->with($errorMock)->andReturn(['foo.bar' => 'Foo is required', 'bar.foo' => 'Bar is required']); - - $exception = new FormValidationException($resultMock, 'What a failure', $errorFormatterMock); + $exception = new FormValidationException('What a failure', [ + 'foo.bar' => 'Foo is required', + 'bar.foo' => 'Bar is required', + ]); $response = $exception->render(); diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index df3df90..6ca0019 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -6,26 +6,17 @@ use Mockery; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; -use Opis\JsonSchema\ValidationResult; use PHPUnit\Framework\TestCase; -use Opis\JsonSchema\Validator as JsonSchemaValidator; class ValidatorTest extends TestCase { - protected Validator $validator; - protected array $schema; - protected function setUp(): void + public function test_it_validates(): void { - parent::setUp(); - $this->validator = new Validator(); - $this->schema = [ + $schema = [ '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'type' => 'object', 'additionalProperties' => false, - 'required' => [ - 0 => 'name_2', - 1 => 'name_3', - ], + 'required' => ['name_2', 'name_3'], 'properties' => [ 'name_1' => [ 'type' => 'string', @@ -93,30 +84,36 @@ protected function setUp(): void ], ], ]; - } - /** - */ - public function test_it_validates(): void - { - $this->expectNotToPerformAssertions(); + $values = [ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + 'name_4' => [ + 'name_1' => 'value', + 'name_2' => false, + 'name_3' => 'a', + ], + ]; - $validationResult = Mockery::mock(ValidationResult::class); - $validationResult->expects('isValid'); - $validationResult->expects('error'); + $validator = new Validator($values, $schema); + self::assertTrue($validator->isValid()); + dump($validator->getErrors()); - $jsonSchemaValidator = Mockery::mock(JsonSchemaValidator::class); - $jsonSchemaValidator->expects('validate')->with()->andReturn($validationResult); + self::assertContains('name_1', $validator->getErrors()); - $this->validator->validate([ + $values = [ 'name_1' => 'value', - 'name_2' => false, 'name_3' => 'a', 'name_4' => [ 'name_1' => 'value', 'name_2' => false, 'name_3' => 'a', ], - ], $this->schema); + ]; + + $validator = new Validator($values, $schema); + self::assertFalse($validator->isValid()); + } } From dc8e9e894fdca1a243df4e0c5bf3c90fe48887d0 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Mon, 1 Dec 2025 16:47:27 +0100 Subject: [PATCH 16/22] Fixed validator test --- tests/Validation/ValidatorTest.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index 6ca0019..4bc9a65 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -98,9 +98,7 @@ public function test_it_validates(): void $validator = new Validator($values, $schema); self::assertTrue($validator->isValid()); - dump($validator->getErrors()); - - self::assertContains('name_1', $validator->getErrors()); + self::assertEquals([], $validator->getErrors()); $values = [ 'name_1' => 'value', @@ -114,6 +112,9 @@ public function test_it_validates(): void $validator = new Validator($values, $schema); self::assertFalse($validator->isValid()); - + self::assertContains( + ['The required properties (name_2) are missing'], + $validator->getErrors() + ); } } From af7f8493a227744f4e470bd72ce25d0f95abf193 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Mon, 1 Dec 2025 16:52:59 +0100 Subject: [PATCH 17/22] typo --- src/Validation/Validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index ec83d6e..df527ea 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -18,7 +18,7 @@ public function __construct( } /** - * Validate form the values against the JSON Schema. Returns a ValidationResult. + * Validate the form values against the JSON Schema. Returns a ValidationResult. */ public function isValid(): bool { From 1490c7959e66ba9e71701ca72f891617ddc39d59 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Mon, 1 Dec 2025 16:53:33 +0100 Subject: [PATCH 18/22] typo --- src/Validation/Validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index df527ea..797b00e 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -18,7 +18,7 @@ public function __construct( } /** - * Validate the form values against the JSON Schema. Returns a ValidationResult. + * Validates the form values against the JSON Schema. */ public function isValid(): bool { From d19b5390de1a67a5389bc4aa00b02d071d14e6fe Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 3 Dec 2025 10:24:59 +0100 Subject: [PATCH 19/22] Fix import mistake after rebase --- src/Form/Form.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Form/Form.php b/src/Form/Form.php index 7ad6c3b..6d82a6b 100644 --- a/src/Form/Form.php +++ b/src/Form/Form.php @@ -5,9 +5,8 @@ namespace MyParcelCom\JsonSchema\FormBuilder\Form; use ArrayObject; -use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; use MyParcelCom\JsonSchema\FormBuilder\Validation\Validator; -use Opis\JsonSchema\Errors\ErrorFormatter; +use MyParcelCom\JsonSchema\FormBuilder\Validation\Exceptions\FormValidationException; /** * @extends ArrayObject From b630463098eff023b0c0958fae9a3de31557aa6b Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 3 Dec 2025 11:51:02 +0100 Subject: [PATCH 20/22] initialize `ValidationResult` instance in the constructor of Validator to avoid redundantly reinstantiating that class --- src/Validation/Validator.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index 797b00e..31323f8 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -11,10 +11,13 @@ class Validator { + private ValidationResult $validationResult; public function __construct( private readonly array $values, private readonly array $schema, ) { + $jsonSchemaValidator = new JsonSchemaValidator(); + $this->validationResult = $jsonSchemaValidator->validate(Helper::toJSON($this->values), Helper::toJSON($this->schema)); } /** @@ -22,19 +25,12 @@ public function __construct( */ public function isValid(): bool { - return $this->getValidationResult()->isValid(); + return $this->validationResult->isValid(); } public function getErrors(): array { - return $this->getValidationResult()->hasError() - ? new ErrorFormatter()->format($this->getValidationResult()->error()) + return $this->validationResult->hasError() + ? new ErrorFormatter()->format($this->validationResult->error()) : []; } - - private function getValidationResult(): ValidationResult - { - $validator = new JsonSchemaValidator(); - - return $validator->validate(Helper::toJSON($this->values), Helper::toJSON($this->schema)); - } } From 75c30aa8391d89118cbdf4ac6e7f9dbd874f90a4 Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 3 Dec 2025 12:00:31 +0100 Subject: [PATCH 21/22] divide ValidatorTest to 2 separate test cases - success and failure --- tests/Validation/ValidatorTest.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/Validation/ValidatorTest.php b/tests/Validation/ValidatorTest.php index 4bc9a65..43b564b 100644 --- a/tests/Validation/ValidatorTest.php +++ b/tests/Validation/ValidatorTest.php @@ -10,9 +10,11 @@ class ValidatorTest extends TestCase { - public function test_it_validates(): void + private array $schema; + protected function setUp(): void { - $schema = [ + parent::setUp(); + $this->schema = [ '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'type' => 'object', 'additionalProperties' => false, @@ -84,7 +86,10 @@ public function test_it_validates(): void ], ], ]; + } + public function test_it_validates_success(): void + { $values = [ 'name_1' => 'value', 'name_2' => false, @@ -96,10 +101,13 @@ public function test_it_validates(): void ], ]; - $validator = new Validator($values, $schema); + $validator = new Validator($values, $this->schema); self::assertTrue($validator->isValid()); self::assertEquals([], $validator->getErrors()); + } + public function test_it_validates_failure(): void + { $values = [ 'name_1' => 'value', 'name_3' => 'a', @@ -110,7 +118,7 @@ public function test_it_validates(): void ], ]; - $validator = new Validator($values, $schema); + $validator = new Validator($values, $this->schema); self::assertFalse($validator->isValid()); self::assertContains( ['The required properties (name_2) are missing'], From 89b9d0805055af7491d27e6b74144064f4fd24ca Mon Sep 17 00:00:00 2001 From: Daniel Verner Date: Wed, 3 Dec 2025 12:03:03 +0100 Subject: [PATCH 22/22] slightly cleaner mocking in FormTest --- tests/Form/FormTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Form/FormTest.php b/tests/Form/FormTest.php index 3272c2e..464a09e 100644 --- a/tests/Form/FormTest.php +++ b/tests/Form/FormTest.php @@ -259,7 +259,7 @@ public function test_it_validates_success(): void $validator = Mockery::mock(Validator::class); - $validator->expects('isValid')->andReturn(true); + $validator->expects('isValid')->andReturnTrue(); $validator->expects('getErrors')->never(); $this->form->validate([ @@ -279,7 +279,7 @@ public function test_it_validates_failure(): void $this->expectException(FormValidationException::class); $validator = Mockery::mock(Validator::class); - $validator->expects('isValid')->andReturn(false); + $validator->expects('isValid')->andReturnFalse(); $validator->expects('getErrors'); $this->form->validate([