Обновление клиента (apps, 3rdparty, install)
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
use JsonSchema\Exception\InvalidArgumentException;
|
||||
|
||||
class ConstraintError extends Enum
|
||||
{
|
||||
public const ADDITIONAL_ITEMS = 'additionalItems';
|
||||
public const ADDITIONAL_PROPERTIES = 'additionalProp';
|
||||
public const ALL_OF = 'allOf';
|
||||
public const ANY_OF = 'anyOf';
|
||||
public const DEPENDENCIES = 'dependencies';
|
||||
public const DISALLOW = 'disallow';
|
||||
public const DIVISIBLE_BY = 'divisibleBy';
|
||||
public const ENUM = 'enum';
|
||||
public const CONSTANT = 'const';
|
||||
public const EXCLUSIVE_MINIMUM = 'exclusiveMinimum';
|
||||
public const EXCLUSIVE_MAXIMUM = 'exclusiveMaximum';
|
||||
public const FORMAT_COLOR = 'colorFormat';
|
||||
public const FORMAT_DATE = 'dateFormat';
|
||||
public const FORMAT_DATE_TIME = 'dateTimeFormat';
|
||||
public const FORMAT_DATE_UTC = 'dateUtcFormat';
|
||||
public const FORMAT_EMAIL = 'emailFormat';
|
||||
public const FORMAT_HOSTNAME = 'styleHostName';
|
||||
public const FORMAT_IP = 'ipFormat';
|
||||
public const FORMAT_PHONE = 'phoneFormat';
|
||||
public const FORMAT_REGEX= 'regexFormat';
|
||||
public const FORMAT_STYLE = 'styleFormat';
|
||||
public const FORMAT_TIME = 'timeFormat';
|
||||
public const FORMAT_URL = 'urlFormat';
|
||||
public const FORMAT_URL_REF = 'urlRefFormat';
|
||||
public const INVALID_SCHEMA = 'invalidSchema';
|
||||
public const LENGTH_MAX = 'maxLength';
|
||||
public const LENGTH_MIN = 'minLength';
|
||||
public const MAXIMUM = 'maximum';
|
||||
public const MIN_ITEMS = 'minItems';
|
||||
public const MINIMUM = 'minimum';
|
||||
public const MISSING_ERROR = 'missingError';
|
||||
public const MISSING_MAXIMUM = 'missingMaximum';
|
||||
public const MISSING_MINIMUM = 'missingMinimum';
|
||||
public const MAX_ITEMS = 'maxItems';
|
||||
public const MULTIPLE_OF = 'multipleOf';
|
||||
public const NOT = 'not';
|
||||
public const ONE_OF = 'oneOf';
|
||||
public const REQUIRED = 'required';
|
||||
public const REQUIRES = 'requires';
|
||||
public const PATTERN = 'pattern';
|
||||
public const PREGEX_INVALID = 'pregrex';
|
||||
public const PROPERTIES_MIN = 'minProperties';
|
||||
public const PROPERTIES_MAX = 'maxProperties';
|
||||
public const TYPE = 'type';
|
||||
public const UNIQUE_ITEMS = 'uniqueItems';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
$name = $this->getValue();
|
||||
static $messages = [
|
||||
self::ADDITIONAL_ITEMS => 'The item %s[%s] is not defined and the definition does not allow additional items',
|
||||
self::ADDITIONAL_PROPERTIES => 'The property %s is not defined and the definition does not allow additional properties',
|
||||
self::ALL_OF => 'Failed to match all schemas',
|
||||
self::ANY_OF => 'Failed to match at least one schema',
|
||||
self::DEPENDENCIES => '%s depends on %s, which is missing',
|
||||
self::DISALLOW => 'Disallowed value was matched',
|
||||
self::DIVISIBLE_BY => 'Is not divisible by %d',
|
||||
self::ENUM => 'Does not have a value in the enumeration %s',
|
||||
self::CONSTANT => 'Does not have a value equal to %s',
|
||||
self::EXCLUSIVE_MINIMUM => 'Must have a minimum value greater than %d',
|
||||
self::EXCLUSIVE_MAXIMUM => 'Must have a maximum value less than %d',
|
||||
self::FORMAT_COLOR => 'Invalid color',
|
||||
self::FORMAT_DATE => 'Invalid date %s, expected format YYYY-MM-DD',
|
||||
self::FORMAT_DATE_TIME => 'Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm',
|
||||
self::FORMAT_DATE_UTC => 'Invalid time %s, expected integer of milliseconds since Epoch',
|
||||
self::FORMAT_EMAIL => 'Invalid email',
|
||||
self::FORMAT_HOSTNAME => 'Invalid hostname',
|
||||
self::FORMAT_IP => 'Invalid IP address',
|
||||
self::FORMAT_PHONE => 'Invalid phone number',
|
||||
self::FORMAT_REGEX=> 'Invalid regex format %s',
|
||||
self::FORMAT_STYLE => 'Invalid style',
|
||||
self::FORMAT_TIME => 'Invalid time %s, expected format hh:mm:ss',
|
||||
self::FORMAT_URL => 'Invalid URL format',
|
||||
self::FORMAT_URL_REF => 'Invalid URL reference format',
|
||||
self::LENGTH_MAX => 'Must be at most %d characters long',
|
||||
self::INVALID_SCHEMA => 'Schema is not valid',
|
||||
self::LENGTH_MIN => 'Must be at least %d characters long',
|
||||
self::MAX_ITEMS => 'There must be a maximum of %d items in the array, %d found',
|
||||
self::MAXIMUM => 'Must have a maximum value less than or equal to %d',
|
||||
self::MIN_ITEMS => 'There must be a minimum of %d items in the array, %d found',
|
||||
self::MINIMUM => 'Must have a minimum value greater than or equal to %d',
|
||||
self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum',
|
||||
self::MISSING_MINIMUM => 'Use of exclusiveMinimum requires presence of minimum',
|
||||
/*self::MISSING_ERROR => 'Used for tests; this error is deliberately commented out',*/
|
||||
self::MULTIPLE_OF => 'Must be a multiple of %s',
|
||||
self::NOT => 'Matched a schema which it should not',
|
||||
self::ONE_OF => 'Failed to match exactly one schema',
|
||||
self::REQUIRED => 'The property %s is required',
|
||||
self::REQUIRES => 'The presence of the property %s requires that %s also be present',
|
||||
self::PATTERN => 'Does not match the regex pattern %s',
|
||||
self::PREGEX_INVALID => 'The pattern %s is invalid',
|
||||
self::PROPERTIES_MIN => 'Must contain a minimum of %d properties',
|
||||
self::PROPERTIES_MAX => 'Must contain no more than %d properties',
|
||||
self::TYPE => '%s value found, but %s is required',
|
||||
self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array'
|
||||
];
|
||||
|
||||
if (!isset($messages[$name])) {
|
||||
throw new InvalidArgumentException('Missing error message for ' . $name);
|
||||
}
|
||||
|
||||
return $messages[$name];
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use const JSON_ERROR_NONE;
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Exception\InvalidArgumentException;
|
||||
use JsonSchema\Exception\ValidationException;
|
||||
use JsonSchema\Validator;
|
||||
|
||||
/**
|
||||
* A more basic constraint definition - used for the public
|
||||
* interface to avoid exposing library internals.
|
||||
*/
|
||||
class BaseConstraint
|
||||
{
|
||||
/**
|
||||
* @var array Errors
|
||||
*/
|
||||
protected $errors = [];
|
||||
|
||||
/**
|
||||
* @var int All error types which have occurred
|
||||
* @phpstan-var int-mask-of<Validator::ERROR_*>
|
||||
*/
|
||||
protected $errorMask = Validator::ERROR_NONE;
|
||||
|
||||
/**
|
||||
* @var Factory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
public function __construct(?Factory $factory = null)
|
||||
{
|
||||
$this->factory = $factory ?: new Factory();
|
||||
}
|
||||
|
||||
public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void
|
||||
{
|
||||
$message = $constraint->getMessage();
|
||||
$name = $constraint->getValue();
|
||||
$error = [
|
||||
'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
|
||||
'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'),
|
||||
'message' => ucfirst(vsprintf($message, array_map(static function ($val) {
|
||||
if (is_scalar($val)) {
|
||||
return is_bool($val) ? var_export($val, true) : $val;
|
||||
}
|
||||
|
||||
return json_encode($val);
|
||||
}, array_values($more)))),
|
||||
'constraint' => [
|
||||
'name' => $name,
|
||||
'params' => $more
|
||||
],
|
||||
'context' => $this->factory->getErrorContext(),
|
||||
];
|
||||
|
||||
if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
|
||||
throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message']));
|
||||
}
|
||||
|
||||
$this->errors[] = $error;
|
||||
$this->errorMask |= $error['context'];
|
||||
}
|
||||
|
||||
public function addErrors(array $errors): void
|
||||
{
|
||||
if ($errors) {
|
||||
$this->errors = array_merge($this->errors, $errors);
|
||||
$errorMask = &$this->errorMask;
|
||||
array_walk($errors, static function ($error) use (&$errorMask) {
|
||||
if (isset($error['context'])) {
|
||||
$errorMask |= $error['context'];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param int-mask-of<Validator::ERROR_*> $errorContext
|
||||
*/
|
||||
public function getErrors(int $errorContext = Validator::ERROR_ALL): array
|
||||
{
|
||||
if ($errorContext === Validator::ERROR_ALL) {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
return array_filter($this->errors, static function ($error) use ($errorContext) {
|
||||
return (bool) ($errorContext & $error['context']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param int-mask-of<Validator::ERROR_*> $errorContext
|
||||
*/
|
||||
public function numErrors(int $errorContext = Validator::ERROR_ALL): int
|
||||
{
|
||||
if ($errorContext === Validator::ERROR_ALL) {
|
||||
return count($this->errors);
|
||||
}
|
||||
|
||||
return count($this->getErrors($errorContext));
|
||||
}
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
return !$this->getErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any reported errors. Should be used between
|
||||
* multiple validation checks.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->errors = [];
|
||||
$this->errorMask = Validator::ERROR_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error mask
|
||||
*
|
||||
* @phpstan-return int-mask-of<Validator::ERROR_*>
|
||||
*/
|
||||
public function getErrorMask(): int
|
||||
{
|
||||
return $this->errorMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively cast an associative array to an object
|
||||
*/
|
||||
public static function arrayToObjectRecursive(array $array): object
|
||||
{
|
||||
$json = json_encode($array);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$message = 'Unable to encode schema array as JSON';
|
||||
if (function_exists('json_last_error_msg')) {
|
||||
$message .= ': ' . json_last_error_msg();
|
||||
}
|
||||
throw new InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
return (object) json_decode($json, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a JSON pattern into a PCRE regex
|
||||
*/
|
||||
public static function jsonPatternToPhpRegex(string $pattern): string
|
||||
{
|
||||
return '~' . str_replace('~', '\\~', $pattern) . '~u';
|
||||
}
|
||||
|
||||
protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string
|
||||
{
|
||||
$result = array_map(
|
||||
static function ($path) {
|
||||
return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path);
|
||||
},
|
||||
$pointer->getPropertyPaths()
|
||||
);
|
||||
|
||||
return trim(implode('', $result), '.');
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Tool\DeepComparer;
|
||||
|
||||
/**
|
||||
* The CollectionConstraint Constraints, validates an array against a given schema
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
|
||||
*/
|
||||
class CollectionConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
// Verify minItems
|
||||
if (isset($schema->minItems) && count($value) < $schema->minItems) {
|
||||
$this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => count($value)]);
|
||||
}
|
||||
|
||||
// Verify maxItems
|
||||
if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
|
||||
$this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => count($value)]);
|
||||
}
|
||||
|
||||
// Verify uniqueItems
|
||||
if (isset($schema->uniqueItems) && $schema->uniqueItems) {
|
||||
$count = count($value);
|
||||
for ($x = 0; $x < $count - 1; $x++) {
|
||||
for ($y = $x + 1; $y < $count; $y++) {
|
||||
if (DeepComparer::isEqual($value[$x], $value[$y])) {
|
||||
$this->addError(ConstraintError::UNIQUE_ITEMS(), $path);
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->validateItems($value, $schema, $path, $i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the items
|
||||
*
|
||||
* @param array $value
|
||||
* @param \stdClass $schema
|
||||
* @param string $i
|
||||
*/
|
||||
protected function validateItems(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
if (\is_null($schema) || !isset($schema->items)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($schema->items === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_object($schema->items)) {
|
||||
// just one type definition for the whole array
|
||||
foreach ($value as $k => &$v) {
|
||||
$initErrors = $this->getErrors();
|
||||
|
||||
// First check if its defined in "items"
|
||||
$this->checkUndefined($v, $schema->items, $path, $k);
|
||||
|
||||
// Recheck with "additionalItems" if the first test fails
|
||||
if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
|
||||
$secondErrors = $this->getErrors();
|
||||
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
|
||||
}
|
||||
|
||||
// Reset errors if needed
|
||||
if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
|
||||
$this->errors = $secondErrors;
|
||||
} elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
|
||||
$this->errors = $initErrors;
|
||||
}
|
||||
}
|
||||
unset($v); /* remove dangling reference to prevent any future bugs
|
||||
* caused by accidentally using $v elsewhere */
|
||||
} else {
|
||||
// Defined item type definitions
|
||||
foreach ($value as $k => &$v) {
|
||||
if (array_key_exists($k, $schema->items)) {
|
||||
$this->checkUndefined($v, $schema->items[$k], $path, $k);
|
||||
} else {
|
||||
// Additional items
|
||||
if (property_exists($schema, 'additionalItems')) {
|
||||
if ($schema->additionalItems !== false) {
|
||||
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
|
||||
} else {
|
||||
$this->addError(
|
||||
ConstraintError::ADDITIONAL_ITEMS(),
|
||||
$path,
|
||||
[
|
||||
'item' => $i,
|
||||
'property' => $k,
|
||||
'additionalItems' => $schema->additionalItems
|
||||
]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Should be valid against an empty schema
|
||||
$this->checkUndefined($v, new \stdClass(), $path, $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($v); /* remove dangling reference to prevent any future bugs
|
||||
* caused by accidentally using $v elsewhere */
|
||||
|
||||
// Treat when we have more schema definitions than values, not for empty arrays
|
||||
if (count($value) > 0) {
|
||||
for ($k = count($value); $k < count($schema->items); $k++) {
|
||||
$undefinedInstance = $this->factory->createInstanceFor('undefined');
|
||||
$this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Tool\DeepComparer;
|
||||
|
||||
/**
|
||||
* The ConstConstraint Constraints, validates an element against a constant value
|
||||
*
|
||||
* @author Martin Helmich <martin@helmich.me>
|
||||
*/
|
||||
class ConstConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
// Only validate const if the attribute exists
|
||||
if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
|
||||
return;
|
||||
}
|
||||
$const = $schema->const;
|
||||
|
||||
$type = gettype($element);
|
||||
$constType = gettype($const);
|
||||
|
||||
if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type === 'array' && $constType === 'object') {
|
||||
if (DeepComparer::isEqual((object) $element, $const)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (DeepComparer::isEqual($element, $const)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]);
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
|
||||
abstract class Constraint extends BaseConstraint implements ConstraintInterface
|
||||
{
|
||||
/** @var string */
|
||||
protected $inlineSchemaProperty = '$schema';
|
||||
|
||||
public const CHECK_MODE_NONE = 0x00000000;
|
||||
public const CHECK_MODE_NORMAL = 0x00000001;
|
||||
public const CHECK_MODE_TYPE_CAST = 0x00000002;
|
||||
public const CHECK_MODE_COERCE_TYPES = 0x00000004;
|
||||
public const CHECK_MODE_APPLY_DEFAULTS = 0x00000008;
|
||||
public const CHECK_MODE_EXCEPTIONS = 0x00000010;
|
||||
public const CHECK_MODE_DISABLE_FORMAT = 0x00000020;
|
||||
public const CHECK_MODE_EARLY_COERCE = 0x00000040;
|
||||
public const CHECK_MODE_ONLY_REQUIRED_DEFAULTS = 0x00000080;
|
||||
public const CHECK_MODE_VALIDATE_SCHEMA = 0x00000100;
|
||||
|
||||
/**
|
||||
* Bubble down the path
|
||||
*
|
||||
* @param JsonPointer|null $path Current path
|
||||
* @param mixed $i What to append to the path
|
||||
*/
|
||||
protected function incrementPath(?JsonPointer $path, $i): JsonPointer
|
||||
{
|
||||
$path = $path ?? new JsonPointer('');
|
||||
|
||||
if ($i === null || $i === '') {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return $path->withPropertyPaths(
|
||||
array_merge(
|
||||
$path->getPropertyPaths(),
|
||||
[$i]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an array
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkArray(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$validator = $this->factory->createInstanceFor('collection');
|
||||
$validator->check($value, $schema, $path, $i);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an object
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $properties
|
||||
* @param mixed $additionalProperties
|
||||
* @param mixed $patternProperties
|
||||
* @param array<string> $appliedDefaults
|
||||
*/
|
||||
protected function checkObject(
|
||||
&$value,
|
||||
$schema = null,
|
||||
?JsonPointer $path = null,
|
||||
$properties = null,
|
||||
$additionalProperties = null,
|
||||
$patternProperties = null,
|
||||
array $appliedDefaults = []
|
||||
): void {
|
||||
/** @var ObjectConstraint $validator */
|
||||
$validator = $this->factory->createInstanceFor('object');
|
||||
$validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the type of the value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkType(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$validator = $this->factory->createInstanceFor('type');
|
||||
$validator->check($value, $schema, $path, $i);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a undefined element
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkUndefined(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void
|
||||
{
|
||||
/** @var UndefinedConstraint $validator */
|
||||
$validator = $this->factory->createInstanceFor('undefined');
|
||||
|
||||
$validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a string element
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkString($value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$validator = $this->factory->createInstanceFor('string');
|
||||
$validator->check($value, $schema, $path, $i);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a number element
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkNumber($value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$validator = $this->factory->createInstanceFor('number');
|
||||
$validator->check($value, $schema, $path, $i);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a enum element
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkEnum($value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$validator = $this->factory->createInstanceFor('enum');
|
||||
$validator->check($value, $schema, $path, $i);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a const element
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkConst($value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$validator = $this->factory->createInstanceFor('const');
|
||||
$validator->check($value, $schema, $path, $i);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks format of an element
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*/
|
||||
protected function checkFormat($value, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$validator = $this->factory->createInstanceFor('format');
|
||||
$validator->check($value, $schema, $path, $i);
|
||||
|
||||
$this->addErrors($validator->getErrors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type check based on the set check mode.
|
||||
*/
|
||||
protected function getTypeCheck(): TypeCheck\TypeCheckInterface
|
||||
{
|
||||
return $this->factory->getTypeCheck();
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
|
||||
/**
|
||||
* The Constraints Interface
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
*/
|
||||
interface ConstraintInterface
|
||||
{
|
||||
/**
|
||||
* returns all collected errors
|
||||
*/
|
||||
public function getErrors(): array;
|
||||
|
||||
/**
|
||||
* adds errors to this validator
|
||||
*/
|
||||
public function addErrors(array $errors): void;
|
||||
|
||||
/**
|
||||
* adds an error
|
||||
*
|
||||
* @param ConstraintError $constraint the constraint/rule that is broken, e.g.: ConstraintErrors::LENGTH_MIN()
|
||||
* @param array $more more array elements to add to the error
|
||||
*/
|
||||
public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void;
|
||||
|
||||
/**
|
||||
* checks if the validator has not raised errors
|
||||
*/
|
||||
public function isValid(): bool;
|
||||
|
||||
/**
|
||||
* invokes the validation of an element
|
||||
*
|
||||
* @abstract
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param mixed $i
|
||||
*
|
||||
* @throws \JsonSchema\Exception\ExceptionInterface
|
||||
*/
|
||||
public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Tool\DeepComparer;
|
||||
|
||||
/**
|
||||
* The EnumConstraint Constraints, validates an element against a given set of possibilities
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
|
||||
*/
|
||||
class EnumConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
// Only validate enum if the attribute exists
|
||||
if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
|
||||
return;
|
||||
}
|
||||
$type = gettype($element);
|
||||
|
||||
foreach ($schema->enum as $enum) {
|
||||
$enumType = gettype($enum);
|
||||
|
||||
if ($enumType === 'object'
|
||||
&& $type === 'array'
|
||||
&& $this->factory->getConfig(self::CHECK_MODE_TYPE_CAST)
|
||||
&& DeepComparer::isEqual((object) $element, $enum)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (($type === $enumType) && DeepComparer::isEqual($element, $enum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_numeric($element) && is_numeric($enum) && DeepComparer::isEqual((float) $element, (float) $enum)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\Exception\InvalidArgumentException;
|
||||
use JsonSchema\SchemaStorage;
|
||||
use JsonSchema\SchemaStorageInterface;
|
||||
use JsonSchema\Uri\UriRetriever;
|
||||
use JsonSchema\UriRetrieverInterface;
|
||||
use JsonSchema\Validator;
|
||||
|
||||
/**
|
||||
* Factory for centralize constraint initialization.
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* @var SchemaStorageInterface
|
||||
*/
|
||||
protected $schemaStorage;
|
||||
|
||||
/**
|
||||
* @var UriRetriever
|
||||
*/
|
||||
protected $uriRetriever;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @phpstan-var int-mask-of<Constraint::CHECK_MODE_*>
|
||||
*/
|
||||
private $checkMode = Constraint::CHECK_MODE_NORMAL;
|
||||
|
||||
/**
|
||||
* @var array<int, TypeCheck\TypeCheckInterface>
|
||||
* @phpstan-var array<int-mask-of<Constraint::CHECK_MODE_*>, TypeCheck\TypeCheckInterface>
|
||||
*/
|
||||
private $typeCheck = [];
|
||||
|
||||
/**
|
||||
* @var int Validation context
|
||||
*/
|
||||
protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $constraintMap = [
|
||||
'array' => 'JsonSchema\Constraints\CollectionConstraint',
|
||||
'collection' => 'JsonSchema\Constraints\CollectionConstraint',
|
||||
'object' => 'JsonSchema\Constraints\ObjectConstraint',
|
||||
'type' => 'JsonSchema\Constraints\TypeConstraint',
|
||||
'undefined' => 'JsonSchema\Constraints\UndefinedConstraint',
|
||||
'string' => 'JsonSchema\Constraints\StringConstraint',
|
||||
'number' => 'JsonSchema\Constraints\NumberConstraint',
|
||||
'enum' => 'JsonSchema\Constraints\EnumConstraint',
|
||||
'const' => 'JsonSchema\Constraints\ConstConstraint',
|
||||
'format' => 'JsonSchema\Constraints\FormatConstraint',
|
||||
'schema' => 'JsonSchema\Constraints\SchemaConstraint',
|
||||
'validator' => 'JsonSchema\Validator'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<ConstraintInterface>
|
||||
*/
|
||||
private $instanceCache = [];
|
||||
|
||||
/**
|
||||
* @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
|
||||
*/
|
||||
public function __construct(
|
||||
?SchemaStorageInterface $schemaStorage = null,
|
||||
?UriRetrieverInterface $uriRetriever = null,
|
||||
int $checkMode = Constraint::CHECK_MODE_NORMAL
|
||||
) {
|
||||
// set provided config options
|
||||
$this->setConfig($checkMode);
|
||||
|
||||
$this->uriRetriever = $uriRetriever ?: new UriRetriever();
|
||||
$this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set config values
|
||||
*
|
||||
* @param int $checkMode Set checkMode options - does not preserve existing flags
|
||||
* @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
|
||||
*/
|
||||
public function setConfig(int $checkMode = Constraint::CHECK_MODE_NORMAL): void
|
||||
{
|
||||
$this->checkMode = $checkMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable checkMode flags
|
||||
*
|
||||
* @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $options
|
||||
*/
|
||||
public function addConfig(int $options): void
|
||||
{
|
||||
$this->checkMode |= $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable checkMode flags
|
||||
*
|
||||
* @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $options
|
||||
*/
|
||||
public function removeConfig(int $options): void
|
||||
{
|
||||
$this->checkMode &= ~$options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get checkMode option
|
||||
*
|
||||
* @param int|null $options Options to get, if null then return entire bitmask
|
||||
* @phpstan-param int-mask-of<Constraint::CHECK_MODE_*>|null $options Options to get, if null then return entire bitmask
|
||||
*
|
||||
* @phpstan-return int-mask-of<Constraint::CHECK_MODE_*>
|
||||
*/
|
||||
public function getConfig(?int $options = null): int
|
||||
{
|
||||
if ($options === null) {
|
||||
return $this->checkMode;
|
||||
}
|
||||
|
||||
return $this->checkMode & $options;
|
||||
}
|
||||
|
||||
public function getUriRetriever(): UriRetrieverInterface
|
||||
{
|
||||
return $this->uriRetriever;
|
||||
}
|
||||
|
||||
public function getSchemaStorage(): SchemaStorageInterface
|
||||
{
|
||||
return $this->schemaStorage;
|
||||
}
|
||||
|
||||
public function getTypeCheck(): TypeCheck\TypeCheckInterface
|
||||
{
|
||||
if (!isset($this->typeCheck[$this->checkMode])) {
|
||||
$this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST)
|
||||
? new TypeCheck\LooseTypeCheck()
|
||||
: new TypeCheck\StrictTypeCheck();
|
||||
}
|
||||
|
||||
return $this->typeCheck[$this->checkMode];
|
||||
}
|
||||
|
||||
public function setConstraintClass(string $name, string $class): Factory
|
||||
{
|
||||
// Ensure class exists
|
||||
if (!class_exists($class)) {
|
||||
throw new InvalidArgumentException('Unknown constraint ' . $name);
|
||||
}
|
||||
// Ensure class is appropriate
|
||||
if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) {
|
||||
throw new InvalidArgumentException('Invalid class ' . $name);
|
||||
}
|
||||
$this->constraintMap[$name] = $class;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a constraint instance for the given constraint name.
|
||||
*
|
||||
* @param string $constraintName
|
||||
*
|
||||
* @throws InvalidArgumentException if is not possible create the constraint instance
|
||||
*
|
||||
* @return ConstraintInterface&BaseConstraint
|
||||
* @phpstan-return ConstraintInterface&BaseConstraint
|
||||
*/
|
||||
public function createInstanceFor($constraintName)
|
||||
{
|
||||
if (!isset($this->constraintMap[$constraintName])) {
|
||||
throw new InvalidArgumentException('Unknown constraint ' . $constraintName);
|
||||
}
|
||||
|
||||
if (!isset($this->instanceCache[$constraintName])) {
|
||||
$this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this);
|
||||
}
|
||||
|
||||
return clone $this->instanceCache[$constraintName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error context
|
||||
*
|
||||
* @phpstan-return Validator::ERROR_DOCUMENT_VALIDATION|Validator::ERROR_SCHEMA_VALIDATION
|
||||
*/
|
||||
public function getErrorContext(): int
|
||||
{
|
||||
return $this->errorContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the error context
|
||||
*
|
||||
* @phpstan-param Validator::ERROR_DOCUMENT_VALIDATION|Validator::ERROR_SCHEMA_VALIDATION $errorContext
|
||||
*/
|
||||
public function setErrorContext(int $errorContext): void
|
||||
{
|
||||
$this->errorContext = $errorContext;
|
||||
}
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Rfc3339;
|
||||
use JsonSchema\Tool\Validator\RelativeReferenceValidator;
|
||||
use JsonSchema\Tool\Validator\UriValidator;
|
||||
|
||||
/**
|
||||
* Validates against the "format" property
|
||||
*
|
||||
* @author Justin Rainbow <justin.rainbow@gmail.com>
|
||||
*
|
||||
* @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
|
||||
*/
|
||||
class FormatConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($schema->format) {
|
||||
case 'date':
|
||||
if (is_string($element) && !$date = $this->validateDateTime($element, 'Y-m-d')) {
|
||||
$this->addError(ConstraintError::FORMAT_DATE(), $path, [
|
||||
'date' => $element,
|
||||
'format' => $schema->format
|
||||
]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'time':
|
||||
if (is_string($element) && !$this->validateDateTime($element, 'H:i:s')) {
|
||||
$this->addError(ConstraintError::FORMAT_TIME(), $path, [
|
||||
'time' => json_encode($element),
|
||||
'format' => $schema->format,
|
||||
]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'date-time':
|
||||
if (is_string($element) && null === Rfc3339::createFromString($element)) {
|
||||
$this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, [
|
||||
'dateTime' => json_encode($element),
|
||||
'format' => $schema->format
|
||||
]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'utc-millisec':
|
||||
if (!$this->validateDateTime($element, 'U')) {
|
||||
$this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, [
|
||||
'value' => $element,
|
||||
'format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'regex':
|
||||
if (!$this->validateRegex($element)) {
|
||||
$this->addError(ConstraintError::FORMAT_REGEX(), $path, [
|
||||
'value' => $element,
|
||||
'format' => $schema->format
|
||||
]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'color':
|
||||
if (!$this->validateColor($element)) {
|
||||
$this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'style':
|
||||
if (!$this->validateStyle($element)) {
|
||||
$this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'phone':
|
||||
if (!$this->validatePhone($element)) {
|
||||
$this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'uri':
|
||||
if (is_string($element) && !UriValidator::isValid($element)) {
|
||||
$this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'uriref':
|
||||
case 'uri-reference':
|
||||
if (is_string($element) && !(UriValidator::isValid($element) || RelativeReferenceValidator::isValid($element))) {
|
||||
$this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'email':
|
||||
if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE)) {
|
||||
$this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ip-address':
|
||||
case 'ipv4':
|
||||
if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
|
||||
$this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ipv6':
|
||||
if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
|
||||
$this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'host-name':
|
||||
case 'hostname':
|
||||
if (!$this->validateHostname($element)) {
|
||||
$this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Empty as it should be:
|
||||
// The value of this keyword is called a format attribute. It MUST be a string.
|
||||
// A format attribute can generally only validate a given set of instance types.
|
||||
// If the type of the instance to validate is not in this set, validation for
|
||||
// this format attribute and instance SHOULD succeed.
|
||||
// http://json-schema.org/latest/json-schema-validation.html#anchor105
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateDateTime($datetime, $format)
|
||||
{
|
||||
$dt = \DateTime::createFromFormat($format, (string) $datetime);
|
||||
|
||||
if (!$dt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($datetime === $dt->format($format)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function validateRegex($regex)
|
||||
{
|
||||
if (!is_string($regex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false !== @preg_match(self::jsonPatternToPhpRegex($regex), '');
|
||||
}
|
||||
|
||||
protected function validateColor($color)
|
||||
{
|
||||
if (!is_string($color)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia',
|
||||
'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
|
||||
'red', 'silver', 'teal', 'white', 'yellow'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color);
|
||||
}
|
||||
|
||||
protected function validateStyle($style)
|
||||
{
|
||||
$properties = explode(';', rtrim($style, ';'));
|
||||
$invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);
|
||||
|
||||
return empty($invalidEntries);
|
||||
}
|
||||
|
||||
protected function validatePhone($phone)
|
||||
{
|
||||
return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
|
||||
}
|
||||
|
||||
protected function validateHostname($host)
|
||||
{
|
||||
if (!is_string($host)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i';
|
||||
|
||||
return preg_match($hostnameRegex, $host);
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
|
||||
/**
|
||||
* The NumberConstraint Constraints, validates an number against a given schema
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
|
||||
*/
|
||||
class NumberConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
// Verify minimum
|
||||
if (isset($schema->exclusiveMinimum)) {
|
||||
if (isset($schema->minimum)) {
|
||||
if ($schema->exclusiveMinimum && $element <= $schema->minimum) {
|
||||
$this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['minimum' => $schema->minimum]);
|
||||
} elseif ($element < $schema->minimum) {
|
||||
$this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]);
|
||||
}
|
||||
} else {
|
||||
$this->addError(ConstraintError::MISSING_MINIMUM(), $path);
|
||||
}
|
||||
} elseif (isset($schema->minimum) && $element < $schema->minimum) {
|
||||
$this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]);
|
||||
}
|
||||
|
||||
// Verify maximum
|
||||
if (isset($schema->exclusiveMaximum)) {
|
||||
if (isset($schema->maximum)) {
|
||||
if ($schema->exclusiveMaximum && $element >= $schema->maximum) {
|
||||
$this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['maximum' => $schema->maximum]);
|
||||
} elseif ($element > $schema->maximum) {
|
||||
$this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]);
|
||||
}
|
||||
} else {
|
||||
$this->addError(ConstraintError::MISSING_MAXIMUM(), $path);
|
||||
}
|
||||
} elseif (isset($schema->maximum) && $element > $schema->maximum) {
|
||||
$this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]);
|
||||
}
|
||||
|
||||
// Verify divisibleBy - Draft v3
|
||||
if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
|
||||
$this->addError(ConstraintError::DIVISIBLE_BY(), $path, ['divisibleBy' => $schema->divisibleBy]);
|
||||
}
|
||||
|
||||
// Verify multipleOf - Draft v4
|
||||
if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) {
|
||||
$this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf]);
|
||||
}
|
||||
|
||||
$this->checkFormat($element, $schema, $path, $i);
|
||||
}
|
||||
|
||||
private function fmod($number1, $number2)
|
||||
{
|
||||
$modulus = ($number1 - round($number1 / $number2) * $number2);
|
||||
$precision = 0.0000000001;
|
||||
|
||||
if (-$precision < $modulus && $modulus < $precision) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return $modulus;
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
|
||||
class ObjectConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* @var list<string> List of properties to which a default value has been applied
|
||||
*/
|
||||
protected $appliedDefaults = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param list<string> $appliedDefaults
|
||||
*/
|
||||
public function check(
|
||||
&$element,
|
||||
$schema = null,
|
||||
?JsonPointer $path = null,
|
||||
$properties = null,
|
||||
$additionalProp = null,
|
||||
$patternProperties = null,
|
||||
$appliedDefaults = []
|
||||
): void {
|
||||
if ($element instanceof UndefinedConstraint) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->appliedDefaults = $appliedDefaults;
|
||||
|
||||
$matches = [];
|
||||
if ($patternProperties) {
|
||||
// validate the element pattern properties
|
||||
$matches = $this->validatePatternProperties($element, $path, $patternProperties);
|
||||
}
|
||||
|
||||
if ($properties) {
|
||||
// validate the element properties
|
||||
$this->validateProperties($element, $properties, $path);
|
||||
}
|
||||
|
||||
// validate additional element properties & constraints
|
||||
$this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp);
|
||||
}
|
||||
|
||||
public function validatePatternProperties($element, ?JsonPointer $path, $patternProperties)
|
||||
{
|
||||
$matches = [];
|
||||
foreach ($patternProperties as $pregex => $schema) {
|
||||
$fullRegex = self::jsonPatternToPhpRegex($pregex);
|
||||
|
||||
// Validate the pattern before using it to test for matches
|
||||
if (@preg_match($fullRegex, '') === false) {
|
||||
$this->addError(ConstraintError::PREGEX_INVALID(), $path, ['pregex' => $pregex]);
|
||||
continue;
|
||||
}
|
||||
foreach ($element as $i => $value) {
|
||||
if (preg_match($fullRegex, $i)) {
|
||||
$matches[] = $i;
|
||||
$this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the element properties
|
||||
*
|
||||
* @param \StdClass $element Element to validate
|
||||
* @param array $matches Matches from patternProperties (if any)
|
||||
* @param \StdClass $schema ObjectConstraint definition
|
||||
* @param JsonPointer|null $path Current test path
|
||||
* @param \StdClass $properties Properties
|
||||
* @param mixed $additionalProp Additional properties
|
||||
*/
|
||||
public function validateElement($element, $matches, $schema = null, ?JsonPointer $path = null,
|
||||
$properties = null, $additionalProp = null)
|
||||
{
|
||||
$this->validateMinMaxConstraint($element, $schema, $path);
|
||||
|
||||
foreach ($element as $i => $value) {
|
||||
$definition = $this->getProperty($properties, $i);
|
||||
|
||||
// no additional properties allowed
|
||||
if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
|
||||
$this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['property' => $i]);
|
||||
}
|
||||
|
||||
// additional properties defined
|
||||
if (!in_array($i, $matches) && $additionalProp && !$definition) {
|
||||
if ($additionalProp === true) {
|
||||
$this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults));
|
||||
} else {
|
||||
$this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults));
|
||||
}
|
||||
}
|
||||
|
||||
// property requires presence of another
|
||||
$require = $this->getProperty($definition, 'requires');
|
||||
if ($require && !$this->getProperty($element, $require)) {
|
||||
$this->addError(ConstraintError::REQUIRES(), $path, [
|
||||
'property' => $i,
|
||||
'requiredProperty' => $require
|
||||
]);
|
||||
}
|
||||
|
||||
$property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined'));
|
||||
if (is_object($property)) {
|
||||
$this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the definition properties
|
||||
*
|
||||
* @param \stdClass $element Element to validate
|
||||
* @param \stdClass $properties Property definitions
|
||||
* @param JsonPointer|null $path Path?
|
||||
*/
|
||||
public function validateProperties(&$element, $properties = null, ?JsonPointer $path = null)
|
||||
{
|
||||
$undefinedConstraint = $this->factory->createInstanceFor('undefined');
|
||||
|
||||
foreach ($properties as $i => $value) {
|
||||
$property = &$this->getProperty($element, $i, $undefinedConstraint);
|
||||
$definition = $this->getProperty($properties, $i);
|
||||
|
||||
if (is_object($definition)) {
|
||||
// Undefined constraint will check for is_object() and quit if is not - so why pass it?
|
||||
$this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieves a property from an object or array
|
||||
*
|
||||
* @param mixed $element Element to validate
|
||||
* @param string $property Property to retrieve
|
||||
* @param mixed $fallback Default value if property is not found
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function &getProperty(&$element, $property, $fallback = null)
|
||||
{
|
||||
if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
|
||||
return $element[$property];
|
||||
} elseif (is_object($element) && property_exists($element, (string) $property)) {
|
||||
return $element->$property;
|
||||
}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* validating minimum and maximum property constraints (if present) against an element
|
||||
*
|
||||
* @param \stdClass $element Element to validate
|
||||
* @param \stdClass $objectDefinition ObjectConstraint definition
|
||||
* @param JsonPointer|null $path Path to test?
|
||||
*/
|
||||
protected function validateMinMaxConstraint($element, $objectDefinition, ?JsonPointer $path = null)
|
||||
{
|
||||
if (!$this->getTypeCheck()::isObject($element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify minimum number of properties
|
||||
if (isset($objectDefinition->minProperties) && is_int($objectDefinition->minProperties)) {
|
||||
if ($this->getTypeCheck()->propertyCount($element) < max(0, $objectDefinition->minProperties)) {
|
||||
$this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $objectDefinition->minProperties]);
|
||||
}
|
||||
}
|
||||
// Verify maximum number of properties
|
||||
if (isset($objectDefinition->maxProperties) && is_int($objectDefinition->maxProperties)) {
|
||||
if ($this->getTypeCheck()->propertyCount($element) > max(0, $objectDefinition->maxProperties)) {
|
||||
$this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $objectDefinition->maxProperties]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Exception\InvalidArgumentException;
|
||||
use JsonSchema\Exception\InvalidSchemaException;
|
||||
use JsonSchema\Exception\RuntimeException;
|
||||
use JsonSchema\Validator;
|
||||
|
||||
/**
|
||||
* The SchemaConstraint Constraints, validates an element against a given schema
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
|
||||
*/
|
||||
class SchemaConstraint extends Constraint
|
||||
{
|
||||
private const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
if ($schema !== null) {
|
||||
// passed schema
|
||||
$validationSchema = $schema;
|
||||
} elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) {
|
||||
// inline schema
|
||||
$validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty);
|
||||
} else {
|
||||
throw new InvalidArgumentException('no schema found to verify against');
|
||||
}
|
||||
|
||||
// cast array schemas to object
|
||||
if (is_array($validationSchema)) {
|
||||
$validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema);
|
||||
}
|
||||
|
||||
// validate schema against whatever is defined in $validationSchema->$schema. If no
|
||||
// schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04).
|
||||
if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) {
|
||||
if (!$this->getTypeCheck()->isObject($validationSchema)) {
|
||||
throw new RuntimeException('Cannot validate the schema of a non-object');
|
||||
}
|
||||
if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) {
|
||||
$schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema');
|
||||
} else {
|
||||
$schemaSpec = self::DEFAULT_SCHEMA_SPEC;
|
||||
}
|
||||
|
||||
// get the spec schema
|
||||
$schemaStorage = $this->factory->getSchemaStorage();
|
||||
if (!$this->getTypeCheck()->isObject($schemaSpec)) {
|
||||
$schemaSpec = $schemaStorage->getSchema($schemaSpec);
|
||||
}
|
||||
|
||||
// save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA
|
||||
$initialErrorCount = $this->numErrors();
|
||||
$initialConfig = $this->factory->getConfig();
|
||||
$initialContext = $this->factory->getErrorContext();
|
||||
$this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS);
|
||||
$this->factory->addConfig(self::CHECK_MODE_TYPE_CAST);
|
||||
$this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION);
|
||||
|
||||
// validate schema
|
||||
try {
|
||||
$this->check($validationSchema, $schemaSpec);
|
||||
} catch (\Exception $e) {
|
||||
if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) {
|
||||
throw new InvalidSchemaException('Schema did not pass validation', 0, $e);
|
||||
}
|
||||
}
|
||||
if ($this->numErrors() > $initialErrorCount) {
|
||||
$this->addError(ConstraintError::INVALID_SCHEMA(), $path);
|
||||
}
|
||||
|
||||
// restore the initial config
|
||||
$this->factory->setConfig($initialConfig);
|
||||
$this->factory->setErrorContext($initialContext);
|
||||
}
|
||||
|
||||
// validate element against $validationSchema
|
||||
$this->checkUndefined($element, $validationSchema, $path, $i);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
|
||||
/**
|
||||
* The StringConstraint Constraints, validates an string against a given schema
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
|
||||
*/
|
||||
class StringConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
// Verify maxLength
|
||||
if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
|
||||
$this->addError(ConstraintError::LENGTH_MAX(), $path, [
|
||||
'maxLength' => $schema->maxLength,
|
||||
]);
|
||||
}
|
||||
|
||||
//verify minLength
|
||||
if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
|
||||
$this->addError(ConstraintError::LENGTH_MIN(), $path, [
|
||||
'minLength' => $schema->minLength,
|
||||
]);
|
||||
}
|
||||
|
||||
// Verify a regex pattern
|
||||
if (isset($schema->pattern) && !preg_match(self::jsonPatternToPhpRegex($schema->pattern), $element)) {
|
||||
$this->addError(ConstraintError::PATTERN(), $path, [
|
||||
'pattern' => $schema->pattern,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->checkFormat($element, $schema, $path, $i);
|
||||
}
|
||||
|
||||
private function strlen($string)
|
||||
{
|
||||
if (extension_loaded('mbstring')) {
|
||||
return mb_strlen($string, mb_detect_encoding($string));
|
||||
}
|
||||
|
||||
// mbstring is present on all test platforms, so strlen() can be ignored for coverage
|
||||
return strlen($string); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Constraints\TypeCheck;
|
||||
|
||||
class LooseTypeCheck implements TypeCheckInterface
|
||||
{
|
||||
public static function isObject($value)
|
||||
{
|
||||
return
|
||||
is_object($value) ||
|
||||
(is_array($value) && (count($value) == 0 || self::isAssociativeArray($value)));
|
||||
}
|
||||
|
||||
public static function isArray($value)
|
||||
{
|
||||
return
|
||||
is_array($value) &&
|
||||
(count($value) == 0 || !self::isAssociativeArray($value));
|
||||
}
|
||||
|
||||
public static function propertyGet($value, $property)
|
||||
{
|
||||
if (is_object($value)) {
|
||||
return $value->{$property};
|
||||
}
|
||||
|
||||
return $value[$property];
|
||||
}
|
||||
|
||||
public static function propertySet(&$value, $property, $data)
|
||||
{
|
||||
if (is_object($value)) {
|
||||
$value->{$property} = $data;
|
||||
} else {
|
||||
$value[$property] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
public static function propertyExists($value, $property)
|
||||
{
|
||||
if (is_object($value)) {
|
||||
return property_exists($value, $property);
|
||||
}
|
||||
|
||||
return is_array($value) && array_key_exists($property, $value);
|
||||
}
|
||||
|
||||
public static function propertyCount($value)
|
||||
{
|
||||
if (is_object($value)) {
|
||||
return count(get_object_vars($value));
|
||||
}
|
||||
|
||||
return count($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided array is associative or not
|
||||
*
|
||||
* @param array $arr
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isAssociativeArray($arr)
|
||||
{
|
||||
return array_keys($arr) !== range(0, count($arr) - 1);
|
||||
}
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Constraints\TypeCheck;
|
||||
|
||||
class StrictTypeCheck implements TypeCheckInterface
|
||||
{
|
||||
public static function isObject($value)
|
||||
{
|
||||
return is_object($value);
|
||||
}
|
||||
|
||||
public static function isArray($value)
|
||||
{
|
||||
return is_array($value);
|
||||
}
|
||||
|
||||
public static function propertyGet($value, $property)
|
||||
{
|
||||
return $value->{$property};
|
||||
}
|
||||
|
||||
public static function propertySet(&$value, $property, $data)
|
||||
{
|
||||
$value->{$property} = $data;
|
||||
}
|
||||
|
||||
public static function propertyExists($value, $property)
|
||||
{
|
||||
return property_exists($value, $property);
|
||||
}
|
||||
|
||||
public static function propertyCount($value)
|
||||
{
|
||||
if (!is_object($value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return count(get_object_vars($value));
|
||||
}
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Constraints\TypeCheck;
|
||||
|
||||
interface TypeCheckInterface
|
||||
{
|
||||
public static function isObject($value);
|
||||
|
||||
public static function isArray($value);
|
||||
|
||||
public static function propertyGet($value, $property);
|
||||
|
||||
public static function propertySet(&$value, $property, $data);
|
||||
|
||||
public static function propertyExists($value, $property);
|
||||
|
||||
public static function propertyCount($value);
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Exception\InvalidArgumentException;
|
||||
use UnexpectedValueException as StandardUnexpectedValueException;
|
||||
|
||||
/**
|
||||
* The TypeConstraint Constraints, validates an element against a given type
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
|
||||
*/
|
||||
class TypeConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* @var array|string[] type wordings for validation error messages
|
||||
*/
|
||||
public static $wording = [
|
||||
'integer' => 'an integer',
|
||||
'number' => 'a number',
|
||||
'boolean' => 'a boolean',
|
||||
'object' => 'an object',
|
||||
'array' => 'an array',
|
||||
'string' => 'a string',
|
||||
'null' => 'a null',
|
||||
'any' => null, // validation of 'any' is always true so is not needed in message wording
|
||||
0 => null, // validation of a false-y value is always true, so not needed as well
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(&$value = null, $schema = null, ?JsonPointer $path = null, $i = null): void
|
||||
{
|
||||
$type = isset($schema->type) ? $schema->type : null;
|
||||
$isValid = false;
|
||||
$coerce = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES);
|
||||
$earlyCoerce = $this->factory->getConfig(self::CHECK_MODE_EARLY_COERCE);
|
||||
$wording = [];
|
||||
|
||||
if (is_array($type)) {
|
||||
$this->validateTypesArray($value, $type, $wording, $isValid, $path, $coerce && $earlyCoerce);
|
||||
if (!$isValid && $coerce && !$earlyCoerce) {
|
||||
$this->validateTypesArray($value, $type, $wording, $isValid, $path, true);
|
||||
}
|
||||
} elseif (is_object($type)) {
|
||||
$this->checkUndefined($value, $type, $path);
|
||||
|
||||
return;
|
||||
} else {
|
||||
$isValid = $this->validateType($value, $type, $coerce && $earlyCoerce);
|
||||
if (!$isValid && $coerce && !$earlyCoerce) {
|
||||
$isValid = $this->validateType($value, $type, true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isValid === false) {
|
||||
if (!is_array($type)) {
|
||||
$this->validateTypeNameWording($type);
|
||||
$wording[] = self::$wording[$type];
|
||||
}
|
||||
$this->addError(ConstraintError::TYPE(), $path, [
|
||||
'found' => gettype($value),
|
||||
'expected' => $this->implodeWith($wording, ', ', 'or')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given $value against the array of types in $type. Sets the value
|
||||
* of $isValid to true, if at least one $type mateches the type of $value or the value
|
||||
* passed as $isValid is already true.
|
||||
*
|
||||
* @param mixed $value Value to validate
|
||||
* @param array $type TypeConstraints to check against
|
||||
* @param array $validTypesWording An array of wordings of the valid types of the array $type
|
||||
* @param bool $isValid The current validation value
|
||||
* @param ?JsonPointer $path
|
||||
* @param bool $coerce
|
||||
*/
|
||||
protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path, $coerce = false)
|
||||
{
|
||||
foreach ($type as $tp) {
|
||||
// already valid, so no need to waste cycles looping over everything
|
||||
if ($isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// $tp can be an object, if it's a schema instead of a simple type, validate it
|
||||
// with a new type constraint
|
||||
if (is_object($tp)) {
|
||||
if (!$isValid) {
|
||||
$validator = $this->factory->createInstanceFor('type');
|
||||
$subSchema = new \stdClass();
|
||||
$subSchema->type = $tp;
|
||||
$validator->check($value, $subSchema, $path, null);
|
||||
$error = $validator->getErrors();
|
||||
$isValid = !(bool) $error;
|
||||
$validTypesWording[] = self::$wording['object'];
|
||||
}
|
||||
} else {
|
||||
$this->validateTypeNameWording($tp);
|
||||
$validTypesWording[] = self::$wording[$tp];
|
||||
if (!$isValid) {
|
||||
$isValid = $this->validateType($value, $tp, $coerce);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implodes the given array like implode() with turned around parameters and with the
|
||||
* difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
|
||||
* $delimiter.
|
||||
*
|
||||
* @param array $elements The elements to implode
|
||||
* @param string $delimiter The delimiter to use
|
||||
* @param bool $listEnd The last delimiter to use (defaults to $delimiter)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false)
|
||||
{
|
||||
if ($listEnd === false || !isset($elements[1])) {
|
||||
return implode($delimiter, $elements);
|
||||
}
|
||||
$lastElement = array_slice($elements, -1);
|
||||
$firsElements = join($delimiter, array_slice($elements, 0, -1));
|
||||
$implodedElements = array_merge([$firsElements], $lastElement);
|
||||
|
||||
return join(" $listEnd ", $implodedElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given $type, if there's an associated self::$wording. If not, throws an
|
||||
* exception.
|
||||
*
|
||||
* @param string $type The type to validate
|
||||
*
|
||||
* @throws StandardUnexpectedValueException
|
||||
*/
|
||||
protected function validateTypeNameWording($type)
|
||||
{
|
||||
if (!array_key_exists($type, self::$wording)) {
|
||||
throw new StandardUnexpectedValueException(
|
||||
sprintf(
|
||||
'No wording for %s available, expected wordings are: [%s]',
|
||||
var_export($type, true),
|
||||
implode(', ', array_filter(self::$wording)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a given value is of a certain type
|
||||
*
|
||||
* @param mixed $value Value to validate
|
||||
* @param string $type TypeConstraint to check against
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateType(&$value, $type, $coerce = false)
|
||||
{
|
||||
//mostly the case for inline schema
|
||||
if (!$type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('any' === $type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('object' === $type) {
|
||||
return $this->getTypeCheck()->isObject($value);
|
||||
}
|
||||
|
||||
if ('array' === $type) {
|
||||
if ($coerce) {
|
||||
$value = $this->toArray($value);
|
||||
}
|
||||
|
||||
return $this->getTypeCheck()->isArray($value);
|
||||
}
|
||||
|
||||
if ('integer' === $type) {
|
||||
if ($coerce) {
|
||||
$value = $this->toInteger($value);
|
||||
}
|
||||
|
||||
return is_int($value);
|
||||
}
|
||||
|
||||
if ('number' === $type) {
|
||||
if ($coerce) {
|
||||
$value = $this->toNumber($value);
|
||||
}
|
||||
|
||||
return is_numeric($value) && !is_string($value);
|
||||
}
|
||||
|
||||
if ('boolean' === $type) {
|
||||
if ($coerce) {
|
||||
$value = $this->toBoolean($value);
|
||||
}
|
||||
|
||||
return is_bool($value);
|
||||
}
|
||||
|
||||
if ('string' === $type) {
|
||||
if ($coerce) {
|
||||
$value = $this->toString($value);
|
||||
}
|
||||
|
||||
return is_string($value);
|
||||
}
|
||||
|
||||
if ('null' === $type) {
|
||||
if ($coerce) {
|
||||
$value = $this->toNull($value);
|
||||
}
|
||||
|
||||
return is_null($value);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to boolean. For example, "true" becomes true.
|
||||
*
|
||||
* @param mixed $value The value to convert to boolean
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
protected function toBoolean($value)
|
||||
{
|
||||
if ($value === 1 || $value === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (is_null($value) || $value === 0 || $value === 'false') {
|
||||
return false;
|
||||
}
|
||||
if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
|
||||
return $this->toBoolean(reset($value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to a number. For example, "4.5" becomes 4.5.
|
||||
*
|
||||
* @param mixed $value the value to convert to a number
|
||||
*
|
||||
* @return int|float|mixed
|
||||
*/
|
||||
protected function toNumber($value)
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return $value + 0; // cast to number
|
||||
}
|
||||
if (is_bool($value) || is_null($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
|
||||
return $this->toNumber(reset($value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to an integer. For example, "4" becomes 4.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return int|mixed
|
||||
*/
|
||||
protected function toInteger($value)
|
||||
{
|
||||
$numberValue = $this->toNumber($value);
|
||||
if (is_numeric($numberValue) && (int) $numberValue == $numberValue) {
|
||||
return (int) $numberValue; // cast to number
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to an array containing that value. For example, [4] becomes 4.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array|mixed
|
||||
*/
|
||||
protected function toArray($value)
|
||||
{
|
||||
if (is_scalar($value) || is_null($value)) {
|
||||
return [$value];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to a string representation of that value. For example, null becomes "".
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return string|mixed
|
||||
*/
|
||||
protected function toString($value)
|
||||
{
|
||||
if (is_numeric($value)) {
|
||||
return "$value";
|
||||
}
|
||||
if ($value === true) {
|
||||
return 'true';
|
||||
}
|
||||
if ($value === false) {
|
||||
return 'false';
|
||||
}
|
||||
if (is_null($value)) {
|
||||
return '';
|
||||
}
|
||||
if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
|
||||
return $this->toString(reset($value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to a null. For example, 0 becomes null.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return null|mixed
|
||||
*/
|
||||
protected function toNull($value)
|
||||
{
|
||||
if ($value === 0 || $value === false || $value === '') {
|
||||
return null;
|
||||
}
|
||||
if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
|
||||
return $this->toNull(reset($value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Constraints;
|
||||
|
||||
use JsonSchema\ConstraintError;
|
||||
use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Exception\ValidationException;
|
||||
use JsonSchema\Tool\DeepCopy;
|
||||
use JsonSchema\Uri\UriResolver;
|
||||
|
||||
#[\AllowDynamicProperties]
|
||||
class UndefinedConstraint extends Constraint
|
||||
{
|
||||
/**
|
||||
* @var list<string> List of properties to which a default value has been applied
|
||||
*/
|
||||
protected $appliedDefaults = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* */
|
||||
public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void
|
||||
{
|
||||
if (is_null($schema) || !is_object($schema)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $this->incrementPath($path, $i);
|
||||
if ($fromDefault) {
|
||||
$path->setFromDefault();
|
||||
}
|
||||
|
||||
// check special properties
|
||||
$this->validateCommonProperties($value, $schema, $path, $i);
|
||||
|
||||
// check allOf, anyOf, and oneOf properties
|
||||
$this->validateOfProperties($value, $schema, $path, '');
|
||||
|
||||
// check known types
|
||||
$this->validateTypes($value, $schema, $path, $i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the value against the types
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param JsonPointer $path
|
||||
* @param string $i
|
||||
*/
|
||||
public function validateTypes(&$value, $schema, JsonPointer $path, $i = null)
|
||||
{
|
||||
// check array
|
||||
if ($this->getTypeCheck()->isArray($value)) {
|
||||
$this->checkArray($value, $schema, $path, $i);
|
||||
}
|
||||
|
||||
// check object
|
||||
if (LooseTypeCheck::isObject($value)) {
|
||||
// object processing should always be run on assoc arrays,
|
||||
// so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST
|
||||
// is not set (i.e. don't use $this->getTypeCheck() here).
|
||||
$this->checkObject(
|
||||
$value,
|
||||
$schema,
|
||||
$path,
|
||||
isset($schema->properties) ? $schema->properties : null,
|
||||
isset($schema->additionalProperties) ? $schema->additionalProperties : null,
|
||||
isset($schema->patternProperties) ? $schema->patternProperties : null,
|
||||
$this->appliedDefaults
|
||||
);
|
||||
}
|
||||
|
||||
// check string
|
||||
if (is_string($value)) {
|
||||
$this->checkString($value, $schema, $path, $i);
|
||||
}
|
||||
|
||||
// check numeric
|
||||
if (is_numeric($value)) {
|
||||
$this->checkNumber($value, $schema, $path, $i);
|
||||
}
|
||||
|
||||
// check enum
|
||||
if (isset($schema->enum)) {
|
||||
$this->checkEnum($value, $schema, $path, $i);
|
||||
}
|
||||
|
||||
// check const
|
||||
if (isset($schema->const)) {
|
||||
$this->checkConst($value, $schema, $path, $i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates common properties
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param JsonPointer $path
|
||||
* @param string $i
|
||||
*/
|
||||
protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '')
|
||||
{
|
||||
// if it extends another schema, it must pass that schema as well
|
||||
if (isset($schema->extends)) {
|
||||
if (is_string($schema->extends)) {
|
||||
$schema->extends = $this->validateUri($schema, $schema->extends);
|
||||
}
|
||||
if (is_array($schema->extends)) {
|
||||
foreach ($schema->extends as $extends) {
|
||||
$this->checkUndefined($value, $extends, $path, $i);
|
||||
}
|
||||
} else {
|
||||
$this->checkUndefined($value, $schema->extends, $path, $i);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply default values from schema
|
||||
if (!$path->fromDefault()) {
|
||||
$this->applyDefaultValues($value, $schema, $path);
|
||||
}
|
||||
|
||||
// Verify required values
|
||||
if ($this->getTypeCheck()->isObject($value)) {
|
||||
if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) {
|
||||
// Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
|
||||
foreach ($schema->required as $required) {
|
||||
if (!$this->getTypeCheck()->propertyExists($value, $required)) {
|
||||
$this->addError(
|
||||
ConstraintError::REQUIRED(),
|
||||
$this->incrementPath($path, $required), [
|
||||
'property' => $required
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif (isset($schema->required) && !is_array($schema->required)) {
|
||||
// Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
|
||||
if ($schema->required && $value instanceof self) {
|
||||
$propertyPaths = $path->getPropertyPaths();
|
||||
$propertyName = end($propertyPaths);
|
||||
$this->addError(ConstraintError::REQUIRED(), $path, ['property' => $propertyName]);
|
||||
}
|
||||
} else {
|
||||
// if the value is both undefined and not required, skip remaining checks
|
||||
// in this method which assume an actual, defined instance when validating.
|
||||
if ($value instanceof self) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify type
|
||||
if (!($value instanceof self)) {
|
||||
$this->checkType($value, $schema, $path, $i);
|
||||
}
|
||||
|
||||
// Verify disallowed items
|
||||
if (isset($schema->disallow)) {
|
||||
$initErrors = $this->getErrors();
|
||||
|
||||
$typeSchema = new \stdClass();
|
||||
$typeSchema->type = $schema->disallow;
|
||||
$this->checkType($value, $typeSchema, $path);
|
||||
|
||||
// if no new errors were raised it must be a disallowed value
|
||||
if (count($this->getErrors()) == count($initErrors)) {
|
||||
$this->addError(ConstraintError::DISALLOW(), $path);
|
||||
} else {
|
||||
$this->errors = $initErrors;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($schema->not)) {
|
||||
$initErrors = $this->getErrors();
|
||||
$this->checkUndefined($value, $schema->not, $path, $i);
|
||||
|
||||
// if no new errors were raised then the instance validated against the "not" schema
|
||||
if (count($this->getErrors()) == count($initErrors)) {
|
||||
$this->addError(ConstraintError::NOT(), $path);
|
||||
} else {
|
||||
$this->errors = $initErrors;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that dependencies are met
|
||||
if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) {
|
||||
$this->validateDependencies($value, $schema->dependencies, $path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a default should be applied for this value
|
||||
*
|
||||
* @param mixed $schema
|
||||
* @param mixed $parentSchema
|
||||
* @param bool $requiredOnly
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null)
|
||||
{
|
||||
// required-only mode is off
|
||||
if (!$requiredOnly) {
|
||||
return true;
|
||||
}
|
||||
// draft-04 required is set
|
||||
if (
|
||||
$name !== null
|
||||
&& isset($parentSchema->required)
|
||||
&& is_array($parentSchema->required)
|
||||
&& in_array($name, $parentSchema->required)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// draft-03 required is set
|
||||
if (isset($schema->required) && !is_array($schema->required) && $schema->required) {
|
||||
return true;
|
||||
}
|
||||
// default case
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply default values
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param JsonPointer $path
|
||||
*/
|
||||
protected function applyDefaultValues(&$value, $schema, $path): void
|
||||
{
|
||||
// only apply defaults if feature is enabled
|
||||
if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// apply defaults if appropriate
|
||||
$requiredOnly = (bool) $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS);
|
||||
if (isset($schema->properties) && LooseTypeCheck::isObject($value)) {
|
||||
// $value is an object or assoc array, and properties are defined - treat as an object
|
||||
foreach ($schema->properties as $currentProperty => $propertyDefinition) {
|
||||
$propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition);
|
||||
if (
|
||||
!LooseTypeCheck::propertyExists($value, $currentProperty)
|
||||
&& property_exists($propertyDefinition, 'default')
|
||||
&& $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)
|
||||
) {
|
||||
// assign default value
|
||||
if (is_object($propertyDefinition->default)) {
|
||||
LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default);
|
||||
} else {
|
||||
LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default);
|
||||
}
|
||||
$this->appliedDefaults[] = $currentProperty;
|
||||
}
|
||||
}
|
||||
} elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) {
|
||||
$items = [];
|
||||
if (LooseTypeCheck::isArray($schema->items)) {
|
||||
$items = $schema->items;
|
||||
} elseif (isset($schema->minItems) && count($value) < $schema->minItems) {
|
||||
$items = array_fill(count($value), $schema->minItems - count($value), $schema->items);
|
||||
}
|
||||
// $value is an array, and items are defined - treat as plain array
|
||||
foreach ($items as $currentItem => $itemDefinition) {
|
||||
$itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition);
|
||||
if (
|
||||
!array_key_exists($currentItem, $value)
|
||||
&& property_exists($itemDefinition, 'default')
|
||||
&& $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) {
|
||||
if (is_object($itemDefinition->default)) {
|
||||
$value[$currentItem] = clone $itemDefinition->default;
|
||||
} else {
|
||||
$value[$currentItem] = $itemDefinition->default;
|
||||
}
|
||||
}
|
||||
$path->setFromDefault();
|
||||
}
|
||||
} elseif (
|
||||
$value instanceof self
|
||||
&& property_exists($schema, 'default')
|
||||
&& $this->shouldApplyDefaultValue($requiredOnly, $schema)) {
|
||||
// $value is a leaf, not a container - apply the default directly
|
||||
$value = is_object($schema->default) ? clone $schema->default : $schema->default;
|
||||
$path->setFromDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate allOf, anyOf, and oneOf properties
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
* @param JsonPointer $path
|
||||
* @param string $i
|
||||
*/
|
||||
protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '')
|
||||
{
|
||||
// Verify type
|
||||
if ($value instanceof self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($schema->allOf)) {
|
||||
$isValid = true;
|
||||
foreach ($schema->allOf as $allOf) {
|
||||
$initErrors = $this->getErrors();
|
||||
$this->checkUndefined($value, $allOf, $path, $i);
|
||||
$isValid = $isValid && (count($this->getErrors()) == count($initErrors));
|
||||
}
|
||||
if (!$isValid) {
|
||||
$this->addError(ConstraintError::ALL_OF(), $path);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($schema->anyOf)) {
|
||||
$isValid = false;
|
||||
$startErrors = $this->getErrors();
|
||||
$coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS);
|
||||
|
||||
foreach ($schema->anyOf as $anyOf) {
|
||||
$initErrors = $this->getErrors();
|
||||
try {
|
||||
$anyOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value;
|
||||
$this->checkUndefined($anyOfValue, $anyOf, $path, $i);
|
||||
if ($isValid = (count($this->getErrors()) === count($initErrors))) {
|
||||
$value = $anyOfValue;
|
||||
break;
|
||||
}
|
||||
} catch (ValidationException $e) {
|
||||
$isValid = false;
|
||||
}
|
||||
}
|
||||
if (!$isValid) {
|
||||
$this->addError(ConstraintError::ANY_OF(), $path);
|
||||
} else {
|
||||
$this->errors = $startErrors;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($schema->oneOf)) {
|
||||
$allErrors = [];
|
||||
$matchedSchemas = [];
|
||||
$startErrors = $this->getErrors();
|
||||
$coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS);
|
||||
|
||||
foreach ($schema->oneOf as $oneOf) {
|
||||
try {
|
||||
$this->errors = [];
|
||||
|
||||
$oneOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value;
|
||||
$this->checkUndefined($oneOfValue, $oneOf, $path, $i);
|
||||
if (count($this->getErrors()) === 0) {
|
||||
$matchedSchemas[] = ['schema' => $oneOf, 'value' => $oneOfValue];
|
||||
}
|
||||
$allErrors = array_merge($allErrors, array_values($this->getErrors()));
|
||||
} catch (ValidationException $e) {
|
||||
// deliberately do nothing here - validation failed, but we want to check
|
||||
// other schema options in the OneOf field.
|
||||
}
|
||||
}
|
||||
if (count($matchedSchemas) !== 1) {
|
||||
$this->addErrors(array_merge($allErrors, $startErrors));
|
||||
$this->addError(ConstraintError::ONE_OF(), $path);
|
||||
} else {
|
||||
$this->errors = $startErrors;
|
||||
$value = $matchedSchemas[0]['value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate dependencies
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $dependencies
|
||||
* @param JsonPointer $path
|
||||
* @param string $i
|
||||
*/
|
||||
protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '')
|
||||
{
|
||||
foreach ($dependencies as $key => $dependency) {
|
||||
if ($this->getTypeCheck()->propertyExists($value, $key)) {
|
||||
if (is_string($dependency)) {
|
||||
// Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
|
||||
if (!$this->getTypeCheck()->propertyExists($value, $dependency)) {
|
||||
$this->addError(ConstraintError::DEPENDENCIES(), $path, [
|
||||
'key' => $key,
|
||||
'dependency' => $dependency
|
||||
]);
|
||||
}
|
||||
} elseif (is_array($dependency)) {
|
||||
// Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
|
||||
foreach ($dependency as $d) {
|
||||
if (!$this->getTypeCheck()->propertyExists($value, $d)) {
|
||||
$this->addError(ConstraintError::DEPENDENCIES(), $path, [
|
||||
'key' => $key,
|
||||
'dependency' => $dependency
|
||||
]);
|
||||
}
|
||||
}
|
||||
} elseif (is_object($dependency)) {
|
||||
// Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
|
||||
$this->checkUndefined($value, $dependency, $path, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateUri($schema, $schemaUri = null)
|
||||
{
|
||||
$resolver = new UriResolver();
|
||||
$retriever = $this->factory->getUriRetriever();
|
||||
|
||||
$jsonSchema = null;
|
||||
if ($resolver->isValid($schemaUri)) {
|
||||
$schemaId = property_exists($schema, 'id') ? $schema->id : null;
|
||||
$jsonSchema = $retriever->retrieve($schemaId, $schemaUri);
|
||||
}
|
||||
|
||||
return $jsonSchema;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Entity;
|
||||
|
||||
use JsonSchema\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @package JsonSchema\Entity
|
||||
*
|
||||
* @author Joost Nijhuis <jnijhuis81@gmail.com>
|
||||
*/
|
||||
class JsonPointer
|
||||
{
|
||||
/** @var string */
|
||||
private $filename;
|
||||
|
||||
/** @var string[] */
|
||||
private $propertyPaths = [];
|
||||
|
||||
/**
|
||||
* @var bool Whether the value at this path was set from a schema default
|
||||
*/
|
||||
private $fromDefault = false;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @throws InvalidArgumentException when $value is not a string
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
throw new InvalidArgumentException('Ref value must be a string');
|
||||
}
|
||||
|
||||
$splitRef = explode('#', $value, 2);
|
||||
$this->filename = $splitRef[0];
|
||||
if (array_key_exists(1, $splitRef)) {
|
||||
$this->propertyPaths = $this->decodePropertyPaths($splitRef[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyPathString
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function decodePropertyPaths($propertyPathString)
|
||||
{
|
||||
$paths = [];
|
||||
foreach (explode('/', trim($propertyPathString, '/')) as $path) {
|
||||
$path = $this->decodePath($path);
|
||||
if (is_string($path) && '' !== $path) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function encodePropertyPaths()
|
||||
{
|
||||
return array_map(
|
||||
[$this, 'encodePath'],
|
||||
$this->getPropertyPaths()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function decodePath($path)
|
||||
{
|
||||
return strtr($path, ['~1' => '/', '~0' => '~', '%25' => '%']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encodePath($path)
|
||||
{
|
||||
return strtr($path, ['/' => '~1', '~' => '~0', '%' => '%25']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPropertyPaths()
|
||||
{
|
||||
return $this->propertyPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $propertyPaths
|
||||
*
|
||||
* @return JsonPointer
|
||||
*/
|
||||
public function withPropertyPaths(array $propertyPaths)
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->propertyPaths = array_map(function ($p): string { return (string) $p; }, $propertyPaths);
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPropertyPathAsString()
|
||||
{
|
||||
return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getFilename() . $this->getPropertyPathAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the value at this path as being set from a schema default
|
||||
*/
|
||||
public function setFromDefault(): void
|
||||
{
|
||||
$this->fromDefault = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value at this path was set from a schema default
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function fromDefault()
|
||||
{
|
||||
return $this->fromDefault;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
abstract class Enum extends \MabeEnum\Enum
|
||||
{
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the InvalidArgumentException
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the ResourceNotFoundException
|
||||
*/
|
||||
class InvalidConfigException extends RuntimeException
|
||||
{
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the InvalidSchemaMediaType
|
||||
*/
|
||||
class InvalidSchemaException extends RuntimeException
|
||||
{
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the InvalidSchemaMediaType
|
||||
*/
|
||||
class InvalidSchemaMediaTypeException extends RuntimeException
|
||||
{
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the InvalidSourceUriException
|
||||
*/
|
||||
class InvalidSourceUriException extends InvalidArgumentException
|
||||
{
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the JsonDecodingException
|
||||
*/
|
||||
class JsonDecodingException extends RuntimeException
|
||||
{
|
||||
public function __construct($code = JSON_ERROR_NONE, ?\Exception $previous = null)
|
||||
{
|
||||
switch ($code) {
|
||||
case JSON_ERROR_DEPTH:
|
||||
$message = 'The maximum stack depth has been exceeded';
|
||||
break;
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
$message = 'Invalid or malformed JSON';
|
||||
break;
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
$message = 'Control character error, possibly incorrectly encoded';
|
||||
break;
|
||||
case JSON_ERROR_UTF8:
|
||||
$message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||
break;
|
||||
case JSON_ERROR_SYNTAX:
|
||||
$message = 'JSON syntax is malformed';
|
||||
break;
|
||||
default:
|
||||
$message = 'Syntax error';
|
||||
}
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the ResourceNotFoundException
|
||||
*/
|
||||
class ResourceNotFoundException extends RuntimeException
|
||||
{
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the RuntimeException
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* @package JsonSchema\Exception
|
||||
*
|
||||
* @author Joost Nijhuis <jnijhuis81@gmail.com>
|
||||
*/
|
||||
class UnresolvableJsonPointerException extends InvalidArgumentException
|
||||
{
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
/**
|
||||
* Wrapper for the UriResolverException
|
||||
*/
|
||||
class UriResolverException extends RuntimeException
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Exception;
|
||||
|
||||
class ValidationException extends RuntimeException
|
||||
{
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Iterator;
|
||||
|
||||
/**
|
||||
* @package JsonSchema\Iterator
|
||||
*
|
||||
* @author Joost Nijhuis <jnijhuis81@gmail.com>
|
||||
*/
|
||||
class ObjectIterator implements \Iterator, \Countable
|
||||
{
|
||||
/** @var object */
|
||||
private $object;
|
||||
|
||||
/** @var int */
|
||||
private $position = 0;
|
||||
|
||||
/** @var array */
|
||||
private $data = [];
|
||||
|
||||
/** @var bool */
|
||||
private $initialized = false;
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
*/
|
||||
public function __construct($object)
|
||||
{
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return $this->data[$this->position];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
$this->initialize();
|
||||
$this->position++;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return isset($this->data[$this->position]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->initialize();
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return count($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
$this->data = $this->buildDataFromObject($this->object);
|
||||
$this->initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function buildDataFromObject($object)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$stack = new \SplStack();
|
||||
$stack->push($object);
|
||||
|
||||
while (!$stack->isEmpty()) {
|
||||
$current = $stack->pop();
|
||||
if (is_object($current)) {
|
||||
array_push($result, $current);
|
||||
}
|
||||
|
||||
foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) {
|
||||
if (is_object($propertyValue) || is_array($propertyValue)) {
|
||||
$stack->push($propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object|array $item
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDataFromItem($item)
|
||||
{
|
||||
if (!is_object($item) && !is_array($item)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return is_object($item) ? get_object_vars($item) : $item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
class Rfc3339
|
||||
{
|
||||
private const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ]{1}\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/';
|
||||
|
||||
/**
|
||||
* Try creating a DateTime instance
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public static function createFromString($string)
|
||||
{
|
||||
if (!preg_match(self::REGEX, strtoupper($string), $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dateAndTime = $matches[1];
|
||||
$microseconds = $matches[2] ?: '.000000';
|
||||
$timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00';
|
||||
$dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP';
|
||||
$dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));
|
||||
|
||||
return $dateTime ?: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
use JsonSchema\Constraints\BaseConstraint;
|
||||
use JsonSchema\Entity\JsonPointer;
|
||||
use JsonSchema\Exception\UnresolvableJsonPointerException;
|
||||
use JsonSchema\Uri\UriResolver;
|
||||
use JsonSchema\Uri\UriRetriever;
|
||||
|
||||
class SchemaStorage implements SchemaStorageInterface
|
||||
{
|
||||
public const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema/';
|
||||
|
||||
protected $uriRetriever;
|
||||
protected $uriResolver;
|
||||
protected $schemas = [];
|
||||
|
||||
public function __construct(
|
||||
?UriRetrieverInterface $uriRetriever = null,
|
||||
?UriResolverInterface $uriResolver = null
|
||||
) {
|
||||
$this->uriRetriever = $uriRetriever ?: new UriRetriever();
|
||||
$this->uriResolver = $uriResolver ?: new UriResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UriRetrieverInterface
|
||||
*/
|
||||
public function getUriRetriever()
|
||||
{
|
||||
return $this->uriRetriever;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UriResolverInterface
|
||||
*/
|
||||
public function getUriResolver()
|
||||
{
|
||||
return $this->uriResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addSchema(string $id, $schema = null): void
|
||||
{
|
||||
if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) {
|
||||
// if the schema was user-provided to Validator and is still null, then assume this is
|
||||
// what the user intended, as there's no way for us to retrieve anything else. User-supplied
|
||||
// schemas do not have an associated URI when passed via Validator::validate().
|
||||
$schema = $this->uriRetriever->retrieve($id);
|
||||
}
|
||||
|
||||
// cast array schemas to object
|
||||
if (is_array($schema)) {
|
||||
$schema = BaseConstraint::arrayToObjectRecursive($schema);
|
||||
}
|
||||
|
||||
// workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format)
|
||||
// see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367
|
||||
if (is_object($schema) && property_exists($schema, 'id')) {
|
||||
if ($schema->id === 'http://json-schema.org/draft-04/schema#') {
|
||||
$schema->properties->id->format = 'uri-reference';
|
||||
} elseif ($schema->id === 'http://json-schema.org/draft-03/schema#') {
|
||||
$schema->properties->id->format = 'uri-reference';
|
||||
$schema->properties->{'$ref'}->format = 'uri-reference';
|
||||
}
|
||||
}
|
||||
|
||||
$this->scanForSubschemas($schema, $id);
|
||||
|
||||
// resolve references
|
||||
$this->expandRefs($schema, $id);
|
||||
|
||||
$this->schemas[$id] = $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively resolve all references against the provided base
|
||||
*
|
||||
* @param mixed $schema
|
||||
*/
|
||||
private function expandRefs(&$schema, ?string $parentId = null): void
|
||||
{
|
||||
if (!is_object($schema)) {
|
||||
if (is_array($schema)) {
|
||||
foreach ($schema as &$member) {
|
||||
$this->expandRefs($member, $parentId);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) {
|
||||
$refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $parentId));
|
||||
$schema->{'$ref'} = (string) $refPointer;
|
||||
}
|
||||
|
||||
foreach ($schema as $propertyName => &$member) {
|
||||
if (in_array($propertyName, ['enum', 'const'])) {
|
||||
// Enum and const don't allow $ref as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/445
|
||||
continue;
|
||||
}
|
||||
|
||||
$childId = $parentId;
|
||||
if (property_exists($schema, 'id') && is_string($schema->id) && $childId !== $schema->id) {
|
||||
$childId = $this->uriResolver->resolve($schema->id, $childId);
|
||||
}
|
||||
|
||||
$this->expandRefs($member, $childId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSchema(string $id)
|
||||
{
|
||||
if (!array_key_exists($id, $this->schemas)) {
|
||||
$this->addSchema($id);
|
||||
}
|
||||
|
||||
return $this->schemas[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolveRef(string $ref, $resolveStack = [])
|
||||
{
|
||||
$jsonPointer = new JsonPointer($ref);
|
||||
|
||||
// resolve filename for pointer
|
||||
$fileName = $jsonPointer->getFilename();
|
||||
if (!strlen($fileName)) {
|
||||
throw new UnresolvableJsonPointerException(sprintf(
|
||||
"Could not resolve fragment '%s': no file is defined",
|
||||
$jsonPointer->getPropertyPathAsString()
|
||||
));
|
||||
}
|
||||
|
||||
// get & process the schema
|
||||
$refSchema = $this->getSchema($fileName);
|
||||
foreach ($jsonPointer->getPropertyPaths() as $path) {
|
||||
if (is_object($refSchema) && property_exists($refSchema, $path)) {
|
||||
$refSchema = $this->resolveRefSchema($refSchema->{$path}, $resolveStack);
|
||||
} elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) {
|
||||
$refSchema = $this->resolveRefSchema($refSchema[$path], $resolveStack);
|
||||
} else {
|
||||
throw new UnresolvableJsonPointerException(sprintf(
|
||||
'File: %s is found, but could not resolve fragment: %s',
|
||||
$jsonPointer->getFilename(),
|
||||
$jsonPointer->getPropertyPathAsString()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $refSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolveRefSchema($refSchema, $resolveStack = [])
|
||||
{
|
||||
if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) {
|
||||
if (in_array($refSchema, $resolveStack, true)) {
|
||||
throw new UnresolvableJsonPointerException(sprintf(
|
||||
'Dereferencing a pointer to %s results in an infinite loop',
|
||||
$refSchema->{'$ref'}
|
||||
));
|
||||
}
|
||||
$resolveStack[] = $refSchema;
|
||||
|
||||
return $this->resolveRef($refSchema->{'$ref'}, $resolveStack);
|
||||
}
|
||||
|
||||
return $refSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $schema
|
||||
*/
|
||||
private function scanForSubschemas($schema, string $parentId): void
|
||||
{
|
||||
if (!$schema instanceof \stdClass && !is_array($schema)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($schema as $propertyName => $potentialSubSchema) {
|
||||
if (!is_object($potentialSubSchema)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property_exists($potentialSubSchema, 'id') && is_string($potentialSubSchema->id) && property_exists($potentialSubSchema, 'type')) {
|
||||
// Enum and const don't allow id as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/471
|
||||
if (in_array($propertyName, ['enum', 'const'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found sub schema
|
||||
$this->addSchema($this->uriResolver->resolve($potentialSubSchema->id, $parentId), $potentialSubSchema);
|
||||
}
|
||||
|
||||
$this->scanForSubschemas($potentialSubSchema, $parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
interface SchemaStorageInterface
|
||||
{
|
||||
/**
|
||||
* Adds schema with given identifier
|
||||
*
|
||||
* @param object $schema
|
||||
*/
|
||||
public function addSchema(string $id, $schema = null): void;
|
||||
|
||||
/**
|
||||
* Returns schema for given identifier, or null if it does not exist
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getSchema(string $id);
|
||||
|
||||
/**
|
||||
* Returns schema for given reference with all sub-references resolved
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function resolveRef(string $ref);
|
||||
|
||||
/**
|
||||
* Returns schema referenced by '$ref' property
|
||||
*
|
||||
* @param mixed $refSchema
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function resolveRefSchema($refSchema);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Tool;
|
||||
|
||||
class DeepComparer
|
||||
{
|
||||
/**
|
||||
* @param mixed $left
|
||||
* @param mixed $right
|
||||
*/
|
||||
public static function isEqual($left, $right): bool
|
||||
{
|
||||
if ($left === null && $right === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$isLeftScalar = is_scalar($left);
|
||||
$isLeftNumber = is_int($left) || is_float($left);
|
||||
$isRightScalar = is_scalar($right);
|
||||
$isRightNumber = is_int($right) || is_float($right);
|
||||
|
||||
if ($isLeftScalar && $isRightScalar) {
|
||||
/*
|
||||
* In Json-Schema mathematically equal numbers are compared equal
|
||||
*/
|
||||
if ($isLeftNumber && $isRightNumber && (float) $left === (float) $right) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $left === $right;
|
||||
}
|
||||
|
||||
if ($isLeftScalar !== $isRightScalar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_array($left) && is_array($right)) {
|
||||
return self::isArrayEqual($left, $right);
|
||||
}
|
||||
|
||||
if ($left instanceof \stdClass && $right instanceof \stdClass) {
|
||||
return self::isArrayEqual((array) $left, (array) $right);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $left
|
||||
* @param array<string|int, mixed> $right
|
||||
*/
|
||||
private static function isArrayEqual(array $left, array $right): bool
|
||||
{
|
||||
if (count($left) !== count($right)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($left as $key => $value) {
|
||||
if (!array_key_exists($key, $right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self::isEqual($value, $right[$key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Tool;
|
||||
|
||||
use JsonSchema\Exception\JsonDecodingException;
|
||||
use JsonSchema\Exception\RuntimeException;
|
||||
|
||||
class DeepCopy
|
||||
{
|
||||
/**
|
||||
* @param mixed $input
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function copyOf($input)
|
||||
{
|
||||
$json = json_encode($input);
|
||||
if (JSON_ERROR_NONE < $error = json_last_error()) {
|
||||
throw new JsonDecodingException($error);
|
||||
}
|
||||
|
||||
if ($json === false) {
|
||||
throw new RuntimeException('Failed to encode input to JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return json_decode($json, self::isAssociativeArray($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $input
|
||||
*/
|
||||
private static function isAssociativeArray($input): bool
|
||||
{
|
||||
return is_array($input) && array_keys($input) !== range(0, count($input) - 1);
|
||||
}
|
||||
}
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Tool\Validator;
|
||||
|
||||
class RelativeReferenceValidator
|
||||
{
|
||||
public static function isValid(string $ref): bool
|
||||
{
|
||||
// Relative reference pattern as per RFC 3986, Section 4.1
|
||||
$pattern = '/^(([^\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/';
|
||||
|
||||
if (preg_match($pattern, $ref) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Additional checks for invalid cases
|
||||
if (preg_match('/^(http|https):\/\//', $ref)) {
|
||||
return false; // Absolute URI
|
||||
}
|
||||
|
||||
if (preg_match('/^:\/\//', $ref)) {
|
||||
return false; // Missing scheme in authority
|
||||
}
|
||||
|
||||
if (preg_match('/^:\//', $ref)) {
|
||||
return false; // Invalid scheme separator
|
||||
}
|
||||
|
||||
if (preg_match('/^\/\/$/', $ref)) {
|
||||
return false; // Empty authority
|
||||
}
|
||||
|
||||
if (preg_match('/^\/\/\/[^\/]/', $ref)) {
|
||||
return false; // Invalid authority with three slashes
|
||||
}
|
||||
|
||||
if (preg_match('/\s/', $ref)) {
|
||||
return false; // Spaces are not allowed in URIs
|
||||
}
|
||||
|
||||
if (preg_match('/^\?#|^#$/', $ref)) {
|
||||
return false; // Missing path but having query and fragment
|
||||
}
|
||||
|
||||
if ($ref === '#' || $ref === '?') {
|
||||
return false; // Missing path and having only fragment or query
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Tool\Validator;
|
||||
|
||||
class UriValidator
|
||||
{
|
||||
public static function isValid(string $uri): bool
|
||||
{
|
||||
// RFC 3986: Hierarchical URIs (http, https, ftp, etc.)
|
||||
$hierarchicalPattern = '/^
|
||||
([a-z][a-z0-9+\-.]*):\/\/ # Scheme (http, https, ftp, etc.)
|
||||
(?:([^:@\/?#]+)(?::([^@\/?#]*))?@)? # Optional userinfo (user:pass@)
|
||||
([a-z0-9.-]+|\[[a-f0-9:.]+\]) # Hostname or IPv6 in brackets
|
||||
(?::(\d{1,5}))? # Optional port
|
||||
(\/[a-zA-Z0-9._~!$&\'()*+,;=:@\/%-]*)* # Path (valid characters only)
|
||||
(\?([^#]*))? # Optional query
|
||||
(\#(.*))? # Optional fragment
|
||||
$/ix';
|
||||
|
||||
// RFC 3986: Non-Hierarchical URIs (mailto, data, urn)
|
||||
$nonHierarchicalPattern = '/^
|
||||
(mailto|data|urn): # Only allow known non-hierarchical schemes
|
||||
(.+) # Must contain at least one character after scheme
|
||||
$/ix';
|
||||
|
||||
// RFC 5322-compliant email validation for `mailto:` URIs
|
||||
$emailPattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
|
||||
|
||||
// First, check if it's a valid hierarchical URI
|
||||
if (preg_match($hierarchicalPattern, $uri, $matches) === 1) {
|
||||
// Validate domain name (no double dots like example..com)
|
||||
if (!empty($matches[4]) && preg_match('/\.\./', $matches[4])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate port (should be between 1 and 65535 if specified)
|
||||
if (!empty($matches[5]) && ($matches[5] < 1 || $matches[5] > 65535)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate path (reject illegal characters: < > { } | \ ^ `)
|
||||
if (!empty($matches[6]) && preg_match('/[<>{}|\\\^`]/', $matches[6])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not hierarchical, check non-hierarchical URIs
|
||||
if (preg_match($nonHierarchicalPattern, $uri, $matches) === 1) {
|
||||
$scheme = strtolower($matches[1]); // Extract the scheme
|
||||
|
||||
// Special case: `mailto:` must contain a **valid email address**
|
||||
if ($scheme === 'mailto') {
|
||||
return preg_match($emailPattern, $matches[2]) === 1;
|
||||
}
|
||||
|
||||
return true; // Valid non-hierarchical URI
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* JsonSchema
|
||||
*
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Uri\Retrievers;
|
||||
|
||||
/**
|
||||
* AbstractRetriever implements the default shared behavior
|
||||
* that all descendant Retrievers should inherit
|
||||
*
|
||||
* @author Steven Garcia <webwhammy@gmail.com>
|
||||
*/
|
||||
abstract class AbstractRetriever implements UriRetrieverInterface
|
||||
{
|
||||
/**
|
||||
* Media content type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contentType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType()
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return $this->contentType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Uri\Retrievers;
|
||||
|
||||
use JsonSchema\Exception\RuntimeException;
|
||||
use JsonSchema\Validator;
|
||||
|
||||
/**
|
||||
* Tries to retrieve JSON schemas from a URI using cURL library
|
||||
*
|
||||
* @author Sander Coolen <sander@jibber.nl>
|
||||
*/
|
||||
class Curl extends AbstractRetriever
|
||||
{
|
||||
protected $messageBody;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!function_exists('curl_init')) {
|
||||
// Cannot test this, because curl_init is present on all test platforms plus mock
|
||||
throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
|
||||
*/
|
||||
public function retrieve($uri)
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $uri);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: ' . Validator::SCHEMA_MEDIA_TYPE]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
if (false === $response) {
|
||||
throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found');
|
||||
}
|
||||
|
||||
$this->fetchMessageBody($response);
|
||||
$this->fetchContentType($response);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $this->messageBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $response cURL HTTP response
|
||||
*/
|
||||
private function fetchMessageBody($response)
|
||||
{
|
||||
preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match);
|
||||
$this->messageBody = $match[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $response cURL HTTP response
|
||||
*
|
||||
* @return bool Whether the Content-Type header was found or not
|
||||
*/
|
||||
protected function fetchContentType($response)
|
||||
{
|
||||
if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) {
|
||||
$this->contentType = trim($match[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Uri\Retrievers;
|
||||
|
||||
use JsonSchema\Exception\ResourceNotFoundException;
|
||||
|
||||
/**
|
||||
* Tries to retrieve JSON schemas from a URI using file_get_contents()
|
||||
*
|
||||
* @author Sander Coolen <sander@jibber.nl>
|
||||
*/
|
||||
class FileGetContents extends AbstractRetriever
|
||||
{
|
||||
protected $messageBody;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
|
||||
*/
|
||||
public function retrieve($uri)
|
||||
{
|
||||
$errorMessage = null;
|
||||
set_error_handler(function ($errno, $errstr) use (&$errorMessage) {
|
||||
$errorMessage = $errstr;
|
||||
});
|
||||
$response = file_get_contents($uri);
|
||||
restore_error_handler();
|
||||
|
||||
if ($errorMessage) {
|
||||
throw new ResourceNotFoundException($errorMessage);
|
||||
}
|
||||
|
||||
if (false === $response) {
|
||||
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
|
||||
}
|
||||
|
||||
if ($response == ''
|
||||
&& substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
|
||||
) {
|
||||
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
|
||||
}
|
||||
|
||||
$this->messageBody = $response;
|
||||
if (!empty($http_response_header)) {
|
||||
// $http_response_header cannot be tested, because it's defined in the method's local scope
|
||||
// See http://php.net/manual/en/reserved.variables.httpresponseheader.php for more info.
|
||||
$this->fetchContentType($http_response_header); // @codeCoverageIgnore
|
||||
} else { // @codeCoverageIgnore
|
||||
// Could be a "file://" url or something else - fake up the response
|
||||
$this->contentType = null;
|
||||
}
|
||||
|
||||
return $this->messageBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $headers HTTP Response Headers
|
||||
*
|
||||
* @return bool Whether the Content-Type header was found or not
|
||||
*/
|
||||
private function fetchContentType(array $headers)
|
||||
{
|
||||
foreach (array_reverse($headers) as $header) {
|
||||
if ($this->contentType = self::getContentTypeMatchInHeader($header)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $header
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function getContentTypeMatchInHeader($header)
|
||||
{
|
||||
if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) {
|
||||
return trim($match[1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace JsonSchema\Uri\Retrievers;
|
||||
|
||||
use JsonSchema\Validator;
|
||||
|
||||
/**
|
||||
* URI retrieved based on a predefined array of schemas
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* $retriever = new PredefinedArray(array(
|
||||
* 'http://acme.com/schemas/person#' => '{ ... }',
|
||||
* 'http://acme.com/schemas/address#' => '{ ... }',
|
||||
* ))
|
||||
*
|
||||
* $schema = $retriever->retrieve('http://acme.com/schemas/person#');
|
||||
*/
|
||||
class PredefinedArray extends AbstractRetriever
|
||||
{
|
||||
/**
|
||||
* Contains schemas as URI => JSON
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $schemas;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $schemas
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE)
|
||||
{
|
||||
$this->schemas = $schemas;
|
||||
$this->contentType = $contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
|
||||
*/
|
||||
public function retrieve($uri)
|
||||
{
|
||||
if (!array_key_exists($uri, $this->schemas)) {
|
||||
throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf(
|
||||
'The JSON schema "%s" was not found.',
|
||||
$uri
|
||||
));
|
||||
}
|
||||
|
||||
return $this->schemas[$uri];
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Uri\Retrievers;
|
||||
|
||||
/**
|
||||
* Interface for URI retrievers
|
||||
*
|
||||
* @author Sander Coolen <sander@jibber.nl>
|
||||
*/
|
||||
interface UriRetrieverInterface
|
||||
{
|
||||
/**
|
||||
* Retrieve a schema from the specified URI
|
||||
*
|
||||
* @param string $uri URI that resolves to a JSON schema
|
||||
*
|
||||
* @throws \JsonSchema\Exception\ResourceNotFoundException
|
||||
*
|
||||
* @return mixed string|null
|
||||
*/
|
||||
public function retrieve($uri);
|
||||
|
||||
/**
|
||||
* Get media content type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType();
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Uri;
|
||||
|
||||
use JsonSchema\Exception\UriResolverException;
|
||||
use JsonSchema\UriResolverInterface;
|
||||
|
||||
/**
|
||||
* Resolves JSON Schema URIs
|
||||
*
|
||||
* @author Sander Coolen <sander@jibber.nl>
|
||||
*/
|
||||
class UriResolver implements UriResolverInterface
|
||||
{
|
||||
/**
|
||||
* Parses a URI into five main components
|
||||
*
|
||||
* @param string $uri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse($uri)
|
||||
{
|
||||
preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', (string) $uri, $match);
|
||||
|
||||
$components = [];
|
||||
if (5 < count($match)) {
|
||||
$components = [
|
||||
'scheme' => $match[2],
|
||||
'authority' => $match[4],
|
||||
'path' => $match[5]
|
||||
];
|
||||
}
|
||||
if (7 < count($match)) {
|
||||
$components['query'] = $match[7];
|
||||
}
|
||||
if (9 < count($match)) {
|
||||
$components['fragment'] = $match[9];
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URI based on n array with the main components
|
||||
*
|
||||
* @param array $components
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate(array $components)
|
||||
{
|
||||
$uri = $components['scheme'] . '://'
|
||||
. $components['authority']
|
||||
. $components['path'];
|
||||
|
||||
if (array_key_exists('query', $components) && strlen($components['query'])) {
|
||||
$uri .= '?' . $components['query'];
|
||||
}
|
||||
if (array_key_exists('fragment', $components)) {
|
||||
$uri .= '#' . $components['fragment'];
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolve($uri, $baseUri = null)
|
||||
{
|
||||
// treat non-uri base as local file path
|
||||
if (
|
||||
!is_null($baseUri) &&
|
||||
!filter_var($baseUri, \FILTER_VALIDATE_URL) &&
|
||||
!preg_match('|^[^/]+://|u', $baseUri)
|
||||
) {
|
||||
if (is_file($baseUri)) {
|
||||
$baseUri = 'file://' . realpath($baseUri);
|
||||
} elseif (is_dir($baseUri)) {
|
||||
$baseUri = 'file://' . realpath($baseUri) . '/';
|
||||
} else {
|
||||
$baseUri = 'file://' . getcwd() . '/' . $baseUri;
|
||||
}
|
||||
}
|
||||
|
||||
if ($uri == '') {
|
||||
return $baseUri;
|
||||
}
|
||||
|
||||
$components = $this->parse($uri);
|
||||
$path = $components['path'];
|
||||
|
||||
if (!empty($components['scheme'])) {
|
||||
return $uri;
|
||||
}
|
||||
$baseComponents = $this->parse($baseUri);
|
||||
$basePath = $baseComponents['path'];
|
||||
|
||||
$baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
|
||||
if (isset($components['fragment'])) {
|
||||
$baseComponents['fragment'] = $components['fragment'];
|
||||
}
|
||||
|
||||
return $this->generate($baseComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to glue a relative path onto an absolute one
|
||||
*
|
||||
* @param string $relativePath
|
||||
* @param string $basePath
|
||||
*
|
||||
* @throws UriResolverException
|
||||
*
|
||||
* @return string Merged path
|
||||
*/
|
||||
public static function combineRelativePathWithBasePath($relativePath, $basePath)
|
||||
{
|
||||
$relativePath = self::normalizePath($relativePath);
|
||||
if (!$relativePath) {
|
||||
return $basePath;
|
||||
}
|
||||
if ($relativePath[0] === '/') {
|
||||
return $relativePath;
|
||||
}
|
||||
if (!$basePath) {
|
||||
throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
|
||||
}
|
||||
|
||||
$dirname = $basePath[strlen($basePath) - 1] === '/' ? $basePath : dirname($basePath);
|
||||
$combined = rtrim($dirname, '/') . '/' . ltrim($relativePath, '/');
|
||||
$combinedSegments = explode('/', $combined);
|
||||
$collapsedSegments = [];
|
||||
while ($combinedSegments) {
|
||||
$segment = array_shift($combinedSegments);
|
||||
if ($segment === '..') {
|
||||
if (count($collapsedSegments) <= 1) {
|
||||
// Do not remove the top level (domain)
|
||||
// This is not ideal - the domain should not be part of the path here. parse() and generate()
|
||||
// should handle the "domain" separately, like the schema.
|
||||
// Then the if-condition here would be `if (!$collapsedSegments) {`.
|
||||
throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
|
||||
}
|
||||
array_pop($collapsedSegments);
|
||||
} else {
|
||||
$collapsedSegments[] = $segment;
|
||||
}
|
||||
}
|
||||
|
||||
return implode('/', $collapsedSegments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a URI path component by removing dot-slash and double slashes
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function normalizePath($path)
|
||||
{
|
||||
$path = preg_replace('|((?<!\.)\./)*|', '', $path);
|
||||
$path = preg_replace('|//|', '/', $path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($uri)
|
||||
{
|
||||
$components = $this->parse($uri);
|
||||
|
||||
return !empty($components);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema\Uri;
|
||||
|
||||
use JsonSchema\Exception\InvalidSchemaMediaTypeException;
|
||||
use JsonSchema\Exception\JsonDecodingException;
|
||||
use JsonSchema\Exception\ResourceNotFoundException;
|
||||
use JsonSchema\Uri\Retrievers\FileGetContents;
|
||||
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
|
||||
use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface;
|
||||
use JsonSchema\Validator;
|
||||
|
||||
/**
|
||||
* Retrieves JSON Schema URIs
|
||||
*
|
||||
* @author Tyler Akins <fidian@rumkin.com>
|
||||
*/
|
||||
class UriRetriever implements BaseUriRetrieverInterface
|
||||
{
|
||||
/**
|
||||
* @var array Map of URL translations
|
||||
*/
|
||||
protected $translationMap = [
|
||||
// use local copies of the spec schemas
|
||||
'|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array A list of endpoints for media type check exclusion
|
||||
*/
|
||||
protected $allowedInvalidContentTypeEndpoints = [
|
||||
'http://json-schema.org/',
|
||||
'https://json-schema.org/'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var null|UriRetrieverInterface
|
||||
*/
|
||||
protected $uriRetriever = null;
|
||||
|
||||
/**
|
||||
* @var array|object[]
|
||||
*
|
||||
* @see loadSchema
|
||||
*/
|
||||
private $schemaCache = [];
|
||||
|
||||
/**
|
||||
* Adds an endpoint to the media type validation exclusion list
|
||||
*
|
||||
* @param string $endpoint
|
||||
*/
|
||||
public function addInvalidContentTypeEndpoint($endpoint)
|
||||
{
|
||||
$this->allowedInvalidContentTypeEndpoints[] = $endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarantee the correct media type was encountered
|
||||
*
|
||||
* @param UriRetrieverInterface $uriRetriever
|
||||
* @param string $uri
|
||||
*
|
||||
* @return bool|void
|
||||
*/
|
||||
public function confirmMediaType($uriRetriever, $uri)
|
||||
{
|
||||
$contentType = $uriRetriever->getContentType();
|
||||
|
||||
if (is_null($contentType)) {
|
||||
// Well, we didn't get an invalid one
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($contentType, [Validator::SCHEMA_MEDIA_TYPE, 'application/json'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) {
|
||||
if (!\is_null($uri) && strpos($uri, $endpoint) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URI Retriever
|
||||
*
|
||||
* If none is specified, sets a default FileGetContents retriever and
|
||||
* returns that object.
|
||||
*
|
||||
* @return UriRetrieverInterface
|
||||
*/
|
||||
public function getUriRetriever()
|
||||
{
|
||||
if (is_null($this->uriRetriever)) {
|
||||
$this->setUriRetriever(new FileGetContents());
|
||||
}
|
||||
|
||||
return $this->uriRetriever;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a schema based on pointer
|
||||
*
|
||||
* URIs can have a fragment at the end in the format of
|
||||
* #/path/to/object and we are to look up the 'path' property of
|
||||
* the first object then the 'to' and 'object' properties.
|
||||
*
|
||||
* @param object $jsonSchema JSON Schema contents
|
||||
* @param string $uri JSON Schema URI
|
||||
*
|
||||
* @throws ResourceNotFoundException
|
||||
*
|
||||
* @return object JSON Schema after walking down the fragment pieces
|
||||
*/
|
||||
public function resolvePointer($jsonSchema, $uri)
|
||||
{
|
||||
$resolver = new UriResolver();
|
||||
$parsed = $resolver->parse($uri);
|
||||
if (empty($parsed['fragment'])) {
|
||||
return $jsonSchema;
|
||||
}
|
||||
|
||||
$path = explode('/', $parsed['fragment']);
|
||||
while ($path) {
|
||||
$pathElement = array_shift($path);
|
||||
if (!empty($pathElement)) {
|
||||
$pathElement = str_replace('~1', '/', $pathElement);
|
||||
$pathElement = str_replace('~0', '~', $pathElement);
|
||||
if (!empty($jsonSchema->$pathElement)) {
|
||||
$jsonSchema = $jsonSchema->$pathElement;
|
||||
} else {
|
||||
throw new ResourceNotFoundException(
|
||||
'Fragment "' . $parsed['fragment'] . '" not found'
|
||||
. ' in ' . $uri
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_object($jsonSchema)) {
|
||||
throw new ResourceNotFoundException(
|
||||
'Fragment part "' . $pathElement . '" is no object '
|
||||
. ' in ' . $uri
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $jsonSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function retrieve($uri, $baseUri = null, $translate = true)
|
||||
{
|
||||
$resolver = new UriResolver();
|
||||
$resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);
|
||||
|
||||
//fetch URL without #fragment
|
||||
$arParts = $resolver->parse($resolvedUri);
|
||||
if (isset($arParts['fragment'])) {
|
||||
unset($arParts['fragment']);
|
||||
$fetchUri = $resolver->generate($arParts);
|
||||
}
|
||||
|
||||
// apply URI translations
|
||||
if ($translate) {
|
||||
$fetchUri = $this->translate($fetchUri);
|
||||
}
|
||||
|
||||
$jsonSchema = $this->loadSchema($fetchUri);
|
||||
|
||||
// Use the JSON pointer if specified
|
||||
$jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri);
|
||||
|
||||
if ($jsonSchema instanceof \stdClass) {
|
||||
$jsonSchema->id = $resolvedUri;
|
||||
}
|
||||
|
||||
return $jsonSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a schema from the given URI, json-decode it and return it.
|
||||
* Caches schema objects.
|
||||
*
|
||||
* @param string $fetchUri Absolute URI
|
||||
*
|
||||
* @return object JSON schema object
|
||||
*/
|
||||
protected function loadSchema($fetchUri)
|
||||
{
|
||||
if (isset($this->schemaCache[$fetchUri])) {
|
||||
return $this->schemaCache[$fetchUri];
|
||||
}
|
||||
|
||||
$uriRetriever = $this->getUriRetriever();
|
||||
$contents = $this->uriRetriever->retrieve($fetchUri);
|
||||
$this->confirmMediaType($uriRetriever, $fetchUri);
|
||||
$jsonSchema = json_decode($contents);
|
||||
|
||||
if (JSON_ERROR_NONE < $error = json_last_error()) {
|
||||
throw new JsonDecodingException($error);
|
||||
}
|
||||
|
||||
$this->schemaCache[$fetchUri] = $jsonSchema;
|
||||
|
||||
return $jsonSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URI Retriever
|
||||
*
|
||||
* @param UriRetrieverInterface $uriRetriever
|
||||
*
|
||||
* @return $this for chaining
|
||||
*/
|
||||
public function setUriRetriever(UriRetrieverInterface $uriRetriever)
|
||||
{
|
||||
$this->uriRetriever = $uriRetriever;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a URI into five main components
|
||||
*
|
||||
* @param string $uri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse($uri)
|
||||
{
|
||||
preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);
|
||||
|
||||
$components = [];
|
||||
if (5 < count($match)) {
|
||||
$components = [
|
||||
'scheme' => $match[2],
|
||||
'authority' => $match[4],
|
||||
'path' => $match[5]
|
||||
];
|
||||
}
|
||||
|
||||
if (7 < count($match)) {
|
||||
$components['query'] = $match[7];
|
||||
}
|
||||
|
||||
if (9 < count($match)) {
|
||||
$components['fragment'] = $match[9];
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URI based on n array with the main components
|
||||
*
|
||||
* @param array $components
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate(array $components)
|
||||
{
|
||||
$uri = $components['scheme'] . '://'
|
||||
. $components['authority']
|
||||
. $components['path'];
|
||||
|
||||
if (array_key_exists('query', $components)) {
|
||||
$uri .= $components['query'];
|
||||
}
|
||||
|
||||
if (array_key_exists('fragment', $components)) {
|
||||
$uri .= $components['fragment'];
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a URI
|
||||
*
|
||||
* @param string $uri Absolute or relative
|
||||
* @param string $baseUri Optional base URI
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function resolve($uri, $baseUri = null)
|
||||
{
|
||||
$components = $this->parse($uri);
|
||||
$path = $components['path'];
|
||||
|
||||
if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$baseComponents = $this->parse($baseUri);
|
||||
$basePath = $baseComponents['path'];
|
||||
|
||||
$baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);
|
||||
|
||||
return $this->generate($baseComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($uri)
|
||||
{
|
||||
$components = $this->parse($uri);
|
||||
|
||||
return !empty($components);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a URL translation rule
|
||||
*/
|
||||
public function setTranslation($from, $to)
|
||||
{
|
||||
$this->translationMap[$from] = $to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply URI translation rules
|
||||
*/
|
||||
public function translate($uri)
|
||||
{
|
||||
foreach ($this->translationMap as $from => $to) {
|
||||
$uri = preg_replace($from, $to, $uri);
|
||||
}
|
||||
|
||||
// translate references to local files within the json-schema package
|
||||
$uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri);
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
/**
|
||||
* @package JsonSchema
|
||||
*/
|
||||
interface UriResolverInterface
|
||||
{
|
||||
/**
|
||||
* Resolves a URI
|
||||
*
|
||||
* @param string $uri Absolute or relative
|
||||
* @param null|string $baseUri Optional base URI
|
||||
*
|
||||
* @return string Absolute URI
|
||||
*/
|
||||
public function resolve($uri, $baseUri = null);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
/**
|
||||
* @package JsonSchema
|
||||
*/
|
||||
interface UriRetrieverInterface
|
||||
{
|
||||
/**
|
||||
* Retrieve a URI
|
||||
*
|
||||
* @param string $uri JSON Schema URI
|
||||
* @param null|string $baseUri
|
||||
*
|
||||
* @return object JSON Schema contents
|
||||
*/
|
||||
public function retrieve($uri, $baseUri = null);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the JsonSchema package.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace JsonSchema;
|
||||
|
||||
use JsonSchema\Constraints\BaseConstraint;
|
||||
use JsonSchema\Constraints\Constraint;
|
||||
use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;
|
||||
|
||||
/**
|
||||
* A JsonSchema Constraint
|
||||
*
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
|
||||
*
|
||||
* @see README.md
|
||||
*/
|
||||
class Validator extends BaseConstraint
|
||||
{
|
||||
public const SCHEMA_MEDIA_TYPE = 'application/schema+json';
|
||||
|
||||
public const ERROR_NONE = 0;
|
||||
public const ERROR_ALL = -1;
|
||||
public const ERROR_DOCUMENT_VALIDATION = 1;
|
||||
public const ERROR_SCHEMA_VALIDATION = 2;
|
||||
|
||||
/**
|
||||
* Validates the given data against the schema and returns an object containing the results
|
||||
* Both the php object and the schema are supposed to be a result of a json_decode call.
|
||||
* The validation works as defined by the schema proposal in http://json-schema.org.
|
||||
*
|
||||
* Note that the first argument is passed by reference, so you must pass in a variable.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
*
|
||||
* @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
|
||||
* @phpstan-return int-mask-of<Validator::ERROR_*>
|
||||
*/
|
||||
public function validate(&$value, $schema = null, ?int $checkMode = null): int
|
||||
{
|
||||
// reset errors prior to validation
|
||||
$this->reset();
|
||||
|
||||
// set checkMode
|
||||
$initialCheckMode = $this->factory->getConfig();
|
||||
if ($checkMode !== null) {
|
||||
$this->factory->setConfig($checkMode);
|
||||
}
|
||||
|
||||
// add provided schema to SchemaStorage with internal URI to allow internal $ref resolution
|
||||
$schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI;
|
||||
if (LooseTypeCheck::propertyExists($schema, 'id')) {
|
||||
$schemaURI = LooseTypeCheck::propertyGet($schema, 'id');
|
||||
}
|
||||
$this->factory->getSchemaStorage()->addSchema($schemaURI, $schema);
|
||||
|
||||
$validator = $this->factory->createInstanceFor('schema');
|
||||
$validator->check(
|
||||
$value,
|
||||
$this->factory->getSchemaStorage()->getSchema($schemaURI)
|
||||
);
|
||||
|
||||
$this->factory->setConfig($initialCheckMode);
|
||||
|
||||
$this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));
|
||||
|
||||
return $validator->getErrorMask();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias to validate(), to maintain backwards-compatibility with the previous API
|
||||
*
|
||||
* @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
*
|
||||
* @phpstan-return int-mask-of<Validator::ERROR_*>
|
||||
*/
|
||||
public function check($value, $schema): int
|
||||
{
|
||||
return $this->validate($value, $schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias to validate(), to maintain backwards-compatibility with the previous API
|
||||
*
|
||||
* @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $schema
|
||||
*
|
||||
* @phpstan-return int-mask-of<Validator::ERROR_*>
|
||||
*/
|
||||
public function coerce(&$value, $schema): int
|
||||
{
|
||||
return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user