plugin updates

This commit is contained in:
Tony Volpe
2024-09-17 10:43:54 -04:00
parent 44b413346f
commit b7c8882c8c
1359 changed files with 58219 additions and 11364 deletions

View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,9 @@
Opis Json Schema
Copyright 2018-2021 Zindex Software
This product includes software developed at
Zindex Software (http://zindex.software).
This software was originally developed by Marius Sarca and Sorin Sarca
(Copyright 2017-2018). The copyright info was changed with the permission
of the original authors.

View File

@@ -0,0 +1,23 @@
<?php
spl_autoload_register(function ($class) {
$class = ltrim($class, '\\');
$dir = __DIR__ . '/src';
$namespace = 'Opis\JsonSchema';
if (strpos($class, $namespace) === 0) {
$class = substr($class, strlen($namespace));
$path = '';
if (($pos = strripos($class, '\\')) !== false) {
$path = str_replace('\\', '/', substr($class, 0, $pos)) . '/';
$class = substr($class, $pos + 1);
}
$path .= str_replace('_', '/', $class) . '.php';
$dir .= '/' . $path;
if (is_file($dir)) {
include $dir;
return true;
}
return false;
}
return false;
});

View File

@@ -0,0 +1,51 @@
<?php
/* ============================================================================
* Copyright 2021 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
class CompliantValidator extends Validator
{
protected const COMPLIANT_OPTIONS = [
'allowFilters' => false,
'allowFormats' => true,
'allowMappers' => false,
'allowTemplates' => false,
'allowGlobals' => false,
'allowDefaults' => false,
'allowSlots' => false,
'allowKeywordValidators' => false,
'allowPragmas' => false,
'allowDataKeyword' => false,
'allowKeywordsAlongsideRef' => false,
'allowUnevaluated' => true,
'allowRelativeJsonPointerInRef' => false,
'allowExclusiveMinMaxAsBool' => false,
'keepDependenciesKeyword' => false,
'keepAdditionalItemsKeyword' => false,
];
public function __construct(?SchemaLoader $loader = null, int $max_errors = 1)
{
parent::__construct($loader, $max_errors);
// Set parser options
$parser = $this->parser();
foreach (static::COMPLIANT_OPTIONS as $name => $value) {
$parser->setOption($name, $value);
}
}
}

View File

@@ -0,0 +1,28 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
interface ContentEncoding
{
/**
* @param string $value
* @param string $type
* @return null|string
*/
public function decode(string $value, string $type): ?string;
}

View File

@@ -0,0 +1,28 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
interface ContentMediaType
{
/**
* @param string $content
* @param string $media_type
* @return bool
*/
public function validate(string $content, string $media_type): bool;
}

View File

@@ -0,0 +1,34 @@
<?php
/* ============================================================================
* Copyright 2021 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Errors;
use Exception;
class CustomError extends Exception
{
protected array $args;
public function __construct(string $message, array $args = []) {
parent::__construct($message);
$this->args = $args;
}
public function getArgs(): array {
return $this->args;
}
}

View File

@@ -0,0 +1,148 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Errors;
use Countable, Iterator;
class ErrorContainer implements Countable, Iterator
{
protected int $maxErrors;
/** @var ValidationError[] */
protected array $errors = [];
/**
* ErrorContainer constructor.
* @param int $max_errors
*/
public function __construct(int $max_errors = 1)
{
if ($max_errors < 0) {
$max_errors = PHP_INT_MAX;
} elseif ($max_errors === 0) {
$max_errors = 1;
}
$this->maxErrors = $max_errors;
}
/**
* @return int
*/
public function maxErrors(): int
{
return $this->maxErrors;
}
/**
* @param ValidationError $error
* @return ErrorContainer
*/
public function add(ValidationError $error): self
{
$this->errors[] = $error;
return $this;
}
/**
* @return ValidationError[]
*/
public function all(): array
{
return $this->errors;
}
/**
* @return ValidationError|null
*/
public function first(): ?ValidationError
{
if (!$this->errors) {
return null;
}
return reset($this->errors);
}
/**
* @return bool
*/
public function isFull(): bool
{
return count($this->errors) >= $this->maxErrors;
}
/**
* @return bool
*/
public function isEmpty(): bool
{
return !$this->errors;
}
/**
* @inheritDoc
*/
public function count(): int
{
return count($this->errors);
}
/**
* @inheritDoc
*/
public function current(): ?ValidationError
{
return current($this->errors) ?: null;
}
/**
* @inheritDoc
*/
#[\ReturnTypeWillChange]
public function next(): ?ValidationError
{
return next($this->errors) ?: null;
}
/**
* @inheritDoc
*/
public function key(): ?int
{
return key($this->errors);
}
/**
* @inheritDoc
*/
public function valid(): bool
{
return key($this->errors) !== null;
}
/**
* @inheritDoc
*/
#[\ReturnTypeWillChange]
public function rewind(): ?ValidationError
{
return reset($this->errors) ?: null;
}
}

View File

