Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/JsonSchema/ConstraintError.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ class ConstraintError extends Enum
public const INVALID_SCHEMA = 'invalidSchema';
public const LENGTH_MAX = 'maxLength';
public const LENGTH_MIN = 'minLength';
public const MAX_CONTAINS = 'maxContains';
public const MAXIMUM = 'maximum';
public const MIN_CONTAINS = 'minContains';
public const MIN_ITEMS = 'minItems';
public const MINIMUM = 'minimum';
public const MISSING_ERROR = 'missingError';
Expand Down Expand Up @@ -99,8 +101,10 @@ public function getMessage()
self::LENGTH_MAX => 'Must be at most %d characters long',
self::INVALID_SCHEMA => 'Schema is not valid',
self::LENGTH_MIN => 'Must be at least %d characters long',
self::MAX_CONTAINS => 'There must be a maximum of %d valid items in the array, %d found',
self::MAX_ITEMS => 'There must be a maximum of %d items in the array, %d found',
self::MAXIMUM => 'Must have a maximum value less than or equal to %d',
self::MIN_CONTAINS => 'There must be a minimum of %d valid items in the array, %d found',
self::MIN_ITEMS => 'There must be a minimum of %d items in the array, %d found',
self::MINIMUM => 'Must have a minimum value greater than or equal to %d',
self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft2019;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AdditionalItemsConstraint implements ConstraintInterface
{
use ErrorBagProxy;

/** @var Factory */
private $factory;

public function __construct(?Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
$this->initialiseErrorBag($this->factory);
}

public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
if (!property_exists($schema, 'additionalItems')) {
return;
}

if ($schema->additionalItems === true) {
return;
}
if ($schema->additionalItems === false && !property_exists($schema, 'items')) {
return;
}

if (!is_array($value)) {
return;
}
if (!property_exists($schema, 'items')) {
return;
}
if (property_exists($schema, 'items') && is_object($schema->items)) {
return;
}

$additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []);

foreach ($additionalItems as $propertyName => $propertyValue) {
$schemaConstraint = $this->factory->createInstanceFor('schema');
$schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i);

if ($schemaConstraint->isValid()) {
continue;
}

$this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft2019;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AdditionalPropertiesConstraint implements ConstraintInterface
{
use ErrorBagProxy;

/** @var Factory */
private $factory;

public function __construct(?Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
$this->initialiseErrorBag($this->factory);
}

public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
if (!property_exists($schema, 'additionalProperties')) {
return;
}

if ($schema->additionalProperties === true) {
return;
}

if (!is_object($value)) {
return;
}

$additionalProperties = get_object_vars($value);

if (isset($schema->properties)) {
$additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties);
}

if (isset($schema->patternProperties)) {
$patterns = array_keys(get_object_vars($schema->patternProperties));

foreach ($additionalProperties as $key => $_) {
foreach ($patterns as $pattern) {
if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) {
unset($additionalProperties[$key]);
break;
}
}
}
}

if (is_object($schema->additionalProperties)) {
foreach ($additionalProperties as $key => $additionalPropertiesValue) {
$schemaConstraint = $this->factory->createInstanceFor('schema');
$schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); // @todo increment path
if ($schemaConstraint->isValid()) {
unset($additionalProperties[$key]);
}
}
}

foreach ($additionalProperties as $key => $additionalPropertiesValue) {
$this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]);
}
}

