migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,148 @@
<?php
/*
* 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\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 = array();
/**
* @var int All error types which have occurred
*/
protected $errorMask = Validator::ERROR_NONE;
/**
* @var Factory
*/
protected $factory;
/**
* @param Factory $factory
*/
public function __construct(Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
}
public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null)
{
$error = array(
'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'),
'message' => $message,
'constraint' => $constraint,
'context' => $this->factory->getErrorContext(),
);
if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message']));
}
if (is_array($more) && count($more) > 0) {
$error += $more;
}
$this->errors[] = $error;
$this->errorMask |= $error['context'];
}
public function addErrors(array $errors)
{
if ($errors) {
$this->errors = array_merge($this->errors, $errors);
$errorMask = &$this->errorMask;
array_walk($errors, function ($error) use (&$errorMask) {
if (isset($error['context'])) {
$errorMask |= $error['context'];
}
});
}
}
public function getErrors($errorContext = Validator::ERROR_ALL)
{
if ($errorContext === Validator::ERROR_ALL) {
return $this->errors;
}
return array_filter($this->errors, function ($error) use ($errorContext) {
if ($errorContext & $error['context']) {
return true;
}
});
}
public function numErrors($errorContext = Validator::ERROR_ALL)
{
if ($errorContext === Validator::ERROR_ALL) {
return count($this->errors);
}
return count($this->getErrors($errorContext));
}
public function isValid()
{
return !$this->getErrors();
}
/**
* Clears any reported errors. Should be used between
* multiple validation checks.
*/
public function reset()
{
$this->errors = array();
$this->errorMask = Validator::ERROR_NONE;
}
/**
* Get the error mask
*
* @return int
*/
public function getErrorMask()
{
return $this->errorMask;
}
/**
* Recursively cast an associative array to an object
*
* @param array $array
*
* @return object
*/
public static function arrayToObjectRecursive($array)
{
$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);
}
}
@@ -0,0 +1,121 @@
<?php
/*
* 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\Entity\JsonPointer;
/**
* 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)
{
// Verify minItems
if (isset($schema->minItems) && count($value) < $schema->minItems) {
$this->addError($path, 'There must be a minimum of ' . $schema->minItems . ' items in the array', 'minItems', array('minItems' => $schema->minItems));
}
// Verify maxItems
if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
$this->addError($path, 'There must be a maximum of ' . $schema->maxItems . ' items in the array', 'maxItems', array('maxItems' => $schema->maxItems));
}
// Verify uniqueItems
if (isset($schema->uniqueItems) && $schema->uniqueItems) {
$unique = $value;
if (is_array($value) && count($value)) {
$unique = array_map(function ($e) {
return var_export($e, true);
}, $value);
}
if (count(array_unique($unique)) != count($value)) {
$this->addError($path, 'There are no duplicates allowed in the array', 'uniqueItems');
}
}
// Verify items
if (isset($schema->items)) {
$this->validateItems($value, $schema, $path, $i);
}
}
/**
* Validates the items
*
* @param array $value
* @param \stdClass $schema
* @param JsonPointer|null $path
* @param string $i
*/
protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
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(
$path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('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);
}
}
}
}
}
@@ -0,0 +1,213 @@
<?php
/*
* 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\Entity\JsonPointer;
/**
* The Base Constraints, all Validators should extend this class
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
abstract class Constraint extends BaseConstraint implements ConstraintInterface
{
protected $inlineSchemaProperty = '$schema';
const CHECK_MODE_NONE = 0x00000000;
const CHECK_MODE_NORMAL = 0x00000001;
const CHECK_MODE_TYPE_CAST = 0x00000002;
const CHECK_MODE_COERCE_TYPES = 0x00000004;
const CHECK_MODE_APPLY_DEFAULTS = 0x00000008;
const CHECK_MODE_EXCEPTIONS = 0x00000010;
const CHECK_MODE_DISABLE_FORMAT = 0x00000020;
const CHECK_MODE_ONLY_REQUIRED_DEFAULTS = 0x00000080;
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
*
* @return JsonPointer;
*/
protected function incrementPath(JsonPointer $path = null, $i)
{
$path = $path ?: new JsonPointer('');
$path = $path->withPropertyPaths(
array_merge(
$path->getPropertyPaths(),
array_filter(array($i), 'strlen')
)
);
return $path;
}
/**
* Validates an array
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$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 JsonPointer|null $path
* @param mixed $properties
* @param mixed $additionalProperties
* @param mixed $patternProperties
*/
protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $properties = null,
$additionalProperties = null, $patternProperties = null, $appliedDefaults = array())
{
$validator = $this->factory->createInstanceFor('object');
$validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults);
$this->addErrors($validator->getErrors());
}
/**
* Validates the type of a property
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$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 JsonPointer|null $path
* @param mixed $i
*/
protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false)
{
$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 JsonPointer|null $path
* @param mixed $i
*/
protected function checkString($value, $schema = null, JsonPointer $path = null, $i = null)
{
$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 JsonPointer $path
* @param mixed $i
*/
protected function checkNumber($value, $schema = null, JsonPointer $path = null, $i = null)
{
$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 JsonPointer|null $path
* @param mixed $i
*/
protected function checkEnum($value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('enum');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks format of an element
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkFormat($value, $schema = null, JsonPointer $path = null, $i = null)
{
$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.
*
* @return TypeCheck\TypeCheckInterface
*/
protected function getTypeCheck()
{
return $this->factory->getTypeCheck();
}
/**
* @param JsonPointer $pointer
*
* @return string property path
*/
protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer)
{
$result = array_map(
function ($path) {
return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path);
},
$pointer->getPropertyPaths()
);
return trim(implode('', $result), '.');
}
}
@@ -0,0 +1,65 @@
<?php
/*
* 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\Entity\JsonPointer;
/**
* The Constraints Interface
*
* @author Robert Schönthal <seroscho@googlemail.com>
*/
interface ConstraintInterface
{
/**
* returns all collected errors
*
* @return array
*/
public function getErrors();
/**
* adds errors to this validator
*
* @param array $errors
*/
public function addErrors(array $errors);
/**
* adds an error
*
* @param JsonPointer|null $path
* @param string $message
* @param string $constraint the constraint/rule that is broken, e.g.: 'minLength'
* @param array $more more array elements to add to the error
*/
public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null);
/**
* checks if the validator has not raised errors
*
* @return bool
*/
public function isValid();
/**
* invokes the validation of an element
*
* @abstract
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*
* @throws \JsonSchema\Exception\ExceptionInterface
*/
public function check(&$value, $schema = null, JsonPointer $path = null, $i = null);
}
@@ -0,0 +1,54 @@
<?php
/*
* 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\Entity\JsonPointer;
/**
* 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)
{
// 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 ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == 'array' && $enumType == 'object') {
if ((object) $element == $enum) {
return;
}
}
if ($type === gettype($enum)) {
if ($type == 'object') {
if ($element == $enum) {
return;
}
} elseif ($element === $enum) {
return;
}
}
}
$this->addError($path, 'Does not have a value in the enumeration ' . json_encode($schema->enum), 'enum', array('enum' => $schema->enum));
}
}
@@ -0,0 +1,220 @@
<?php
/*
* 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 SchemaStorage
*/
protected $schemaStorage;
/**
* @var UriRetriever
*/
protected $uriRetriever;
/**
* @var int
*/
private $checkMode = Constraint::CHECK_MODE_NORMAL;
/**
* @var TypeCheck\TypeCheckInterface[]
*/
private $typeCheck = array();
/**
* @var int Validation context
*/
protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION;
/**
* @var array
*/
protected $constraintMap = array(
'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',
'format' => 'JsonSchema\Constraints\FormatConstraint',
'schema' => 'JsonSchema\Constraints\SchemaConstraint',
'validator' => 'JsonSchema\Validator'
);
/**
* @var array<ConstraintInterface>
*/
private $instanceCache = array();
/**
* @param SchemaStorage $schemaStorage
* @param UriRetrieverInterface $uriRetriever
* @param int $checkMode
*/
public function __construct(
SchemaStorageInterface $schemaStorage = null,
UriRetrieverInterface $uriRetriever = null,
$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
*/
public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL)
{
$this->checkMode = $checkMode;
}
/**
* Enable checkMode flags
*
* @param int $options
*/
public function addConfig($options)
{
$this->checkMode |= $options;
}
/**
* Disable checkMode flags
*
* @param int $options
*/
public function removeConfig($options)
{
$this->checkMode &= ~$options;
}
/**
* Get checkMode option
*
* @param int $options Options to get, if null then return entire bitmask
*
* @return int
*/
public function getConfig($options = null)
{
if ($options === null) {
return $this->checkMode;
}
return $this->checkMode & $options;
}
/**
* @return UriRetrieverInterface
*/
public function getUriRetriever()
{
return $this->uriRetriever;
}
public function getSchemaStorage()
{
return $this->schemaStorage;
}
public function getTypeCheck()
{
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];
}
/**
* @param string $name
* @param string $class
*
* @return Factory
*/
public function setConstraintClass($name, $class)
{
// 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|ObjectConstraint
*/
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
*
* @return string
*/
public function getErrorContext()
{
return $this->errorContext;
}
/**
* Set the error context
*
* @param string $validationContext
*/
public function setErrorContext($errorContext)
{
$this->errorContext = $errorContext;
}
}
@@ -0,0 +1,214 @@
<?php
/*
* 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\Entity\JsonPointer;
use JsonSchema\Rfc3339;
/**
* 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)
{
if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) {
return;
}
switch ($schema->format) {
case 'date':
if (!$date = $this->validateDateTime($element, 'Y-m-d')) {
$this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'time':
if (!$this->validateDateTime($element, 'H:i:s')) {
$this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'date-time':
if (null === Rfc3339::createFromString($element)) {
$this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'utc-millisec':
if (!$this->validateDateTime($element, 'U')) {
$this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'regex':
if (!$this->validateRegex($element)) {
$this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format));
}
break;
case 'color':
if (!$this->validateColor($element)) {
$this->addError($path, 'Invalid color', 'format', array('format' => $schema->format));
}
break;
case 'style':
if (!$this->validateStyle($element)) {
$this->addError($path, 'Invalid style', 'format', array('format' => $schema->format));
}
break;
case 'phone':
if (!$this->validatePhone($element)) {
$this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format));
}
break;
case 'uri':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
$this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
}
break;
case 'uriref':
case 'uri-reference':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
// FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but
// the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them.
// See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information.
if (substr($element, 0, 2) === '//') { // network-path reference
$validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
} elseif (substr($element, 0, 1) === '/') { // absolute-path reference
$validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
} elseif (strlen($element)) { // relative-path reference
$pathParts = explode('/', $element, 2);
if (strpos($pathParts[0], ':') !== false) {
$validURL = null;
} else {
$validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
}
} else {
$validURL = null;
}
if ($validURL === null) {
$this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
}
}
break;
case 'email':
$filterFlags = FILTER_NULL_ON_FAILURE;
if (defined('FILTER_FLAG_EMAIL_UNICODE')) {
// Only available from PHP >= 7.1.0, so ignore it for coverage checks
$filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore
}
if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) {
$this->addError($path, 'Invalid email', 'format', array('format' => $schema->format));
}
break;
case 'ip-address':
case 'ipv4':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
$this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format));
}
break;
case 'ipv6':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
$this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format));
}
break;
case 'host-name':
case 'hostname':
if (!$this->validateHostname($element)) {
$this->addError($path, 'Invalid hostname', 'format', array('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, $datetime);
if (!$dt) {
return false;
}
if ($datetime === $dt->format($format)) {
return true;
}
// handles the case where a non-6 digit microsecond datetime is passed
// which will fail the above string comparison because the passed
// $datetime may be '2000-05-01T12:12:12.123Z' but format() will return
// '2000-05-01T12:12:12.123000Z'
if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) {
return true;
}
return false;
}
protected function validateRegex($regex)
{
return false !== @preg_match('/' . $regex . '/u', '');
}
protected function validateColor($color)
{
if (in_array(strtolower($color), array('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)
{
$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);
}
}
@@ -0,0 +1,81 @@
<?php
/*
* 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\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)
{
// Verify minimum
if (isset($schema->exclusiveMinimum)) {
if (isset($schema->minimum)) {
if ($schema->exclusiveMinimum && $element <= $schema->minimum) {
$this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum));
} elseif ($element < $schema->minimum) {
$this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum));
}
} else {
$this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum');
}
} elseif (isset($schema->minimum) && $element < $schema->minimum) {
$this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum));
}
// Verify maximum
if (isset($schema->exclusiveMaximum)) {
if (isset($schema->maximum)) {
if ($schema->exclusiveMaximum && $element >= $schema->maximum) {
$this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum));
} elseif ($element > $schema->maximum) {
$this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum));
}
} else {
$this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum');
}
} elseif (isset($schema->maximum) && $element > $schema->maximum) {
$this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum));
}
// Verify divisibleBy - Draft v3
if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
$this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy));
}
// Verify multipleOf - Draft v4
if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) {
$this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('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;
}
}
@@ -0,0 +1,192 @@
<?php
/*
* 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\Entity\JsonPointer;
/**
* The ObjectConstraint Constraints, validates an object against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class ObjectConstraint extends Constraint
{
/**
* @var array List of properties to which a default value has been applied
*/
protected $appliedDefaults = array();
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null,
$additionalProp = null, $patternProperties = null, $appliedDefaults = array())
{
if ($element instanceof UndefinedConstraint) {
return;
}
$this->appliedDefaults = $appliedDefaults;
$matches = array();
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 = null, $patternProperties)
{
$try = array('/', '#', '+', '~', '%');
$matches = array();
foreach ($patternProperties as $pregex => $schema) {
$delimiter = '/';
// Choose delimiter. Necessary for patterns like ^/ , otherwise you get error
foreach ($try as $delimiter) {
if (strpos($pregex, $delimiter) === false) { // safe to use
break;
}
}
// Validate the pattern before using it to test for matches
if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) {
$this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex));
continue;
}
foreach ($element as $i => $value) {
if (preg_match($delimiter . $pregex . $delimiter . 'u', $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($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp');
}
// 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($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires');
}
$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, $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)
{
// Verify minimum number of properties
if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) {
if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) {
$this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties));
}
}
// Verify maximum number of properties
if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) {
if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) {
$this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties));
}
}
}
}
@@ -0,0 +1,94 @@
<?php
/*
* 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\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
{
const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#';
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
{
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($path, 'Schema is not valid', 'schema');
}
// restore the initial config
$this->factory->setConfig($initialConfig);
$this->factory->setErrorContext($initialContext);
}
// validate element against $validationSchema
$this->checkUndefined($element, $validationSchema, $path, $i);
}
}
@@ -0,0 +1,60 @@
<?php
/*
* 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\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)
{
// Verify maxLength
if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
$this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array(
'maxLength' => $schema->maxLength,
));
}
//verify minLength
if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
$this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array(
'minLength' => $schema->minLength,
));
}
// Verify a regex pattern
if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) {
$this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array(
'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
}
}
@@ -0,0 +1,68 @@
<?php
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 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);
}
}
@@ -0,0 +1,40 @@
<?php
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));
}
}
@@ -0,0 +1,18 @@
<?php
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);
}
@@ -0,0 +1,261 @@
<?php
/*
* 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\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 = array(
'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)
{
$type = isset($schema->type) ? $schema->type : null;
$isValid = false;
$wording = array();
if (is_array($type)) {
$this->validateTypesArray($value, $type, $wording, $isValid, $path);
} elseif (is_object($type)) {
$this->checkUndefined($value, $type, $path);
return;
} else {
$isValid = $this->validateType($value, $type);
}
if ($isValid === false) {
if (!is_array($type)) {
$this->validateTypeNameWording($type);
$wording[] = self::$wording[$type];
}
$this->addError($path, ucwords(gettype($value)) . ' value found, but ' .
$this->implodeWith($wording, ', ', 'or') . ' is required', 'type');
}
}
/**
* 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 agains
* @param array $validTypesWording An array of wordings of the valid types of the array $type
* @param bool $isValid The current validation value
* @param $path
*/
protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path)
{
foreach ($type as $tp) {
// $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);
}
}
}
}
/**
* 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(array($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 (!isset(self::$wording[$type])) {
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)
{
//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) {
return $this->getTypeCheck()->isArray($value);
}
$coerce = $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES);
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) {
return is_string($value);
}
if ('email' === $type) {
return is_string($value);
}
if ('null' === $type) {
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 $value The value to convert to boolean
*
* @return bool|mixed
*/
protected function toBoolean($value)
{
if ($value === 'true') {
return true;
}
if ($value === 'false') {
return false;
}
return $value;
}
/**
* Converts a numeric string to a number. For example, "4" becomes 4.
*
* @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
}
return $value;
}
protected function toInteger($value)
{
if (is_numeric($value) && (int) $value == $value) {
return (int) $value; // cast to number
}
return $value;
}
}
@@ -0,0 +1,419 @@
<?php
/*
* 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\Constraints\TypeCheck\LooseTypeCheck;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Uri\UriResolver;
/**
* The UndefinedConstraint Constraints
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class UndefinedConstraint extends Constraint
{
/**
* @var array List of properties to which a default value has been applied
*/
protected $appliedDefaults = array();
/**
* {@inheritdoc}
*/
public function check(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false)
{
if (is_null($schema) || !is_object($schema)) {
return;
}
$path = $this->incrementPath($path ?: new JsonPointer(''), $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);
}
}
/**
* 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(
$this->incrementPath($path ?: new JsonPointer(''), $required),
'The property ' . $required . ' is required',
'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(
$path,
'The property ' . $propertyName . ' is required',
'required'
);
}
} 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($path, 'Disallowed value was matched', 'disallow');
} 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($path, 'Matched a schema which it should not', 'not');
} 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)
{
// only apply defaults if feature is enabled
if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
return;
}
// apply defaults if appropriate
$requiredOnly = $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 = array();
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($path, 'Failed to match all schemas', 'allOf');
}
}
if (isset($schema->anyOf)) {
$isValid = false;
$startErrors = $this->getErrors();
$caughtException = null;
foreach ($schema->anyOf as $anyOf) {
$initErrors = $this->getErrors();
try {
$this->checkUndefined($value, $anyOf, $path, $i);
if ($isValid = (count($this->getErrors()) == count($initErrors))) {
break;
}
} catch (ValidationException $e) {
$isValid = false;
}
}
if (!$isValid) {
$this->addError($path, 'Failed to match at least one schema', 'anyOf');
} else {
$this->errors = $startErrors;
}
}
if (isset($schema->oneOf)) {
$allErrors = array();
$matchedSchemas = 0;
$startErrors = $this->getErrors();
foreach ($schema->oneOf as $oneOf) {
try {
$this->errors = array();
$this->checkUndefined($value, $oneOf, $path, $i);
if (count($this->getErrors()) == 0) {
$matchedSchemas++;
}
$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 ($matchedSchemas !== 1) {
$this->addErrors(array_merge($allErrors, $startErrors));
$this->addError($path, 'Failed to match exactly one schema', 'oneOf');
} else {
$this->errors = $startErrors;
}
}
}
/**
* 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($path, "$key depends on $dependency and $dependency is missing", 'dependencies');
}
} 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($path, "$key depends on $d and $d is missing", 'dependencies');
}
}
} 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;
}
}