From 167108f4f287c35bf6ced4e333149bcedc0d9284 Mon Sep 17 00:00:00 2001 From: Erayd Date: Fri, 24 Feb 2017 15:31:24 +1300 Subject: [PATCH 1/3] Add URI translation for retrieval & add local copies of spec schema --- dist/schema/json-schema-draft-03.json | 174 ++++++++++++++++++++++++++ dist/schema/json-schema-draft-04.json | 150 ++++++++++++++++++++++ src/JsonSchema/Uri/UriRetriever.php | 41 +++++- tests/Uri/UriRetrieverTest.php | 49 ++++++++ tests/fixtures/foobar.json | 12 ++ 5 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 dist/schema/json-schema-draft-03.json create mode 100644 dist/schema/json-schema-draft-04.json create mode 100644 tests/fixtures/foobar.json diff --git a/dist/schema/json-schema-draft-03.json b/dist/schema/json-schema-draft-03.json new file mode 100644 index 00000000..7a1a2d38 --- /dev/null +++ b/dist/schema/json-schema-draft-03.json @@ -0,0 +1,174 @@ +{ + "$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": {} +} diff --git a/dist/schema/json-schema-draft-04.json b/dist/schema/json-schema-draft-04.json new file mode 100644 index 00000000..85eb502a --- /dev/null +++ b/dist/schema/json-schema-draft-04.json @@ -0,0 +1,150 @@ +{ + "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": {} +} diff --git a/src/JsonSchema/Uri/UriRetriever.php b/src/JsonSchema/Uri/UriRetriever.php index ebb7eb33..51584d23 100644 --- a/src/JsonSchema/Uri/UriRetriever.php +++ b/src/JsonSchema/Uri/UriRetriever.php @@ -24,6 +24,14 @@ */ class UriRetriever implements BaseUriRetrieverInterface { + /** + * @var array Map of URL translations + */ + protected $translationMap = array( + // use local copies of the spec schemas + '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json' + ); + /** * @var null|UriRetrieverInterface */ @@ -36,6 +44,12 @@ class UriRetriever implements BaseUriRetrieverInterface */ private $schemaCache = array(); + public function __construct() + { + // translate references to local files within the json-schema package + $this->setTranslation('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..'))); + } + /** * Guarantee the correct media type was encountered * @@ -134,7 +148,7 @@ public function resolvePointer($jsonSchema, $uri) /** * {@inheritdoc} */ - public function retrieve($uri, $baseUri = null) + public function retrieve($uri, $baseUri = null, $translate = true) { $resolver = new UriResolver(); $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); @@ -146,6 +160,11 @@ public function retrieve($uri, $baseUri = null) $fetchUri = $resolver->generate($arParts); } + // apply URI translations + if ($translate) { + $fetchUri = $this->translate($fetchUri); + } + $jsonSchema = $this->loadSchema($fetchUri); // Use the JSON pointer if specified @@ -291,4 +310,24 @@ public function isValid($uri) return !empty($components); } + + /** + * Set a URL translation rule + */ + public function setTranslation($from, $to) + { + $this->translationMap[$from] = $to; + } + + /** + * Apply URI translation rules + */ + public function translate($uri) + { + foreach ($this->translationMap as $from => $to) { + $uri = preg_replace($from, $to, $uri); + } + + return $uri; + } } diff --git a/tests/Uri/UriRetrieverTest.php b/tests/Uri/UriRetrieverTest.php index 5d0d0e95..01df161b 100644 --- a/tests/Uri/UriRetrieverTest.php +++ b/tests/Uri/UriRetrieverTest.php @@ -10,6 +10,7 @@ namespace JsonSchema\Tests\Uri; use JsonSchema\Exception\JsonDecodingException; +use JsonSchema\Uri\UriRetriever; use JsonSchema\Validator; /** @@ -279,4 +280,52 @@ private function mockRetriever($schema) $retriever->setAccessible(true); $retriever->setValue($factory, $retrieverMock); } + + public function testTranslations() + { + $retriever = new UriRetriever(); + + $uri = 'http://example.com/foo/bar'; + $translated = 'file://another/bar'; + + $retriever->setTranslation('|^https?://example.com/foo/bar#?|', 'file://another/bar'); + $this->assertEquals($translated, $retriever->translate($uri)); + } + + public function testPackageURITranslation() + { + $retriever = new UriRetriever(); + $root = sprintf('file://%s/', realpath(__DIR__ . '/../..')); + + $uri = $retriever->translate('package://foo/bar.json'); + $this->assertEquals("${root}foo/bar.json", $uri); + } + + public function testDefaultDistTranslations() + { + $retriever = new UriRetriever(); + $root = sprintf('file://%s/dist/schema/', realpath(__DIR__ . '/../..')); + + $this->assertEquals( + $root . 'json-schema-draft-03.json', + $retriever->translate('http://json-schema.org/draft-03/schema#') + ); + + $this->assertEquals( + $root . 'json-schema-draft-04.json', + $retriever->translate('http://json-schema.org/draft-04/schema#') + ); + } + + public function testRetrieveSchemaFromPackage() + { + $retriever = new UriRetriever(); + + // load schema from package + $schema = $retriever->retrieve('package://tests/fixtures/foobar.json'); + $this->assertNotFalse($schema); + + // check that the schema was loaded & processed correctly + $this->assertEquals('454f423bd7edddf0bc77af4130ed9161', md5(json_encode($schema))); + } } diff --git a/tests/fixtures/foobar.json b/tests/fixtures/foobar.json new file mode 100644 index 00000000..b27b6861 --- /dev/null +++ b/tests/fixtures/foobar.json @@ -0,0 +1,12 @@ +{ + "$id": "http://example.com/foo/bar#", + "type": "object", + "properties": { + "foo": { + "type": "string", + "default": "bar" + } + }, + "required": ["foo"], + "additionalProperties": false +} From 61c3c69b141e04599a877f995c3bb604235e6b1f Mon Sep 17 00:00:00 2001 From: Erayd Date: Fri, 24 Feb 2017 15:44:31 +1300 Subject: [PATCH 2/3] Use dist copies of schemas No need to keep duplicate files around in package://tests/fixtures/ if we're distributing them for users anyway. --- tests/Constraints/VeryBaseTestCase.php | 4 +- tests/fixtures/json-schema-draft-03.json | 193 -------------------- tests/fixtures/json-schema-draft-04.json | 221 ----------------------- 3 files changed, 2 insertions(+), 416 deletions(-) delete mode 100644 tests/fixtures/json-schema-draft-03.json delete mode 100644 tests/fixtures/json-schema-draft-04.json diff --git a/tests/Constraints/VeryBaseTestCase.php b/tests/Constraints/VeryBaseTestCase.php index 7d8eb267..7cc0d1c6 100644 --- a/tests/Constraints/VeryBaseTestCase.php +++ b/tests/Constraints/VeryBaseTestCase.php @@ -66,7 +66,7 @@ private function getJsonSchemaDraft03() { if (!$this->jsonSchemaDraft03) { $this->jsonSchemaDraft03 = json_decode( - file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-03.json') + file_get_contents(__DIR__ . '/../../dist/schema/json-schema-draft-03.json') ); } @@ -80,7 +80,7 @@ private function getJsonSchemaDraft04() { if (!$this->jsonSchemaDraft04) { $this->jsonSchemaDraft04 = json_decode( - file_get_contents(__DIR__ . '/../fixtures/json-schema-draft-04.json') + file_get_contents(__DIR__ . '/../../dist/schema/json-schema-draft-04.json') ); } diff --git a/tests/fixtures/json-schema-draft-03.json b/tests/fixtures/json-schema-draft-03.json deleted file mode 100644 index dcf07342..00000000 --- a/tests/fixtures/json-schema-draft-03.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - "$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/fixtures/json-schema-draft-04.json b/tests/fixtures/json-schema-draft-04.json deleted file mode 100644 index 96e7f16a..00000000 --- a/tests/fixtures/json-schema-draft-04.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "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 From cd1ca020b72cc4b606978b50181bef4fe8feb846 Mon Sep 17 00:00:00 2001 From: Erayd Date: Sat, 25 Feb 2017 09:25:17 +1300 Subject: [PATCH 3/3] Move package:// translation after all other rules Allows users to rewrite to package:// targets and still have the URI work. --- src/JsonSchema/Uri/UriRetriever.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/JsonSchema/Uri/UriRetriever.php b/src/JsonSchema/Uri/UriRetriever.php index 51584d23..65452788 100644 --- a/src/JsonSchema/Uri/UriRetriever.php +++ b/src/JsonSchema/Uri/UriRetriever.php @@ -44,12 +44,6 @@ class UriRetriever implements BaseUriRetrieverInterface */ private $schemaCache = array(); - public function __construct() - { - // translate references to local files within the json-schema package - $this->setTranslation('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..'))); - } - /** * Guarantee the correct media type was encountered * @@ -328,6 +322,9 @@ public function translate($uri) $uri = preg_replace($from, $to, $uri); } + // translate references to local files within the json-schema package + $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri); + return $uri; } }