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

View File

@@ -0,0 +1,68 @@
<?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 RequiredKeyword implements Keyword
{
use ErrorTrait;
/** @var string[] */
protected ?array $required;
/**
* @param string[] $required
*/
public function __construct(array $required)
{
$this->required = $required;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$data = $context->currentData();
$max = $context->maxErrors();
$list = [];
foreach ($this->required as $name) {
if (!property_exists($data, $name)) {
$list[] = $name;
if (--$max <= 0) {
break;
}
}
}
if (!$list) {
return null;
}
return $this->error($schema, $context, 'required', 'The required properties ({missing}) are missing', [
'missing' => $list,
]);
}
}

View File

@@ -0,0 +1,151 @@
<?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 SlotsKeyword implements Keyword
{
use ErrorTrait;
/** @var bool[]|Schema[]|object[]|string[] */
protected array $slots;
/** @var string[] */
protected array $stack = [];
/**
* @param string[]|bool[]|object[]|Schema[] $slots
*/
public function __construct(array $slots)
{
$this->slots = $slots;
}
/**
* @inheritDoc
*/
public function validate(ValidationContext $context, Schema $schema): ?ValidationError
{
$newContext = $context->newInstance($context->currentData(), $schema);
foreach ($this->slots as $name => $fallback) {
$slot = $this->resolveSlotSchema($name, $context);
if ($slot === null) {
$save = true;
if (is_string($fallback)) {
$save = false;
$fallback = $this->resolveSlot($fallback, $context);
}
if ($fallback === true) {
continue;
}
if ($fallback === false) {
return $this->error($schema, $context, '$slots', "Required slot '{slot}' is missing", [
'slot' => $name,
]);
}
if (is_object($fallback) && !($fallback instanceof Schema)) {
$fallback = $context->loader()->loadObjectSchema($fallback);
if ($save) {
$this->slots[$name] = $fallback;
}
}
$slot = $fallback;
}
if ($error = $slot->validate($newContext)) {
return $this->error($schema, $context,'$slots', "Schema for slot '{slot}' was not matched", [
'slot' => $name,
], $error);
}
}
return null;
}
/**
* @param string $name
* @param ValidationContext $context
* @return Schema|null
*/
protected function resolveSlotSchema(string $name, ValidationContext $context): ?Schema
{
do {
$slot = $context->slot($name);
} while ($slot === null && $context = $context->parent());
return $slot;
}
/**
* @param string $name
* @param ValidationContext $context
* @return bool|Schema
*/
protected function resolveSlot(string $name, ValidationContext $context)
{
$slot = $this->resolveSlotSchema($name, $context);
if ($slot !== null) {
return $slot;
}
if (!isset($this->slots[$name])) {
return false;
}
$slot = $this->slots[$name];
if (is_bool($slot)) {
return $slot;
}
if (is_object($slot)) {
if ($slot instanceof Schema) {
return $slot;
}
$slot = $context->loader()->loadObjectSchema($slot);
$this->slots[$name] = $slot;
return $slot;
}
if (!is_string($slot)) {
// Looks like the slot is missing
return false;
}
if (in_array($slot, $this->stack)) {
// Recursive
return false;
}
$this->stack[] = $slot;
$slot = $this->resolveSlot($slot, $context);
array_pop($this->stack);
return $slot;
}
}

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 Opis\Uri\UriTemplate;
use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\Exceptions\UnresolvedReferenceException;
use Opis\JsonSchema\{JsonPointer, Schema, SchemaLoader, Uri, ValidationContext, Variables};
class TemplateRefKeyword extends AbstractRefKeyword
{
protected UriTemplate $template;
protected ?Variables $vars = null;
/** @var Schema[]|null[] */
protected ?array $cached = [];
protected bool $allowRelativeJsonPointerInRef;
public function __construct(
UriTemplate $template,
?Variables $vars,
?Variables $mapper = null,
?Variables $globals = null,
?array $slots = null,
string $keyword = '$ref',
bool $allowRelativeJsonPointerInRef = true
) {
parent::__construct($mapper, $globals, $slots, $keyword);
$this->template = $template;
$this->vars = $vars;
$this->allowRelativeJsonPointerInRef = $allowRelativeJsonPointerInRef;
}
protected function doValidate(ValidationContext $context, Schema $schema): ?ValidationError
{
if ($this->vars) {
$vars = $this->vars->resolve($context->rootData(), $context->currentDataPath());
if (!is_array($vars)) {
$vars = (array)$vars;
}
$vars += $context->globals();
} else {
$vars = $context->globals();
}
$ref = $this->template->resolve($vars);
$key = isset($ref[32]) ? md5($ref) : $ref;
if (!array_key_exists($key, $this->cached)) {
$this->cached[$key] = $this->resolveRef($ref, $context->loader(), $schema);
}
$resolved = $this->cached[$key];
unset($key);
if (!$resolved) {
throw new UnresolvedReferenceException($ref, $schema, $context);
}
return $resolved->validate($this->createContext($context, $schema));
}
/**
* @param string $ref
* @param SchemaLoader $repo
* @param Schema $schema
* @return null|Schema
*/
protected function resolveRef(string $ref, SchemaLoader $repo, Schema $schema): ?Schema
{
if ($ref === '') {
return null;
}
$baseUri = $schema->info()->idBaseRoot();
if ($ref === '#') {
return $repo->loadSchemaById($baseUri);
}
// Check if is pointer
if ($ref[0] === '#') {
if ($pointer = JsonPointer::parse(substr($ref, 1))) {
if ($pointer->isAbsolute()) {
return $this->resolvePointer($repo, $pointer, $baseUri);
}
unset($pointer);
}
} elseif ($this->allowRelativeJsonPointerInRef && ($pointer = JsonPointer::parse($ref))) {
if ($pointer->isRelative()) {
return $this->resolvePointer($repo, $pointer, $baseUri, $schema->info()->path());
}
unset($pointer);
}
$ref = Uri::merge($ref, $baseUri, true);
if ($ref === null || !$ref->isAbsolute()) {
return null;
}
return $repo->loadSchemaById($ref);
}
}

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