@@ -0,0 +1,423 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Errors;
use Opis\JsonSchema\JsonPointer;
class ErrorFormatter
{
/**
* @param ValidationError $error
* @param bool $multiple True if the same key can have multiple errors
* @param ?callable(ValidationError,?string=null):mixed $formatter
* @param ?callable(ValidationError):string $key_formatter
* @return array
*/
public function format(
ValidationError $error,
bool $multiple = true,
?callable $formatter = null,
?callable $key_formatter = null
): array {
if (!$formatter) {
$formatter = [$this, 'formatErrorMessage'];
}
if (!$key_formatter) {
$key_formatter = [$this, 'formatErrorKey'];
}
$list = [];
/**
* @var ValidationError $error
* @var string $message
*/
foreach ($this->getErrors($error) as $error => $message) {
$key = $key_formatter($error);
if ($multiple) {
if (!isset($list[$key])) {
$list[$key] = [];
}
$list[$key][] = $formatter($error, $message);
} else {
if (!isset($list[$key])) {
$list[$key] = $formatter($error, $message);
}
}
}
return $list;
}
/**
* @param ValidationError|null $error
* @param string $mode One of: flag, basic, detailed or verbose
* @return array
*/
public function formatOutput(?ValidationError $error, string $mode = "flag"): array
{
if ($error === null) {
return ['valid' => true];
}
if ($mode === 'flag') {
return ['valid' => false];
}
if ($mode === 'basic') {
return [
'valid' => false,
'errors' => $this->formatFlat($error, [$this, 'formatOutputError']),
];
}
if ($mode === 'detailed' || $mode === 'verbose') {
$isVerbose = $mode === 'verbose';
return $this->getNestedErrors($error, function (ValidationError $error, ?array $subErrors = null) use ($isVerbose) {
$info = $this->formatOutputError($error);
$info['valid'] = false;
if ($isVerbose) {
$id = $error->schema()->info();
$id = $id->root() ?? $id->id();
if ($id) {
$id = rtrim($id, '#');
}
$info['absoluteKeywordLocation'] = $id . $info['keywordLocation'];
}
if ($subErrors) {
$info['errors'] = $subErrors;
if (!$isVerbose) {
unset($info['error']);
}
}
return $info;
}
);
}
return ['valid' => false];
}
/**
* @param ValidationError $error
* @param ?callable(ValidationError,?array):mixed $formatter
* @return mixed
*/
public function formatNested(ValidationError $error, ?callable $formatter = null)
{
if (!$formatter) {
$formatter = function (ValidationError $error, ?array $subErrors = null): array {
$ret = [
'message' => $this->formatErrorMessage($error),
'keyword' => $error->keyword(),
'path' => $this->formatErrorKey($error),
];
if ($subErrors) {
$ret['errors'] = $subErrors;
}
return $ret;
};
}
return $this->getNestedErrors($error, $formatter);
}
/**
* @param ValidationError $error
* @param ?callable(ValidationError):mixed $formatter
* @return array
*/
public function formatFlat(ValidationError $error, ?callable $formatter = null): array
{
if (!$formatter) {
$formatter = [$this, 'formatErrorMessage'];
}
$list = [];
foreach ($this->getFlatErrors($error) as $error) {
$list[] = $formatter($error);
}
return $list;
}
/**
* @param ValidationError $error
* @param ?callable(ValidationError):mixed $formatter
* @param ?callable(ValidationError):string $key_formatter
* @return array
*/
public function formatKeyed(
ValidationError $error,
?callable $formatter = null,
?callable $key_formatter = null
): array {
if (!$formatter) {
$formatter = [$this, 'formatErrorMessage'];
}
if (!$key_formatter) {
$key_formatter = [$this, 'formatErrorKey'];
}
$list = [];
foreach ($this->getLeafErrors($error) as $error) {
$key = $key_formatter($error);
if (!isset($list[$key])) {
$list[$key] = [];
}
$list[$key][] = $formatter($error);
}
return $list;
}
/**
* @param ValidationError $error
* @param string|null $message The message to use, if null $error->message() is used
* @return string
*/
public function formatErrorMessage(ValidationError $error, ?string $message = null): string
{
$message ??= $error->message();
$args = $this->getDefaultArgs($error) + $error->args();
if (!$args) {
return $message;
}
return preg_replace_callback(
'~{([^}]+)}~imu',
static function (array $m) use ($args) {
if (!isset($args[$m[1]])) {
return $m[0];
}
$value = $args[$m[1]];
if (is_array($value)) {
return implode(', ', $value);
}
return (string) $value;
},
$message
);
}
public function formatErrorKey(ValidationError $error): string
{
return JsonPointer::pathToString($error->data()->fullPath());
}
protected function getDefaultArgs(ValidationError $error): array
{
$data = $error->data();
$info = $error->schema()->info();
$path = $info->path();
$path[] = $error->keyword();
return [
'data:type' => $data->type(),
'data:value' => $data->value(),
'data:path' => JsonPointer::pathToString($data->fullPath()),
'schema:id' => $info->id(),
'schema:root' => $info->root(),
'schema:base' => $info->base(),
'schema:draft' => $info->draft(),
'schema:keyword' => $error->keyword(),
'schema:path' => JsonPointer::pathToString($path),
];
}
protected function formatOutputError(ValidationError $error): array
{
$path = $error->schema()->info()->path();
$path[] = $error->keyword();
return [
'keywordLocation' => JsonPointer::pathToFragment($path),
'instanceLocation' => JsonPointer::pathToFragment($error->data()->fullPath()),
'error' => $this->formatErrorMessage($error),
];
}
/**
* @param ValidationError $error
* @param callable(ValidationError,?array):mixed $formatter
* @return mixed
*/
protected function getNestedErrors(ValidationError $error, callable $formatter)
{
if ($subErrors = $error->subErrors()) {
foreach ($subErrors as &$subError) {
$subError = $this->getNestedErrors($subError, $formatter);
unset($subError);
}
}
return $formatter($error, $subErrors);
}
/**
* @param ValidationError $error
* @return iterable|ValidationError[]
*/
protected function getFlatErrors(ValidationError $error): iterable
{
yield $error;
foreach ($error->subErrors() as $subError) {
yield from $this->getFlatErrors($subError);
}
}
/**
* @param ValidationError $error
* @return iterable|ValidationError[]
*/
protected function getLeafErrors(ValidationError $error): iterable
{
if ($subErrors = $error->subErrors()) {
foreach ($subErrors as $subError) {
yield from $this->getLeafErrors($subError);
}
} else {
yield $error;
}
}
/**
* @param ValidationError $error
* @return iterable
*/
protected function getErrors(ValidationError $error): iterable
{
$data = $error->schema()->info()->data();
$map = null;
$pMap = null;
if (is_object($data)) {
switch ($error->keyword()) {
case 'required':
if (isset($data->{'$error'}->required) && is_object($data->{'$error'}->required)) {
$e = $data->{'$error'}->required;
$found = false;
foreach ($error->args()['missing'] as $prop) {
if (isset($e->{$prop})) {
yield $error => $e->{$prop};
$found = true;
}
}
if ($found) {
return;
}
if (isset($e->{'*'})) {
yield $error => $e->{'*'};
return;
}
unset($e, $found, $prop);
}
break;
case '$filters':
if (($args = $error->args()) && isset($args['args']['$error'])) {
yield $error => $args['args']['$error'];
return;
}
unset($args);
break;
}
if (isset($data->{'$error'})) {
$map = $data->{'$error'};
if (is_string($map)) {
// We have an global error
yield $error => $map;
return;
}
if (is_object($map)) {
if (isset($map->{$error->keyword()})) {
$pMap = $map->{'*'} ?? null;
$map = $map->{$error->keyword()};
if (is_string($map)) {
yield $error => $map;
return;
}
} elseif (isset($map->{'*'})) {
yield $error => $map->{'*'};
return;
}
}
}
}
if (!is_object($map)) {
$map = null;
}
$subErrors = $error->subErrors();
if (!$subErrors) {
yield $error => $pMap ?? $error->message();
return;
}
if (!$map) {
foreach ($subErrors as $subError) {
yield from $this->getErrors($subError);
}
return;
}
foreach ($subErrors as $subError) {
$path = $subError->data()->path();
if (count($path) !== 1) {
yield from $this->getErrors($subError);
} else {
$path = $path[0];
if (isset($map->{$path})) {
yield $subError => $map->{$path};
} elseif (isset($map->{'*'})) {
yield $subError => $map->{'*'};
} else {
yield from $this->getErrors($subError);
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Errors;
use Opis\JsonSchema\Schema;
use Opis\JsonSchema\Info\DataInfo;
class ValidationError
{
protected string $keyword;
protected Schema $schema;
protected DataInfo $data;
protected array $args;
protected string $message;
/** @var ValidationError[] */
protected array $subErrors;
/**
* @param string $keyword
* @param Schema $schema
* @param DataInfo $data
* @param string $message
* @param array $args
* @param ValidationError[] $subErrors
*/
public function __construct(
string $keyword,
Schema $schema,
DataInfo $data,
string $message,
array $args = [],
array $subErrors = []
) {
$this->keyword = $keyword;
$this->schema = $schema;
$this->data = $data;
$this->message = $message;
$this->args = $args;
$this->subErrors = $subErrors;
}
public function keyword(): string
{
return $this->keyword;
}
public function schema(): Schema
{
return $this->schema;
}
public function data(): DataInfo
{
return $this->data;
}
public function args(): array
{
return $this->args;
}
public function message(): string
{
return $this->message;
}
public function subErrors(): array
{
return $this->subErrors;
}
public function __toString(): string
{
return $this->message;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use RuntimeException;
use Opis\JsonSchema\Uri;
class DuplicateSchemaIdException extends RuntimeException implements SchemaException
{
protected Uri $id;
protected ?object $data = null;
/**
* DuplicateSchemaIdException constructor.
* @param Uri $id
* @param object|null $data
*/
public function __construct(Uri $id, ?object $data = null)
{
parent::__construct("Duplicate schema id: {$id}", 0);
$this->id = $id;
$this->data = $data;
}
/**
* @return null|object
*/
public function getData(): ?object
{
return $this->data;
}
/**
* @return Uri
*/
public function getId(): Uri
{
return $this->id;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use Opis\JsonSchema\Info\SchemaInfo;
class InvalidKeywordException extends ParseException
{
protected string $keyword;
/**
* InvalidKeywordException constructor.
* @param string $message
* @param string $keyword
* @param SchemaInfo|null $info
*/
public function __construct(string $message, string $keyword, ?SchemaInfo $info = null)
{
parent::__construct($message, $info);
$this->keyword = $keyword;
}
/**
* @return string
*/
public function keyword(): string
{
return $this->keyword;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/* ===========================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use Opis\JsonSchema\Info\SchemaInfo;
class InvalidPragmaException extends InvalidKeywordException
{
protected string $pragma;
/**
* InvalidPragmaException constructor.
* @param string $message
* @param string $pragma
* @param SchemaInfo|null $info
*/
public function __construct(string $message, string $pragma, ?SchemaInfo $info = null)
{
parent::__construct($message, '$pragma', $info);
$this->pragma = $pragma;
}
/**
* @return string
*/
public function pragma(): string
{
return $this->pragma;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use RuntimeException;
use Opis\JsonSchema\Info\SchemaInfo;
class ParseException extends RuntimeException implements SchemaException
{
protected ?SchemaInfo $info = null;
/**
* @param string $message
* @param SchemaInfo|null $info
*/
public function __construct(string $message, ?SchemaInfo $info = null)
{
parent::__construct($message, 0);
$this->info = $info;
}
/**
* @return SchemaInfo|null
*/
public function schemaInfo(): ?SchemaInfo
{
return $this->info;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use Throwable;
interface SchemaException extends Throwable
{
}

View File

@@ -0,0 +1,44 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use Opis\JsonSchema\{ValidationContext, Schema};
class UnresolvedContentEncodingException extends UnresolvedException
{
protected string $encoding;
/**
* @param string $encoding
* @param Schema $schema
* @param ValidationContext $context
*/
public function __construct(string $encoding, Schema $schema, ValidationContext $context)
{
parent::__construct("Cannot resolve '{$encoding}' content encoding", $schema, $context);
$this->encoding = $encoding;
}
/**
* @return string
*/
public function getContentEncoding(): string
{
return $this->encoding;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use Opis\JsonSchema\{ValidationContext, Schema};
class UnresolvedContentMediaTypeException extends UnresolvedException
{
protected string $media;
/**
* @param string $media
* @param Schema $schema
* @param ValidationContext $context
*/
public function __construct(string $media, Schema $schema, ValidationContext $context)
{
parent::__construct("Cannot resolve '{$media}' content media type", $schema, $context);
$this->media = $media;
}
/**
* @return string
*/
public function getContentMediaType(): string
{
return $this->media;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use RuntimeException;
use Opis\JsonSchema\{ValidationContext, Schema};
class UnresolvedException extends RuntimeException implements SchemaException
{
protected Schema $schema;
protected ValidationContext $context;
/**
* @param string $message
* @param Schema $schema
* @param ValidationContext $context
*/
public function __construct(string $message, Schema $schema, ValidationContext $context)
{
parent::__construct($message);
$this->schema = $schema;
$this->context = $context;
}
/**
* @return Schema
*/
public function getSchema(): Schema
{
return $this->schema;
}
/**
* @return ValidationContext
*/
public function getContext(): ValidationContext
{
return $this->context;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use Opis\JsonSchema\{ValidationContext, Schema};
class UnresolvedFilterException extends UnresolvedException
{
protected string $filter;
protected string $type;
/**
* @param string $filter
* @param string $type
* @param Schema $schema
* @param ValidationContext $context
*/
public function __construct(string $filter, string $type, Schema $schema, ValidationContext $context)
{
parent::__construct("Cannot resolve filter '{$filter}' for type '{$type}'", $schema, $context);
$this->filter = $filter;
$this->type = $type;
}
/**
* @return string
*/
public function getFilter(): string
{
return $this->filter;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Exceptions;
use Opis\JsonSchema\{ValidationContext, Schema};
class UnresolvedReferenceException extends UnresolvedException
{
protected string $ref;
/**
* @param string $ref
* @param Schema $schema
* @param ValidationContext $context
*/
public function __construct(string $ref, Schema $schema, ValidationContext $context)
{
parent::__construct("Unresolved reference: {$ref}", $schema, $context);
$this->ref = $ref;
}
/**
* @return string
*/
public function getRef(): string
{
return $this->ref;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
interface Filter
{
/**
* @param ValidationContext $context
* @param Schema $schema
* @param array $args
* @return bool
*/
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool;
}

View File

@@ -0,0 +1,43 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
final class CommonFilters
{
public static function Regex(string $value, array $args): bool
{
if (!isset($args['pattern']) || !is_string($args['pattern'])) {
return false;
}
return (bool)preg_match($args['pattern'], $value);
}
public static function Equals($value, array $args): bool
{
if (!array_key_exists('value', $args)) {
return false;
}
if ($args['strict'] ?? false) {
return $value === $args['value'];
}
return $value == $args['value'];
}
}

View File

@@ -0,0 +1,41 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
use Opis\JsonSchema\{ValidationContext, Filter, Schema, JsonPointer};
class DataExistsFilter implements Filter
{
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$ref = $args['ref'] ?? $context->currentData();
if (!is_string($ref)) {
return false;
}
$ref = JsonPointer::parse($ref);
if ($ref === null) {
return false;
}
return $ref->data($context->rootData(), $context->currentDataPath(), $this) !== $this;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
use DateTime;
final class DateTimeFilters
{
public static function MinDate(string $date, array $args): bool
{
$min = $args['value'];
$tz = $args['timezone'] ?? null;
return self::CreateDate($date, $tz, false) >= self::CreateDate($min, $tz, false);
}
public static function MaxDate(string $date, array $args): bool
{
$max = $args['value'];
$tz = $args['timezone'] ?? null;
return self::CreateDate($date, $tz, false) <= self::CreateDate($max, $tz, false);
}
public static function NotDate(string $date, array $args): bool
{
$not = $args['value'];
$tz = $args['timezone'] ?? null;
if (!is_array($not)) {
$not = [$not];
}
$date = self::CreateDate($date, $tz, false);
foreach ($not as $d) {
if ($date == self::CreateDate($d, $tz, false)) {
return false;
}
}
return true;
}
public static function MinDateTime(string $date, array $args): bool
{
$min = $args['value'];
$tz = $args['timezone'] ?? null;
return self::CreateDate($date, $tz) >= self::CreateDate($min, $tz);
}
public static function MaxDateTime(string $date, array $args): bool
{
$max = $args['value'];
$tz = $args['timezone'] ?? null;
return self::CreateDate($date, $tz) <= self::CreateDate($max, $tz);
}
public static function MinTime(string $time, array $args): bool
{
$min = $args['value'];
$prefix = '1970-01-01 ';
return self::CreateDate($prefix . $time) >= self::CreateDate($prefix . $min);
}
public static function MaxTime(string $time, array $args): bool
{
$max = $args['value'];
$prefix = '1970-01-01 ';
return self::CreateDate($prefix . $time) <= self::CreateDate($prefix . $max);
}
private static function CreateDate(string $value, ?string $timezone = null, bool $time = true): DateTime
{
$date = new DateTime($value, $timezone);
if (!$time) {
return $date->setTime(0, 0, 0, 0);
}
return $date;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
use Opis\JsonSchema\{ValidationContext, Filter, Schema};
class FilterExistsFilter implements Filter
{
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$filter = $args['filter'] ?? $context->currentData();
if (!is_string($filter)) {
return false;
}
$type = null;
if (isset($args['type'])) {
if (!is_string($args['type'])) {
return false;
}
$type = $args['type'];
}
$resolver = $context->loader()->parser()->getFilterResolver();
if (!$resolver) {
return false;
}
if ($type === null) {
return (bool)$resolver->resolveAll($filter);
}
return (bool)$resolver->resolve($filter, $type);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
use Opis\JsonSchema\{ValidationContext, Filter, Schema};
class FormatExistsFilter implements Filter
{
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$format = $args['format'] ?? $context->currentData();
if (!is_string($format)) {
return false;
}
$type = null;
if (isset($args['type'])) {
if (!is_string($args['type'])) {
return false;
}
$type = $args['type'];
}
$resolver = $context->loader()->parser()->getFormatResolver();
if (!$resolver) {
return false;
}
if ($type === null) {
return (bool)$resolver->resolveAll($format);
}
return (bool)$resolver->resolve($format, $type);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
use Opis\JsonSchema\{ValidationContext, Filter, Schema};
class GlobalVarExistsFilter implements Filter
{
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$var = $args['var'] ?? $context->currentData();
if (!is_string($var)) {
return false;
}
$globals = $context->globals();
if (!array_key_exists($var, $globals)) {
return false;
}
if (array_key_exists('value', $args)) {
return $globals[$var] == $args['value'];
}
return true;
}
}

View File

@@ -0,0 +1,84 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
use Opis\Uri\UriTemplate;
use Opis\JsonSchema\{ValidationContext, Filter, Schema, Uri};
use Opis\JsonSchema\Variables\VariablesContainer;
class SchemaExistsFilter implements Filter
{
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$ref = $args['ref'] ?? $context->currentData();
if (!is_string($ref)) {
return false;
}
if (UriTemplate::isTemplate($ref)) {
if (isset($args['vars']) && is_object($args['vars'])) {
$vars = new VariablesContainer($args['vars'], false);
$vars = $vars->resolve($context->rootData(), $context->currentDataPath());
if (!is_array($vars)) {
$vars = (array)$vars;
}
$vars += $context->globals();
} else {
$vars = $context->globals();
}
$ref = (new UriTemplate($ref))->resolve($vars);
unset($vars);
}
unset($args);
return $this->refExists($ref, $context, $schema);
}
/**
* @param string $ref
* @param ValidationContext $context
* @param Schema $schema
* @return bool
*/
protected function refExists(string $ref, ValidationContext $context, Schema $schema): bool
{
if ($ref === '') {
return false;
}
if ($ref === '#') {
return true;
}
$info = $schema->info();
$id = Uri::merge($ref, $info->idBaseRoot(), true);
if ($id === null) {
return false;
}
return $context->loader()->loadSchemaById($id) !== null;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Filters;
use Opis\JsonSchema\{ValidationContext, Filter, Schema};
class SlotExistsFilter implements Filter
{
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$slot = $args['slot'] ?? $context->currentData();
if (!is_string($slot)) {
return false;
}
return $context->slot($slot) !== null;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
interface Format
{
/**
* @param $data
* @return bool
*/
public function validate($data): bool;
}

View File

@@ -0,0 +1,70 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Formats;
class DateTimeFormats
{
const DATE_REGEX = '/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/i';
const TIME_REGEX = '/^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))?$/i';
const DATE_TIME_REGEX = '/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))?$/i';
const DURATION_REGEX = '/^P?((((\d+D)|((\d+M(\d+D)?)|(\d+Y(\d+M(\d+D)?)?)))(T((\d+S)|(\d+M(\d+S)?)|(\d+H(\d+M(\d+S)?)?)))?)|(T((\d+S)|(\d+M(\d+S)?)|(\d+H(\d+M(\d+S)?)?)))|(\d+W))$/i';
/**
* @param string $value
* @return bool
*/
public static function date(string $value): bool
{
if (preg_match(self::DATE_REGEX, $value, $m)) {
return checkdate($m[2], $m[3], $m[1]);
}
return false;
}
/**
* @param string $value
* @return bool
*/
public static function time(string $value): bool
{
return (bool)preg_match(self::TIME_REGEX, $value);
}
/**
* @param string $value
* @return bool
*/
public static function dateTime(string $value): bool
{
if (preg_match(self::DATE_TIME_REGEX, $value, $m)) {
return checkdate($m[2], $m[3], $m[1]);
}
return false;
}
/**
* @param string $value
* @return bool
*/
public static function duration(string $value): bool
{
return (bool) preg_match(self::DURATION_REGEX, $value);
}
}

View File

@@ -0,0 +1,135 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Formats;
use Throwable;
use Opis\JsonSchema\Uri;
class IriFormats
{
private const SKIP = [0x23, 0x26, 0x2F, 0x3A, 0x3D, 0x3F, 0x40, 0x5B, 0x5C, 0x5D];
/** @var bool|null|callable */
private static $idn = false;
/**
* @param string $value
* @return bool
*/
public static function iri(string $value): bool
{
if ($value === '') {
return false;
}
try {
$components = Uri::parseComponents(Uri::encodeComponent($value, self::SKIP), true, true);
} catch (Throwable $e) {
return false;
}
return isset($components['scheme']) && $components['scheme'] !== '';
}
/**
* @param string $value
* @return bool
*/
public static function iriReference(string $value): bool
{
if ($value === '') {
return true;
}
try {
return Uri::parseComponents(Uri::encodeComponent($value, self::SKIP), true, true) !== null;
} catch (Throwable $e) {
return false;
}
}
/**
* @param string $value
* @param callable|null $idn
* @return bool
*/
public static function idnHostname(string $value, ?callable $idn = null): bool
{
$idn = $idn ?? static::idn();
if ($idn) {
$value = $idn($value);
if ($value === null) {
return false;
}
}
return Uri::isValidHost($value);
}
/**
* @param string $value
* @param callable|null $idn
* @return bool
*/
public static function idnEmail(string $value, ?callable $idn = null): bool
{
$idn = $idn ?? static::idn();
if ($idn) {
if (!preg_match('/^(?<name>.+)@(?<domain>.+)$/u', $value, $m)) {
return false;
}
$m['name'] = $idn($m['name']);
if ($m['name'] === null) {
return false;
}
$m['domain'] = $idn($m['domain']);
if ($m['domain'] === null) {
return false;
}
$value = $m['name'] . '@' . $m['domain'];
}
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* @return callable|null
*/
public static function idn(): ?callable
{
if (static::$idn === false) {
if (function_exists('idn_to_ascii')) {
static::$idn = static function (string $value): ?string {
/** @noinspection PhpComposerExtensionStubsInspection */
$value = idn_to_ascii($value, 0, INTL_IDNA_VARIANT_UTS46);
return is_string($value) ? $value : null;
};
} else {
static::$idn = null;
}
}
return static::$idn;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Formats;
class MiscFormats
{
const UUID_REGEX = '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i';
/**
* @param string $value
* @return bool
*/
public static function ipv4(string $value): bool
{
return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
}
/**
* @param string $value
* @return bool
*/
public static function ipv6(string $value): bool
{
return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
}
/**
* @param string $value
* @return bool
*/
public static function email(string $value): bool
{
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* @param string $value
* @return bool
*/
public static function uuid(string $value): bool
{
return (bool) preg_match(self::UUID_REGEX, $value);
}
}

View File

@@ -0,0 +1,69 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Formats;
use Opis\JsonSchema\Uri;
use Opis\Uri\UriTemplate;
class UriFormats
{
/**
* @param string $value
* @return bool
*/
public static function uri(string $value): bool
{
if ($value === '') {
return false;
}
$uri = Uri::parse($value);
return $uri !== null && $uri->isAbsolute();
}
/**
* @param string $value
* @return bool
*/
public static function uriReference(string $value): bool
{
if ($value === '') {
return true;
}
return Uri::parse($value) !== null;
}
/**
* @param string $value
* @return bool
*/
public static function uriTemplate(string $value): bool
{
if ($value === '') {
return true;
}
if (UriTemplate::isTemplate($value)) {
return true;
}
return Uri::parse($value) !== null;
}
}

View File

@@ -0,0 +1,351 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
final class Helper
{
/** @var string[] */
public const JSON_TYPES = ['string', 'number', 'boolean', 'null', 'object', 'array'];
/** @var string[] */
public const JSON_SUBTYPES = ['integer' => 'number'];
/** @var string[] */
protected const PHP_TYPE_MAP = [
'NULL' => 'null',
'integer' => 'integer',
'double' => 'number',
'boolean' => 'boolean',
'array' => 'array',
'object' => 'object',
'string' => 'string',
];
/**
* @param string $type
* @return bool
*/
public static function isValidJsonType(string $type): bool
{
if (isset(self::JSON_SUBTYPES[$type])) {
return true;
}
return in_array($type, self::JSON_TYPES, true);
}
/**
* @param string $type
* @return null|string
*/
public static function getJsonSuperType(string $type): ?string
{
return self::JSON_SUBTYPES[$type] ?? null;
}
/**
* @param mixed $value
* @param bool $use_subtypes
* @return null|string
*/
public static function getJsonType($value, bool $use_subtypes = true): ?string
{
$type = self::PHP_TYPE_MAP[gettype($value)] ?? null;
if ($type === null) {
return null;
} elseif ($type === 'array') {
return self::isIndexedArray($value) ? 'array' : null;
}
if ($use_subtypes) {
if ($type === 'number' && self::isMultipleOf($value, 1)) {
return 'integer';
}
} elseif ($type === 'integer') {
return 'number';
}
return $type;
}
/**
* @param string $type
* @param string|string[] $allowed
* @return bool
*/
public static function jsonTypeMatches(string $type, $allowed): bool
{
if (!$allowed) {
return false;
}
if (is_string($allowed)) {
if ($type === $allowed) {
return true;
}
return $allowed === self::getJsonSuperType($type);
}
if (is_array($allowed)) {
if (in_array($type, $allowed, true)) {
return true;
}
if ($type = self::getJsonSuperType($type)) {
return in_array($type, $allowed, true);
}
}
return false;
}
/**
* @param mixed $value
* @param string|string[] $type
* @return bool
*/
public static function valueIsOfJsonType($value, $type): bool
{
$t = self::getJsonType($value);
if ($t === null) {
return false;
}
return self::jsonTypeMatches($t, $type);
}
/**
* @param array $array
* @return bool
*/
public static function isIndexedArray(array $array): bool
{
for ($i = 0, $max = count($array); $i < $max; $i++) {
if (!array_key_exists($i, $array)) {
return false;
}
}
return true;
}
/**
* Converts assoc-arrays to objects (recursive)
* @param scalar|object|array|null $schema
* @return scalar|object|array|null
*/
public static function convertAssocArrayToObject($schema)
{
if (is_null($schema) || is_scalar($schema)) {
return $schema;
}
$keepArray = is_array($schema) && self::isIndexedArray($schema);
$data = [];
foreach ($schema as $key => $value) {
$data[$key] = is_array($value) || is_object($value) ? self::convertAssocArrayToObject($value) : $value;
}
return $keepArray ? $data : (object) $data;
}
/**
* @param mixed $a
* @param mixed $b
* @return bool
*/
public static function equals($a, $b): bool
{
if ($a === $b) {
return true;
}
$type = self::getJsonType($a, false);
if ($type === null || $type !== self::getJsonType($b, false)) {
return false;
}
if ($type === 'number') {
return $a == $b;
}
if ($type === "array") {
$count = count($a);
if ($count !== count($b)) {
return false;
}
for ($i = 0; $i < $count; $i++) {
if (!array_key_exists($i, $a) || !array_key_exists($i, $b)) {
return false;
}
if (!self::equals($a[$i], $b[$i])) {
return false;
}
}
return true;
}
if ($type === "object") {
$a = get_object_vars($a);
if ($a === null) {
return false;
}
$b = get_object_vars($b);
if ($b === null) {
return false;
}
if (count($a) !== count($b)) {
return false;
}
foreach ($a as $prop => $value) {
if (!array_key_exists($prop, $b)) {
return false;
}
if (!self::equals($value, $b[$prop])) {
return false;
}
}
return true;
}
return false;
}
/**
* @param $number
* @param $divisor
* @param int $scale
* @return bool
*/
public static function isMultipleOf($number, $divisor, int $scale = 14): bool
{
static $bcMath = null;
if ($bcMath === null) {
$bcMath = extension_loaded('bcmath');
}
if ($divisor == 0) {
return $number == 0;
}
if ($bcMath) {
$number = number_format($number, $scale, '.', '');
$divisor = number_format($divisor, $scale, '.', '');
/** @noinspection PhpComposerExtensionStubsInspection */
$x = bcdiv($number, $divisor, 0);
/** @noinspection PhpComposerExtensionStubsInspection */
$x = bcmul($divisor, $x, $scale);
/** @noinspection PhpComposerExtensionStubsInspection */
$x = bcsub($number, $x, $scale);
/** @noinspection PhpComposerExtensionStubsInspection */
return 0 === bccomp($x, 0, $scale);
}
$div = $number / $divisor;
return $div == (int)$div;
}
/**
* @param $value
* @return mixed
*/
public static function cloneValue($value)
{
if ($value === null || is_scalar($value)) {
return $value;
}
if (is_array($value)) {
return array_map(self::class . '::cloneValue', $value);
}
if (is_object($value)) {
return (object)array_map(self::class . '::cloneValue', get_object_vars($value));
}
return null;
}
/**
* @param string $pattern
* @return bool
*/
public static function isValidPattern(string $pattern): bool
{
if (strpos($pattern, '\Z') !== false) {
return false;
}
return @preg_match("\x07{$pattern}\x07u", '') !== false;
}
/**
* @param string $pattern
* @return string
*/
public static function patternToRegex(string $pattern): string
{
return "\x07{$pattern}\x07uD";
}
/**
* @param mixed $data
* @return mixed
*/
public static function toJSON($data)
{
if ($data === null || is_scalar($data)) {
return $data;
}
$map = [];
$isArray = true;
$index = 0;
foreach ($data as $key => $value) {
$map[$key] = self::toJSON($value);
if ($isArray) {
if ($index !== $key) {
$isArray = false;
} else {
$index++;
}
}
}
if ($isArray) {
if (!$map && is_object($data)) {
return (object) $map;
}
return $map;
}
return (object) $map;
}
}

View File

@@ -0,0 +1,114 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Info;
use Opis\JsonSchema\ValidationContext;
class DataInfo
{
/** @var mixed */
protected $value;
protected ?string $type;
/** @var mixed */
protected $root;
/** @var string[]|int[] */
protected array $path;
protected ?DataInfo $parent = null;
/** @var string[]|int[]|null */
protected ?array $fullPath = null;
/**
* DataInfo constructor.
* @param $value
* @param string|null $type
* @param $root
* @param string[]|int[] $path
* @param DataInfo|null $parent
*/
public function __construct($value, ?string $type, $root, array $path = [], ?DataInfo $parent = null)
{
$this->value = $value;
$this->type = $type;
$this->root = $root;
$this->path = $path;
$this->parent = $parent;
}
public function value()
{
return $this->value;
}
public function type(): ?string
{
return $this->type;
}
public function root()
{
return $this->root;
}
/**
* @return int[]|string[]
*/
public function path(): array
{
return $this->path;
}
public function parent(): ?DataInfo
{
return $this->parent;
}
/**
* @return int[]|string[]
*/
public function fullPath(): array
{
if ($this->parent === null) {
return $this->path;
}
if ($this->fullPath === null) {
$this->fullPath = array_merge($this->parent->fullPath(), $this->path);
}
return $this->fullPath;
}
/**
* @param ValidationContext $context
* @return static
*/
public static function fromContext(ValidationContext $context): self
{
if ($parent = $context->parent()) {
$parent = self::fromContext($parent);
}
return new self($context->currentData(), $context->currentDataType(), $context->rootData(),
$context->currentDataPath(), $parent);
}
}

View File

@@ -0,0 +1,117 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Info;
use Opis\JsonSchema\Uri;
class SchemaInfo
{
/** @var bool|object */
protected $data;
protected ?Uri $id;
protected ?Uri $root;
protected ?Uri $base;
/** @var string[]|int[] */
protected array $path;
protected ?string $draft;
/**
* @param object|bool $data
* @param Uri|null $id
* @param Uri|null $base
* @param Uri|null $root
* @param string[]|int[] $path
* @param string|null $draft
*/
public function __construct($data, ?Uri $id, ?Uri $base = null, ?Uri $root = null, array $path = [], ?string $draft = null)
{
if ($root === $id || ((string)$root === (string)$id)) {
$root = null;
}
if ($root === null) {
$base = null;
}
$this->data = $data;
$this->id = $id;
$this->root = $root;
$this->base = $base;
$this->path = $path;
$this->draft = $draft;
}
public function id(): ?Uri
{
return $this->id;
}
public function root(): ?Uri
{
return $this->root;
}
public function base(): ?Uri
{
return $this->base;
}
public function draft(): ?string
{
return $this->draft;
}
public function data()
{
return $this->data;
}
public function path(): array
{
return $this->path;
}
/**
* Returns first non-null property: id, base or root
* @return Uri|null
*/
public function idBaseRoot(): ?Uri
{
return $this->id ?? $this->base ?? $this->root;
}
public function isBoolean(): bool
{
return is_bool($this->data);
}
public function isObject(): bool
{
return is_object($this->data);
}
public function isDocumentRoot(): bool
{
return $this->id && !$this->root && !$this->base;
}
}

View File

@@ -0,0 +1,410 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
final class JsonPointer
{
/** @var string */
protected const PATTERN = '~^(?:(?<level>0|[1-9][0-9]*)(?<shift>(?:\+|-)(?:0|[1-9][0-9]*))?)?(?<pointer>(?:/[^/#]*)*)(?<fragment>#)?$~';
/** @var string */
protected const UNESCAPED = '/~([^01]|$)/';
protected int $level = -1;
protected int $shift = 0;
protected bool $fragment = false;
/** @var string[]|int[] */
protected array $path;
protected ?string $str = null;
final protected function __construct(array $path, int $level = -1, int $shift = 0, bool $fragment = false)
{
$this->path = $path;
$this->level = $level < 0 ? -1 : $level;
$this->shift = $shift;
$this->fragment = $level >= 0 && $fragment;
}
public function isRelative(): bool
{
return $this->level >= 0;
}
public function isAbsolute(): bool
{
return $this->level < 0;
}
public function level(): int
{
return $this->level;
}
public function shift(): int
{
return $this->shift;
}
/**
* @return string[]
*/
public function path(): array
{
return $this->path;
}
/**
* @return bool
*/
public function hasFragment(): bool
{
return $this->fragment;
}
/**
* @return string
*/
public function __toString(): string
{
if ($this->str === null) {
if ($this->level >= 0) {
$this->str = (string)$this->level;
if ($this->shift !== 0) {
if ($this->shift > 0) {
$this->str .= '+';
}
$this->str .= $this->shift;
}
if ($this->path) {
$this->str .= '/';
$this->str .= implode('/', self::encodePath($this->path));
}
if ($this->fragment) {
$this->str .= '#';
}
} else {
$this->str = '/';
$this->str .= implode('/', self::encodePath($this->path));
}
}
return $this->str;
}
/**
* @param $data
* @param array|null $path
* @param null $default
* @return mixed
*/
public function data($data, ?array $path = null, $default = null)
{
if ($this->level < 0) {
return self::getData($data, $this->path, false, $default);
}
if ($path !== null) {
$path = $this->absolutePath($path);
}
if ($path === null) {
return $default;
}
return self::getData($data, $path, $this->fragment, $default);
}
/**
* @param array $path
* @return array|null
*/
public function absolutePath(array $path = []): ?array
{
if ($this->level < 0) {
// Absolute pointer
return $this->path;
}
if ($this->level === 0) {
if ($this->shift && !$this->handleShift($path)) {
return null;
}
return $this->path ? array_merge($path, $this->path) : $path;
}
$count = count($path);
if ($count === $this->level) {
if ($this->shift) {
return null;
}
return $this->path;
}
if ($count > $this->level) {
$count -= $this->level;
/** @var array $path */
$path = array_slice($path, 0, $count);
if ($this->shift && !$this->handleShift($path, $count)) {
return null;
}
return $this->path ? array_merge($path, $this->path) : $path;
}
return null;
}
protected function handleShift(array &$path, ?int $count = null): bool
{
if (!$path) {
return false;
}
$count ??= count($path);
$last = $path[$count - 1];
if (is_string($last) && preg_match('/^[1-9]\d*$/', $last)) {
$last = (int) $last;
}
if (!is_int($last)) {
return false;
}
$path[$count - 1] = $last + $this->shift;
return true;
}
public static function parse(string $pointer, bool $decode = true): ?self
{
if ($pointer === '' || !preg_match(self::PATTERN, $pointer, $m)) {
// Not a pointer
return null;
}
$pointer = $m['pointer'] ?? null;
// Check if the pointer is escaped
if ($decode && $pointer && preg_match(self::UNESCAPED, $pointer)) {
// Invalid pointer
return null;
}
$level = isset($m['level']) && $m['level'] !== ''
? (int)$m['level']
: -1;
$shift = 0;
if ($level >= 0 && isset($m['shift']) && $m['shift'] !== '') {
$shift = (int) $m['shift'];
}
$fragment = isset($m['fragment']) && $m['fragment'] === '#';
unset($m);
if ($fragment && $level < 0) {
return null;
}
if ($pointer === '') {
$pointer = null;
} elseif ($pointer !== null) {
// Remove leading slash
$pointer = substr($pointer, 1);
if ($pointer !== '') {
$pointer = self::decodePath(explode('/', $pointer));
} else {
$pointer = null;
}
}
return new self($pointer ?? [], $level, $shift, $fragment);
}
/**
* @param $data
* @param array|null $path
* @param bool $fragment
* @param null $default
* @return mixed
*/
public static function getData($data, ?array $path = null, bool $fragment = false, $default = null)
{
if ($path === null) {
return $default;
}
if (!$path) {
return $fragment ? $default : $data;
}
if ($fragment) {
return end($path);
}
foreach ($path as $key) {
if (is_array($data)) {
if (!array_key_exists($key, $data)) {
return $default;
}
$data = $data[$key];
} elseif (is_object($data)) {
if (!property_exists($data, $key)) {
return $default;
}
$data = $data->{$key};
} else {
return $default;
}
}
return $data;
}
/**
* @param string|string[] $path
* @return string|string[]
*/
public static function encodePath($path)
{
$path = str_replace('~', '~0', $path);
$path = str_replace('/', '~1', $path);
if (is_array($path)) {
return array_map('rawurlencode', $path);
}
return rawurlencode($path);
}
/**
* @param string|string[] $path
* @return string|string[]
*/
public static function decodePath($path)
{
if (is_array($path)) {
$path = array_map('rawurldecode', $path);
} else {
$path = rawurldecode($path);
}
$path = str_replace('~1', '/', $path);
$path = str_replace('~0', '~', $path);
return $path;
}
/**
* @param array $path
* @return string
*/
public static function pathToString(array $path): string
{
if (!$path) {
return '/';
}
return '/' . implode('/', self::encodePath($path));
}
/**
* @param array $path
* @return string
*/
public static function pathToFragment(array $path): string
{
if (!$path) {
return '#';
}
return '#/' . implode('/', self::encodePath($path));
}
/**
* @param string $pointer
* @return bool
*/
public static function isAbsolutePointer(string $pointer): bool
{
if ($pointer === '/') {
return true;
}
if (!preg_match(self::PATTERN, $pointer, $m)) {
return false;
}
if (isset($m['fragment']) || isset($m['level']) && $m['level'] !== '') {
return false;
}
if (!isset($m['pointer']) || $m['pointer'] === '') {
return true;
}
return !preg_match(self::UNESCAPED, $m['pointer']);
}
/**
* @param string $pointer
* @return bool
*/
public static function isRelativePointer(string $pointer): bool
{
if ($pointer === '') {
return false;
}
if (!preg_match(self::PATTERN, $pointer, $m)) {
return false;
}
if (!isset($m['level']) || $m['level'] === '' || (int)$m['level'] < 0) {
return false;
}
if (!isset($m['pointer']) || $m['pointer'] === '') {
return true;
}
return !preg_match(self::UNESCAPED, $m['pointer']);
}
public static function createAbsolute(array $path): self
{
return new self($path, -1, 0, false);
}
public static function createRelative(int $level, array $path = [], int $shift = 0, bool $fragment = false): self
{
return new self($path, $level, $shift, $fragment);
}
}

View File

@@ -0,0 +1,30 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
use Opis\JsonSchema\Errors\ValidationError;
interface Keyword
{
/**
* @param ValidationContext $context
* @param Schema $schema
* @return null|ValidationError
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError;
}

View File

@@ -0,0 +1,32 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema;
interface KeywordValidator extends SchemaValidator
{
/**
* @return KeywordValidator|null
*/
public function next(): ?KeywordValidator;
/**
* @param KeywordValidator|null $next
* @return KeywordValidator
*/
public function setNext(?KeywordValidator $next): KeywordValidator;
}

View File

@@ -0,0 +1,44 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\KeywordValidators;
use Opis\JsonSchema\KeywordValidator;
abstract class AbstractKeywordValidator implements KeywordValidator
{
protected ?KeywordValidator $next = null;
/**
* @inheritDoc
*/
public function next(): ?KeywordValidator
{
return $this->next;
}
/**
* @inheritDoc
*/
public function setNext(?KeywordValidator $next): KeywordValidator
{
$this->next = $next;
return $this;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\KeywordValidators;
use Opis\JsonSchema\{ValidationContext, KeywordValidator};
use Opis\JsonSchema\Errors\ValidationError;
final class CallbackKeywordValidator implements KeywordValidator
{
/** @var callable */
private $callback;
/**
* @param callable $callback
*/
public function __construct(callable $callback)
{
$this->callback = $callback;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context): ?ValidationError
{
return ($this->callback)($context);
}
/**
* @inheritDoc
*/
public function next(): ?KeywordValidator
{
return null;
}
/**
* @inheritDoc
*/
public function setNext(?KeywordValidator $next): KeywordValidator
{
return $this;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\KeywordValidators;
use Opis\JsonSchema\{ValidationContext, Pragma};
use Opis\JsonSchema\Errors\ValidationError;
final class PragmaKeywordValidator extends AbstractKeywordValidator
{
/** @var Pragma[] */
protected array $pragmas = [];
/**
* @param Pragma[] $pragmas
*/
public function __construct(array $pragmas)
{
$this->pragmas = $pragmas;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context): ?ValidationError
{
if (!$this->next) {
return null;
}
if (!$this->pragmas) {
return $this->next->validate($context);
}
$data = [];
foreach ($this->pragmas as $key => $handler) {
$data[$key] = $handler->enter($context);
}
$error = $this->next->validate($context);
foreach (array_reverse($this->pragmas, true) as $key => $handler) {
$handler->leave($context, $data[$key] ?? null);
}
return $error;
}
}

View File

@@ -0,0 +1,129 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{Errors\ValidationError,
JsonPointer,
Keyword,
Schema,
SchemaLoader,
Uri,
ValidationContext,
Variables};
abstract class AbstractRefKeyword implements Keyword
{
use ErrorTrait;
protected string $keyword;
protected ?Variables $mapper;
protected ?Variables $globals;
protected ?array $slots = null;
protected ?Uri $lastRefUri = null;
/**
* @param Variables|null $mapper
* @param Variables|null $globals
* @param array|null $slots
* @param string $keyword
*/
protected function __construct(?Variables $mapper, ?Variables $globals, ?array $slots = null, string $keyword = '$ref')
{
$this->mapper = $mapper;
$this->globals = $globals;
$this->slots = $slots;
$this->keyword = $keyword;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($error = $this->doValidate($context, $schema)) {
$uri = $this->lastRefUri;
$this->lastRefUri = null;
return $this->error($schema, $context, $this->keyword, 'The data must match {keyword}', [
'keyword' => $this->keyword,
'uri' => (string) $uri,
], $error);
}
$this->lastRefUri = null;
return null;
}
abstract protected function doValidate(ValidationContext $context, Schema $schema): ?ValidationError;
protected function setLastRefUri(?Uri $uri): void
{
$this->lastRefUri = $uri;
}
protected function setLastRefSchema(Schema $schema): void
{
$info = $schema->info();
if ($info->id()) {
$this->lastRefUri = $info->id();
} else {
$this->lastRefUri = Uri::merge(JsonPointer::pathToFragment($info->path()), $info->idBaseRoot());
}
}
/**
* @param ValidationContext $context
* @param Schema $schema
* @return ValidationContext
*/
protected function createContext(ValidationContext $context, Schema $schema): ValidationContext
{
return $context->create($schema, $this->mapper, $this->globals, $this->slots);
}
/**
* @param SchemaLoader $repo
* @param JsonPointer $pointer
* @param Uri $base
* @param array|null $path
* @return null|Schema
*/
protected function resolvePointer(SchemaLoader $repo, JsonPointer $pointer,
Uri $base, ?array $path = null): ?Schema
{
if ($pointer->isAbsolute()) {
$path = (string)$pointer;
} else {
if ($pointer->hasFragment()) {
return null;
}
$path = $path ? $pointer->absolutePath($path) : $pointer->path();
if ($path === null) {
return null;
}
$path = JsonPointer::pathToString($path);
}
return $repo->loadSchemaById(Uri::merge('#' . $path, $base));
}
}

View File

@@ -0,0 +1,97 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class AdditionalItemsKeyword implements Keyword
{
use OfTrait;
use IterableDataValidationTrait;
/** @var bool|object|Schema */
protected $value;
protected int $index;
/**
* @param bool|object $value
* @param int $startIndex
*/
public function __construct($value, int $startIndex)
{
$this->value = $value;
$this->index = $startIndex;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->value === true) {
$context->markAllAsEvaluatedItems();
return null;
}
$data = $context->currentData();
$count = count($data);
if ($this->index >= $count) {
return null;
}
if ($this->value === false) {
return $this->error($schema, $context, 'additionalItems', 'Array should not have additional items', [
'index' => $this->index,
]);
}
if (is_object($this->value) && !($this->value instanceof Schema)) {
$this->value = $context->loader()->loadObjectSchema($this->value);
}
$object = $this->createArrayObject($context);
$error = $this->validateIterableData($schema, $this->value, $context, $this->indexes($this->index, $count),
'additionalItems', 'All additional array items must match schema', [], $object);
if ($object && $object->count()) {
$context->addEvaluatedItems($object->getArrayCopy());
}
return $error;
}
/**
* @param int $start
* @param int $max
* @return iterable|int[]
*/
protected function indexes(int $start, int $max): iterable
{
for ($i = $start; $i < $max; $i++) {
yield $i;
}
}
}

View File

@@ -0,0 +1,83 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class AdditionalPropertiesKeyword implements Keyword
{
use OfTrait;
use IterableDataValidationTrait;
/** @var bool|object|Schema */
protected $value;
/**
* @param bool|object|Schema $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->value === true) {
$context->markAllAsEvaluatedProperties();
return null;
}
$props = $context->getUncheckedProperties();
if (!$props) {
return null;
}
if ($this->value === false) {
return $this->error($schema, $context,
'additionalProperties', 'Additional object properties are not allowed: {properties}', [
'properties' => $props
]);
}
if (is_object($this->value) && !($this->value instanceof Schema)) {
$this->value = $context->loader()->loadObjectSchema($this->value);
}
$object = $this->createArrayObject($context);
$error = $this->validateIterableData($schema, $this->value, $context, $props,
'additionalProperties', 'All additional object properties must match schema: {properties}', [
'properties' => $props
], $object);
if ($object && $object->count()) {
$context->addEvaluatedProperties($object->getArrayCopy());
}
return $error;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class AllOfKeyword implements Keyword
{
use OfTrait;
use ErrorTrait;
/** @var bool[]|object[] */
protected array $value;
/**
* @param bool[]|object[] $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$object = $this->createArrayObject($context);
foreach ($this->value as $index => $value) {
if ($value === true) {
continue;
}
if ($value === false) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'allOf', 'The data should match all schemas', [
'index' => $index,
]);
}
if (is_object($value) && !($value instanceof Schema)) {
$value = $this->value[$index] = $context->loader()->loadObjectSchema($value);
}
if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'allOf', 'The data should match all schemas', [
'index' => $index,
], $error);
}
}
$this->addEvaluatedFromArrayObject($object, $context);
return null;
}
}

View File

@@ -0,0 +1,94 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class AnyOfKeyword implements Keyword
{
use OfTrait;
use ErrorTrait;
/** @var bool[]|object[] */
protected array $value;
protected bool $alwaysValid;
/**
* @param bool[]|object[] $value
*/
public function __construct(array $value, bool $alwaysValid = false)
{
$this->value = $value;
$this->alwaysValid = $alwaysValid;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$object = $this->createArrayObject($context);
if ($this->alwaysValid && !$object) {
return null;
}
$errors = [];
$ok = false;
foreach ($this->value as $index => $value) {
if ($value === true) {
$ok = true;
if ($object) {
continue;
}
return null;
}
if ($value === false) {
continue;
}
if (is_object($value) && !($value instanceof Schema)) {
$value = $this->value[$index] = $context->loader()->loadObjectSchema($value);
}
if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) {
$errors[] = $error;
continue;
}
if (!$object) {
return null;
}
$ok = true;
}
$this->addEvaluatedFromArrayObject($object, $context);
if ($ok) {
return null;
}
return $this->error($schema, $context, 'anyOf', 'The data should match at least one schema', [], $errors);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class ConstDataKeyword extends ConstKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(null);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$value = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($value === $this) {
return $this->error($schema, $context, 'const', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->const = $value;
$ret = parent::validate($context, $schema);
$this->const = null;
return $ret;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\{Helper, ValidationContext, Keyword, Schema};
class ConstKeyword implements Keyword
{
use ErrorTrait;
/** @var mixed */
protected $const;
/**
* @param $const
*/
public function __construct($const)
{
$this->const = $const;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if (Helper::equals($this->const, $context->currentData())) {
return null;
}
return $this->error($schema, $context, 'const', 'The data must must match the const value', [
'const' => $this->const
]);
}
}

View File

@@ -0,0 +1,143 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class ContainsKeyword implements Keyword
{
use ErrorTrait;
/** @var bool|object */
protected $value;
protected ?int $min = null;
protected ?int $max = null;
/**
* @param bool|object $value
* @param int|null $min
* @param int|null $max
*/
public function __construct($value, ?int $min = null, ?int $max = null)
{
$this->value = $value;
$this->min = $min;
$this->max = $max;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$data = $context->currentData();
$count = count($data);
$context->markAllAsEvaluatedItems();
if ($this->min > $count) {
return $this->error($schema, $context, 'minContains', 'Array must have at least {min} items', [
'min' => $this->min,
'count' => $count,
]);
}
$isMaxNull = $this->max === null;
if ($this->value === true) {
if ($count) {
if (!$isMaxNull && $count > $this->max) {
return $this->error($schema, $context, 'maxContains', 'Array must have at most {max} items', [
'max' => $this->max,
'count' => $count,
]);
}
return null;
}
return $this->error($schema, $context, 'contains', 'Array must not be empty');
}
if ($this->value === false) {
return $this->error($schema, $context, 'contains', 'Any array is invalid');
}
if (is_object($this->value) && !($this->value instanceof Schema)) {
$this->value = $context->loader()->loadObjectSchema($this->value);
}
$errors = [];
$valid = 0;
$isMinNull = $this->min === null;
if ($isMaxNull && $isMinNull) {
foreach ($data as $key => $item) {
$context->pushDataPath($key);
$error = $this->value->validate($context);
$context->popDataPath();
if ($error) {
$errors[] = $error;
} else {
return null;
}
}
return $this->error($schema, $context, 'contains', 'At least one array item must match schema', [],
$errors);
}
foreach ($data as $key => $item) {
$context->pushDataPath($key);
$error = $this->value->validate($context);
$context->popDataPath();
if ($error) {
$errors[] = $error;
} else {
$valid++;
}
}
if (!$isMinNull && $valid < $this->min) {
return $this->error($schema, $context, 'minContains', 'At least {min} array items must match schema', [
'min' => $this->min,
'count' => $valid,
]);
}
if (!$isMaxNull && $valid > $this->max) {
return $this->error($schema, $context, 'maxContains', 'At most {max} array items must match schema', [
'max' => $this->max,
'count' => $valid,
]);
}
if ($valid) {
return null;
}
return $this->error($schema, $context, 'contains', 'At least one array item must match schema', [],
$errors);
}
}

View File

@@ -0,0 +1,77 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Keyword, Schema, ContentEncoding};
use Opis\JsonSchema\Resolvers\ContentEncodingResolver;
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\Exceptions\UnresolvedContentEncodingException;
class ContentEncodingKeyword implements Keyword
{
use ErrorTrait;
protected string $name;
protected ?ContentEncodingResolver $resolver;
/** @var bool|null|callable|ContentEncoding */
protected $encoding = false;
/**
* @param string $name
* @param null|ContentEncodingResolver $resolver
*/
public function __construct(string $name, ?ContentEncodingResolver $resolver = null)
{
$this->name = $name;
$this->resolver = $resolver;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if (!$this->resolver) {
return null;
}
if ($this->encoding === false) {
$this->encoding = $this->resolver->resolve($this->name);
}
if ($this->encoding === null) {
throw new UnresolvedContentEncodingException($this->name, $schema, $context);
}
$result = $this->encoding instanceof ContentEncoding
? $this->encoding->decode($context->currentData(), $this->name)
: ($this->encoding)($context->currentData(), $this->name);
if ($result === null) {
return $this->error($schema, $context, 'contentEncoding', "The value must be encoded as '{encoding}'", [
'encoding' => $this->name,
]);
}
$context->setDecodedContent($result);
return null;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema,
ContentMediaType
};
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\Resolvers\ContentMediaTypeResolver;
use Opis\JsonSchema\Exceptions\UnresolvedContentMediaTypeException;
class ContentMediaTypeKeyword implements Keyword
{
use ErrorTrait;
protected string $name;
/** @var bool|callable|ContentMediaType|null */
protected $media = false;
protected ?ContentMediaTypeResolver $resolver;
/**
* @param string $name
* @param null|ContentMediaTypeResolver $resolver
*/
public function __construct(string $name, ?ContentMediaTypeResolver $resolver)
{
$this->name = $name;
$this->resolver = $resolver;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if (!$this->resolver) {
return null;
}
if ($this->media === false) {
$this->media = $this->resolver->resolve($this->name);
}
if ($this->media === null) {
throw new UnresolvedContentMediaTypeException($this->name, $schema, $context);
}
$data = $context->getDecodedContent();
$ok = $this->media instanceof ContentMediaType
? $this->media->validate($data, $this->name)
: ($this->media)($data, $this->name);
if ($ok) {
return null;
}
unset($data);
return $this->error($schema, $context, 'contentMediaType', "The media type of the data must be '{media}'", [
'media' => $this->name,
]);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Keyword, Schema};
use Opis\JsonSchema\Errors\ValidationError;
class ContentSchemaKeyword implements Keyword
{
use ErrorTrait;
/** @var bool|object */
protected $value;
/**
* @param object $value
*/
public function __construct(object $value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$data = json_decode($context->getDecodedContent(), false);
if ($error = json_last_error() !== JSON_ERROR_NONE) {
$message = json_last_error_msg();
return $this->error($schema, $context, 'contentSchema', "Invalid JSON content: {message}", [
'error' => $error,
'message' => $message,
]);
}
if (is_object($this->value) && !($this->value instanceof Schema)) {
$this->value = $context->loader()->loadObjectSchema($this->value);
}
if ($error = $this->value->validate($context->newInstance($data, $schema))) {
return $this->error($schema, $context, 'contentSchema', "The JSON content must match schema", [], $error);
}
return null;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{Helper, ValidationContext, Keyword, Schema};
use Opis\JsonSchema\Errors\ValidationError;
class DefaultKeyword implements Keyword
{
protected array $defaults;
/**
* @param array $defaults
*/
public function __construct(array $defaults)
{
$this->defaults = $defaults;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$data = $context->currentData();
if (is_object($data)) {
foreach ($this->defaults as $name => $value) {
if (!property_exists($data, $name)) {
$data->{$name} = Helper::cloneValue($value);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,95 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class DependenciesKeyword implements Keyword
{
use OfTrait;
use ErrorTrait;
/** @var array|object[]|string[][] */
protected array $value;
/**
* @param object[]|string[][] $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$data = $context->currentData();
$object = $this->createArrayObject($context);
foreach ($this->value as $name => $value) {
if ($value === true || !property_exists($data, $name)) {
continue;
}
if ($value === false) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'dependencies', "Property '{property}' is not allowed", [
'property' => $name,
]);
}
if (is_array($value)) {
foreach ($value as $prop) {
if (!property_exists($data, $prop)) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'dependencies',
"Property '{missing}' property is required by property '{property}'", [
'property' => $name,
'missing' => $prop,
]);
}
}
continue;
}
if (is_object($value) && !($value instanceof Schema)) {
$value = $this->value[$name] = $context->loader()->loadObjectSchema($value);
}
if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'dependencies',
"The object must match dependency schema defined on property '{property}'", [
'property' => $name,
], $error);
}
}
$this->addEvaluatedFromArrayObject($object, $context);
return null;
}
}

View File

@@ -0,0 +1,66 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class DependentRequiredKeyword implements Keyword
{
use ErrorTrait;
/** @var string[][] */
protected array $value;
/**
* @param string[][] $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$data = $context->currentData();
foreach ($this->value as $name => $value) {
if (!property_exists($data, $name)) {
continue;
}
foreach ($value as $prop) {
if (!property_exists($data, $prop)) {
return $this->error($schema, $context, 'dependentRequired',
"'{$prop}' property is required by '{$name}' property", [
'property' => $name,
'missing' => $prop,
]);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class DependentSchemasKeyword implements Keyword
{
use OfTrait;
use ErrorTrait;
protected array $value;
public function __construct(object $value)
{
$this->value = (array)$value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$data = $context->currentData();
$object = $this->createArrayObject($context);
foreach ($this->value as $name => $value) {
if ($value === true || !property_exists($data, $name)) {
continue;
}
if ($value === false) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'dependentSchemas', "'{$name}' property is not allowed", [
'property' => $name,
]);
}
if (is_object($value) && !($value instanceof Schema)) {
$value = $this->value[$name] = $context->loader()->loadObjectSchema($value);
}
if ($error = $context->validateSchemaWithoutEvaluated($value, null, false, $object)) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'dependentSchemas',
"The object must match dependency schema defined on property '{$name}'", [
'property' => $name,
], $error);
}
}
$this->addEvaluatedFromArrayObject($object, $context);
return null;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class EnumDataKeyword extends EnumKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct([]);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$value = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($value === $this || !is_array($value) || empty($value)) {
return $this->error($schema, $context, 'enum', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->enum = $this->listByType($value);
$ret = parent::validate($context, $schema);
$this->enum = null;
return $ret;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
Helper,
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class EnumKeyword implements Keyword
{
use ErrorTrait;
protected ?array $enum;
/**
* @param array $enum
*/
public function __construct(array $enum)
{
$this->enum = $this->listByType($enum);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$type = $context->currentDataType();
$data = $context->currentData();
if (isset($this->enum[$type])) {
foreach ($this->enum[$type] as $value) {
if (Helper::equals($value, $data)) {
return null;
}
}
}
return $this->error($schema, $context, 'enum', 'The data should match one item from enum');
}
/**
* @param array $values
* @return array
*/
protected function listByType(array $values): array
{
$list = [];
foreach ($values as $value) {
$type = Helper::getJsonType($value);
if (!isset($list[$type])) {
$list[$type] = [];
}
$list[$type][] = $value;
}
return $list;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\Info\DataInfo;
use Opis\JsonSchema\{ValidationContext, Schema};
use Opis\JsonSchema\Errors\{ErrorContainer, ValidationError};
trait ErrorTrait
{
/**
* @param Schema $schema
* @param ValidationContext $context
* @param string $keyword
* @param string $message
* @param array $args
* @param ErrorContainer|ValidationError|ValidationError[]|null $errors
* @return ValidationError
*/
protected function error(
Schema $schema,
ValidationContext $context,
string $keyword,
string $message,
array $args = [],
$errors = null
): ValidationError
{
if ($errors) {
if ($errors instanceof ValidationError) {
$errors = [$errors];
} elseif ($errors instanceof ErrorContainer) {
$errors = $errors->all();
}
}
return new ValidationError($keyword, $schema, DataInfo::fromContext($context), $message, $args,
is_array($errors) ? $errors : []);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class ExclusiveMaximumDataKeyword extends ExclusiveMaximumKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var float|int $number */
$number = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($number === $this || !(is_float($number) || is_int($number))) {
return $this->error($schema, $context, 'exclusiveMaximum', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->number = $number;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Keyword, Schema};
use Opis\JsonSchema\Errors\ValidationError;
class ExclusiveMaximumKeyword implements Keyword
{
use ErrorTrait;
protected float $number;
/**
* @param float $number
*/
public function __construct(float $number)
{
$this->number = $number;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($context->currentData() < $this->number) {
return null;
}
return $this->error($schema, $context, 'exclusiveMaximum', "Number must be lower than {max}", [
'max' => $this->number,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class ExclusiveMinimumDataKeyword extends ExclusiveMinimumKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var float|int $number */
$number = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($number === $this || !(is_float($number) || is_int($number))) {
return $this->error($schema, $context, 'exclusiveMinimum', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->number = $number;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Keyword, Schema};
use Opis\JsonSchema\Errors\ValidationError;
class ExclusiveMinimumKeyword implements Keyword
{
use ErrorTrait;
protected float $number;
/**
* @param float $number
*/
public function __construct(float $number)
{
$this->number = $number;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($context->currentData() > $this->number) {
return null;
}
return $this->error($schema, $context, 'exclusiveMinimum', "Number must be greater than {min}", [
'min' => $this->number,
]);
}
}

View File

@@ -0,0 +1,93 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
Filter,
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\{ValidationError, CustomError};
use Opis\JsonSchema\Exceptions\UnresolvedFilterException;
class FiltersKeyword implements Keyword
{
use ErrorTrait;
/** @var array|object[] */
protected array $filters;
/**
* @param object[] $filters
*/
public function __construct(array $filters)
{
$this->filters = $filters;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$type = $context->currentDataType();
foreach ($this->filters as $filter) {
if (!isset($filter->types[$type])) {
throw new UnresolvedFilterException($filter->name, $type, $schema, $context);
}
$func = $filter->types[$type];
if ($filter->args) {
$args = (array)$filter->args->resolve($context->rootData(), $context->currentDataPath());
$args += $context->globals();
} else {
$args = $context->globals();
}
try {
if ($func instanceof Filter) {
$ok = $func->validate($context, $schema, $args);
} else {
$ok = $func($context->currentData(), $args);
}
} catch (CustomError $error) {
return $this->error($schema, $context, '$filters', $error->getMessage(), $error->getArgs() + [
'filter' => $filter->name,
'type' => $type,
'args' => $args,
]);
}
if ($ok) {
unset($func, $args, $ok);
continue;
}
return $this->error($schema, $context, '$filters', "Filter '{filter}' ({type}) was not passed", [
'filter' => $filter->name,
'type' => $type,
'args' => $args,
]);
}
return null;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{Helper, ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\Resolvers\FormatResolver;
class FormatDataKeyword extends FormatKeyword
{
protected JsonPointer $value;
protected FormatResolver $resolver;
/**
* @param JsonPointer $value
* @param FormatResolver $resolver
*/
public function __construct(JsonPointer $value, FormatResolver $resolver)
{
$this->value = $value;
$this->resolver = $resolver;
parent::__construct('', []);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$value = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($value === $this || !is_string($value)) {
return $this->error($schema, $context, 'format', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
/** @var string $value */
$type = $context->currentDataType();
$types = [
$type => $this->resolver->resolve($value, $type),
];
if (!$types[$type] && ($super = Helper::getJsonSuperType($type))) {
$types[$super] = $this->resolver->resolve($value, $super);
unset($super);
}
unset($type);
$this->name = $value;
$this->types = $types;
$ret = parent::validate($context, $schema);
$this->name = $this->types = null;
return $ret;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Format,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\{ValidationError, CustomError};
class FormatKeyword implements Keyword
{
use ErrorTrait;
protected ?string $name;
/** @var callable[]|Format[] */
protected ?array $types;
/**
* @param string $name
* @param callable[]|Format[] $types
*/
public function __construct(string $name, array $types)
{
$this->name = $name;
$this->types = $types;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$type = $context->currentDataType();
if (!isset($this->types[$type])) {
return null;
}
$format = $this->types[$type];
try {
if ($format instanceof Format) {
$ok = $format->validate($context->currentData());
} else {
$ok = $format($context->currentData());
}
} catch (CustomError $error) {
return $this->error($schema, $context, 'format', $error->getMessage(), $error->getArgs() + [
'format' => $this->name,
'type' => $type,
]);
}
if ($ok) {
return null;
}
return $this->error($schema, $context, 'format', "The data must match the '{format}' format", [
'format' => $this->name,
'type' => $type,
]);
}
}

View File

@@ -0,0 +1,104 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class IfThenElseKeyword implements Keyword
{
use ErrorTrait;
/** @var bool|object */
protected $if;
/** @var bool|object */
protected $then;
/** @var bool|object */
protected $else;
/**
* @param bool|object $if
* @param bool|object $then
* @param bool|object $else
*/
public function __construct($if, $then, $else)
{
$this->if = $if;
$this->then = $then;
$this->else = $else;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->if === true) {
return $this->validateBranch('then', $context, $schema);
} elseif ($this->if === false) {
return $this->validateBranch('else', $context, $schema);
}
if (is_object($this->if) && !($this->if instanceof Schema)) {
$this->if = $context->loader()->loadObjectSchema($this->if);
}
if ($context->validateSchemaWithoutEvaluated($this->if, null, true)) {
return $this->validateBranch('else', $context, $schema);
}
return $this->validateBranch('then', $context, $schema);
}
/**
* @param string $branch
* @param ValidationContext $context
* @param Schema $schema
* @return ValidationError|null
*/
protected function validateBranch(string $branch, ValidationContext $context, Schema $schema): ?ValidationError
{
$value = $this->{$branch};
if ($value === true) {
return null;
} elseif ($value === false) {
return $this->error($schema, $context, $branch, "The data is never valid on '{branch}' branch", [
'branch' => $branch,
]);
}
if (is_object($value) && !($value instanceof Schema)) {
$value = $this->{$branch} = $context->loader()->loadObjectSchema($value);
}
if ($error = $value->validate($context)) {
return $this->error($schema, $context, $branch, "The data is not valid on '{branch}' branch", [
'branch' => $branch,
], $error);
}
return null;
}
}

View File

@@ -0,0 +1,162 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class ItemsKeyword implements Keyword
{
use OfTrait;
use IterableDataValidationTrait;
/** @var bool|object|Schema|bool[]|object[]|Schema[] */
protected $value;
protected int $count = -1;
protected bool $alwaysValid;
protected string $keyword;
protected int $startIndex;
/**
* @param bool|object|Schema|bool[]|object[]|Schema[] $value
* @param bool $alwaysValid
* @param string $keyword
* @param int $startIndex
*/
public function __construct($value, bool $alwaysValid = false, string $keyword = 'items', int $startIndex = 0)
{
$this->value = $value;
$this->alwaysValid = $alwaysValid;
if (is_array($value)) {
$this->count = count($value);
}
$this->keyword = $keyword;
$this->startIndex = $startIndex;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->alwaysValid || $this->value === true) {
if ($this->count === -1) {
$context->markAllAsEvaluatedItems();
} else {
$context->markCountAsEvaluatedItems($this->count);
}
return null;
}
$count = count($context->currentData());
if ($this->startIndex >= $count) {
// Already validated by other keyword
return null;
}
if ($this->value === false) {
if ($count === 0) {
return null;
}
return $this->error($schema, $context, $this->keyword, 'Array must be empty');
}
if ($this->count >= 0) {
$errors = $this->errorContainer($context->maxErrors());
$max = min($count, $this->count);
$evaluated = [];
for ($i = $this->startIndex; $i < $max; $i++) {
if ($this->value[$i] === true) {
$evaluated[] = $i;
continue;
}
if ($this->value[$i] === false) {
$context->addEvaluatedItems($evaluated);
return $this->error($schema, $context, $this->keyword, "Array item at index {index} is not allowed", [
'index' => $i,
]);
}
if (is_object($this->value[$i]) && !($this->value[$i] instanceof Schema)) {
$this->value[$i] = $context->loader()->loadObjectSchema($this->value[$i]);
}
$context->pushDataPath($i);
$error = $this->value[$i]->validate($context);
$context->popDataPath();
if ($error) {
$errors->add($error);
if ($errors->isFull()) {
break;
}
} else {
$evaluated[] = $i;
}
}
$context->addEvaluatedItems($evaluated);
if ($errors->isEmpty()) {
return null;
}
return $this->error($schema, $context, $this->keyword, 'Array items must match corresponding schemas', [],
$errors);
}
if (is_object($this->value) && !($this->value instanceof Schema)) {
$this->value = $context->loader()->loadObjectSchema($this->value);
}
$object = $this->createArrayObject($context);
$error = $this->validateIterableData($schema, $this->value, $context, $this->indexes($this->startIndex, $count),
$this->keyword, 'All array items must match schema', [], $object);
if ($object && $object->count()) {
$context->addEvaluatedItems($object->getArrayCopy());
}
return $error;
}
/**
* @param int $start
* @param int $max
* @return iterable|int[]
*/
protected function indexes(int $start, int $max): iterable
{
for ($i = $start; $i < $max; $i++) {
yield $i;
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use ArrayObject;
use Opis\JsonSchema\{ValidationContext, Schema};
use Opis\JsonSchema\Errors\{ValidationError, ErrorContainer};
trait IterableDataValidationTrait
{
use ErrorTrait;
/**
* @param int $maxErrors
* @return ErrorContainer
*/
protected function errorContainer(int $maxErrors = 1): ErrorContainer
{
return new ErrorContainer($maxErrors);
}
/**
* @param Schema $schema
* @param ValidationContext $context
* @param iterable $iterator
* @param ArrayObject|null $keys
* @return ErrorContainer
*/
protected function iterateAndValidate(
Schema $schema,
ValidationContext $context,
iterable $iterator,
?ArrayObject $keys = null
): ErrorContainer {
$container = $this->errorContainer($context->maxErrors());
if ($keys) {
foreach ($iterator as $key) {
$context->pushDataPath($key);
$error = $schema->validate($context);
$context->popDataPath();
if ($error) {
if (!$container->isFull()) {
$container->add($error);
}
} else {
$keys[] = $key;
}
}
} else {
foreach ($iterator as $key) {
$context->pushDataPath($key);
$error = $schema->validate($context);
$context->popDataPath();
if ($error && $container->add($error)->isFull()) {
break;
}
}
}
return $container;
}
/**
* @param Schema $parentSchema
* @param Schema $schema
* @param ValidationContext $context
* @param iterable $iterator
* @param string $keyword
* @param string $message
* @param array $args
* @param ArrayObject|null $visited_keys
* @return ValidationError|null
*/
protected function validateIterableData(
Schema $parentSchema,
Schema $schema,
ValidationContext $context,
iterable $iterator,
string $keyword,
string $message,
array $args = [],
?ArrayObject $visited_keys = null
): ?ValidationError {
$errors = $this->iterateAndValidate($schema, $context, $iterator, $visited_keys);
if ($errors->isEmpty()) {
return null;
}
return $this->error($parentSchema, $context, $keyword, $message, $args, $errors);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MaxItemsDataKeyword extends MaxItemsKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var int $count */
$count = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($count === $this || !is_int($count) || $count < 0) {
return $this->error($schema, $context, 'maxItems', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->count = $count;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MaxItemsKeyword implements Keyword
{
use ErrorTrait;
protected int $count;
/**
* @param int $count
*/
public function __construct(int $count)
{
$this->count = $count;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$count = count($context->currentData());
if ($count <= $this->count) {
return null;
}
return $this->error($schema, $context, "maxItems",
"Array should have at most {max} items, {count} found", [
'max' => $this->count,
'count' => $count,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MaxLengthDataKeyword extends MaxLengthKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var int $length */
$length = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($length === $this || !is_int($length) || $length < 0) {
return $this->error($schema, $context, 'maxLength', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->length = $length;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,62 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MaxLengthKeyword implements Keyword
{
use ErrorTrait;
protected int $length;
/**
* @param int $length
*/
public function __construct(int $length)
{
$this->length = $length;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->length === 0) {
return null;
}
$length = $context->getStringLength();
if ($length <= $this->length) {
return null;
}
return $this->error($schema, $context, 'maxLength', "Maximum string length is {max}, found {length}",
[
'max' => $this->length,
'length' => $length,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MaxPropertiesDataKeyword extends MaxPropertiesKeywords
{
protected JsonPointer $value;
/**
* @inheritDoc
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var int $count */
$count = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($count === $this || !is_int($count) || $count < 0) {
return $this->error($schema, $context, 'maxProperties', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->count = $count;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MaxPropertiesKeywords implements Keyword
{
use ErrorTrait;
protected int $count;
/**
* @param int $count
*/
public function __construct(int $count)
{
$this->count = $count;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$count = count($context->getObjectProperties());
if ($count <= $this->count) {
return null;
}
return $this->error($schema, $context, 'maxProperties',
"Object must have at most {max} properties, {count} found", [
'max' => $this->count,
'count' => $count,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MaximumDataKeyword extends MaximumKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var float|int $number */
$number = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($number === $this || !(is_float($number) || is_int($number))) {
return $this->error($schema, $context, 'maximum', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->number = $number;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MaximumKeyword implements Keyword
{
use ErrorTrait;
protected float $number;
/**
* @param float $number
*/
public function __construct(float $number)
{
$this->number = $number;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($context->currentData() <= $this->number) {
return null;
}
return $this->error($schema, $context, 'maximum', "Number must be lower than or equal to {max}", [
'max' => $this->number,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MinItemsDataKeyword extends MinItemsKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var int $count */
$count = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($count === $this || !is_int($count) || $count < 0) {
return $this->error($schema, $context, 'minItems', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->count = $count;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MinItemsKeyword implements Keyword
{
use ErrorTrait;
protected int $count;
/**
* @param int $count
*/
public function __construct(int $count)
{
$this->count = $count;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$count = count($context->currentData());
if ($count >= $this->count) {
return null;
}
return $this->error($schema, $context, "minItems",
"Array should have at least {min} items, {count} found", [
'min' => $this->count,
'count' => $count,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MinLengthDataKeyword extends MinLengthKeyword
{
/** @var JsonPointer */
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var int $length */
$length = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($length === $this || !is_int($length) || $length < 0) {
return $this->error($schema, $context, 'minLength', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->length = $length;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,61 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MinLengthKeyword implements Keyword
{
use ErrorTrait;
protected int $length;
/**
* @param int $length
*/
public function __construct(int $length)
{
$this->length = $length;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->length === 0) {
return null;
}
$length = $context->getStringLength();
if ($length >= $this->length) {
return null;
}
return $this->error($schema, $context, 'minLength', "Minimum string length is {min}, found {length}", [
'min' => $this->length,
'length' => $length,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MinPropertiesDataKeyword extends MinPropertiesKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var int $count */
$count = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($count === $this || !is_int($count) || $count < 0) {
return $this->error($schema, $context, 'minProperties', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->count = $count;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MinPropertiesKeyword implements Keyword
{
use ErrorTrait;
protected int $count;
/**
* @param int $count
*/
public function __construct(int $count)
{
$this->count = $count;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$count = count($context->getObjectProperties());
if ($this->count <= $count) {
return null;
}
return $this->error($schema, $context, 'minProperties',
"Object must have at least {min} properties, {count} found", [
'min' => $this->count,
'count' => $count,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MinimumDataKeyword extends MinimumKeyword
{
protected JsonPointer $value;
/**
* @param $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var float|int $number */
$number = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($number === $this || !(is_float($number) || is_int($number))) {
return $this->error($schema, $context, 'minimum', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->number = $number;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class MinimumKeyword implements Keyword
{
use ErrorTrait;
protected float $number;
/**
* @param float $number
*/
public function __construct(float $number)
{
$this->number = $number;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($context->currentData() >= $this->number) {
return null;
}
return $this->error($schema, $context, 'minimum', "Number must be greater than or equal to {min}", [
'min' => $this->number,
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class MultipleOfDataKeyword extends MultipleOfKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct(0);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
/** @var float|int $number */
$number = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($number === $this || !(is_float($number) || is_int($number)) || $number <= 0) {
return $this->error($schema, $context, 'multipleOf', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->number = $number;
return parent::validate($context, $schema);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema,
Helper
};
use Opis\JsonSchema\Errors\ValidationError;
class MultipleOfKeyword implements Keyword
{
use ErrorTrait;
protected float $number;
/**
* @param float $number
*/
public function __construct(float $number)
{
$this->number = $number;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if (Helper::isMultipleOf($context->currentData(), $this->number)) {
return null;
}
return $this->error($schema, $context, 'multipleOf', "Number must be a multiple of {divisor}", [
'divisor' => $this->number,
]);
}
}

View File

@@ -0,0 +1,66 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class NotKeyword implements Keyword
{
use ErrorTrait;
/** @var bool|object|Schema */
protected $value;
/**
* @param bool|object $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->value === false) {
return null;
}
if ($this->value === true) {
return $this->error($schema, $context, 'not', "The data is never valid");
}
if (is_object($this->value) && !($this->value instanceof Schema)) {
$this->value = $context->loader()->loadObjectSchema($this->value);
}
$error = $context->validateSchemaWithoutEvaluated($this->value, 1);
if ($error) {
return null;
}
return $this->error($schema, $context, 'not', 'The data must not match schema');
}
}

View File

@@ -0,0 +1,45 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use ArrayObject;
use Opis\JsonSchema\ValidationContext;
trait OfTrait
{
protected function createArrayObject(ValidationContext $context): ?ArrayObject
{
return $context->trackUnevaluated() ? new ArrayObject() : null;
}
protected function addEvaluatedFromArrayObject(?ArrayObject $object, ValidationContext $context): void
{
if (!$object || !$object->count()) {
return;
}
foreach ($object as $value) {
if (isset($value['properties'])) {
$context->addEvaluatedProperties($value['properties']);
}
if (isset($value['items'])) {
$context->addEvaluatedItems($value['items']);
}
}
}
}

View File

@@ -0,0 +1,98 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class OneOfKeyword implements Keyword
{
use OfTrait;
use ErrorTrait;
/** @var bool[]|object[] */
protected array $value;
/**
* @param bool[]|object[] $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$count = 0;
$matchedIndex = -1;
$object = $this->createArrayObject($context);
$errors = [];
foreach ($this->value as $index => $value) {
if ($value === false) {
continue;
}
if ($value === true) {
if (++$count > 1) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [
'matched' => [$matchedIndex, $index],
]);
}
$matchedIndex = $index;
continue;
}
if (is_object($value) && !($value instanceof Schema)) {
$value = $this->value[$index] = $context->loader()->loadObjectSchema($value);
}
$error = $context->validateSchemaWithoutEvaluated($value, null, false, $object);
if ($error) {
$errors[] = $error;
} else {
if (++$count > 1) {
$this->addEvaluatedFromArrayObject($object, $context);
return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [
'matched' => [$matchedIndex, $index],
]);
}
$matchedIndex = $index;
}
}
$this->addEvaluatedFromArrayObject($object, $context);
if ($count === 1) {
return null;
}
return $this->error($schema, $context, 'oneOf', 'The data should match exactly one schema', [
'matched' => [],
], $errors);
}
}

View File

@@ -0,0 +1,56 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{Helper, ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class PatternDataKeyword extends PatternKeyword
{
protected JsonPointer $value;
/**
* @param JsonPointer $value
*/
public function __construct(JsonPointer $value)
{
$this->value = $value;
parent::__construct('');
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$pattern = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($pattern === $this || !is_string($pattern) || !Helper::isValidPattern($pattern)) {
return $this->error($schema, $context, 'pattern', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$this->pattern = $pattern;
$this->regex = Helper::patternToRegex($pattern);
$ret = parent::validate($context, $schema);
$this->pattern = $this->regex = null;
return $ret;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{Helper, ValidationContext, Keyword, Schema};
use Opis\JsonSchema\Errors\ValidationError;
class PatternKeyword implements Keyword
{
use ErrorTrait;
protected ?string $pattern;
protected ?string $regex;
/**
* @param string $pattern
*/
public function __construct(string $pattern)
{
$this->pattern = $pattern;
$this->regex = Helper::patternToRegex($pattern);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if (preg_match($this->regex, $context->currentData())) {
return null;
}
return $this->error($schema, $context, 'pattern', "The string should match pattern: {pattern}", [
'pattern' => $this->pattern,
]);
}
}

View File

@@ -0,0 +1,119 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Traversable;
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\{Helper, ValidationContext, Keyword, Schema};
class PatternPropertiesKeyword implements Keyword
{
use IterableDataValidationTrait;
/** @var bool[]|object[] */
protected array $value;
/**
* @param bool[]|object[] $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$props = $context->getObjectProperties();
if (!$props) {
return null;
}
$checked = [];
foreach ($this->value as $pattern => $value) {
if ($value === true) {
iterator_to_array($this->matchedProperties($pattern, $props, $checked));
continue;
}
if ($value === false) {
$list = iterator_to_array($this->matchedProperties($pattern, $props, $checked));
if ($list) {
if ($context->trackUnevaluated()) {
$context->addEvaluatedProperties(array_diff(array_keys($checked), $list));
}
return $this->error($schema, $context, 'patternProperties', "Object properties that match pattern '{pattern}' are not allowed", [
'pattern' => $pattern,
'forbidden' => $list,
]);
}
unset($list);
continue;
}
if (is_object($value) && !($value instanceof Schema)) {
$value = $this->value[$pattern] = $context->loader()->loadObjectSchema($value);
}
$subErrors = $this->iterateAndValidate($value, $context, $this->matchedProperties($pattern, $props, $checked));
if (!$subErrors->isEmpty()) {
if ($context->trackUnevaluated()) {
$context->addEvaluatedProperties(array_keys($checked));
}
return $this->error($schema, $context, 'patternProperties', "Object properties that match pattern '{pattern}' must also match pattern's schema", [
'pattern' => $pattern,
], $subErrors);
}
unset($subErrors);
}
if ($checked) {
$checked = array_keys($checked);
$context->addCheckedProperties($checked);
$context->addEvaluatedProperties($checked);
}
return null;
}
/**
* @param string $pattern
* @param array $props
* @param array $checked
* @return Traversable|string[]
*/
protected function matchedProperties(string $pattern, array $props, array &$checked): Traversable
{
$pattern = Helper::patternToRegex($pattern);
foreach ($props as $prop) {
if (preg_match($pattern, (string)$prop)) {
$checked[$prop] = true;
yield $prop;
}
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\Exceptions\UnresolvedReferenceException;
use Opis\JsonSchema\{JsonPointer, Schema, ValidationContext, Variables};
class PointerRefKeyword extends AbstractRefKeyword
{
protected JsonPointer $pointer;
/** @var bool|null|Schema */
protected $resolved = false;
public function __construct(
JsonPointer $pointer,
?Variables $mapper,
?Variables $globals,
?array $slots = null,
string $keyword = '$ref'
) {
parent::__construct($mapper, $globals, $slots, $keyword);
$this->pointer = $pointer;
}
protected function doValidate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->resolved === false) {
$info = $schema->info();
$this->resolved = $this->resolvePointer($context->loader(), $this->pointer, $info->idBaseRoot(), $info->path());
}
if ($this->resolved === null) {
throw new UnresolvedReferenceException((string)$this->pointer, $schema, $context);
}
return $this->resolved->validate($this->createContext($context, $schema));
}
}

View File

@@ -0,0 +1,109 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class PropertiesKeyword implements Keyword
{
use IterableDataValidationTrait;
protected array $properties;
protected array $propertyKeys;
/**
* @param array $properties
*/
public function __construct(array $properties)
{
$this->properties = $properties;
$this->propertyKeys = array_keys($properties);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if (!$this->properties) {
return null;
}
$checked = [];
$evaluated = [];
$data = $context->currentData();
$errors = $this->errorContainer($context->maxErrors());
foreach ($this->properties as $name => $prop) {
if (!property_exists($data, $name)) {
continue;
}
$checked[] = $name;
if ($prop === true) {
$evaluated[] = $name;
continue;
}
if ($prop === false) {
$context->addEvaluatedProperties($evaluated);
return $this->error($schema, $context, 'properties', "Property '{property}' is not allowed", [
'property' => $name,
]);
}
if (is_object($prop) && !($prop instanceof Schema)) {
$prop = $this->properties[$name] = $context->loader()->loadObjectSchema($prop);
}
$context->pushDataPath($name);
$error = $prop->validate($context);
$context->popDataPath();
if ($error) {
$errors->add($error);
if ($errors->isFull()) {
break;
}
} else {
$evaluated[] = $name;
}
}
$context->addEvaluatedProperties($evaluated);
if (!$errors->isEmpty()) {
return $this->error($schema, $context, 'properties', "The properties must match schema: {properties}", [
'properties' => array_values(array_diff($checked, $evaluated))
], $errors);
}
unset($errors);
$context->addCheckedProperties($checked);
return null;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{
ValidationContext,
Keyword,
Schema
};
use Opis\JsonSchema\Errors\ValidationError;
class PropertyNamesKeyword implements Keyword
{
use ErrorTrait;
/** @var bool|object */
protected $value;
/**
* @param bool|object $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->value === true) {
return null;
}
$props = $context->getObjectProperties();
if (!$props) {
return null;
}
if ($this->value === false) {
return $this->error($schema, $context, 'propertyNames', "No properties are allowed");
}
if (is_object($this->value) && !($this->value instanceof Schema)) {
$this->value = $context->loader()->loadObjectSchema($this->value);
}
foreach ($props as $prop) {
if ($error = $this->value->validate($context->newInstance($prop, $schema))) {
return $this->error($schema, $context, 'propertyNames', "Property '{property}' must match schema", [
'property' => $prop,
], $error);
}
}
return null;
}
}

View File

@@ -0,0 +1,138 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\Exceptions\UnresolvedReferenceException;
use Opis\JsonSchema\{Schema, ValidationContext, Variables, Uri};
class RecursiveRefKeyword extends AbstractRefKeyword
{
protected Uri $uri;
/** @var bool|null|Schema */
protected $resolved = false;
protected string $anchor;
protected $anchorValue;
public function __construct(
Uri $uri,
?Variables $mapper,
?Variables $globals,
?array $slots = null,
string $keyword = '$recursiveRef',
string $anchor = '$recursiveAnchor',
$anchorValue = true
) {
parent::__construct($mapper, $globals, $slots, $keyword);
$this->uri = $uri;
$this->anchor = $anchor;
$this->anchorValue = $anchorValue;
}
/**
* @inheritDoc
*/
public function doValidate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->resolved === false) {
$this->resolved = $context->loader()->loadSchemaById($this->uri);
}
if ($this->resolved === null) {
throw new UnresolvedReferenceException((string)$this->uri, $schema, $context);
}
$new_context = $this->createContext($context, $schema);
if (!$this->hasRecursiveAnchor($this->resolved)) {
$this->setLastRefSchema($this->resolved);
return $this->resolved->validate($new_context);
}
$ok_sender = $this->resolveSchema($context);
if (!$ok_sender) {
$this->setLastRefSchema($this->resolved);
return $this->resolved->validate($new_context);
}
$this->setLastRefSchema($ok_sender);
return $ok_sender->validate($new_context);
}
protected function resolveSchema(ValidationContext $context): ?Schema
{
$ok = null;
$loader = $context->loader();
while ($context) {
$sender = $context->sender();
if (!$sender) {
break;
}
if (!$this->hasRecursiveAnchor($sender)) {
if ($sender->info()->id()) {
// id without recursiveAnchor
break;
}
$sender = $loader->loadSchemaById($sender->info()->root());
if (!$sender || !$this->hasRecursiveAnchor($sender)) {
// root without recursiveAnchor
break;
}
}
if ($sender->info()->id()) {
// id with recursiveAnchor
$ok = $sender;
} else {
// root with recursiveAnchor
$ok = $loader->loadSchemaById($sender->info()->root());
}
$context = $context->parent();
}
return $ok;
}
protected function hasRecursiveAnchor(?Schema $schema): bool
{
if (!$schema) {
return false;
}
$info = $schema->info();
if (!$info->isObject()) {
return false;
}
$data = $info->data();
if (!property_exists($data, $this->anchor)) {
return false;
}
return $data->{$this->anchor} === $this->anchorValue;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/* ============================================================================
* Copyright 2020 Zindex Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================================ */
namespace Opis\JsonSchema\Keywords;
use Opis\JsonSchema\{ValidationContext, Schema, JsonPointer};
use Opis\JsonSchema\Errors\ValidationError;
class RequiredDataKeyword extends RequiredKeyword
{
protected JsonPointer $value;
/** @var callable|null */
protected $filter;
/**
* @param JsonPointer $value
* @param callable|null $filter
*/
public function __construct(JsonPointer $value, ?callable $filter = null)
{
$this->value = $value;
$this->filter = $filter;
parent::__construct([]);
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$required = $this->value->data($context->rootData(), $context->currentDataPath(), $this);
if ($required === $this || !is_array($required) || !$this->requiredPropsAreValid($required)) {
return $this->error($schema, $context, 'required', 'Invalid $data', [
'pointer' => (string)$this->value,
]);
}
$required = array_unique($required);
if ($this->filter) {
$required = array_filter($required, $this->filter);
}
if (!$required) {
return null;
}
$this->required = $required;
$ret = parent::validate($context, $schema);
$this->required = null;
return $ret;
}
/**
* @param array $props
* @return bool
*/
protected function requiredPropsAreValid(array $props): bool
{
foreach ($props as $prop) {
if (!is_string($prop)) {
return false;
}
}
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More