-
-
Notifications
You must be signed in to change notification settings - Fork 373
feat: support unevaluatedProperties in Draft 2019-09 #931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6678519
fd21c59
4bbddfa
f6bf33a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,112 @@ | ||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| declare(strict_types=1); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| namespace JsonSchema\Constraints\Drafts\Draft2019; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| use JsonSchema\ConstraintError; | ||||||||||||||||||||||||||||||||||
| use JsonSchema\Constraints\ConstraintInterface; | ||||||||||||||||||||||||||||||||||
| use JsonSchema\Entity\ErrorBagProxy; | ||||||||||||||||||||||||||||||||||
| use JsonSchema\Entity\JsonPointer; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Proof-of-concept support for unevaluatedProperties. | ||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Before merging we should remove the proof on concept comment.
Suggested change
|
||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * The current validator does not carry annotations between applicators, so this | ||||||||||||||||||||||||||||||||||
| * first implementation derives the evaluated property names from properties, | ||||||||||||||||||||||||||||||||||
| * patternProperties, and allOf branches in the current schema. | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+17
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: The comment doesn't need to inform about this being the first implementation
Suggested change
|
||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| class UnevaluatedPropertiesConstraint implements ConstraintInterface | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| use ErrorBagProxy; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** @var Factory */ | ||||||||||||||||||||||||||||||||||
| private $factory; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| public function __construct(?Factory $factory = null) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| $this->factory = $factory ?: new Factory(); | ||||||||||||||||||||||||||||||||||
| $this->initialiseErrorBag($this->factory); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| if (!is_object($schema) || !property_exists($schema, 'unevaluatedProperties') || !is_object($value)) { | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if ($schema->unevaluatedProperties === true) { | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||
| $evaluated = $this->collectEvaluatedProperties($schema, $value); | ||||||||||||||||||||||||||||||||||
| $unevaluated = array_diff_key(get_object_vars($value), array_flip($evaluated)); | ||||||||||||||||||||||||||||||||||
| if (!$unevaluated) { | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $basePath = $path ?? new JsonPointer(''); | ||||||||||||||||||||||||||||||||||
| foreach ($unevaluated as $propertyName => $propertyValue) { | ||||||||||||||||||||||||||||||||||
| $propertyPath = $basePath->withPropertyPaths(array_merge($basePath->getPropertyPaths(), [$propertyName])); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (is_object($schema->unevaluatedProperties)) { | ||||||||||||||||||||||||||||||||||
| $propertyConstraint = $this->factory->createInstanceFor('schema'); | ||||||||||||||||||||||||||||||||||
| $propertyConstraint->check($propertyValue, $schema->unevaluatedProperties, $propertyPath, $i); | ||||||||||||||||||||||||||||||||||
| if ($propertyConstraint->isValid()) { | ||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $this->addErrors($propertyConstraint->getErrors()); | ||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $this->addError(ConstraintError::UNEVALUATED_PROPERTIES(), $propertyPath, ['found' => $propertyName]); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * @param object $schema | ||||||||||||||||||||||||||||||||||
| * @param object $value | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * @return array<int, string> | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| private function collectEvaluatedProperties($schema, object $value): array | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| if (!is_object($schema)) { | ||||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+78
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Since
Suggested change
|
||||||||||||||||||||||||||||||||||
| $evaluated = []; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (isset($schema->properties) && is_object($schema->properties)) { | ||||||||||||||||||||||||||||||||||
| $evaluated = array_merge($evaluated, array_keys(get_object_vars($schema->properties))); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (isset($schema->patternProperties) && is_object($schema->patternProperties)) { | ||||||||||||||||||||||||||||||||||
| foreach (get_object_vars($value) as $propertyName => $_) { | ||||||||||||||||||||||||||||||||||
| foreach (array_keys(get_object_vars($schema->patternProperties)) as $pattern) { | ||||||||||||||||||||||||||||||||||
| if (preg_match($this->createPregMatchPattern($pattern), (string) $propertyName)) { | ||||||||||||||||||||||||||||||||||
| $evaluated[] = $propertyName; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (isset($schema->allOf) && is_array($schema->allOf)) { | ||||||||||||||||||||||||||||||||||
| foreach ($schema->allOf as $branch) { | ||||||||||||||||||||||||||||||||||
| $evaluated = array_merge($evaluated, $this->collectEvaluatedProperties($branch, $value)); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: This should include (either now or at a later state) |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return array_values(array_unique($evaluated)); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| private function createPregMatchPattern(string $pattern): string | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| $pattern = str_replace('\\p{digit}', '\\p{Nd}', $pattern); | ||||||||||||||||||||||||||||||||||
| $pattern = str_replace('\\p{Letter}', '\\p{L}', $pattern); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return '/' . str_replace('/', '\\/', $pattern) . '/u'; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: I think this file would belong in |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace JsonSchema\Tests\Constraints; | ||
|
|
||
| use JsonSchema\DraftIdentifiers; | ||
| use JsonSchema\Constraints\Constraint; | ||
|
|
||
| class UnevaluatedPropertiesTest extends BaseTestCase | ||
| { | ||
| protected $schemaSpec = DraftIdentifiers::DRAFT_2019_09; | ||
|
|
||
| public function getInvalidTests(): \Generator | ||
| { | ||
| yield [ | ||
| '{"hello":"world","world":"hello","unexpected":true}', | ||
| '{ | ||
| "$schema":"' . DraftIdentifiers::DRAFT_2019_09 . '", | ||
| "type":"object", | ||
| "unevaluatedProperties":false, | ||
| "allOf":[ | ||
| {"properties":{"hello":{"type":"string"}},"required":["hello"]}, | ||
| {"properties":{"world":{"type":"string"}},"required":["world"]} | ||
| ] | ||
| }', | ||
| Constraint::CHECK_MODE_STRICT, | ||
| ]; | ||
| } | ||
|
|
||
| public function getValidTests(): \Generator | ||
| { | ||
| yield [ | ||
| '{"hello":"world","world":"hello"}', | ||
| '{ | ||
| "$schema":"' . DraftIdentifiers::DRAFT_2019_09 . '", | ||
| "type":"object", | ||
| "unevaluatedProperties":false, | ||
| "allOf":[ | ||
| {"properties":{"hello":{"type":"string"}},"required":["hello"]}, | ||
| {"properties":{"world":{"type":"string"}},"required":["world"]} | ||
| ] | ||
| }', | ||
| Constraint::CHECK_MODE_STRICT, | ||
| ]; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: With this keyword now supported we should alos enable the test in
json-schema/tests/JsonSchemaTestSuiteTest.php
Lines 206 to 245 in bbe268a