From b133775b8ef4f80998828691fdda9357e6696295 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Fri, 23 May 2025 14:51:33 +0200 Subject: [PATCH 1/8] fix: itterate through schema to discover and register sub schema --- src/JsonSchema/SchemaStorage.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 34f0daae..363fa53b 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -70,6 +70,8 @@ public function addSchema($id, $schema = null) } } + $this->addSubschemas($schema, $id); + // resolve references $this->expandRefs($schema, $id); @@ -174,4 +176,24 @@ public function resolveRefSchema($refSchema, $resolveStack = []) return $refSchema; } + + private function addSubschemas($schema, string $parentId): void + { + if (!is_object($schema) && !is_array($schema)) { + return; + } + + foreach ($schema as $potentialSubSchema) { + if (!is_object($potentialSubSchema)) { + continue; + } + + // Found sub schema + if (property_exists($potentialSubSchema, 'id') && is_string($potentialSubSchema->id) && property_exists($potentialSubSchema, 'type')) { + $this->addSchema($parentId . $potentialSubSchema->id, $potentialSubSchema); + } + + $this->addSubschemas($potentialSubSchema, $parentId); + } + } } From e8fcdbe1a3548a7a8abc1631b9acb2032abef6de Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Fri, 23 May 2025 14:54:03 +0200 Subject: [PATCH 2/8] fix: only alter id for recursive calls --- src/JsonSchema/SchemaStorage.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 363fa53b..7d93538f 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -82,31 +82,31 @@ public function addSchema($id, $schema = null) * Recursively resolve all references against the provided base * * @param mixed $schema - * @param string $base */ - private function expandRefs(&$schema, $base = null) + private function expandRefs(&$schema, string $parentId = null): void { if (!is_object($schema)) { if (is_array($schema)) { foreach ($schema as &$member) { - $this->expandRefs($member, $base); + $this->expandRefs($member, $parentId); } } return; } - if (property_exists($schema, 'id') && is_string($schema->id) && $base != $schema->id) { - $base = $this->uriResolver->resolve($schema->id, $base); - } - if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { - $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base)); + $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $parentId)); $schema->{'$ref'} = (string) $refPointer; } foreach ($schema as &$member) { - $this->expandRefs($member, $base); + $childId = $parentId; + if (property_exists($schema, 'id') && is_string($schema->id) && $childId !== $schema->id) { + $childId = $this->uriResolver->resolve($schema->id, $childId); + } + + $this->expandRefs($member, $childId); } } From 3a977a50c891b9f1f6b0ef7d0b96313fbd96a7fd Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Fri, 23 May 2025 14:54:38 +0200 Subject: [PATCH 3/8] refactor: add native type hints; upgrade to strict comparison --- src/JsonSchema/SchemaStorage.php | 10 +++++----- src/JsonSchema/SchemaStorageInterface.php | 11 +++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 7d93538f..0960889d 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -45,7 +45,7 @@ public function getUriResolver() /** * {@inheritdoc} */ - public function addSchema($id, $schema = null) + public function addSchema($id, $schema = null): void { if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { // if the schema was user-provided to Validator and is still null, then assume this is @@ -62,9 +62,9 @@ public function addSchema($id, $schema = null) // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format) // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367 if (is_object($schema) && property_exists($schema, 'id')) { - if ($schema->id == 'http://json-schema.org/draft-04/schema#') { + if ($schema->id === 'http://json-schema.org/draft-04/schema#') { $schema->properties->id->format = 'uri-reference'; - } elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') { + } elseif ($schema->id === 'http://json-schema.org/draft-03/schema#') { $schema->properties->id->format = 'uri-reference'; $schema->properties->{'$ref'}->format = 'uri-reference'; } @@ -113,7 +113,7 @@ private function expandRefs(&$schema, string $parentId = null): void /** * {@inheritdoc} */ - public function getSchema($id) + public function getSchema(string $id) { if (!array_key_exists($id, $this->schemas)) { $this->addSchema($id); @@ -125,7 +125,7 @@ public function getSchema($id) /** * {@inheritdoc} */ - public function resolveRef($ref, $resolveStack = []) + public function resolveRef(string $ref, $resolveStack = []) { $jsonPointer = new JsonPointer($ref); diff --git a/src/JsonSchema/SchemaStorageInterface.php b/src/JsonSchema/SchemaStorageInterface.php index eca44283..f625cdd2 100644 --- a/src/JsonSchema/SchemaStorageInterface.php +++ b/src/JsonSchema/SchemaStorageInterface.php @@ -9,28 +9,23 @@ interface SchemaStorageInterface /** * Adds schema with given identifier * - * @param string $id * @param object $schema */ - public function addSchema($id, $schema = null); + public function addSchema(string $id, $schema = null): void; /** * Returns schema for given identifier, or null if it does not exist * - * @param string $id - * * @return object */ - public function getSchema($id); + public function getSchema(string $id); /** * Returns schema for given reference with all sub-references resolved * - * @param string $ref - * * @return object */ - public function resolveRef($ref); + public function resolveRef(string $ref); /** * Returns schema referenced by '$ref' property From 2aeeec67049fc7dc9c5ac5e95654480ca7aa8593 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Fri, 23 May 2025 15:05:50 +0200 Subject: [PATCH 4/8] fix: correct phpstan issues --- phpstan-baseline.neon | 16 +++------------- src/JsonSchema/SchemaStorage.php | 7 +++++-- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 42bea6be..19dba14a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -646,22 +646,17 @@ parameters: path: src/JsonSchema/SchemaStorage.php - - message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/JsonSchema/SchemaStorage.php - - - - message: "#^Call to function is_array\\(\\) with object will always evaluate to false\\.$#" + message: "#^Argument of an invalid type array\\|stdClass supplied for foreach, only iterables are supported\\.$#" count: 1 path: src/JsonSchema/SchemaStorage.php - - message: "#^Method JsonSchema\\\\SchemaStorage\\:\\:addSchema\\(\\) has no return type specified\\.$#" + message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" count: 1 path: src/JsonSchema/SchemaStorage.php - - message: "#^Method JsonSchema\\\\SchemaStorage\\:\\:expandRefs\\(\\) has no return type specified\\.$#" + message: "#^Call to function is_array\\(\\) with object will always evaluate to false\\.$#" count: 1 path: src/JsonSchema/SchemaStorage.php @@ -695,11 +690,6 @@ parameters: count: 1 path: src/JsonSchema/SchemaStorage.php - - - message: "#^Method JsonSchema\\\\SchemaStorageInterface\\:\\:addSchema\\(\\) has no return type specified\\.$#" - count: 1 - path: src/JsonSchema/SchemaStorageInterface.php - - message: "#^Method JsonSchema\\\\Uri\\\\Retrievers\\\\Curl\\:\\:fetchMessageBody\\(\\) has no return type specified\\.$#" count: 1 diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 0960889d..a8bb700b 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -45,7 +45,7 @@ public function getUriResolver() /** * {@inheritdoc} */ - public function addSchema($id, $schema = null): void + public function addSchema(string $id, $schema = null): void { if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { // if the schema was user-provided to Validator and is still null, then assume this is @@ -177,9 +177,12 @@ public function resolveRefSchema($refSchema, $resolveStack = []) return $refSchema; } + /** + * @param mixed $schema + */ private function addSubschemas($schema, string $parentId): void { - if (!is_object($schema) && !is_array($schema)) { + if (! $schema instanceof \stdClass && !is_array($schema)) { return; } From 5137da40abe2245f94548e9a6b0cd282bac2c93b Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Fri, 23 May 2025 15:13:26 +0200 Subject: [PATCH 5/8] docs: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 406d6a4a..24170612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed - Fix objects are non-unique despite key order ([#819](https://github.com/jsonrainbow/json-schema/pull/819)) +- Id's not being resolved and id property affects sibling ref which it should not do ([#828](https://github.com/jsonrainbow/json-schema/pull/828)) ### Changed - Added extra breaking change to UPDATE-6.0.md regarding BaseConstraint::addError signature change ([#823](https://github.com/jsonrainbow/json-schema/pull/823)) From bac73b242e0d2f9b8a724f2a3662cb91a8a75f16 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Fri, 23 May 2025 15:29:05 +0200 Subject: [PATCH 6/8] style: correct code style violations --- src/JsonSchema/SchemaStorage.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index a8bb700b..16288999 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -81,9 +81,9 @@ public function addSchema(string $id, $schema = null): void /** * Recursively resolve all references against the provided base * - * @param mixed $schema + * @param mixed $schema */ - private function expandRefs(&$schema, string $parentId = null): void + private function expandRefs(&$schema, ?string $parentId = null): void { if (!is_object($schema)) { if (is_array($schema)) { @@ -182,7 +182,7 @@ public function resolveRefSchema($refSchema, $resolveStack = []) */ private function addSubschemas($schema, string $parentId): void { - if (! $schema instanceof \stdClass && !is_array($schema)) { + if (!$schema instanceof \stdClass && !is_array($schema)) { return; } From 55c74904b8d244ff43985c273f2fefb7371cde86 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Tue, 27 May 2025 20:51:03 +0200 Subject: [PATCH 7/8] test: copy test from json schema test suite --- tests/Drafts/Draft3Test.php | 54 +++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/Drafts/Draft3Test.php b/tests/Drafts/Draft3Test.php index 42982062..91c5f6da 100644 --- a/tests/Drafts/Draft3Test.php +++ b/tests/Drafts/Draft3Test.php @@ -9,6 +9,10 @@ namespace JsonSchema\Tests\Drafts; +use JsonSchema\Constraints\Factory; +use JsonSchema\SchemaStorage; +use JsonSchema\Validator; + /** * @package JsonSchema\Tests\Drafts */ @@ -17,6 +21,56 @@ class Draft3Test extends BaseDraftTestCase protected $schemaSpec = 'http://json-schema.org/draft-03/schema#'; protected $validateSchema = true; + /** + * This test is a copy of https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft3/ref.json#L203-L225 + * + * @todo cleanup when #821 gets merged + * + * @param mixed $data + * @dataProvider refPreventsASiblingIdFromChangingTheBaseUriProvider + */ + public function testRefPreventsASiblingIdFromChangingTheBaseUriProvider($data, bool $expectedResult): void + { + $schema = json_decode(<<<'JSON' + { + "id": "http://localhost:1234/sibling_id/base/", + "definitions": { + "foo": { + "id": "http://localhost:1234/sibling_id/foo.json", + "type": "string" + }, + "base_foo": { + "$comment": "this canonical uri is http://localhost:1234/sibling_id/base/foo.json", + "id": "foo.json", + "type": "number" + } + }, + "extends": [ + { + "$comment": "$ref resolves to http://localhost:1234/sibling_id/base/foo.json, not http://localhost:1234/sibling_id/foo.json", + "id": "http://localhost:1234/sibling_id/", + "$ref": "foo.json" + } + ] + } +JSON + , false); + + $schemaStorage = new SchemaStorage(); + $schemaStorage->addSchema(property_exists($schema, 'id') ? $schema->id : 'internal://mySchema', $schema); + $validator = new Validator(new Factory($schemaStorage)); + $validator->validate($data, $schema); + + self::assertEquals($expectedResult, $validator->isValid()); + } + + public function refPreventsASiblingIdFromChangingTheBaseUriProvider(): \Generator + { + yield '$ref resolves to /definitions/base_foo, data does not validate' => ['data' => 'a', 'valid' => false]; + yield '$ref resolves to /definitions/base_foo, data validate' => ['data' => 1, 'valid' => true]; + } + + /** * {@inheritdoc} */ From a85ac5996d83010ecf8d842a69a8058d1d9e5e08 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Tue, 3 Jun 2025 20:14:11 +0200 Subject: [PATCH 8/8] fix: dont register schema or expand refs when found in enum or const --- src/JsonSchema/SchemaStorage.php | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 16288999..e7e48ef0 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -70,7 +70,7 @@ public function addSchema(string $id, $schema = null): void } } - $this->addSubschemas($schema, $id); + $this->scanForSubschemas($schema, $id); // resolve references $this->expandRefs($schema, $id); @@ -100,7 +100,12 @@ private function expandRefs(&$schema, ?string $parentId = null): void $schema->{'$ref'} = (string) $refPointer; } - foreach ($schema as &$member) { + foreach ($schema as $propertyName => &$member) { + if (in_array($propertyName, ['enum', 'const'])) { + // Enum and const don't allow $ref as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/445 + continue; + } + $childId = $parentId; if (property_exists($schema, 'id') && is_string($schema->id) && $childId !== $schema->id) { $childId = $this->uriResolver->resolve($schema->id, $childId); @@ -180,23 +185,28 @@ public function resolveRefSchema($refSchema, $resolveStack = []) /** * @param mixed $schema */ - private function addSubschemas($schema, string $parentId): void + private function scanForSubschemas($schema, string $parentId): void { if (!$schema instanceof \stdClass && !is_array($schema)) { return; } - foreach ($schema as $potentialSubSchema) { + foreach ($schema as $propertyName => $potentialSubSchema) { if (!is_object($potentialSubSchema)) { continue; } - // Found sub schema if (property_exists($potentialSubSchema, 'id') && is_string($potentialSubSchema->id) && property_exists($potentialSubSchema, 'type')) { - $this->addSchema($parentId . $potentialSubSchema->id, $potentialSubSchema); + // Enum and const don't allow id as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/471 + if (in_array($propertyName, ['enum', 'const'])) { + continue; + } + + // Found sub schema + $this->addSchema($this->uriResolver->resolve($potentialSubSchema->id, $parentId), $potentialSubSchema); } - $this->addSubschemas($potentialSubSchema, $parentId); + $this->scanForSubschemas($potentialSubSchema, $parentId); } } }