diff --git a/README.md b/README.md index 3f9c58c3..5eab4ad5 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()); +$refResolver->resolve('file://' . realpath('schema.json')); + +$data = json_decode(file_get_contents('data.json')); // Validate $validator = new JsonSchema\Validator(); diff --git a/src/JsonSchema/Entity/JsonPointer.php b/src/JsonSchema/Entity/JsonPointer.php new file mode 100644 index 00000000..1a32c9fd --- /dev/null +++ b/src/JsonSchema/Entity/JsonPointer.php @@ -0,0 +1,126 @@ + + */ +class JsonPointer +{ + /** @var string */ + private $filename; + + /** @var string[] */ + private $propertyPaths = array(); + + /** + * @param string $value + */ + public function __construct($value) + { + $this->validate($value); + $splitRef = explode('#', $value, 2); + $this->filename = $splitRef[0]; + if (array_key_exists(1, $splitRef)) { + $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); + } + } + + /** + * @param string $value + * @throws \InvalidArgumentException when $value is not a string + * @throws \UnexpectedValueException when $value does not contain a hash sign + */ + private function validate($value) + { + if (!is_string($value)) { + throw new \InvalidArgumentException('Ref value must be a string'); + } + } + + /** + * @param string $propertyPathString + * @return string[] + */ + private function decodePropertyPaths($propertyPathString) + { + $paths = array(); + foreach (explode('/', trim($propertyPathString, '/')) as $path) { + $path = $this->decodePath($path); + if ($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/RefResolver.php b/src/JsonSchema/RefResolver.php index ea0fafd6..17de6b03 100644 --- a/src/JsonSchema/RefResolver.php +++ b/src/JsonSchema/RefResolver.php @@ -9,269 +9,126 @@ namespace JsonSchema; -use JsonSchema\Exception\JsonDecodingException; -use JsonSchema\Uri\Retrievers\UriRetrieverInterface; -use JsonSchema\Uri\UriRetriever; +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 object - */ - protected $rootSchema = null; + /** @var UriResolverInterface */ + private $uriResolver; /** - * @param UriRetriever $retriever + * @param UriRetrieverInterface $retriever + * @param UriResolverInterface $uriResolver */ - 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 - * - * @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. + * Resolves all schema and all $ref references for the give $sourceUri. Recurse through the object to resolve + * references of any child schemas. * - * @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; - } - - 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); - } + $jsonPointer = new JsonPointer($sourceUri); - // These are all objects containing properties whose values are schemas - foreach (array('dependencies', 'patternProperties', 'properties') as $propertyName) { - $this->resolveObjectOfSchemas($schema, $propertyName, $sourceUri); + $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]; - --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); - } - } - - /** - * 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); + $toExplore = new \SplStack(); + $toExplore->push($unresolvedSchema); + + while (!$toExplore->isEmpty()) { + $toResolveSchema = $toExplore->pop(); + foreach (get_object_vars($toResolveSchema) as $propertyName => $propertyValue) { + if (is_object($propertyValue)) { + $toExplore->push($propertyValue); + } elseif ('$ref' === $propertyName) { + $jsonPointer = new JsonPointer($this->uriResolver->resolve($propertyValue, $fileName)); + $refSchema = $this->resolveCached((string) $jsonPointer, $paths); + $this->unionSchemas($refSchema, $toResolveSchema); + } + } } } /** - * 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 JsonPointer $jsonPointer + * @param object $schema + * @return object */ - public function resolveProperty($schema, $propertyName, $sourceUri) + private function getRefSchema(JsonPointer $jsonPointer, $schema) { - if (! isset($schema->$propertyName)) { - return; + $refSchema = $schema; + foreach ($jsonPointer->getPropertyPaths() as $path) { + $refSchema = is_array($refSchema) ? $refSchema[$path] : $refSchema->{$path}; } - $this->resolve($schema->$propertyName, $sourceUri); + return $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 $refSchema + * @param object $schema */ - public function resolveRef($schema, $sourceUri) + private function unionSchemas($refSchema, $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); + unset($schema->{'$ref'}); + if (0 === count(get_object_vars($schema))) { + foreach (get_object_vars($refSchema) as $prop => $value) { + $schema->$prop = $value; + } } 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}; + $newSchema = new \stdClass(); + foreach (get_object_vars($schema) as $prop => $value) { + $newSchema->$prop = $value; + unset($schema->$prop); } + $schema->allOf = array($newSchema, $refSchema); } - - return $data; } } 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 @@ +resolve($schema); + if (is_object($schema)) { + $refResolver = new RefResolver(new UriRetriever(), new UriResolver()); + $refResolver->resolve($schema); + } $validator = new Validator($checkMode); @@ -42,8 +48,10 @@ public function testValidCases($input, $schema, $checkMode = Validator::CHECK_MO { $schema = json_decode($schema); - $refResolver = new RefResolver(new UriRetriever); - $refResolver->resolve($schema); + if (is_object($schema)) { + $refResolver = new RefResolver(new UriRetriever(), new UriResolver()); + $refResolver->resolve($schema); + } $validator = new Validator($checkMode); diff --git a/tests/JsonSchema/Tests/Entity/JsonPointerTest.php b/tests/JsonSchema/Tests/Entity/JsonPointerTest.php new file mode 100644 index 00000000..6aa52e9a --- /dev/null +++ b/tests/JsonSchema/Tests/Entity/JsonPointerTest.php @@ -0,0 +1,86 @@ + + */ +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' + ) + ); + } +} diff --git a/tests/JsonSchema/Tests/RefResolverTest.php b/tests/JsonSchema/Tests/RefResolverTest.php index d9e341db..b3f30c47 100644 --- a/tests/JsonSchema/Tests/RefResolverTest.php +++ b/tests/JsonSchema/Tests/RefResolverTest.php @@ -9,375 +9,191 @@ 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 + ); - /** - * @dataProvider testSchemas - */ - public function testResolveArrayOfSchemas($input, $calls) { - $this->helperResolveMethods('resolveArrayOfSchemas', $input, $calls); + $this->assertNotSame($schema3->wheel->properties->car, $mainSchema->properties->car); + $this->assertSame($schema3->wheel->properties->car->properties, $mainSchema->properties->car->properties); } /** - * @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() - { - $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( - array( - 'http://example.org/schema' => <<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() + private function getSchema2() { - $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' - ) - ); - } - - public function testFetchRefRelativeAnchor() - { - $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( - '#/definitions/foo', - 'http://example.org/schema' - ) - ); - } + return (object) array( + 'version' => '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' + ) + ) + ), - public function testFetchRefArray() - { - $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( - array( - 'http://example.org/array' => <<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 + * @return object */ - public function testMaxDepthExceeded() + private function getSchema3() { - // 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()); + 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' + ) + ) + ) + ); } }