From b547618c80e127de9589c4a908576cb79b992a80 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Wed, 26 Feb 2025 10:14:32 +0100 Subject: [PATCH 1/5] fix: Create deep copy before checking each sub schema in oneOf --- .../Constraints/UndefinedConstraint.php | 15 ++-- src/JsonSchema/Tool/DeepCopy.php | 37 ++++++++++ tests/Constraints/UndefinedConstraintTest.php | 73 +++++++++++++++++++ tests/Tool/DeepCopyTest.php | 53 ++++++++++++++ 4 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 src/JsonSchema/Tool/DeepCopy.php create mode 100644 tests/Constraints/UndefinedConstraintTest.php create mode 100644 tests/Tool/DeepCopyTest.php diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index 4d955cff..3bbd9847 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -15,6 +15,7 @@ use JsonSchema\Constraints\TypeCheck\LooseTypeCheck; use JsonSchema\Entity\JsonPointer; use JsonSchema\Exception\ValidationException; +use JsonSchema\Tool\DeepCopy; use JsonSchema\Uri\UriResolver; /** @@ -352,14 +353,17 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i if (isset($schema->oneOf)) { $allErrors = []; - $matchedSchemas = 0; + $matchedSchemas = []; $startErrors = $this->getErrors(); + foreach ($schema->oneOf as $oneOf) { try { $this->errors = []; - $this->checkUndefined($value, $oneOf, $path, $i); - if (count($this->getErrors()) == 0) { - $matchedSchemas++; + + $valueDeepCopy = DeepCopy::copyOf($value); + $this->checkUndefined($valueDeepCopy, $oneOf, $path, $i); + if (count($this->getErrors()) === 0) { + $matchedSchemas[] = ['schema' => $oneOf, 'value' => $valueDeepCopy]; } $allErrors = array_merge($allErrors, array_values($this->getErrors())); } catch (ValidationException $e) { @@ -367,11 +371,12 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i // other schema options in the OneOf field. } } - if ($matchedSchemas !== 1) { + if (count($matchedSchemas) !== 1) { $this->addErrors(array_merge($allErrors, $startErrors)); $this->addError(ConstraintError::ONE_OF(), $path); } else { $this->errors = $startErrors; + $value = $matchedSchemas[0]['value']; } } } diff --git a/src/JsonSchema/Tool/DeepCopy.php b/src/JsonSchema/Tool/DeepCopy.php new file mode 100644 index 00000000..fe4fa798 --- /dev/null +++ b/src/JsonSchema/Tool/DeepCopy.php @@ -0,0 +1,37 @@ + + */ + public function getValidTests(): array + { + return [ + 'oneOf with type coercion should not affect value passed to each sub schema (#790)' => [ + 'input' => << << Constraint::CHECK_MODE_COERCE_TYPES + ] + ]; + } +} diff --git a/tests/Tool/DeepCopyTest.php b/tests/Tool/DeepCopyTest.php new file mode 100644 index 00000000..4dcfce76 --- /dev/null +++ b/tests/Tool/DeepCopyTest.php @@ -0,0 +1,53 @@ + 'bar']; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + self::assertNotSame($input, $result); + } + + public function testCanDeepCopyObjectWithChildObject(): void + { + $child = (object) ['bar' => 'baz']; + $input = (object) ['foo' => $child]; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + self::assertNotSame($input, $result); + self::assertEquals($input->foo, $result->foo); + self::assertNotSame($input->foo, $result->foo); + } + + public function testCanDeepCopyArray(): void + { + $input = ['foo' => 'bar']; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + } + + public function testCanDeepCopyArrayWithNestedArray(): void + { + $nested = ['bar' => 'baz']; + $input = ['foo' => $nested]; + + $result = DeepCopy::copyOf($input); + + self::assertEquals($input, $result); + } +} From f9a46e49839002b244e84843b3da6312fca63394 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Wed, 26 Feb 2025 10:20:03 +0100 Subject: [PATCH 2/5] style: correct code style violations --- src/JsonSchema/Constraints/UndefinedConstraint.php | 1 - src/JsonSchema/Tool/DeepCopy.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index 3bbd9847..c1fa9612 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -355,7 +355,6 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i $allErrors = []; $matchedSchemas = []; $startErrors = $this->getErrors(); - foreach ($schema->oneOf as $oneOf) { try { $this->errors = []; diff --git a/src/JsonSchema/Tool/DeepCopy.php b/src/JsonSchema/Tool/DeepCopy.php index fe4fa798..8b09156f 100644 --- a/src/JsonSchema/Tool/DeepCopy.php +++ b/src/JsonSchema/Tool/DeepCopy.php @@ -11,6 +11,7 @@ class DeepCopy { /** * @param mixed $input + * * @return mixed */ public static function copyOf($input) From b3579d6205ccd3ce4978fa07416818f0d95e7943 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Wed, 26 Feb 2025 10:28:10 +0100 Subject: [PATCH 3/5] test: correct heredoc/nowdoc syntax --- tests/Constraints/UndefinedConstraintTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Constraints/UndefinedConstraintTest.php b/tests/Constraints/UndefinedConstraintTest.php index b8823a8a..a996be26 100644 --- a/tests/Constraints/UndefinedConstraintTest.php +++ b/tests/Constraints/UndefinedConstraintTest.php @@ -33,7 +33,8 @@ public function getValidTests(): array } ] } -JSON, +JSON + , 'schema' => << Constraint::CHECK_MODE_COERCE_TYPES ] ]; From 9ab6369b0c0c6b818b423444dbf668ed172147f2 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Wed, 26 Feb 2025 10:35:48 +0100 Subject: [PATCH 4/5] refactor: only make a deep copy if type coercion is enabled --- src/JsonSchema/Constraints/UndefinedConstraint.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/JsonSchema/Constraints/UndefinedConstraint.php b/src/JsonSchema/Constraints/UndefinedConstraint.php index c1fa9612..9b6c68ad 100644 --- a/src/JsonSchema/Constraints/UndefinedConstraint.php +++ b/src/JsonSchema/Constraints/UndefinedConstraint.php @@ -355,14 +355,16 @@ protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i $allErrors = []; $matchedSchemas = []; $startErrors = $this->getErrors(); + $coerce = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES); + foreach ($schema->oneOf as $oneOf) { try { $this->errors = []; - $valueDeepCopy = DeepCopy::copyOf($value); - $this->checkUndefined($valueDeepCopy, $oneOf, $path, $i); + $oneOfValue = $coerce ? DeepCopy::copyOf($value) : $value; + $this->checkUndefined($oneOfValue, $oneOf, $path, $i); if (count($this->getErrors()) === 0) { - $matchedSchemas[] = ['schema' => $oneOf, 'value' => $valueDeepCopy]; + $matchedSchemas[] = ['schema' => $oneOf, 'value' => $oneOfValue]; } $allErrors = array_merge($allErrors, array_values($this->getErrors())); } catch (ValidationException $e) { From 14af1d8fed08af9fd730be5c15bb4e202817c299 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Wed, 26 Feb 2025 12:02:14 +0100 Subject: [PATCH 5/5] docs: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da1cc799..54c0f46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Add required permissions for welcome action ([#789](https://github.com/jsonrainbow/json-schema/pull/789)) - Upgrade php cs fixer to latest ([#783](https://github.com/jsonrainbow/json-schema/pull/783)) +- Create deep copy before checking each sub schema in oneOf ([#791](https://github.com/jsonrainbow/json-schema/pull/791)) ### Changed - Used PHPStan's int-mask-of type where applicable ([#779](https://github.com/jsonrainbow/json-schema/pull/779))