Skip to content

Commit

Permalink
Enhancement: Implement SchemaValidator
Browse files Browse the repository at this point in the history
  • Loading branch information
localheinz committed Feb 2, 2021
1 parent 1e03cf1 commit 801ad14
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ 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

[#2]: https://github.com/ergebnis/json-schema-validator/pull/2
[#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
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<?php

use Ergebnis\Json\SchemaValidator;
use JsonSchema\Validator;

$json = SchemaValidator\Json::fromFile('composer.json');

$schema = SchemaValidator\Schema::fromJson(SchemaValidator\Json::fromString(file_get_contents('https://getcomposer.org/schema.json')));

$schemaValidator = new SchemaValidator\SchemaValidator(
new SchemaValidator\Decoder(),
new Validator()
);

$result = $schemaValidator->validate(
$json,
$schema
);

var_dump($result->isValid()); // bool
var_dump($result->errors()); // flat list of error messages
```

## Changelog

Expand Down
4 changes: 2 additions & 2 deletions infection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"logs": {
"text": ".build/infection/infection-log.txt"
},
"minCoveredMsi": 84,
"minMsi": 80,
"minCoveredMsi": 73,
"minMsi": 70,
"phpUnit": {
"configDir": "test\/Unit"
},
Expand Down
80 changes: 80 additions & 0 deletions src/SchemaValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2021 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/json-schema-validator
*/

namespace Ergebnis\Json\SchemaValidator;

use JsonSchema\Validator;

final class SchemaValidator
{
private $decoder;

private $validator;

public function __construct(Decoder $decoder, Validator $validator)
{
$this->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<int, 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);
}
}
221 changes: 221 additions & 0 deletions test/Unit/SchemaValidatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2021 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/json-schema-validator
*/

namespace Ergebnis\Json\SchemaValidator\Test\Unit;

use Ergebnis\Json\SchemaValidator\Decoder;
use Ergebnis\Json\SchemaValidator\Json;
use Ergebnis\Json\SchemaValidator\Schema;
use Ergebnis\Json\SchemaValidator\SchemaValidator;
use Ergebnis\Test\Util;
use JsonSchema\Validator;
use PHPUnit\Framework;

/**
* @internal
*
* @covers \Ergebnis\Json\SchemaValidator\SchemaValidator
*
* @uses \Ergebnis\Json\SchemaValidator\Decoder
* @uses \Ergebnis\Json\SchemaValidator\Json
* @uses \Ergebnis\Json\SchemaValidator\Result
* @uses \Ergebnis\Json\SchemaValidator\Schema
*/
final class SchemaValidatorTest extends Framework\TestCase
{
use Util\Helper;

public function testValidateReturnsResultWhenDataIsNotValidAccordingToSchema(): void
{
$json = Json::fromString(
<<<'JSON'
{
"number": 1600,
"street_name": "Pennsylvania",
"street_type": "Avenue",
"direction": "NW"
}
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::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": "[email protected]"
}
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": "[email protected]"
}
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());
}
}

0 comments on commit 801ad14

Please sign in to comment.