pax_global_header00006660000000000000000000000064126514412050014512gustar00rootroot0000000000000052 comment=cc84765fb7317f6b07bd8ac78364747f95b86341 json-schema-1.6.1/000077500000000000000000000000001265144120500137265ustar00rootroot00000000000000json-schema-1.6.1/.gitattributes000066400000000000000000000002521265144120500166200ustar00rootroot00000000000000/docs export-ignore /tests export-ignore .gitignore export-ignore .travis.yml export-ignore phpunit.dist.xml export-ignore json-schema-1.6.1/LICENSE000066400000000000000000000027671265144120500147470ustar00rootroot00000000000000Copyright (c) 2008, Gradua Networks Author: Bruno Prieto Reis All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Gradua Networks nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. json-schema-1.6.1/README.md000066400000000000000000000033671265144120500152160ustar00rootroot00000000000000# JSON Schema for PHP [![Build Status](https://travis-ci.org/justinrainbow/json-schema.svg?branch=master)](https://travis-ci.org/justinrainbow/json-schema) [![Latest Stable Version](https://poser.pugx.org/justinrainbow/json-schema/v/stable.png)](https://packagist.org/packages/justinrainbow/json-schema) [![Total Downloads](https://poser.pugx.org/justinrainbow/json-schema/downloads.png)](https://packagist.org/packages/justinrainbow/json-schema) A PHP Implementation for validating `JSON` Structures against a given `Schema`. See [json-schema](http://json-schema.org/) for more details. ## Installation ### Library $ git clone https://github.com/justinrainbow/json-schema.git ### Dependencies #### [`Composer`](https://github.com/composer/composer) (*will use the Composer ClassLoader*) $ wget http://getcomposer.org/composer.phar $ php composer.phar require justinrainbow/json-schema:~1.3 ## Usage ```php retrieve('file://' . realpath('schema.json')); $data = json_decode(file_get_contents('data.json')); // If you use $ref or if you are unsure, resolve those references here // This modifies the $schema object $refResolver = new JsonSchema\RefResolver($retriever); $refResolver->resolve($schema, 'file://' . __DIR__); // Validate $validator = new JsonSchema\Validator(); $validator->check($data, $schema); if ($validator->isValid()) { echo "The supplied JSON validates against the schema.\n"; } else { echo "JSON does not validate. Violations:\n"; foreach ($validator->getErrors() as $error) { echo sprintf("[%s] %s\n", $error['property'], $error['message']); } } ``` ## Running the tests $ vendor/bin/phpunit json-schema-1.6.1/bin/000077500000000000000000000000001265144120500144765ustar00rootroot00000000000000json-schema-1.6.1/bin/validate-json000077500000000000000000000146361265144120500171760ustar00rootroot00000000000000#!/usr/bin/env php */ /** * Dead simple autoloader * * @param string $className Name of class to load * * @return void */ function __autoload($className) { $className = ltrim($className, '\\'); $fileName = ''; $namespace = ''; if ($lastNsPos = strrpos($className, '\\')) { $namespace = substr($className, 0, $lastNsPos); $className = substr($className, $lastNsPos + 1); $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; } $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; if (stream_resolve_include_path($fileName)) { require_once $fileName; } } /** * Show the json parse error that happened last * * @return void */ function showJsonError() { $constants = get_defined_constants(true); $json_errors = array(); foreach ($constants['json'] as $name => $value) { if (!strncmp($name, 'JSON_ERROR_', 11)) { $json_errors[$value] = $name; } } echo 'JSON parse error: ' . $json_errors[json_last_error()] . "\n"; } function getUrlFromPath($path) { if (parse_url($path, PHP_URL_SCHEME) !== null) { //already an URL return $path; } if ($path{0} == '/') { //absolute path return 'file://' . $path; } //relative path: make absolute return 'file://' . getcwd() . '/' . $path; } /** * Take a HTTP header value and split it up into parts. * * @return array Key "_value" contains the main value, all others * as given in the header value */ function parseHeaderValue($headerValue) { if (strpos($headerValue, ';') === false) { return array('_value' => $headerValue); } $parts = explode(';', $headerValue); $arData = array('_value' => array_shift($parts)); foreach ($parts as $part) { list($name, $value) = explode('=', $part); $arData[$name] = trim($value, ' "\''); } return $arData; } // support running this tool from git checkout if (is_dir(__DIR__ . '/../src/JsonSchema')) { set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path()); } $arOptions = array(); $arArgs = array(); array_shift($argv);//script itself foreach ($argv as $arg) { if ($arg{0} == '-') { $arOptions[$arg] = true; } else { $arArgs[] = $arg; } } if (count($arArgs) == 0 || isset($arOptions['--help']) || isset($arOptions['-h']) ) { echo << array( 'header' => array( 'Accept: */*', 'Connection: Close' ), 'max_redirects' => 5 ) ) ); $dataString = file_get_contents($pathData, false, $context); if ($dataString == '') { echo "Data file is not readable or empty.\n"; exit(3); } $data = json_decode($dataString); unset($dataString); if ($data === null) { echo "Error loading JSON data file\n"; showJsonError(); exit(5); } if ($pathSchema === null) { if (isset($http_response_header)) { array_shift($http_response_header);//HTTP/1.0 line foreach ($http_response_header as $headerLine) { list($hName, $hValue) = explode(':', $headerLine, 2); $hName = strtolower($hName); if ($hName == 'link') { //Link: ; rel="describedBy" $hParts = parseHeaderValue($hValue); if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') { $pathSchema = trim($hParts['_value'], ' <>'); } } else if ($hName == 'content-type') { //Content-Type: application/my-media-type+json; // profile=http://example.org/schema# $hParts = parseHeaderValue($hValue); if (isset($hParts['profile'])) { $pathSchema = $hParts['profile']; } } } } if (is_object($data) && property_exists($data, '$schema')) { $pathSchema = $data->{'$schema'}; } //autodetect schema if ($pathSchema === null) { echo "JSON data must be an object and have a \$schema property.\n"; echo "You can pass the schema file on the command line as well.\n"; echo "Schema autodetection failed.\n"; exit(6); } } if ($pathSchema{0} == '/') { $pathSchema = 'file://' . $pathSchema; } $resolver = new JsonSchema\Uri\UriResolver(); $retriever = new JsonSchema\Uri\UriRetriever(); try { $urlSchema = $resolver->resolve($pathSchema, $urlData); if (isset($arOptions['--dump-schema-url'])) { echo $urlSchema . "\n"; exit(); } $schema = $retriever->retrieve($urlSchema); if ($schema === null) { echo "Error loading JSON schema file\n"; echo $urlSchema . "\n"; showJsonError(); exit(2); } } catch (Exception $e) { echo "Error loading JSON schema file\n"; echo $urlSchema . "\n"; echo $e->getMessage() . "\n"; exit(2); } $refResolver = new JsonSchema\RefResolver($retriever); $refResolver->resolve($schema, $urlSchema); if (isset($arOptions['--dump-schema'])) { $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; echo json_encode($schema, $options) . "\n"; exit(); } try { $validator = new JsonSchema\Validator(); $validator->check($data, $schema); if ($validator->isValid()) { echo "OK. The supplied JSON validates against the schema.\n"; } else { echo "JSON does not validate. Violations:\n"; foreach ($validator->getErrors() as $error) { echo sprintf("[%s] %s\n", $error['property'], $error['message']); } exit(23); } } catch (Exception $e) { echo "JSON does not validate. Error:\n"; echo $e->getMessage() . "\n"; echo "Error code: " . $e->getCode() . "\n"; exit(24); } ?> json-schema-1.6.1/composer.json000066400000000000000000000030621265144120500164510ustar00rootroot00000000000000{ "name": "justinrainbow/json-schema", "description": "A library to validate a json schema.", "keywords": ["json", "schema"], "homepage": "https://github.com/justinrainbow/json-schema", "type": "library", "license": "BSD-3-Clause", "authors": [ { "name": "Bruno Prieto Reis", "email": "bruno.p.reis@gmail.com" }, { "name": "Justin Rainbow", "email": "justin.rainbow@gmail.com" }, { "name": "Igor Wiedler", "email": "igor@wiedler.ch" }, { "name": "Robert Schönthal", "email": "seroscho@googlemail.com" } ], "repositories": [{ "type": "package", "package": { "name": "json-schema/JSON-Schema-Test-Suite", "version": "1.1.0", "source": { "url": "https://github.com/json-schema/JSON-Schema-Test-Suite", "type": "git", "reference": "1.1.0" } } }], "require": { "php": ">=5.3.29" }, "require-dev": { "json-schema/JSON-Schema-Test-Suite": "1.1.0", "phpunit/phpunit": "~3.7", "phpdocumentor/phpdocumentor": "~2" }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" } }, "autoload-dev": { "psr-4": { "JsonSchema\\Tests\\": "tests/JsonSchema/Tests/" } }, "bin": ["bin/validate-json"], "extra": { "branch-alias": { "dev-master": "1.6.x-dev" } } } json-schema-1.6.1/phpunit.xml.dist000066400000000000000000000013001265144120500170730ustar00rootroot00000000000000 tests ./src/JsonSchema/ json-schema-1.6.1/src/000077500000000000000000000000001265144120500145155ustar00rootroot00000000000000json-schema-1.6.1/src/JsonSchema/000077500000000000000000000000001265144120500165475ustar00rootroot00000000000000json-schema-1.6.1/src/JsonSchema/Constraints/000077500000000000000000000000001265144120500210565ustar00rootroot00000000000000json-schema-1.6.1/src/JsonSchema/Constraints/CollectionConstraint.php000066400000000000000000000107121265144120500257300ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class CollectionConstraint extends Constraint { /** * {@inheritDoc} */ public function check($value, $schema = null, $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 string $path * @param string $i */ protected function validateItems($value, $schema = null, $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; } else if (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { $this->errors = $initErrors; } } } 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); } } } // 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++) { $this->checkUndefined(new UndefinedConstraint(), $schema->items[$k], $path, $k); } } } } } json-schema-1.6.1/src/JsonSchema/Constraints/Constraint.php000066400000000000000000000157771265144120500237340ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ abstract class Constraint implements ConstraintInterface { protected $checkMode = self::CHECK_MODE_NORMAL; protected $uriRetriever; protected $errors = array(); protected $inlineSchemaProperty = '$schema'; const CHECK_MODE_NORMAL = 1; const CHECK_MODE_TYPE_CAST = 2; /** * @var null|Factory */ private $factory; /** * @param int $checkMode * @param UriRetriever $uriRetriever * @param Factory $factory */ public function __construct($checkMode = self::CHECK_MODE_NORMAL, UriRetriever $uriRetriever = null, Factory $factory = null) { $this->checkMode = $checkMode; $this->uriRetriever = $uriRetriever; $this->factory = $factory; } /** * @return UriRetriever $uriRetriever */ public function getUriRetriever() { if (is_null($this->uriRetriever)) { $this->setUriRetriever(new UriRetriever); } return $this->uriRetriever; } /** * @return Factory */ public function getFactory() { if (!$this->factory) { $this->factory = new Factory($this->getUriRetriever()); } return $this->factory; } /** * @param UriRetriever $uriRetriever */ public function setUriRetriever(UriRetriever $uriRetriever) { $this->uriRetriever = $uriRetriever; } /** * {@inheritDoc} */ public function addError($path, $message, $constraint='', array $more=null) { $error = array( 'property' => $path, 'message' => $message, 'constraint' => $constraint, ); if (is_array($more) && count($more) > 0) { $error += $more; } $this->errors[] = $error; } /** * {@inheritDoc} */ public function addErrors(array $errors) { $this->errors = array_merge($this->errors, $errors); } /** * {@inheritDoc} */ public function getErrors() { return $this->errors; } /** * {@inheritDoc} */ public function isValid() { return !$this->getErrors(); } /** * Clears any reported errors. Should be used between * multiple validation checks. */ public function reset() { $this->errors = array(); } /** * Bubble down the path * * @param string $path Current path * @param mixed $i What to append to the path * * @return string */ protected function incrementPath($path, $i) { if ($path !== '') { if (is_int($i)) { $path .= '[' . $i . ']'; } elseif ($i == '') { $path .= ''; } else { $path .= '.' . $i; } } else { $path = $i; } return $path; } /** * Validates an array * * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i */ protected function checkArray($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('collection'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Validates an object * * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i * @param mixed $patternProperties */ protected function checkObject($value, $schema = null, $path = null, $i = null, $patternProperties = null) { $validator = $this->getFactory()->createInstanceFor('object'); $validator->check($value, $schema, $path, $i, $patternProperties); $this->addErrors($validator->getErrors()); } /** * Validates the type of a property * * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i */ protected function checkType($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('type'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a undefined element * * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i */ protected function checkUndefined($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('undefined'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a string element * * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i */ protected function checkString($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('string'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a number element * * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i */ protected function checkNumber($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('number'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * Checks a enum element * * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i */ protected function checkEnum($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('enum'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function checkFormat($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('format'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } /** * @param string $uri JSON Schema URI * @return string JSON Schema contents */ protected function retrieveUri($uri) { if (null === $this->uriRetriever) { $this->setUriRetriever(new UriRetriever); } $jsonSchema = $this->uriRetriever->retrieve($uri); // TODO validate using schema return $jsonSchema; } } json-schema-1.6.1/src/JsonSchema/Constraints/ConstraintInterface.php000066400000000000000000000024701265144120500255370ustar00rootroot00000000000000 */ 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 string $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($path, $message, $constraint='', array $more=null); /** * checks if the validator has not raised errors * * @return boolean */ public function isValid(); /** * invokes the validation of an element * * @abstract * @param mixed $value * @param mixed $schema * @param mixed $path * @param mixed $i */ public function check($value, $schema = null, $path = null, $i = null); }json-schema-1.6.1/src/JsonSchema/Constraints/EnumConstraint.php000066400000000000000000000024741265144120500245470ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class EnumConstraint extends Constraint { /** * {@inheritDoc} */ public function check($element, $schema = null, $path = null, $i = null) { // Only validate enum if the attribute exists if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) { return; } foreach ($schema->enum as $enum) { $type = gettype($element); if ($type === gettype($enum)) { if ($type == "object") { if ($element == $enum) return; } else { if ($element === $enum) return; } } } $this->addError($path, "Does not have a value in the enumeration " . print_r($schema->enum, true), 'enum', array('enum' => $schema->enum,)); } } json-schema-1.6.1/src/JsonSchema/Constraints/Factory.php000066400000000000000000000055121265144120500232010ustar00rootroot00000000000000 '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', ); /** * @param UriRetriever $uriRetriever */ public function __construct(UriRetriever $uriRetriever = null) { if (!$uriRetriever) { $uriRetriever = new UriRetriever(); } $this->uriRetriever = $uriRetriever; } /** * @return UriRetriever */ public function getUriRetriever() { return $this->uriRetriever; } /** * @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 * @return ConstraintInterface|ObjectConstraint * @throws InvalidArgumentException if is not possible create the constraint instance. */ public function createInstanceFor($constraintName) { if (array_key_exists($constraintName, $this->constraintMap)) { return new $this->constraintMap[$constraintName](Constraint::CHECK_MODE_NORMAL, $this->uriRetriever, $this); } throw new InvalidArgumentException('Unknown constraint ' . $constraintName); } } json-schema-1.6.1/src/JsonSchema/Constraints/FormatConstraint.php000066400000000000000000000153451265144120500250740ustar00rootroot00000000000000 * @link http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 */ class FormatConstraint extends Constraint { /** * {@inheritDoc} */ public function check($element, $schema = null, $path = null, $i = null) { if (!isset($schema->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 (!$this->validateDateTime($element, 'Y-m-d\TH:i:s\Z') && !$this->validateDateTime($element, 'Y-m-d\TH:i:s.u\Z') && !$this->validateDateTime($element, 'Y-m-d\TH:i:sP') && !$this->validateDateTime($element, 'Y-m-d\TH:i:sO') ) { $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 'email': if (null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE)) { $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) && (intval($dt->format('u')) > 0)) { return true; } return false; } protected function validateRegex($regex) { return false !== @preg_match('/' . $regex . '/', ''); } 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) { return preg_match('/^[_a-z]+\.([_a-z]+\.?)+$/i', $host); } } json-schema-1.6.1/src/JsonSchema/Constraints/NumberConstraint.php000066400000000000000000000071611265144120500250710ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class NumberConstraint extends Constraint { /** * {@inheritDoc} */ public function check($element, $schema = null, $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,)); } else if ($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'); } } else if (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,)); } else if ($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", 'missingMinimum'); } } else if (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 = fmod($number1, $number2); $precision = abs(0.0000000001); $diff = (float)($modulus - $number2); if (-$precision < $diff && $diff < $precision) { return 0.0; } $decimals1 = mb_strpos($number1, ".") ? mb_strlen($number1) - mb_strpos($number1, ".") - 1 : 0; $decimals2 = mb_strpos($number2, ".") ? mb_strlen($number2) - mb_strpos($number2, ".") - 1 : 0; return (float)round($modulus, max($decimals1, $decimals2)); } } json-schema-1.6.1/src/JsonSchema/Constraints/ObjectConstraint.php000066400000000000000000000130401265144120500250400ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class ObjectConstraint extends Constraint { /** * {@inheritDoc} */ function check($element, $definition = null, $path = null, $additionalProp = null, $patternProperties = null) { if ($element instanceof UndefinedConstraint) { return; } $matches = array(); if ($patternProperties) { $matches = $this->validatePatternProperties($element, $path, $patternProperties); } if ($definition) { // validate the definition properties $this->validateDefinition($element, $definition, $path); } // additional the element properties $this->validateElement($element, $matches, $definition, $path, $additionalProp); } public function validatePatternProperties($element, $path, $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, '') === 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, $i)) { $matches[] = $i; $this->checkUndefined($value, $schema ? : new \stdClass(), $path, $i); } } } return $matches; } /** * Validates the element properties * * @param \stdClass $element Element to validate * @param array $matches Matches from patternProperties (if any) * @param \stdClass $objectDefinition ObjectConstraint definition * @param string $path Path to test? * @param mixed $additionalProp Additional properties */ public function validateElement($element, $matches, $objectDefinition = null, $path = null, $additionalProp = null) { foreach ($element as $i => $value) { $property = $this->getProperty($element, $i, new UndefinedConstraint()); $definition = $this->getProperty($objectDefinition, $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); } else { $this->checkUndefined($value, $additionalProp, $path, $i); } } // 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'); } if (!$definition) { // normal property verification $this->checkUndefined($value, new \stdClass(), $path, $i); } } } /** * Validates the definition properties * * @param \stdClass $element Element to validate * @param \stdClass $objectDefinition ObjectConstraint definition * @param string $path Path? */ public function validateDefinition($element, $objectDefinition = null, $path = null) { foreach ($objectDefinition as $i => $value) { $property = $this->getProperty($element, $i, new UndefinedConstraint()); $definition = $this->getProperty($objectDefinition, $i); $this->checkUndefined($property, $definition, $path, $i); } } /** * 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) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) { return array_key_exists($property, $element) ? $element[$property] : $fallback; } elseif (is_object($element)) { return property_exists($element, $property) ? $element->$property : $fallback; } return $fallback; } } json-schema-1.6.1/src/JsonSchema/Constraints/SchemaConstraint.php000066400000000000000000000020761265144120500250410ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class SchemaConstraint extends Constraint { /** * {@inheritDoc} */ public function check($element, $schema = null, $path = null, $i = null) { if ($schema !== null) { // passed schema $this->checkUndefined($element, $schema, '', ''); } elseif (property_exists($element, $this->inlineSchemaProperty)) { // inline schema $this->checkUndefined($element, $element->{$this->inlineSchemaProperty}, '', ''); } else { throw new InvalidArgumentException('no schema found to verify against'); } } }json-schema-1.6.1/src/JsonSchema/Constraints/StringConstraint.php000066400000000000000000000034631265144120500251100ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class StringConstraint extends Constraint { /** * {@inheritDoc} */ public function check($element, $schema = null, $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) . '#', $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)); } else { return strlen($string); } } } json-schema-1.6.1/src/JsonSchema/Constraints/TypeConstraint.php000066400000000000000000000102261265144120500245560ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class TypeConstraint extends Constraint { /** * @var array|string[] type wordings for validation error messages */ 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, $path = null, $i = null) { $type = isset($schema->type) ? $schema->type : null; $isValid = true; if (is_array($type)) { // @TODO refactor $validatedOneType = false; $errors = array(); foreach ($type as $tp) { $validator = new TypeConstraint($this->checkMode); $subSchema = new \stdClass(); $subSchema->type = $tp; $validator->check($value, $subSchema, $path, null); $error = $validator->getErrors(); if (!count($error)) { $validatedOneType = true; break; } $errors = $error; } if (!$validatedOneType) { $this->addErrors($errors); return; } } elseif (is_object($type)) { $this->checkUndefined($value, $type, $path); } else { $isValid = $this->validateType($value, $type); } if ($isValid === false) { 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))) ); } $this->addError($path, ucwords(gettype($value)) . " value found, but " . self::$wording[$type] . " is required", 'type'); } } /** * Verifies that a given value is of a certain type * * @param mixed $value Value to validate * @param string $type TypeConstraint to check against * * @return boolean * * @throws InvalidArgumentException */ protected function validateType($value, $type) { //mostly the case for inline schema if (!$type) { return true; } if ('integer' === $type) { return is_int($value); } if ('number' === $type) { return is_numeric($value) && !is_string($value); } if ('boolean' === $type) { return is_bool($value); } if ('object' === $type) { return is_object($value); //return ($this::CHECK_MODE_TYPE_CAST == $this->checkMode) ? is_array($value) : is_object($value); } if ('array' === $type) { return is_array($value); } if ('string' === $type) { return is_string($value); } if ('email' === $type) { return is_string($value); } if ('null' === $type) { return is_null($value); } if ('any' === $type) { return true; } throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type); } } json-schema-1.6.1/src/JsonSchema/Constraints/UndefinedConstraint.php000066400000000000000000000254151265144120500255440ustar00rootroot00000000000000 * @author Bruno Prieto Reis */ class UndefinedConstraint extends Constraint { /** * {@inheritDoc} */ public function check($value, $schema = null, $path = null, $i = null) { if (is_null($schema)) { return; } if (!is_object($schema)) { throw new InvalidArgumentException( 'Given schema must be an object in ' . $path . ' but is a ' . gettype($schema) ); } $i = is_null($i) ? "" : $i; $path = $this->incrementPath($path, $i); // check special properties $this->validateCommonProperties($value, $schema, $path); // 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 string $path * @param string $i */ public function validateTypes($value, $schema = null, $path = null, $i = null) { // check array if (is_array($value)) { $this->checkArray($value, $schema, $path, $i); } // check object if (is_object($value) && (isset($schema->properties) || isset($schema->patternProperties) || isset($schema->additionalProperties))) { $this->checkObject( $value, isset($schema->properties) ? $schema->properties : null, $path, isset($schema->additionalProperties) ? $schema->additionalProperties : null, isset($schema->patternProperties) ? $schema->patternProperties : null ); } // 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 string $path * @param string $i */ protected function validateCommonProperties($value, $schema = null, $path = null, $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); } } // Verify required values if (is_object($value)) { if (!($value instanceof UndefinedConstraint) && 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 (!property_exists($value, $required)) { $this->addError((!$path) ? $required : "$path.$required", "The property " . $required . " is required", 'required'); } } } else if (isset($schema->required) && !is_array($schema->required)) { // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true} if ( $schema->required && $value instanceof UndefinedConstraint) { $this->addError($path, "Is missing and it is required", 'required'); } } } // Verify type if (!($value instanceof UndefinedConstraint)) { $this->checkType($value, $schema, $path); } // 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 minimum and maximum number of properties if (is_object($value)) { if (isset($schema->minProperties)) { if (count(get_object_vars($value)) < $schema->minProperties) { $this->addError($path, "Must contain a minimum of " . $schema->minProperties . " properties", 'minProperties', array('minProperties' => $schema->minProperties,)); } } if (isset($schema->maxProperties)) { if (count(get_object_vars($value)) > $schema->maxProperties) { $this->addError($path, "Must contain no more than " . $schema->maxProperties . " properties", 'maxProperties', array('maxProperties' => $schema->maxProperties,)); } } } // Verify that dependencies are met if (is_object($value) && isset($schema->dependencies)) { $this->validateDependencies($value, $schema->dependencies, $path); } } /** * Validate allOf, anyOf, and oneOf properties * * @param mixed $value * @param mixed $schema * @param string $path * @param string $i */ protected function validateOfProperties($value, $schema, $path, $i = "") { // Verify type if ($value instanceof UndefinedConstraint) { 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(); foreach ($schema->anyOf as $anyOf) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $anyOf, $path, $i); if ($isValid = (count($this->getErrors()) == count($initErrors))) { break; } } 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) { $this->errors = array(); $this->checkUndefined($value, $oneOf, $path, $i); if (count($this->getErrors()) == 0) { $matchedSchemas++; } $allErrors = array_merge($allErrors, array_values($this->getErrors())); } if ($matchedSchemas !== 1) { $this->addErrors( array_merge( $allErrors, array(array( 'property' => $path, 'message' => "Failed to match exactly one schema", 'constraint' => 'oneOf', ),), $startErrors ) ); } else { $this->errors = $startErrors; } } } /** * Validate dependencies * * @param mixed $value * @param mixed $dependencies * @param string $path * @param string $i */ protected function validateDependencies($value, $dependencies, $path, $i = "") { foreach ($dependencies as $key => $dependency) { if (property_exists($value, $key)) { if (is_string($dependency)) { // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"} if (!property_exists($value, $dependency)) { $this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies'); } } else if (is_array($dependency)) { // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]} foreach ($dependency as $d) { if (!property_exists($value, $d)) { $this->addError($path, "$key depends on $d and $d is missing", 'dependencies'); } } } else if (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->getUriRetriever(); $jsonSchema = null; if ($resolver->isValid($schemaUri)) { $schemaId = property_exists($schema, 'id') ? $schema->id : null; $jsonSchema = $retriever->retrieve($schemaId, $schemaUri); } return $jsonSchema; } } json-schema-1.6.1/src/JsonSchema/Exception/000077500000000000000000000000001265144120500205055ustar00rootroot00000000000000json-schema-1.6.1/src/JsonSchema/Exception/InvalidArgumentException.php000066400000000000000000000005321265144120500261660ustar00rootroot00000000000000 * @see README.md */ class RefResolver { /** * HACK to prevent too many recursive expansions. * Happens e.g. when you want to validate a schema against the schema * definition. * * @var integer */ protected static $depth = 0; /** * maximum references depth * @var integer */ public static $maxDepth = 7; /** * @var UriRetrieverInterface */ protected $uriRetriever = null; /** * @var object */ protected $rootSchema = null; /** * @param UriRetriever $retriever */ public function __construct($retriever = null) { $this->uriRetriever = $retriever; } /** * Retrieves a given schema given a ref and a source URI * * @param string $ref Reference from schema * @param string $sourceUri URI where original schema was located * @return object Schema */ public function fetchRef($ref, $sourceUri) { $retriever = $this->getUriRetriever(); $jsonSchema = $retriever->retrieve($ref, $sourceUri); $this->resolve($jsonSchema); return $jsonSchema; } /** * Return the URI Retriever, defaulting to making a new one if one * was not yet set. * * @return UriRetriever */ public function getUriRetriever() { if (is_null($this->uriRetriever)) { $this->setUriRetriever(new UriRetriever); } return $this->uriRetriever; } /** * Resolves all $ref references for a given schema. Recurses through * the object to resolve references of any child schemas. * * The 'format' property is omitted because it isn't required for * validation. Theoretically, this class could be extended to look * for URIs in formats: "These custom formats MAY be expressed as * an URI, and this URI MAY reference a schema of that format." * * The 'id' property is not filled in, but that could be made to happen. * * @param object $schema JSON Schema to flesh out * @param string $sourceUri URI where this schema was located */ public function resolve($schema, $sourceUri = null) { if (self::$depth > self::$maxDepth) { self::$depth = 0; throw new JsonDecodingException(JSON_ERROR_DEPTH); } ++self::$depth; if (! is_object($schema)) { --self::$depth; return; } if (null === $sourceUri && ! empty($schema->id)) { $sourceUri = $schema->id; } if (null === $this->rootSchema) { $this->rootSchema = $schema; } // Resolve $ref first $this->resolveRef($schema, $sourceUri); // These properties are just schemas // eg. items can be a schema or an array of schemas foreach (array('additionalItems', 'additionalProperties', 'extends', 'items') as $propertyName) { $this->resolveProperty($schema, $propertyName, $sourceUri); } // These are all potentially arrays that contain schema objects // eg. type can be a value or an array of values/schemas // eg. items can be a schema or an array of schemas foreach (array('disallow', 'extends', 'items', 'type', 'allOf', 'anyOf', 'oneOf') as $propertyName) { $this->resolveArrayOfSchemas($schema, $propertyName, $sourceUri); } // These are all objects containing properties whose values are schemas foreach (array('dependencies', 'patternProperties', 'properties') as $propertyName) { $this->resolveObjectOfSchemas($schema, $propertyName, $sourceUri); } --self::$depth; } /** * Given an object and a property name, that property should be an * array whose values can be schemas. * * @param object $schema JSON Schema to flesh out * @param string $propertyName Property to work on * @param string $sourceUri URI where this schema was located */ public function resolveArrayOfSchemas($schema, $propertyName, $sourceUri) { if (! isset($schema->$propertyName) || ! is_array($schema->$propertyName)) { return; } foreach ($schema->$propertyName as $possiblySchema) { $this->resolve($possiblySchema, $sourceUri); } } /** * Given an object and a property name, that property should be an * object whose properties are schema objects. * * @param object $schema JSON Schema to flesh out * @param string $propertyName Property to work on * @param string $sourceUri URI where this schema was located */ public function resolveObjectOfSchemas($schema, $propertyName, $sourceUri) { if (! isset($schema->$propertyName) || ! is_object($schema->$propertyName)) { return; } foreach (get_object_vars($schema->$propertyName) as $possiblySchema) { $this->resolve($possiblySchema, $sourceUri); } } /** * Given an object and a property name, that property should be a * schema object. * * @param object $schema JSON Schema to flesh out * @param string $propertyName Property to work on * @param string $sourceUri URI where this schema was located */ public function resolveProperty($schema, $propertyName, $sourceUri) { if (! isset($schema->$propertyName)) { return; } $this->resolve($schema->$propertyName, $sourceUri); } /** * Look for the $ref property in the object. If found, remove the * reference and augment this object with the contents of another * schema. * * @param object $schema JSON Schema to flesh out * @param string $sourceUri URI where this schema was located */ public function resolveRef($schema, $sourceUri) { $ref = '$ref'; if (empty($schema->$ref)) { return; } $splitRef = explode('#', $schema->$ref, 2); $refDoc = $splitRef[0]; $refPath = null; if (count($splitRef) === 2) { $refPath = explode('/', $splitRef[1]); array_shift($refPath); } if (empty($refDoc) && empty($refPath)) { // TODO: Not yet implemented - root pointer ref, causes recursion issues return; } if (!empty($refDoc)) { $refSchema = $this->fetchRef($refDoc, $sourceUri); } else { $refSchema = $this->rootSchema; } if (null !== $refPath) { $refSchema = $this->resolveRefSegment($refSchema, $refPath); } unset($schema->$ref); // Augment the current $schema object with properties fetched foreach (get_object_vars($refSchema) as $prop => $value) { $schema->$prop = $value; } } /** * Set URI Retriever for use with the Ref Resolver * * @param UriRetriever $retriever * @return $this for chaining */ public function setUriRetriever(UriRetriever $retriever) { $this->uriRetriever = $retriever; return $this; } protected function resolveRefSegment($data, $pathParts) { foreach ($pathParts as $path) { $path = strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); if (is_array($data)) { $data = $data[$path]; } else { $data = $data->{$path}; } } return $data; } } json-schema-1.6.1/src/JsonSchema/Uri/000077500000000000000000000000001265144120500173065ustar00rootroot00000000000000json-schema-1.6.1/src/JsonSchema/Uri/Retrievers/000077500000000000000000000000001265144120500214405ustar00rootroot00000000000000json-schema-1.6.1/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php000066400000000000000000000011341265144120500256030ustar00rootroot00000000000000 */ 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; } } json-schema-1.6.1/src/JsonSchema/Uri/Retrievers/Curl.php000066400000000000000000000037041265144120500230620ustar00rootroot00000000000000 */ class Curl extends AbstractRetriever { protected $messageBody; public function __construct() { if (!function_exists('curl_init')) { throw new \RuntimeException("cURL not installed"); } } /** * {@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, array('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 boolean 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; } }json-schema-1.6.1/src/JsonSchema/Uri/Retrievers/FileGetContents.php000066400000000000000000000046541265144120500252170ustar00rootroot00000000000000 */ class FileGetContents extends AbstractRetriever { protected $messageBody; /** * {@inheritDoc} * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() */ public function retrieve($uri) { $context = stream_context_create(array( 'http' => array( 'method' => 'GET', 'header' => "Accept: " . Validator::SCHEMA_MEDIA_TYPE ))); set_error_handler(function() use ($uri) { throw new ResourceNotFoundException('JSON schema not found at ' . $uri); }); $response = file_get_contents($uri); restore_error_handler(); 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)) { $this->fetchContentType($http_response_header); } else { // 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 boolean Whether the Content-Type header was found or not */ private function fetchContentType(array $headers) { foreach ($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]); } } } json-schema-1.6.1/src/JsonSchema/Uri/Retrievers/PredefinedArray.php000066400000000000000000000024101265144120500252120ustar00rootroot00000000000000 '{ ... }', * '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]; } } json-schema-1.6.1/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php000066400000000000000000000013231265144120500264200ustar00rootroot00000000000000 */ 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(); }json-schema-1.6.1/src/JsonSchema/Uri/UriResolver.php000066400000000000000000000101621265144120500223000ustar00rootroot00000000000000 */ class UriResolver { /** * Parses a URI into five main components * * @param string $uri * @return array */ public function parse($uri) { preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); $components = array(); if (5 < count($match)) { $components = array( '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 Absolute URI */ public function resolve($uri, $baseUri = null) { 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 * @return string Merged path * @throws UriResolverException */ public static function combineRelativePathWithBasePath($relativePath, $basePath) { $relativePath = self::normalizePath($relativePath); if ($relativePath == '') { return $basePath; } if ($relativePath{0} == '/') { return $relativePath; } $basePathSegments = explode('/', $basePath); preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match); $numLevelUp = strlen($match[0]) /3 + 1; if ($numLevelUp >= count($basePathSegments)) { throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); } $basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp); $path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath); return implode('/', $basePathSegments) . '/' . $path; } /** * 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('|((?parse($uri); return !empty($components); } } json-schema-1.6.1/src/JsonSchema/Uri/UriRetriever.php000066400000000000000000000172071265144120500224550ustar00rootroot00000000000000 */ class UriRetriever { /** * @var null|UriRetrieverInterface */ protected $uriRetriever = null; /** * @var array|object[] * @see loadSchema */ private $schemaCache = array(); /** * 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 (Validator::SCHEMA_MEDIA_TYPE === $contentType) { return; } if (substr($uri, 0, 23) == 'http://json-schema.org/') { //HACK; they deliver broken content types 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 * @return object JSON Schema after walking down the fragment pieces * * @throws ResourceNotFoundException */ 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; } /** * Retrieve a URI * * @param string $uri JSON Schema URI * @param string|null $baseUri * @return object JSON Schema contents */ public function retrieve($uri, $baseUri = null) { $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); } $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 = array(); if (5 < count($match)) { $components = array( '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 boolean */ public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } } json-schema-1.6.1/src/JsonSchema/Validator.php000066400000000000000000000022211265144120500212020ustar00rootroot00000000000000 * @author Bruno Prieto Reis * @see README.md */ class Validator extends Constraint { const SCHEMA_MEDIA_TYPE = 'application/schema+json'; /** * 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 * * {@inheritDoc} */ public function check($value, $schema = null, $path = null, $i = null) { $validator = $this->getFactory()->createInstanceFor('schema'); $validator->check($value, $schema); $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); } }