private function createPregMatchPattern(string $pattern): string
{
$replacements = [
// '\D' => '[^0-9]',
// '\d' => '[0-9]',
'\p{digit}' => '\p{Nd}',
// '\w' => '[A-Za-z0-9_]',
// '\W' => '[^A-Za-z0-9_]',
// '\s' => '[\s\x{200B}]' // Explicitly include zero width white space,
'\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations
];

$pattern = str_replace(
array_keys($replacements),
array_values($replacements),
$pattern
);

return '/' . str_replace('/', '\/', $pattern) . '/u';
}
}
42 changes: 42 additions & 0 deletions src/JsonSchema/Constraints/Drafts/Draft2019/AllOfConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft2019;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AllOfConstraint implements ConstraintInterface
{
use ErrorBagProxy;

/** @var Factory */
private $factory;

public function __construct(?Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
$this->initialiseErrorBag($this->factory);
}

public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
if (!property_exists($schema, 'allOf')) {
return;
}

foreach ($schema->allOf as $allOfSchema) {
$schemaConstraint = $this->factory->createInstanceFor('schema');
$schemaConstraint->check($value, $allOfSchema, $path, $i);

if ($schemaConstraint->isValid()) {
continue;
}
$this->addError(ConstraintError::ALL_OF(), $path);
$this->addErrors($schemaConstraint->getErrors());
}
}
}
51 changes: 51 additions & 0 deletions src/JsonSchema/Constraints/Drafts/Draft2019/AnyOfConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft2019;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;

class AnyOfConstraint implements ConstraintInterface
{
use ErrorBagProxy;

/** @var Factory */
private $factory;

public function __construct(?Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
$this->initialiseErrorBag($this->factory);
}

public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
if (!property_exists($schema, 'anyOf')) {
return;
}

foreach ($schema->anyOf as $anyOfSchema) {
$schemaConstraint = $this->factory->createInstanceFor('schema');

try {
$schemaConstraint->check($value, $anyOfSchema, $path, $i);

if ($schemaConstraint->isValid()) {
$this->errorBag()->reset();

return;
}

$this->addErrors($schemaConstraint->getErrors());
} catch (ValidationException $e) {
}
}

$this->addError(ConstraintError::ANY_OF(), $path);
}
}
35 changes: 35 additions & 0 deletions src/JsonSchema/Constraints/Drafts/Draft2019/ConstConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft2019;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

class ConstConstraint implements ConstraintInterface
{
use ErrorBagProxy;

public function __construct(?Factory $factory = null)
{
$this->initialiseErrorBag($factory ?: new Factory());
}

public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
if (!property_exists($schema, 'const')) {
return;
}

if (DeepComparer::isEqual($value, $schema->const)) {
return;
}

$this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]);
}
}
54 changes: 54 additions & 0 deletions src/JsonSchema/Constraints/Drafts/Draft2019/ContainsConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft2019;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ContainsConstraint implements ConstraintInterface
{
use ErrorBagProxy;

/** @var Factory */
private $factory;

public function __construct(?Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
$this->initialiseErrorBag($this->factory);
}

public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
if (!property_exists($schema, 'contains')) {
return;
}

if (!is_array($value)) {
return;
}

$validElementCount = 0;
foreach ($value as $propertyValue) {
$schemaConstraint = $this->factory->createInstanceFor('schema');

$schemaConstraint->check($propertyValue, $schema->contains, $path, $i);
if ($schemaConstraint->isValid()) {
$validElementCount++;
}
}

if (property_exists($schema, 'maxContains') && $validElementCount > $schema->maxContains) {
$this->addError(ConstraintError::MAX_CONTAINS(), $path, ['maxContains' => $schema->maxContains, 'count' => $validElementCount]);
}

$minContains = property_exists($schema, 'minContains') ? $schema->minContains : 1;
if ($validElementCount < $minContains) {
$this->addError(ConstraintError::MIN_CONTAINS(), $path, ['minContains' => $minContains, 'count' => $validElementCount]);
}
}
}
26 changes: 26 additions & 0 deletions src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

Check warning on line 1 in src/JsonSchema/Constraints/Drafts/Draft2019/ContentConstraint.php

View workflow job for this annotation

GitHub Actions / Style Check

Found violation(s) of type: no_unused_imports

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft2019;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ContentConstraint implements ConstraintInterface
{
use ErrorBagProxy;

public function __construct(?Factory $factory = null)
{
$this->initialiseErrorBag($factory ?: new Factory());
}

public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
// See https://json-schema.org/draft/2019-09/json-schema-validation#rfc.section.8
}
}
Loading
Loading