Skip to content
6 changes: 5 additions & 1 deletion src/JsonSchema/ConstraintError.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class ConstraintError extends Enum
public const PROPERTY_NAMES = 'propertyNames';
public const TYPE = 'type';
public const UNIQUE_ITEMS = 'uniqueItems';
public const CONTENT_MEDIA_TYPE = 'contentMediaType';
public const CONTENT_ENCODING = 'contentEncoding';

/**
* @return string
Expand Down Expand Up @@ -115,7 +117,9 @@ public function getMessage()
self::PROPERTIES_MAX => 'Must contain no more than %d properties',
self::PROPERTY_NAMES => 'Property name %s is invalid',
self::TYPE => '%s value found, but %s is required',
self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array'
self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array',
self::CONTENT_MEDIA_TYPE => 'Value is not valid with content media type',
self::CONTENT_ENCODING => 'Value is not valid with content encoding',
];

if (!isset($messages[$name])) {
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\Draft07;

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\Draft07;

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/Draft07/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\Draft07;

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/Draft07/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\Draft07;

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/Draft07/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\Draft07;

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]);
}
}
47 changes: 47 additions & 0 deletions src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

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;
}

$properties = [];
if (!is_array($value)) {
return;
}

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

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

$this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]);
}
}
Loading
Loading