From 96cb0483d33aa8761552712899fff7c27e0287bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Tue, 2 Feb 2021 13:48:48 +0100 Subject: [PATCH] Enhancement: Implement SchemaValidator --- CHANGELOG.md | 2 + README.md | 32 ++++- infection.json | 4 +- src/SchemaValidator.php | 81 +++++++++++ test/Unit/SchemaValidatorTest.php | 221 ++++++++++++++++++++++++++++++ 5 files changed, 337 insertions(+), 3 deletions(-) create mode 100644 src/SchemaValidator.php create mode 100644 test/Unit/SchemaValidatorTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4482df45..e498e036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ For a full diff see [`dcd4cfb...main`][dcd4cfb...main]. * Added `Schema` ([#3]), by [@localheinz] * Added `Decoder` ([#5]), by [@localheinz] * Added `Result` ([#6]), by [@localheinz] +* Added `SchemaValidator` ([#8]), by [@localheinz] [dcd4cfb...main]: https://github.com/ergebnis/json-schema-validator/compare/dcd4cfb...main @@ -21,5 +22,6 @@ For a full diff see [`dcd4cfb...main`][dcd4cfb...main]. [#3]: https://github.com/ergebnis/json-schema-validator/pull/3 [#5]: https://github.com/ergebnis/json-schema-validator/pull/5 [#6]: https://github.com/ergebnis/json-schema-validator/pull/6 +[#8]: https://github.com/ergebnis/json-schema-validator/pull/8 [@localheinz]: https://github.com/localheinz diff --git a/README.md b/README.md index d77a00fc..6cff98aa 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,37 @@ $ composer require ergebnis/json-schema-validator ## Usage -:bulb: This is a great place for showing a few usage examples! +If you have used the validator from `justinrainbow/json-schema` before, you might have observed that it has a few flaws: + +- The validator is stateful. +- The validator requires decoding JSON strings before validating them. +- The validator returns an `array` of errors, where each error is an `array`. + +This package delegates the validation to `justinrainbow/json-schema` and provides a friendlier interface. + +```php +validate( + $json, + $schema +); + +var_dump($result->isValid()); // bool +var_dump($result->errors()); // flat list of error messages +``` ## Changelog diff --git a/infection.json b/infection.json index 36c87aaf..4f0712c4 100644 --- a/infection.json +++ b/infection.json @@ -3,8 +3,8 @@ "logs": { "text": ".build/infection/infection-log.txt" }, - "minCoveredMsi": 84, - "minMsi": 80, + "minCoveredMsi": 73, + "minMsi": 70, "phpUnit": { "configDir": "test\/Unit" }, diff --git a/src/SchemaValidator.php b/src/SchemaValidator.php new file mode 100644 index 00000000..e5c98010 --- /dev/null +++ b/src/SchemaValidator.php @@ -0,0 +1,81 @@ +decoder = $decoder; + $this->validator = $validator; + } + + public function validate(Json $json, Schema $schema): Result + { + $this->validator->reset(); + + $this->validator->check( + $this->decoder->decodeNonAssociative($json->encoded()), + $schema->decoded() + ); + + /** @var array $originalErrors */ + $originalErrors = $this->validator->getErrors(); + + $errors = \array_map(static function (array $error): string { + $property = ''; + + if ( + \array_key_exists('property', $error) + && \is_string($error['property']) + && '' !== \trim($error['property']) + ) { + $property = \trim($error['property']); + } + + $message = ''; + + if ( + \array_key_exists('message', $error) + && \is_string($error['message']) + && '' !== \trim($error['message']) + ) { + $message = \trim($error['message']); + } + + if ('' === $property) { + return $message; + } + + return sprintf( + '%s: %s', + $property, + $message + ); + }, $originalErrors); + + $filtered = \array_filter($errors, static function (string $error): bool { + return '' !== $error; + }); + + return Result::create(...$filtered); + } +} diff --git a/test/Unit/SchemaValidatorTest.php b/test/Unit/SchemaValidatorTest.php new file mode 100644 index 00000000..fd61acbf --- /dev/null +++ b/test/Unit/SchemaValidatorTest.php @@ -0,0 +1,221 @@ +validate( + $json, + $schema + ); + + self::assertFalse($result->isValid()); + + $expected = [ + 'name: The property name is required', + 'email: The property email is required', + 'The property number is not defined and the definition does not allow additional properties', + 'The property street_name is not defined and the definition does not allow additional properties', + 'The property street_type is not defined and the definition does not allow additional properties', + 'The property direction is not defined and the definition does not allow additional properties', + ]; + + self::assertSame($expected, $result->errors()); + } + + public function testValidateReturnsResultWhenDataIsValidAccordingToSchema(): void + { + $json = Json::fromString( + <<<'JSON' +{ + "name": "Jane Doe", + "email": "jane.doe@example.org" +} +JSON + ); + + $schema = Schema::fromJson(Json::fromString( + <<<'JSON' +{ + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "address": { + "type": "string" + }, + "telephone": { + "type": "string" + } + }, + "required": [ + "name", + "email" + ], + "additionalProperties": false +} +JSON + )); + + $validator = new SchemaValidator( + new Decoder(), + new Validator() + ); + + $result = $validator->validate( + $json, + $schema + ); + + self::assertTrue($result->isValid()); + self::assertSame([], $result->errors()); + } + + public function testValidateClearsStateOfInternalValidator(): void + { + $invalidJson = Json::fromString( + <<<'JSON' +{ + "number": 1600, + "street_name": "Pennsylvania", + "street_type": "Avenue", + "direction": "NW" +} +JSON + ); + + $validJson = Json::fromString( + <<<'JSON' +{ + "name": "Jane Doe", + "email": "jane.doe@example.org" +} +JSON + ); + + $schema = Schema::fromJson(Json::fromString( + <<<'JSON' +{ + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "address": { + "type": "string" + }, + "telephone": { + "type": "string" + } + }, + "required": [ + "name", + "email" + ], + "additionalProperties": false +} +JSON + )); + + $validator = new SchemaValidator( + new Decoder(), + new Validator() + ); + + $validator->validate( + $invalidJson, + $schema + ); + + $secondResult = $validator->validate( + $validJson, + $schema + ); + + self::assertTrue($secondResult->isValid()); + self::assertSame([], $secondResult->errors()); + } +}