From e265e9f66d42f70ad3eb8c8dccc1f20497920c72 Mon Sep 17 00:00:00 2001 From: Joost Nijhuis Date: Tue, 22 Mar 2016 20:16:30 +0100 Subject: [PATCH] Fix RefResolver and make it compatible with draft-04 #240 --- README.md | 10 +- bin/validate-json | 13 +- .../Constraints/ObjectConstraint.php | 4 +- src/JsonSchema/Entity/JsonPointer.php | 118 ++++ .../UnresolvableJsonPointerException.php | 18 + src/JsonSchema/Iterator/ObjectIterator.php | 147 +++++ src/JsonSchema/RefResolver.php | 316 ++++------- src/JsonSchema/Uri/UriResolver.php | 9 +- src/JsonSchema/Uri/UriRetriever.php | 9 +- src/JsonSchema/UriResolverInterface.php | 25 + src/JsonSchema/UriRetrieverInterface.php | 25 + .../Tests/Constraints/BaseTestCase.php | 102 +++- .../Tests/Drafts/BaseDraftTestCase.php | 16 + tests/JsonSchema/Tests/Drafts/Draft3Test.php | 18 +- tests/JsonSchema/Tests/Drafts/Draft4Test.php | 22 +- .../Tests/Entity/JsonPointerTest.php | 95 ++++ tests/JsonSchema/Tests/RefResolverTest.php | 512 +++++++----------- .../Tests/fixtures/json-schema-draft-03.json | 193 +++++++ .../Tests/fixtures/json-schema-draft-04.json | 221 ++++++++ 19 files changed, 1294 insertions(+), 579 deletions(-) create mode 100644 src/JsonSchema/Entity/JsonPointer.php create mode 100644 src/JsonSchema/Exception/UnresolvableJsonPointerException.php create mode 100644 src/JsonSchema/Iterator/ObjectIterator.php create mode 100644 src/JsonSchema/UriResolverInterface.php create mode 100644 src/JsonSchema/UriRetrieverInterface.php create mode 100644 tests/JsonSchema/Tests/Entity/JsonPointerTest.php create mode 100644 tests/JsonSchema/Tests/fixtures/json-schema-draft-03.json create mode 100644 tests/JsonSchema/Tests/fixtures/json-schema-draft-04.json diff --git a/README.md b/README.md index 3f9c58c3..cc0047ff 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,12 @@ See [json-schema](http://json-schema.org/) for more details. 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__); +$refResolver = new JsonSchema\RefResolver(new JsonSchema\Uri\UriRetriever(), new JsonSchema\Uri\UriResolver()); +$schema = $refResolver->resolve('file://' . realpath('schema.json')); + +$data = json_decode(file_get_contents('data.json')); // Validate $validator = new JsonSchema\Validator(); diff --git a/bin/validate-json b/bin/validate-json index e93d53a0..915b5a3e 100755 --- a/bin/validate-json +++ b/bin/validate-json @@ -200,22 +200,14 @@ try { 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); +$refResolver = new JsonSchema\RefResolver($retriever, $resolver); +$schema = $refResolver->resolve($urlSchema); if (isset($arOptions['--dump-schema'])) { $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; @@ -242,4 +234,3 @@ try { echo "Error code: " . $e->getCode() . "\n"; exit(24); } -?> diff --git a/src/JsonSchema/Constraints/ObjectConstraint.php b/src/JsonSchema/Constraints/ObjectConstraint.php index 58ce5c9f..0cfb59a3 100644 --- a/src/JsonSchema/Constraints/ObjectConstraint.php +++ b/src/JsonSchema/Constraints/ObjectConstraint.php @@ -159,13 +159,13 @@ protected function getProperty($element, $property, $fallback = null) */ protected function validateMinMaxConstraint($element, $objectDefinition, $path) { // Verify minimum number of properties - if (isset($objectDefinition->minProperties)) { + if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) { if (count(get_object_vars($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)) { + if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) { if (count(get_object_vars($element)) > $objectDefinition->maxProperties) { $this->addError($path, "Must contain no more than " . $objectDefinition->maxProperties . " properties", 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties,)); } diff --git a/src/JsonSchema/Entity/JsonPointer.php b/src/JsonSchema/Entity/JsonPointer.php new file mode 100644 index 00000000..ff841de0 --- /dev/null +++ b/src/JsonSchema/Entity/JsonPointer.php @@ -0,0 +1,118 @@ + + */ +class JsonPointer +{ + /** @var string */ + private $filename; + + /** @var string[] */ + private $propertyPaths = array(); + + /** + * @param string $value + * @throws \InvalidArgumentException when $value is not a string + */ + public function __construct($value) + { + if (!is_string($value)) { + throw new \InvalidArgumentException('Ref value must be a string'); + } + + $splitRef = explode('#', $value, 2); + $this->filename = $splitRef[0]; + if (array_key_exists(1, $splitRef)) { + $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); + } + } + + /** + * @param string $propertyPathString + * @return string[] + */ + private function decodePropertyPaths($propertyPathString) + { + $paths = array(); + foreach (explode('/', trim($propertyPathString, '/')) as $path) { + $path = $this->decodePath($path); + if (is_string($path) && '' !== $path) { + $paths[] = $path; + } + } + + return $paths; + } + + /** + * @return array + */ + private function encodePropertyPaths() + { + return array_map( + array($this, 'encodePath'), + $this->getPropertyPaths() + ); + } + + /** + * @param string $path + * @return string + */ + private function decodePath($path) + { + return strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); + } + + /** + * @param string $path + * @return string + */ + private function encodePath($path) + { + return strtr($path, array('/' => '~1', '~' => '~0', '%' => '%25')); + } + + /** + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * @return string[] + */ + public function getPropertyPaths() + { + return $this->propertyPaths; + } + + /** + * @return string + */ + public function getPropertyPathAsString() + { + return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/'); + } + + /** + * @return string + */ + public function __toString() + { + return $this->getFilename() . $this->getPropertyPathAsString(); + } +} diff --git a/src/JsonSchema/Exception/UnresolvableJsonPointerException.php b/src/JsonSchema/Exception/UnresolvableJsonPointerException.php new file mode 100644 index 00000000..d9b1f694 --- /dev/null +++ b/src/JsonSchema/Exception/UnresolvableJsonPointerException.php @@ -0,0 +1,18 @@ + + */ +class UnresolvableJsonPointerException extends \InvalidArgumentException +{ +} diff --git a/src/JsonSchema/Iterator/ObjectIterator.php b/src/JsonSchema/Iterator/ObjectIterator.php new file mode 100644 index 00000000..61bf5be6 --- /dev/null +++ b/src/JsonSchema/Iterator/ObjectIterator.php @@ -0,0 +1,147 @@ + + */ +class ObjectIterator implements \Iterator, \Countable +{ + /** @var object */ + private $object; + + /** @var int */ + private $position = 0; + + /** @var array */ + private $data = array(); + + /** @var bool */ + private $initialized = false; + + /** + * @param object $object + */ + public function __construct($object) + { + $this->object = $object; + } + + /** + * {@inheritdoc} + */ + public function current() + { + $this->initialize(); + + return $this->data[$this->position]; + } + + /** + * {@inheritdoc} + */ + public function next() + { + $this->initialize(); + $this->position++; + } + + /** + * {@inheritdoc} + */ + public function key() + { + $this->initialize(); + + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function valid() + { + $this->initialize(); + + return isset($this->data[$this->position]); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->initialize(); + $this->position = 0; + } + + /** + * {@inheritdoc} + */ + public function count() + { + $this->initialize(); + + return count($this->data); + } + + /** + * Initializer + */ + private function initialize() + { + if (!$this->initialized) { + $this->data = $this->buildDataFromObject($this->object); + $this->initialized = true; + } + } + + /** + * @param object $object + * @return array + */ + private function buildDataFromObject($object) + { + $result = array(); + + $stack = new \SplStack(); + $stack->push($object); + + while (!$stack->isEmpty()) { + + $current = $stack->pop(); + if (is_object($current)) { + array_push($result, $current); + } + + foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) { + if (is_object($propertyValue) || is_array($propertyValue)) { + $stack->push($propertyValue); + } + } + } + + return $result; + } + + /** + * @param object|array $item + * @return array + */ + private function getDataFromItem($item) + { + if (!is_object($item) && !is_array($item)) { + return array(); + } + + return is_object($item) ? get_object_vars($item) : $item; + } +} diff --git a/src/JsonSchema/RefResolver.php b/src/JsonSchema/RefResolver.php index ea0fafd6..46acc746 100644 --- a/src/JsonSchema/RefResolver.php +++ b/src/JsonSchema/RefResolver.php @@ -9,269 +9,177 @@ namespace JsonSchema; -use JsonSchema\Exception\JsonDecodingException; -use JsonSchema\Uri\Retrievers\UriRetrieverInterface; -use JsonSchema\Uri\UriRetriever; +use JsonSchema\Exception\UnresolvableJsonPointerException; +use JsonSchema\Iterator\ObjectIterator; +use JsonSchema\Entity\JsonPointer; /** - * Take in an object that's a JSON schema and take care of all $ref references + * Take in a source uri to locate a JSON schema and retrieve it and take care of all $ref references. + * Try to update the resolved schema which looks like a tree, but can be a graph. (so cyclic schema's are allowed). + * This way the current validator does not need to be changed and can work as well with the updated schema. * - * @author Tyler Akins - * @see README.md + * @package JsonSchema + * @author Joost Nijhuis + * @author Rik Jansen */ 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 */ + private $uriRetriever; - /** - * @var UriRetrieverInterface - */ - protected $uriRetriever = null; + /** @var UriResolverInterface */ + private $uriResolver; /** - * @var object + * @param UriRetrieverInterface $retriever + * @param UriResolverInterface $uriResolver */ - protected $rootSchema = null; - - /** - * @param UriRetriever $retriever - */ - public function __construct($retriever = null) + public function __construct(UriRetrieverInterface $retriever, UriResolverInterface $uriResolver) { $this->uriRetriever = $retriever; + $this->uriResolver = $uriResolver; } /** - * Retrieves a given schema given a ref and a source URI + * Resolves all schema and all $ref references for the give $sourceUri. Recurse through the object to resolve + * references of any child schemas and return the schema. * - * @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 + * @param string $sourceUri URI where this schema was located + * @return object */ - public function getUriRetriever() + public function resolve($sourceUri) { - if (is_null($this->uriRetriever)) { - $this->setUriRetriever(new UriRetriever); - } - - return $this->uriRetriever; + return $this->resolveCached($sourceUri, array()); } /** - * 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 + * @param array $paths + * @return object */ - public function resolve($schema, $sourceUri = null) + private function resolveCached($sourceUri, array $paths) { - 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; - } + $jsonPointer = new JsonPointer($sourceUri); - if (null === $this->rootSchema) { - $this->rootSchema = $schema; + $fileName = $jsonPointer->getFilename(); + if (!array_key_exists($fileName, $paths)) { + $schema = $this->uriRetriever->retrieve($jsonPointer->getFilename()); + $paths[$jsonPointer->getFilename()] = $schema; + $this->resolveSchemas($schema, $jsonPointer->getFilename(), $paths); } + $schema = $paths[$fileName]; - // 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; + return $this->getRefSchema($jsonPointer, $schema); } /** - * Given an object and a property name, that property should be an - * array whose values can be schemas. + * Recursive resolve schema by traversing through al nodes * - * @param object $schema JSON Schema to flesh out - * @param string $propertyName Property to work on - * @param string $sourceUri URI where this schema was located + * @param object $unresolvedSchema + * @param string $fileName + * @param array $paths */ - public function resolveArrayOfSchemas($schema, $propertyName, $sourceUri) + private function resolveSchemas($unresolvedSchema, $fileName, array $paths) { - if (! isset($schema->$propertyName) || ! is_array($schema->$propertyName)) { - return; - } - - foreach ($schema->$propertyName as $possiblySchema) { - $this->resolve($possiblySchema, $sourceUri); + $objectIterator = new ObjectIterator($unresolvedSchema); + foreach ($objectIterator as $toResolveSchema) { + if (property_exists($toResolveSchema, '$ref') && is_string($toResolveSchema->{'$ref'})) { + $jsonPointer = new JsonPointer($this->uriResolver->resolve($toResolveSchema->{'$ref'}, $fileName)); + $refSchema = $this->resolveCached((string) $jsonPointer, $paths); + $this->unionSchemas($refSchema, $toResolveSchema, $fileName, $paths); + } } } /** - * 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 + * @param JsonPointer $jsonPointer + * @param object $refSchema + * @throws UnresolvableJsonPointerException when json schema file is found but reference can not be resolved + * @return object */ - public function resolveObjectOfSchemas($schema, $propertyName, $sourceUri) + private function getRefSchema(JsonPointer $jsonPointer, $refSchema) { - if (! isset($schema->$propertyName) || ! is_object($schema->$propertyName)) { - return; + foreach ($jsonPointer->getPropertyPaths() as $path) { + if (is_object($refSchema) && property_exists($refSchema, $path)) { + $refSchema = $refSchema->{$path}; + } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) { + $refSchema = $refSchema[$path]; + } else { + throw new UnresolvableJsonPointerException(sprintf( + 'File: %s is found, but could not resolve fragment: %s', + $jsonPointer->getFilename(), + $jsonPointer->getPropertyPathAsString() + )); + } } - foreach (get_object_vars($schema->$propertyName) as $possiblySchema) { - $this->resolve($possiblySchema, $sourceUri); - } + return $refSchema; } /** - * 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 + * @param object $refSchema + * @param object $schema + * @param string $fileName + * @param array $paths */ - public function resolveProperty($schema, $propertyName, $sourceUri) + private function unionSchemas($refSchema, $schema, $fileName, array $paths) { - if (! isset($schema->$propertyName)) { - return; + if (property_exists($refSchema, '$ref')) { + $jsonPointer = new JsonPointer($this->uriResolver->resolve($refSchema->{'$ref'}, $fileName)); + $newSchema = $this->resolveCached((string) $jsonPointer, $paths); + $this->unionSchemas($newSchema, $refSchema, $fileName, $paths); } - $this->resolve($schema->$propertyName, $sourceUri); + unset($schema->{'$ref'}); + if (!$this->hasSubSchemas($schema)) { + foreach (get_object_vars($refSchema) as $prop => $value) { + $schema->$prop = $value; + } + } else { + $newSchema = new \stdClass(); + foreach (get_object_vars($schema) as $prop => $value) { + $newSchema->$prop = $value; + unset($schema->$prop); + } + $schema->allOf = array($newSchema, $refSchema); + } } /** - * 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 + * @param object $schema + * @return bool */ - public function resolveRef($schema, $sourceUri) + private function hasSubSchemas($schema) { - $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); + foreach (array_keys(get_object_vars($schema)) as $propertyName) { + if (in_array($propertyName, $this->getReservedKeysWhichAreInFactSubSchemas())) { + return true; + } } - unset($schema->$ref); - - // Augment the current $schema object with properties fetched - foreach (get_object_vars($refSchema) as $prop => $value) { - $schema->$prop = $value; - } + return false; } /** - * Set URI Retriever for use with the Ref Resolver - * - * @param UriRetriever $retriever - * @return $this for chaining + * @return string[] */ - public function setUriRetriever(UriRetriever $retriever) - { - $this->uriRetriever = $retriever; - - return $this; - } - - protected function resolveRefSegment($data, $pathParts) + private function getReservedKeysWhichAreInFactSubSchemas() { - foreach ($pathParts as $path) { - $path = strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%')); - - if (is_array($data)) { - $data = $data[$path]; - } else { - $data = $data->{$path}; - } - } - - return $data; + return array( + 'additionalItems', + 'additionalProperties', + 'extends', + 'items', + 'disallow', + 'extends', + 'items', + 'type', + 'allOf', + 'anyOf', + 'oneOf', + 'dependencies', + 'patternProperties', + 'properties' + ); } } diff --git a/src/JsonSchema/Uri/UriResolver.php b/src/JsonSchema/Uri/UriResolver.php index 97841145..a4a63237 100644 --- a/src/JsonSchema/Uri/UriResolver.php +++ b/src/JsonSchema/Uri/UriResolver.php @@ -10,13 +10,14 @@ namespace JsonSchema\Uri; use JsonSchema\Exception\UriResolverException; +use JsonSchema\UriResolverInterface; /** * Resolves JSON Schema URIs * * @author Sander Coolen */ -class UriResolver +class UriResolver implements UriResolverInterface { /** * Parses a URI into five main components @@ -69,11 +70,7 @@ public function generate(array $components) } /** - * Resolves a URI - * - * @param string $uri Absolute or relative - * @param string $baseUri Optional base URI - * @return string Absolute URI + * {@inheritdoc} */ public function resolve($uri, $baseUri = null) { diff --git a/src/JsonSchema/Uri/UriRetriever.php b/src/JsonSchema/Uri/UriRetriever.php index c723cd97..f4ba2ecd 100644 --- a/src/JsonSchema/Uri/UriRetriever.php +++ b/src/JsonSchema/Uri/UriRetriever.php @@ -11,6 +11,7 @@ use JsonSchema\Uri\Retrievers\FileGetContents; use JsonSchema\Uri\Retrievers\UriRetrieverInterface; +use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface; use JsonSchema\Validator; use JsonSchema\Exception\InvalidSchemaMediaTypeException; use JsonSchema\Exception\JsonDecodingException; @@ -21,7 +22,7 @@ * * @author Tyler Akins */ -class UriRetriever +class UriRetriever implements BaseUriRetrieverInterface { /** * @var null|UriRetrieverInterface @@ -128,11 +129,7 @@ public function resolvePointer($jsonSchema, $uri) } /** - * Retrieve a URI - * - * @param string $uri JSON Schema URI - * @param string|null $baseUri - * @return object JSON Schema contents + * {@inheritdoc} */ public function retrieve($uri, $baseUri = null) { diff --git a/src/JsonSchema/UriResolverInterface.php b/src/JsonSchema/UriResolverInterface.php new file mode 100644 index 00000000..1386c187 --- /dev/null +++ b/src/JsonSchema/UriResolverInterface.php @@ -0,0 +1,25 @@ +resolveSchema($schema); + } - $refResolver = new RefResolver(new UriRetriever); - $refResolver->resolve($schema); + $value = json_decode($input); $validator = new Validator($checkMode); - - $validator->check(json_decode($input), $schema); + $validator->check($value, $schema); if (array() !== $errors) { $this->assertEquals($errors, $validator->getErrors(), print_r($validator->getErrors(),true)); @@ -41,17 +52,86 @@ public function testInvalidCases($input, $schema, $checkMode = Validator::CHECK_ public function testValidCases($input, $schema, $checkMode = Validator::CHECK_MODE_NORMAL) { $schema = json_decode($schema); + if (is_object($schema)) { + $schema = $this->resolveSchema($schema); + } - $refResolver = new RefResolver(new UriRetriever); - $refResolver->resolve($schema); - + $value = json_decode($input); $validator = new Validator($checkMode); - $validator->check(json_decode($input), $schema); + $validator->check($value, $schema); $this->assertTrue($validator->isValid(), print_r($validator->getErrors(), true)); } + /** + * @return array[] + */ abstract public function getValidTests(); + /** + * @return array[] + */ abstract public function getInvalidTests(); + + /** + * @param object $schema + * @return object + */ + private function resolveSchema($schema) + { + $relativeTestsRoot = realpath(__DIR__ . '/../../../../vendor/json-schema/JSON-Schema-Test-Suite/remotes'); + + $jsonSchemaDraft03 = $this->getJsonSchemaDraft03(); + $jsonSchemaDraft04 = $this->getJsonSchemaDraft04(); + + $uriRetriever = $this->prophesize('JsonSchema\UriRetrieverInterface'); + $uriRetriever->retrieve('http://www.my-domain.com/schema.json') + ->willReturn($schema) + ->shouldBeCalled(); + $uriRetriever->retrieve(Argument::any()) + ->will(function ($args) use ($jsonSchemaDraft03, $jsonSchemaDraft04, $relativeTestsRoot) { + if ('http://json-schema.org/draft-03/schema' === $args[0]) { + return $jsonSchemaDraft03; + } elseif ('http://json-schema.org/draft-04/schema' === $args[0]) { + return $jsonSchemaDraft04; + } elseif (0 === strpos($args[0], 'http://localhost:1234')) { + $urlParts = parse_url($args[0]); + return json_decode(file_get_contents($relativeTestsRoot . $urlParts['path'])); + } elseif (0 === strpos($args[0], 'http://www.my-domain.com')) { + $urlParts = parse_url($args[0]); + return json_decode(file_get_contents($relativeTestsRoot . '/folder' . $urlParts['path'])); + } + }); + $refResolver = new RefResolver($uriRetriever->reveal(), new UriResolver()); + + return $refResolver->resolve('http://www.my-domain.com/schema.json'); + } + + /** + * @return object + */ + private function getJsonSchemaDraft03() + { + if (!$this->jsonSchemaDraft03) { + $this->jsonSchemaDraft03 = json_decode( + file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-03.json') + ); + } + + return $this->jsonSchemaDraft03; + } + + /** + * @return object + */ + private function getJsonSchemaDraft04() + { + if (!$this->jsonSchemaDraft04) { + $this->jsonSchemaDraft04 = json_decode( + file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-04.json') + ); + } + + return $this->jsonSchemaDraft04; + } } diff --git a/tests/JsonSchema/Tests/Drafts/BaseDraftTestCase.php b/tests/JsonSchema/Tests/Drafts/BaseDraftTestCase.php index d7a505ba..b8835459 100644 --- a/tests/JsonSchema/Tests/Drafts/BaseDraftTestCase.php +++ b/tests/JsonSchema/Tests/Drafts/BaseDraftTestCase.php @@ -4,8 +4,12 @@ use JsonSchema\Tests\Constraints\BaseTestCase; +/** + * @package JsonSchema\Tests\Drafts + */ abstract class BaseDraftTestCase extends BaseTestCase { + /** @var string */ protected $relativeTestsRoot = '/../../../../vendor/json-schema/JSON-Schema-Test-Suite/tests'; private function setUpTests($isValid) @@ -32,17 +36,29 @@ private function setUpTests($isValid) return $tests; } + /** + * {@inheritdoc} + */ public function getInvalidTests() { return $this->setUpTests(false); } + /** + * {@inheritdoc} + */ public function getValidTests() { return $this->setUpTests(true); } + /** + * @return string[] + */ protected abstract function getFilePaths(); + /** + * @return string[] + */ protected abstract function getSkippedTests(); } diff --git a/tests/JsonSchema/Tests/Drafts/Draft3Test.php b/tests/JsonSchema/Tests/Drafts/Draft3Test.php index 0c589ff6..f1ca8f46 100644 --- a/tests/JsonSchema/Tests/Drafts/Draft3Test.php +++ b/tests/JsonSchema/Tests/Drafts/Draft3Test.php @@ -1,9 +1,22 @@ + */ +class JsonPointerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestData + * + * @param string $testValue + * @param string $expectedFileName + * @param array $expectedPropertyPaths + * @param string $expectedPropertyPathAsString + * @param string $expectedToString + */ + public function testJsonPointer( + $testValue, + $expectedFileName, + $expectedPropertyPaths, + $expectedPropertyPathAsString, + $expectedToString + ) { + $jsonPointer = new JsonPointer($testValue); + $this->assertEquals($expectedFileName, $jsonPointer->getFilename()); + $this->assertEquals($expectedPropertyPaths, $jsonPointer->getPropertyPaths()); + $this->assertEquals($expectedPropertyPathAsString, $jsonPointer->getPropertyPathAsString()); + $this->assertEquals($expectedToString, (string) $jsonPointer); + } + + /** + * @return array[] + */ + public function getTestData() + { + return array( + 'testDataSet_01' => array( + 'testValue' => '#/definitions/date', + 'expectedFileName' => '', + 'expectedPropertyPaths' => array('definitions', 'date'), + 'expectedPropertyPathAsString' => '#/definitions/date', + 'expectedToString' => '#/definitions/date' + ), + 'testDataSet_02' => array( + 'testValue' => 'http://www.example.com/definitions.json#/definitions/date', + 'expectedFileName' => 'http://www.example.com/definitions.json', + 'expectedPropertyPaths' => array('definitions', 'date'), + 'expectedPropertyPathAsString' => '#/definitions/date', + 'expectedToString' => 'http://www.example.com/definitions.json#/definitions/date' + ), + 'testDataSet_03' => array( + 'testValue' => '/tmp/schema.json#definitions/common/date/', + 'expectedFileName' => '/tmp/schema.json', + 'expectedPropertyPaths' => array('definitions', 'common', 'date'), + 'expectedPropertyPathAsString' => '#/definitions/common/date', + 'expectedToString' => '/tmp/schema.json#/definitions/common/date' + ), + 'testDataSet_04' => array( + 'testValue' => './definitions.json#', + 'expectedFileName' => './definitions.json', + 'expectedPropertyPaths' => array(), + 'expectedPropertyPathAsString' => '#', + 'expectedToString' => './definitions.json#' + ), + 'testDataSet_05' => array( + 'testValue' => '/schema.json#~0definitions~1general/%custom%25', + 'expectedFileName' => '/schema.json', + 'expectedPropertyPaths' => array('~definitions/general', '%custom%'), + 'expectedPropertyPathAsString' => '#/~0definitions~1general/%25custom%25', + 'expectedToString' => '/schema.json#/~0definitions~1general/%25custom%25' + ), + 'testDataSet_06' => array( + 'testValue' => '#/items/0', + 'expectedFileName' => '', + 'expectedPropertyPaths' => array('items', '0'), + 'expectedPropertyPathAsString' => '#/items/0', + 'expectedToString' => '#/items/0' + ) + + + ); + } +} diff --git a/tests/JsonSchema/Tests/RefResolverTest.php b/tests/JsonSchema/Tests/RefResolverTest.php index d9e341db..0e3d2cc7 100644 --- a/tests/JsonSchema/Tests/RefResolverTest.php +++ b/tests/JsonSchema/Tests/RefResolverTest.php @@ -9,375 +9,237 @@ namespace JsonSchema\Tests; -use JsonSchema\Exception\JsonDecodingException; +use JsonSchema\RefResolver; +use JsonSchema\Uri\UriResolver; +use JsonSchema\Uri\UriRetriever; +use Prophecy\Argument; /** + * @package JsonSchema\Tests + * @author Joost Nijhuis + * @author Rik Jansen * @group RefResolver */ class RefResolverTest extends \PHPUnit_Framework_TestCase { + /** @var RefResolver */ + private $refResolver; + /** - * @dataProvider resolveProvider + * {@inheritdoc} */ - public function testResolve($input, $methods) + public function setUp() { - $resolver = $this->getMock('JsonSchema\RefResolver', array_keys($methods)); - foreach ($methods as $methodName => $methodInvocationCount) { - $resolver->expects($this->exactly($methodInvocationCount)) - ->method($methodName); - } - $resolver->resolve($input); + parent::setUp(); + + $this->refResolver = new RefResolver(new UriRetriever(), new UriResolver()); } - public function resolveProvider() { - return array( - 'non-object' => array( - 'string', - array( - 'resolveRef' => 0, - 'resolveProperty' => 0, - 'resolveArrayOfSchemas' => 0, - 'resolveObjectOfSchemas' => 0 - ) - ), - 'empty object' => array( - (object) array(), - array( - 'resolveRef' => 1, - 'resolveProperty' => 4, - 'resolveArrayOfSchemas' => 7, - 'resolveObjectOfSchemas' => 3 - ) - ) + public function testSchemaWithLocalAndExternalReferencesWithCircularReference() + { + $mainSchema = $this->getMainSchema(); + $schema2 = $this->getSchema2(); + $schema3 = $this->getSchema3(); + + /** @var UriRetriever $uriRetriever */ + $uriRetriever = $this->prophesize('JsonSchema\UriRetrieverInterface'); + $uriRetriever->retrieve('http://www.example.com/schema.json') + ->willReturn($mainSchema) + ->shouldBeCalled($mainSchema); + $uriRetriever->retrieve('http://www.my-domain.com/schema2.json') + ->willReturn($schema2) + ->shouldBeCalled(); + $uriRetriever->retrieve('http://www.my-domain.com/schema3.json') + ->willReturn($schema3) + ->shouldBeCalled(); + + $refResolver = new RefResolver($uriRetriever->reveal(), new UriResolver()); + $refResolver->resolve('http://www.example.com/schema.json'); + + // ref schema merged into schema + $this->assertSame($schema2->definitions->car->type, $mainSchema->properties->car->type); + $this->assertSame( + $schema2->definitions->car->additionalProperties, + $mainSchema->properties->car->additionalProperties + ); + $this->assertSame($schema2->definitions->car->properties, $mainSchema->properties->car->properties); + $this->assertFalse(property_exists($mainSchema->properties->car, '$ref')); + + // ref schema combined with current schema + $this->assertFalse(property_exists($mainSchema->properties->house, '$ref')); + $this->assertSame(true, $mainSchema->properties->house->allOf[0]->additionalProperties); + $this->assertSame($mainSchema->definitions->house, $mainSchema->properties->house->allOf[1]); + + $this->assertNotSame($mainSchema->definitions->house, $mainSchema->definitions->house->properties->house); + $this->assertNotSame( + $mainSchema->definitions->house, + $mainSchema->definitions->house->properties->house->properties->house + ); + $this->assertSame( + $mainSchema->definitions->house->properties->house, + $mainSchema->definitions->house->properties->house->properties->house->properties->house + ); + $this->assertSame( + $mainSchema->definitions->house->properties->house, + $mainSchema->definitions->house->properties->house->properties->house->properties->house->properties->house ); - } - /** - * Helper method for resolve* methods - */ - public function helperResolveMethods($method, $input, $calls) { - $resolver = $this->getMock('JsonSchema\RefResolver', array('resolve')); - $resolver->expects($this->exactly($calls[$method])) - ->method('resolve'); - $resolver->$method($input, 'testProp', 'http://example.com/'); + $this->assertNotSame($schema3->wheel, $mainSchema->properties->car->properties->wheel); + $this->assertSame( + $schema3->wheel->properties->spokes, + $mainSchema->properties->car->properties->wheel->properties->spokes + ); + + $this->assertNotSame($schema3->wheel->properties->car, $mainSchema->properties->car); + $this->assertSame($schema3->wheel->properties->car->properties, $mainSchema->properties->car->properties); } - /** - * @dataProvider testSchemas - */ - public function testResolveArrayOfSchemas($input, $calls) { - $this->helperResolveMethods('resolveArrayOfSchemas', $input, $calls); + function testUnresolvableJsonPointExceptionShouldBeThrown() + { + $this->setExpectedException( + 'JsonSchema\Exception\UnresolvableJsonPointerException', + 'File: http://www.example.com/schema.json is found, but could not resolve fragment: #/definitions/car' + ); + + $mainSchema = $this->getInvalidSchema(); + + $uriRetriever = $this->prophesize('JsonSchema\UriRetrieverInterface'); + $uriRetriever->retrieve('http://www.example.com/schema.json') + ->willReturn($mainSchema) + ->shouldBeCalled($mainSchema); + + $refResolver = new RefResolver($uriRetriever->reveal(), new UriResolver()); + $refResolver->resolve('http://www.example.com/schema.json'); } /** - * @dataProvider testSchemas + * @return object */ - public function testResolveObjectOfSchemas($input, $calls) { - $this->helperResolveMethods('resolveObjectOfSchemas', $input, $calls); - } - - public function testSchemas() { - return array( - 'non-object' => array( - (object) array( - 'testProp' => 'string' - ), - array( - 'resolveArrayOfSchemas' => 0, - 'resolveObjectOfSchemas' => 0, - 'resolveProperty' => 0 - ) - ), - 'undefined' => array( - (object) array( - ), - array( - 'resolveArrayOfSchemas' => 0, - 'resolveObjectOfSchemas' => 0, - 'resolveProperty' => 0 - ) + private function getMainSchema() + { + return (object) array( + 'version' => 'v1', + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'id' => 'http://www.example.com/schema.json', + 'type' => 'object', + 'additionalProperties' => true, + 'required' => array( + 'car' ), - 'empty object' => array( - (object) array( - 'testProp' => (object) array() + 'properties' => (object) array( + 'car' => (object) array( + '$ref' => 'http://www.my-domain.com/schema2.json#/definitions/car' ), - array( - 'resolveArrayOfSchemas' => 0, - 'resolveObjectOfSchemas' => 0, - 'resolveProperty' => 1 + 'house' => (object) array( + 'additionalProperties' => true, + '$ref' => '#/definitions/house' ) ), - 'filled object' => array( - (object) array( - 'testProp' => (object) array( - 'one' => array(), - 'two' => array() + 'definitions' => (object) array( + 'house' => (object) array( + 'type' => 'object', + 'additionalProperties' => false, + 'required' => array( + 'door', + 'window' + ), + 'properties' => (object) array( + 'door' => (object) array( + 'type' => 'string' + ), + 'window' => (object) array( + 'type' => 'string' + ), + 'house' => (object) array( + '$ref' => '#/definitions/house' + ) ) - ), - array( - 'resolveArrayOfSchemas' => 0, - 'resolveObjectOfSchemas' => 2, - 'resolveProperty' => 1 - ) - ), - 'empty array' => array( - (object) array( - 'testProp' => array() - ), - array( - 'resolveArrayOfSchemas' => 0, - 'resolveObjectOfSchemas' => 0, - 'resolveProperty' => 1 - ) - ), - 'filled array' => array( - (object) array( - 'testProp' => array(1, 2, 3) - ), - array( - 'resolveArrayOfSchemas' => 3, - 'resolveObjectOfSchemas' => 0, - 'resolveProperty' => 1 ) ) ); } /** - * @dataProvider refProvider + * @return object */ - public function testResolveRef($expected, $input) { - $resolver = $this->getMock('JsonSchema\RefResolver', array('fetchRef')); - $resolver->expects($this->any()) - ->method('fetchRef') - ->will($this->returnValue((object) array( - 'this was' => array('added', 'because'), - 'the' => (object) array('$ref resolved' => true) - ))); - $resolver->resolveRef($input, 'http://example.com'); - $this->assertEquals($expected, $input); - } - - public function refProvider() { - return array( - 'no ref' => array( - (object) array('test' => 'one'), - (object) array('test' => 'one') - ), - // The $ref is not removed here - 'empty ref' => array( - (object) array( - 'test' => 'two', - '$ref' => '' - ), - (object) array( - 'test' => 'two', - '$ref' => '' - ) - ), - // $ref is removed - 'qualified ref' => array( - (object) array( - 'this is' => 'another test', - 'this was' => array('added', 'because'), - 'the' => (object) array('$ref resolved' => true) - ), - (object) array( - '$ref' => 'http://example.com/', - 'this is' => 'another test' - ) - ), - ); - } - - public function testFetchRefAbsolute() + private function getSchema2() { - $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( - array( - 'http://example.org/schema' => << 'v1', + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'id' => 'http://www.my-domain.com/schema2.json', + 'definitions' => (object) array( + 'car' => (object) array( + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => (object) array( + 'id' => (object) array( + 'type' => 'integer' + ), + 'name' => (object) array( + 'type' => 'string', + 'minLength' => 1 + ), + 'wheel' => (object) array( + '$ref' => './schema3.json#/wheel' + ) + ) + ) ) ); - - $res = new \JsonSchema\RefResolver(); - $res->getUriRetriever()->setUriRetriever($retr); - - $this->assertEquals( - (object) array( - 'title' => 'schema', - 'type' => 'object', - 'id' => 'http://example.org/schema' - ), - $res->fetchRef('http://example.org/schema', 'http://example.org/schema') - ); } - public function testFetchRefAbsoluteAnchor() + /** + * @return object + */ + private function getSchema3() { - $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( - array( - 'http://example.org/schema' => <<getUriRetriever()->setUriRetriever($retr); - - $this->assertEquals( - (object) array( - 'title' => 'foo', - 'type' => 'object', - 'id' => 'http://example.org/schema#/definitions/foo', - ), - $res->fetchRef( - 'http://example.org/schema#/definitions/foo', - 'http://example.org/schema' + return (object) array( + 'version' => 'v1', + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'wheel', + 'wheel' => (object) array( + 'properties' => (object) array( + 'spokes' => (object) array( + 'type' => 'integer' + ), + 'size' => (object) array( + 'type' => 'integer' + ), + 'car' => (object) array( + '$ref' => './schema2.json#/definitions/car' + ) + ) ) ); } - public function testFetchRefRelativeAnchor() + /** + * @return object + */ + private function getInvalidSchema() { - $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( - array( - 'http://example.org/schema' => <<getUriRetriever()->setUriRetriever($retr); - - $this->assertEquals( - (object) array( - 'title' => 'foo', - 'type' => 'object', - 'id' => 'http://example.org/schema#/definitions/foo', + return (object) array( + 'version' => 'v1', + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'object', + 'properties' => (object) array( + 'spokes' => (object) array( + 'type' => 'integer' + ), + 'size' => (object) array( + 'type' => 'integer' + ), + 'car' => (object) array( + '$ref' => '#/definitions/car' + ) ), - $res->fetchRef( - '#/definitions/foo', - 'http://example.org/schema' - ) - ); - } - - public function testFetchRefArray() - { - $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( - array( - 'http://example.org/array' => << (object) array( + 'date' => (object) array( + 'type' => 'string', + 'pattern' => '^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$' + ) ) ); - - $res = new \JsonSchema\RefResolver(); - $res->getUriRetriever()->setUriRetriever($retr); - - $this->assertEquals( - array(1, 2, 3), - $res->fetchRef('http://example.org/array', 'http://example.org/array') - ); - } - - public function testSetGetUriRetriever() - { - $retriever = new \JsonSchema\Uri\UriRetriever; - $resolver = new \JsonSchema\RefResolver; - $this->assertInstanceOf('JsonSchema\Uri\UriRetriever', $resolver->getUriRetriever()); - $this->assertInstanceOf('JsonSchema\RefResolver', $resolver->setUriRetriever($retriever)); - } - - public function testFetchRef() - { - // stub schema - $jsonSchema = new \stdClass; - $jsonSchema->id = 'stub'; - $jsonSchema->additionalItems = 'stub'; - $ref = 'ref'; - $sourceUri = null; - - - // mock retriever - $retriever = $this->getMock('JsonSchema\Uri\UriRetriever', array('retrieve')); - $retriever->expects($this->any())->method('retrieve')->will($this->returnValue($jsonSchema)); - - // stub resolver - $resolver = new \JsonSchema\RefResolver; - $resolver->setUriRetriever($retriever); - - $this->assertEquals($jsonSchema, $resolver->fetchRef($ref, $sourceUri)); - } - - /** - * @expectedException \JsonSchema\Exception\JsonDecodingException - */ - public function testMaxDepthExceeded() - { - // stub schema - $jsonSchema = new \stdClass; - $jsonSchema->id = 'stub'; - $jsonSchema->additionalItems = 'stub'; - - // mock retriever - $retriever = $this->getMock('JsonSchema\Uri\UriRetriever', array('retrieve')); - $retriever->expects($this->any())->method('retrieve')->will($this->returnValue($jsonSchema)); - - // stub resolver - \JsonSchema\RefResolver::$maxDepth = 0; - $resolver = new \JsonSchema\RefResolver($retriever); - - $resolver->resolve($jsonSchema); - } - - public function testDepthRestoration() - { - // stub schema - $jsonSchema = new \stdClass; - $jsonSchema->id = 'stub'; - $jsonSchema->additionalItems = new \stdClass(); - $jsonSchema->additionalItems->additionalItems = 'stub'; - - // stub resolver - \JsonSchema\RefResolver::$maxDepth = 1; - $resolver = new \JsonSchema\RefResolver(); - - try { - $resolver->resolve($jsonSchema); - } catch (JsonDecodingException $e) { - - } - - $reflection = new \ReflectionProperty('\JsonSchema\RefResolver', 'depth'); - $reflection->setAccessible(true); - $this->assertEquals(0, $reflection->getValue()); } } diff --git a/tests/JsonSchema/Tests/fixtures/json-schema-draft-03.json b/tests/JsonSchema/Tests/fixtures/json-schema-draft-03.json new file mode 100644 index 00000000..dcf07342 --- /dev/null +++ b/tests/JsonSchema/Tests/fixtures/json-schema-draft-03.json @@ -0,0 +1,193 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "id": "http://json-schema.org/draft-03/schema#", + "type": "object", + "properties": { + "type": { + "type": [ + "string", + "array" + ], + "items": { + "type": [ + "string", + { + "$ref": "#" + } + ] + }, + "uniqueItems": true, + "default": "any" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "additionalProperties": { + "type": [ + { + "$ref": "#" + }, + "boolean" + ], + "default": {} + }, + "items": { + "type": [ + { + "$ref": "#" + }, + "array" + ], + "items": { + "$ref": "#" + }, + "default": {} + }, + "additionalItems": { + "type": [ + { + "$ref": "#" + }, + "boolean" + ], + "default": {} + }, + "required": { + "type": "boolean", + "default": false + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "type": [ + "string", + "array", + { + "$ref": "#" + } + ], + "items": { + "type": "string" + } + }, + "default": {} + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "maxLength": { + "type": "integer" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "default": { + "type": "any" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "divisibleBy": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true, + "default": 1 + }, + "disallow": { + "type": [ + "string", + "array" + ], + "items": { + "type": [ + "string", + { + "$ref": "#" + } + ] + }, + "uniqueItems": true + }, + "extends": { + "type": [ + { + "$ref": "#" + }, + "array" + ], + "items": { + "$ref": "#" + }, + "default": {} + }, + "id": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri" + }, + "$schema": { + "type": "string", + "format": "uri" + } + }, + "dependencies": { + "exclusiveMinimum": "minimum", + "exclusiveMaximum": "maximum" + }, + "default": {} +} \ No newline at end of file diff --git a/tests/JsonSchema/Tests/fixtures/json-schema-draft-04.json b/tests/JsonSchema/Tests/fixtures/json-schema-draft-04.json new file mode 100644 index 00000000..96e7f16a --- /dev/null +++ b/tests/JsonSchema/Tests/fixtures/json-schema-draft-04.json @@ -0,0 +1,221 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/positiveInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "$ref": "#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#" + } + ], + "default": {} + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": {} + }, + "maxItems": { + "$ref": "#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "$ref": "#/definitions/positiveInteger" + }, + "minProperties": { + "$ref": "#/definitions/positiveIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#" + } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + } + }, + "dependencies": { + "exclusiveMaximum": [ + "maximum" + ], + "exclusiveMinimum": [ + "minimum" + ] + }, + "default": {} +} \ No newline at end of file