diff --git a/README.md b/README.md index 9e3aee3..dc365ae 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ class MyTestCase extends \PHPUnit_Framework_TestCase $json = json_decode('{"foo":1}'); - $this->assertJsonMatchesSchema('./my-schema.json', $json); + $this->assertJsonMatchesSchema($json, './my-schema.json'); $this->assertJsonValueEquals(1, '* | [0]', $json); } } @@ -115,7 +115,63 @@ class MyTestCase extends \PHPUnit_Framework_TestCase $json = json_decode('{"foo":1}'); - JsonAssert::assertJsonMatchesSchema('./my-schema.json', $json); + JsonAssert::assertJsonMatchesSchema($json, './my-schema.json'); + JsonAssert::assertJsonValueEquals(1, '* | [0]', $json); + } +} +``` + +## Schema storage + +The [schema storage](https://github.com/justinrainbow/json-schema/blob/master/src/JsonSchema/SchemaStorage.php) of `justinrainbow/json-schema` allows to register schemas which will effectively override the actual schema location. + +Example: +```json +{"$ref" : "https://iglu.foobar.com/myschema.json#/definitions/positiveInteger"} +``` + +The resolver will fetch the schema from this endpoint and match the JSON document against it. Using schema storage you're able to override this behaviour. + +```php +$schemastorage->addSchema('https://iglu.foobar.com/myschema.json', (object)['type' => 'string']); +``` + +With this in place the resolver will take the schema that is already in place without downloading it again. + +```php +addSchema('', obj); + ... + } + + public function testJsonDocumentIsValid() + { + // my-schema.json + // + // { + // "type" : "object", + // "properties" : { + // "foo" : { + // "type" : "integer" + // } + // }, + // "required" : [ "foo" ] + // } + + $json = json_decode('{"foo":1}'); + + JsonAssert::assertJsonMatchesSchema($json, './my-schema.json'); JsonAssert::assertJsonValueEquals(1, '* | [0]', $json); } } diff --git a/composer.json b/composer.json index 3aa70e3..00700cc 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "minimum-stability": "stable", "require": { "php": "^7.0", - "justinrainbow/json-schema": "^2.0", + "justinrainbow/json-schema": "^5.0", "mtdowling/jmespath.php": "^2.3" }, "require-dev": { diff --git a/src/Assert.php b/src/Assert.php index 23a90d6..aa0aca2 100644 --- a/src/Assert.php +++ b/src/Assert.php @@ -11,10 +11,10 @@ namespace EnricoStahn\JsonAssert; -use JsonSchema\RefResolver; -use JsonSchema\Uri\UriResolver; -use JsonSchema\Uri\UriRetriever; +use JsonSchema\Constraints\Factory; +use JsonSchema\SchemaStorage; use JsonSchema\Validator; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; /** * Asserts to validate JSON data. @@ -25,25 +25,40 @@ */ trait Assert { + /** + * @var SchemaStorage + */ + private static $schemaStorage = null; + /** * Asserts that json content is valid according to the provided schema file. * * Example: * - * static::assertJsonMatchesSchema('./schema.json', json_decode('{"foo":1}')) + * static::assertJsonMatchesSchema(json_decode('{"foo":1}'), './schema.json') * - * @param string $schema Path to the schema file + * @param string|null $schema Path to the schema file * @param array|object $content JSON array or object */ - public static function assertJsonMatchesSchema($schema, $content) + public static function assertJsonMatchesSchema($content, $schema = null) { - // Assume references are relative to the current file - // Create an issue or pull request if you need more complex use cases - $refResolver = new RefResolver(new UriRetriever(), new UriResolver()); - $schemaObj = $refResolver->resolve('file://'.realpath($schema)); + if (self::$schemaStorage === null) { + self::$schemaStorage = new SchemaStorage(); + } + + if ($schema !== null && !file_exists($schema)) { + throw new FileNotFoundException($schema); + } + + $schemaObject = null; - $validator = new Validator(); - $validator->check($content, $schemaObj); + if ($schema !== null) { + $schemaObject = json_decode(file_get_contents($schema)); + self::$schemaStorage->addSchema('file://'.$schema, $schemaObject); + } + + $validator = new Validator(new Factory(self::$schemaStorage)); + $validator->validate($content, $schemaObject); $message = '- Property: %s, Contraint: %s, Message: %s'; $messages = array_map(function ($exception) use ($message) { @@ -54,6 +69,23 @@ public static function assertJsonMatchesSchema($schema, $content) \PHPUnit\Framework\Assert::assertTrue($validator->isValid(), implode("\n", $messages)); } + /** + * Asserts that json content is valid according to the provided schema file. + * + * Example: + * + * static::assertJsonMatchesSchema(json_decode('{"foo":1}'), './schema.json') + * + * @param string|null $schema Path to the schema file + * @param array|object $content JSON array or object + * + * @deprecated This will be removed in the next major version (4.x). + */ + public static function assertJsonMatchesSchemaDepr($schema, $content) + { + self::assertJsonMatchesSchema($content, $schema); + } + /** * Asserts that json content is valid according to the provided schema string. * @@ -65,7 +97,7 @@ public static function assertJsonMatchesSchemaString($schema, $content) $file = tempnam(sys_get_temp_dir(), 'json-schema-'); file_put_contents($file, $schema); - self::assertJsonMatchesSchema($file, $content); + self::assertJsonMatchesSchema($content, $file); } /** diff --git a/src/Extension/Symfony.php b/src/Extension/Symfony.php index 99d3d62..530876a 100644 --- a/src/Extension/Symfony.php +++ b/src/Extension/Symfony.php @@ -28,7 +28,7 @@ trait Symfony */ public static function assertJsonMatchesSchema($schema, Response $response) { - Assert::assertJsonMatchesSchema($schema, json_decode($response->getContent())); + Assert::assertJsonMatchesSchemaDepr($schema, json_decode($response->getContent())); } /** diff --git a/tests/AssertTraitImpl.php b/tests/AssertTraitImpl.php index 56c0a39..17202e7 100644 --- a/tests/AssertTraitImpl.php +++ b/tests/AssertTraitImpl.php @@ -12,9 +12,28 @@ namespace EnricoStahn\JsonAssert\Tests; use EnricoStahn\JsonAssert\Assert as JsonAssert; +use JsonSchema\SchemaStorage; use PHPUnit\Framework\TestCase; class AssertTraitImpl extends TestCase { use JsonAssert; + + public function setUp() + { + self::$schemaStorage = new SchemaStorage(); + } + + /** + * @param string $id + * @param string $schema + * + * @return SchemaStorage + */ + public function testWithSchemaStore($id, $schema) + { + self::$schemaStorage->addSchema($id, $schema); + + return self::$schemaStorage; + } } diff --git a/tests/AssertTraitTest.php b/tests/AssertTraitTest.php index fbc65b7..3ca7721 100644 --- a/tests/AssertTraitTest.php +++ b/tests/AssertTraitTest.php @@ -25,14 +25,14 @@ public function testAssertJsonMatchesSchemaSimple() { $content = json_decode(file_get_contents(Utils::getJsonPath('assertJsonMatchesSchema_simple.json'))); - AssertTraitImpl::assertJsonMatchesSchema(Utils::getSchemaPath('assertJsonMatchesSchema_simple.schema.json'), $content); + AssertTraitImpl::assertJsonMatchesSchemaDepr(Utils::getSchemaPath('assertJsonMatchesSchema_simple.schema.json'), $content); } public function testAssertJsonMatchesSchema() { $content = json_decode('{"foo":123}'); - AssertTraitImpl::assertJsonMatchesSchema(Utils::getSchemaPath('test.schema.json'), $content); + AssertTraitImpl::assertJsonMatchesSchemaDepr(Utils::getSchemaPath('test.schema.json'), $content); } /** @@ -42,7 +42,7 @@ public function testAssertJsonMatchesSchemaFail() { $content = json_decode('{"foo":"123"}'); - AssertTraitImpl::assertJsonMatchesSchema(Utils::getSchemaPath('test.schema.json'), $content); + AssertTraitImpl::assertJsonMatchesSchemaDepr(Utils::getSchemaPath('test.schema.json'), $content); } public function testAssertJsonMatchesSchemaFailMessage() @@ -52,7 +52,7 @@ public function testAssertJsonMatchesSchemaFailMessage() $exception = null; try { - AssertTraitImpl::assertJsonMatchesSchema(Utils::getSchemaPath('test.schema.json'), $content); + AssertTraitImpl::assertJsonMatchesSchemaDepr(Utils::getSchemaPath('test.schema.json'), $content); } catch (ExpectationFailedException $exception) { self::assertContains('- Property: foo, Contraint: type, Message: String value found, but an integer is required', $exception->getMessage()); self::assertContains('- Response: {"foo":"123"}', $exception->getMessage()); @@ -68,7 +68,7 @@ public function testAssertJsonMatchesSchemaWithRefs() { $content = json_decode('{"code":123, "message":"Nothing works."}'); - AssertTraitImpl::assertJsonMatchesSchema(Utils::getSchemaPath('error.schema.json'), $content); + AssertTraitImpl::assertJsonMatchesSchemaDepr(Utils::getSchemaPath('error.schema.json'), $content); } /** @@ -78,7 +78,7 @@ public function testAssertJsonMatchesSchemaWithRefsFails() { $content = json_decode('{"code":"123", "message":"Nothing works."}'); - AssertTraitImpl::assertJsonMatchesSchema(Utils::getSchemaPath('error.schema.json'), $content); + AssertTraitImpl::assertJsonMatchesSchemaDepr(Utils::getSchemaPath('error.schema.json'), $content); } public function testAssertJsonMatchesSchemaString() @@ -104,6 +104,17 @@ public function testAssertJsonValueEquals($expression, $value) AssertTraitImpl::assertJsonValueEquals($value, $expression, $content); } + public function testAssertWithSchemaStore() + { + $obj = new AssertTraitImpl(); + $obj->setUp(); + + $schemastore = $obj->testWithSchemaStore('foobar', (object) ['type' => 'string']); + + self::assertInstanceOf('JsonSchema\SchemaStorage', $schemastore); + self::assertEquals($schemastore->getSchema('foobar'), (object) ['type' => 'string']); + } + public function assertJsonValueEqualsProvider() { return [