Skip to content

Commit 2fe7065

Browse files
committed
Subject: BREAKING: Changed RuntimeEvaluationResult to distinguish between annotations and errors
Body: - Public property "type" added to RuntimeEvaluationResult, which can have the value "error" or "annotation" - Failed applicator keywords now create RuntimeEvaluationResults with type "error" (Note: The "error" property would be empty in this case in order to be able to ignore these errors, as they are not as relevant or informative as the errors of sub-schemas) - Renamed property "annotation", method setAnnotation() and method getAnnotation() of RuntimeEvaluationResult to "annotationValue", setAnnotationValue() and getAnnotationValue() - Removed property "suppressAnnotation" of RuntimeEvaluationResult as no longer required - Removed property "evaluationResult", method setAnnotationValue() and method getAnnotationValue() of RuntimeEvaluationResult as no longer required - The keyword value is now passed to RuntimeEvaluationContext::createResultForKeyword() method (must be passed through to RuntimeEvaluationResult) - The keyword value is now passed to RuntimeEvaluationResult::__construct() method (as additional information and for annotation default values in output units of output formats) - BasicOutput now returns the proper structure, returns the annotations for succeeded validations and each output unit contains now the "type" property, which can have the value "annotation" or "error" Resolves #1
1 parent 5d65058 commit 2fe7065

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+278
-206
lines changed

README.md

+28-15
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ $valid = $evaluator->evaluate(
7373

7474
foreach ($results as $result) {
7575
/** @var $result \Ropi\JsonSchemaEvaluator\EvaluationContext\RuntimeEvaluationResult */
76-
if ($result->error && !$result->suppressAnnotation) {
76+
if ($result->type === 'error') {
7777
echo "Error keyword location: '{$result->keywordLocation}'\n";
7878
echo "Error instance location: '{$result->instanceLocation}'\n";
7979
echo "Error message: {$result->error}\n";
@@ -87,25 +87,38 @@ Error instance location: ''
8787
Error message: At most 5 characters are allowed, but there are 10.
8888
```
8989
### Formatting results
90-
In the following example, the results are formatted as [Basic Output Structure](https://json-schema.org/draft/2020-12/json-schema-core#name-basic).
90+
In the following example, the results are formatted as [Basic Output Structure](https://json-schema.org/draft/2020-12/json-schema-core#name-basic). In addition, only the [Flag Output Structure](https://json-schema.org/draft/2020-12/json-schema-core#name-flag) is also currently supported.
9191
```php
9292
$formattedResults = (new \Ropi\JsonSchemaEvaluator\Output\BasicOutput($valid, $results))->format();
9393
echo json_encode($formattedResults, JSON_PRETTY_PRINT);
9494
```
9595
Output of above example:
96-
```json
96+
```json
9797
{
98-
"valid": false,
99-
"errors": [
100-
{
101-
"valid": false,
102-
"keywordLocation": "\/maxLength",
103-
"instanceLocation": "",
104-
"keywordName": "maxLength",
105-
"error": "At most 5 characters are allowed, but there are 10",
106-
"errorMeta": null
107-
}
108-
]
98+
"valid": false,
99+
"errors": [
100+
{
101+
"type": "annotation",
102+
"valid": true,
103+
"keywordLocation": "\/type",
104+
"instanceLocation": "",
105+
"keywordName": "type",
106+
"error": "",
107+
"errorMeta": null,
108+
"annotation": [
109+
"string"
110+
]
111+
},
112+
{
113+
"type": "error",
114+
"valid": false,
115+
"keywordLocation": "\/maxLength",
116+
"instanceLocation": "",
117+
"keywordName": "maxLength",
118+
"error": "At most 5 characters are allowed, but there are 10.",
119+
"errorMeta": null
120+
}
121+
]
109122
}
110123
```
111124
## Mutations
@@ -258,7 +271,7 @@ class Md5HashKeyword extends \Ropi\JsonSchemaEvaluator\Keyword\AbstractKeyword i
258271
return null;
259272
}
260273

261-
$result = $context->createResultForKeyword($this);
274+
$result = $context->createResultForKeyword($this, $keywordValue);
262275

263276
if (md5($instance) !== $keywordValue) {
264277
$result->invalidate('MD5 hash of "' . $instance . '" does not match ' . $keywordValue);

src/Draft/AbstractDraft.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Psr\Http\Message\UriInterface;
1010
use Ropi\JsonSchemaEvaluator\Draft\Exception\UnsupportedVocabularyException;
1111
use Ropi\JsonSchemaEvaluator\EvaluationContext\RuntimeEvaluationContext;
12+
use Ropi\JsonSchemaEvaluator\EvaluationContext\RuntimeEvaluationResult;
1213
use Ropi\JsonSchemaEvaluator\EvaluationContext\StaticEvaluationContext;
1314
use Ropi\JsonSchemaEvaluator\Keyword\KeywordInterface;
1415
use Ropi\JsonSchemaEvaluator\Keyword\MutationKeywordInterface;
@@ -216,7 +217,9 @@ public function evaluate(RuntimeEvaluationContext $context, bool $mutationsOnly
216217
if ($schema === false) {
217218
$lastResult = $context->getLastResult();
218219
if ($lastResult) {
219-
$context->createResultForKeyword($lastResult->keyword)->invalidate('Not allowed');
220+
$context
221+
->createResultForKeyword($lastResult->keyword, $lastResult->keywordValue)
222+
->invalidate('Not allowed');
220223
}
221224
}
222225

@@ -240,7 +243,7 @@ public function evaluate(RuntimeEvaluationContext $context, bool $mutationsOnly
240243
$context->pushSchema(keywordLocationFragment: $name);
241244

242245
$evaluationResult = $keyword->evaluate($schema->{$name}, $context);
243-
if ($evaluationResult && !$evaluationResult->valid) {
246+
if ($evaluationResult && $evaluationResult->type === RuntimeEvaluationResult::TYPE_ERROR) {
244247
$valid = false;
245248
}
246249

@@ -253,7 +256,7 @@ public function evaluate(RuntimeEvaluationContext $context, bool $mutationsOnly
253256

254257
if (!$valid) {
255258
// Annotations are not retained for failing schemas
256-
$context->suppressAnnotations($lastResultNumber);
259+
$context->discardAnnotationValues($lastResultNumber);
257260
}
258261

259262
return $valid;

src/EvaluationContext/RuntimeEvaluationContext.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ public function &getCurrentInstance(): mixed
8080
return $this->instanceStack[$this->instanceStackPointer]['instance'];
8181
}
8282

83-
public function createResultForKeyword(KeywordInterface $keyword): RuntimeEvaluationResult
83+
public function createResultForKeyword(KeywordInterface $keyword, mixed $keywordValue): RuntimeEvaluationResult
8484
{
8585
$result = new RuntimeEvaluationResult(
8686
++$this->lastResultNumber,
8787
$keyword,
88+
$keywordValue,
8889
$this->schemaStack[$this->schemaStackPointer]['keywordLocation'],
8990
$this->instanceStack[$this->instanceStackPointer]['instanceLocation'],
9091
$this->getCurrentAbsoluteKeywordLocation()
@@ -158,18 +159,19 @@ public function getResults(): array
158159
return $this->results;
159160
}
160161

161-
public function adoptResultsFromContext(RuntimeEvaluationContext $context): void
162+
public function adoptResultsFromContextAsAnnotations(RuntimeEvaluationContext $context): void
162163
{
163164
foreach ($context->getResults() as $result) {
165+
$result->type = RuntimeEvaluationResult::TYPE_ANNOTATION;
164166
$this->results[] = $result;
165167
}
166168
}
167169

168-
public function suppressAnnotations(?int $after = null): void
170+
public function discardAnnotationValues(?int $after = null): void
169171
{
170172
foreach ($this->results as $result) {
171173
if ($result->number > $after) {
172-
$result->suppressAnnotation = true;
174+
$result->setAnnotationValue(null);
173175
}
174176
}
175177
}

src/EvaluationContext/RuntimeEvaluationResult.php

+14-28
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,42 @@
77

88
class RuntimeEvaluationResult
99
{
10+
public const TYPE_ERROR = 'error';
11+
public const TYPE_ANNOTATION = 'annotation';
12+
13+
public string $type = self::TYPE_ANNOTATION;
1014
public bool $valid = true;
1115
public ?string $error = null;
1216
public mixed $errorMeta = null;
13-
private mixed $annotation = null;
14-
public ?bool $suppressAnnotation = false;
15-
private ?bool $evaluationResult = null;
17+
private mixed $annotationValue = null;
1618

1719
public function __construct(
1820
public readonly int $number,
1921
public readonly KeywordInterface $keyword,
22+
public readonly mixed $keywordValue,
2023
public readonly string $keywordLocation,
2124
public readonly string $instanceLocation,
2225
public readonly ?string $absoluteKeywordLocation
2326
) {}
2427

25-
public function setEvaluationResult(bool $evaluationResult): void
28+
public function setAnnotationValue(mixed $annotationValue): void
2629
{
27-
$this->evaluationResult = $evaluationResult;
28-
}
29-
30-
public function getEvaluationResult(): bool
31-
{
32-
if ($this->evaluationResult === null) {
33-
return $this->valid;
34-
}
35-
36-
return $this->evaluationResult;
30+
$this->annotationValue = $annotationValue;
3731
}
3832

39-
public function setAnnotation(mixed $annotation): void
33+
public function getAnnotationValue(): mixed
4034
{
41-
$this->annotation = $annotation;
42-
}
43-
44-
public function getAnnotation(bool $force = false): mixed
45-
{
46-
if (!$force && $this->suppressAnnotation) {
47-
return null;
48-
}
49-
50-
return $this->annotation;
35+
return $this->annotationValue;
5136
}
5237

53-
public function hasAnnotation(bool $force = false): bool
38+
public function hasAnnotationValue(): bool
5439
{
55-
return $this->getAnnotation($force) !== null;
40+
return $this->getAnnotationValue() !== null;
5641
}
5742

58-
public function invalidate(string $error, mixed $errorMeta = null): void
43+
public function invalidate(?string $error = null, mixed $errorMeta = null): void
5944
{
45+
$this->type = self::TYPE_ERROR;
6046
$this->error = $error;
6147
$this->errorMeta = $errorMeta;
6248
$this->valid = false;

src/Keyword/Applicator/AdditionalPropertiesKeyword.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
4747
return null;
4848
}
4949

50-
$result = $context->createResultForKeyword($this);
50+
$result = $context->createResultForKeyword($this, $keywordValue);
5151
$evaluatedPropertyNames = [];
5252

5353
foreach ($context->getResultsByKeywordName('properties') as $propertiesResult) {
54-
$propertiesAnnotation = $propertiesResult->getAnnotation();
54+
$propertiesAnnotation = $propertiesResult->getAnnotationValue();
5555
if (!is_array($propertiesAnnotation)) {
5656
continue;
5757
}
@@ -62,7 +62,7 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
6262
}
6363

6464
foreach ($context->getResultsByKeywordName('patternProperties') as $patternPropertiesResult) {
65-
$patternPropertiesAnnotation = $patternPropertiesResult->getAnnotation();
65+
$patternPropertiesAnnotation = $patternPropertiesResult->getAnnotationValue();
6666
if (!is_array($patternPropertiesAnnotation)) {
6767
continue;
6868
}
@@ -89,7 +89,7 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
8989
$context->popSchema();
9090

9191
if (!$valid) {
92-
$result->valid = false;
92+
$result->invalidate();
9393

9494
if ($context->draft->shortCircuit()) {
9595
break;
@@ -99,7 +99,7 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
9999
$additionalEvaluatedPropertyNames[$propertyName] = $propertyName;
100100
}
101101

102-
$result->setAnnotation($additionalEvaluatedPropertyNames);
102+
$result->setAnnotationValue($additionalEvaluatedPropertyNames);
103103

104104
return $result;
105105
}

src/Keyword/Applicator/AllOfKeyword.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
3333
{
3434
/** @var list<\stdClass|bool> $keywordValue */
3535

36-
$result = $context->createResultForKeyword($this);
36+
$result = $context->createResultForKeyword($this, $keywordValue);
3737
$numMatches = $this->evaluateOf($keywordValue, $context);
3838

3939
if ($numMatches !== count($keywordValue)) {

src/Keyword/Applicator/AnyOfKeyword.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
3333
{
3434
/** @var list<\stdClass|bool> $keywordValue */
3535

36-
$result = $context->createResultForKeyword($this);
36+
$result = $context->createResultForKeyword($this, $keywordValue);
3737
$numMatches = $this->evaluateOf($keywordValue, $context);
3838

3939
if (!$numMatches) {

src/Keyword/Applicator/ContainsKeyword.php

+12-8
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,36 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
4848
}
4949

5050
$currentSchema = $context->getCurrentSchema();
51-
$result = $context->createResultForKeyword($this);
51+
$result = $context->createResultForKeyword($this, $keywordValue);
5252

5353
$matchedIndexes = [];
5454

55+
$intermediateContext = clone $context;
56+
5557
foreach ($instance as $instanceIndex => &$instanceValue) {
56-
$context->pushSchema($keywordValue);
57-
$context->pushInstance($instanceValue, (string)$instanceIndex);
58+
$intermediateContext->pushSchema($keywordValue);
59+
$intermediateContext->pushInstance($instanceValue, (string)$instanceIndex);
5860

59-
$valid = $context->draft->evaluate($context);
61+
$valid = $context->draft->evaluate($intermediateContext);
6062

61-
$context->popInstance();
62-
$context->popSchema();
63+
$intermediateContext->popInstance();
64+
$intermediateContext->popSchema();
6365

6466
if ($valid) {
6567
$matchedIndexes[$instanceIndex] = $instanceIndex;
6668
}
6769
}
6870

71+
$context->adoptResultsFromContextAsAnnotations($intermediateContext);
72+
6973
if ($matchedIndexes) {
7074
ksort($matchedIndexes);
7175
} elseif (!isset($currentSchema->minContains) || $currentSchema->minContains > 0) {
72-
$result->invalidate('No item matches schema');
76+
$result->invalidate('No element matches schema');
7377
return $result;
7478
}
7579

76-
$result->setAnnotation((count($matchedIndexes) === count($instance)) ?: $matchedIndexes);
80+
$result->setAnnotationValue((count($matchedIndexes) === count($instance)) ?: $matchedIndexes);
7781

7882
return $result;
7983
}

src/Keyword/Applicator/DependentSchemasKeyword.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
6363
return null;
6464
}
6565

66-
$result = $context->createResultForKeyword($this);
66+
$result = $context->createResultForKeyword($this, $keywordValue);
6767

6868
foreach (get_object_vars($keywordValue) as $dependencyPropertyName => $dependentSchema) {
6969
/** @var \stdClass|bool $dependentSchema */
@@ -93,7 +93,7 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
9393
$context->popSchema();
9494

9595
if (!$valid) {
96-
$result->valid = false;
96+
$result->invalidate();
9797

9898
if ($context->draft->shortCircuit()) {
9999
break;

src/Keyword/Applicator/ElseKeyword.php

+5-3
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,16 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
4343
/** @var \stdClass|bool $keywordValue */
4444

4545
$ifResult = $context->getLastResultByKeywordLocation($context->getCurrentKeywordLocation(-1) . '/if');
46-
if (!$ifResult || $ifResult->getEvaluationResult()) {
46+
if (!$ifResult || $ifResult->valid) {
4747
return null;
4848
}
4949

50-
$result = $context->createResultForKeyword($this);
50+
$result = $context->createResultForKeyword($this, $keywordValue);
5151
$context->pushSchema($keywordValue);
5252

53-
$result->valid = $context->draft->evaluate($context);
53+
if (!$context->draft->evaluate($context)) {
54+
$result->invalidate();
55+
}
5456

5557
$context->popSchema();
5658

src/Keyword/Applicator/IfKeyword.php

+7-4
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,15 @@ public function evaluate(mixed $keywordValue, RuntimeEvaluationContext $context)
4242
{
4343
/** @var \stdClass|bool $keywordValue */
4444

45-
$context->pushSchema($keywordValue);
45+
$intermediateContext = clone $context;
46+
$intermediateContext->pushSchema($keywordValue);
4647

47-
$result = $context->createResultForKeyword($this);
48-
$result->setEvaluationResult($context->draft->evaluate($context));
48+
$result = $context->createResultForKeyword($this, $keywordValue);
49+
$result->valid = $context->draft->evaluate($intermediateContext);
4950

50-
$context->popSchema();
51+
$intermediateContext->popSchema();
52+
53+
$context->adoptResultsFromContextAsAnnotations($intermediateContext);
5154

5255
return $result;
5356
}

0 commit comments

Comments
 (0)