Skip to content

Commit

Permalink
Add dynamic URIs to Validator class
Browse files Browse the repository at this point in the history
  • Loading branch information
yakimun committed Aug 2, 2021
1 parent 05a18ea commit 0588fd8
Show file tree
Hide file tree
Showing 15 changed files with 262 additions and 71 deletions.
6 changes: 3 additions & 3 deletions src/ProcessedSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class ProcessedSchema
private array $identifiers;

/**
* @var list<SchemaReference>
* @var list<SchemaAnchor>
*/
private array $anchors;

Expand All @@ -34,7 +34,7 @@ final class ProcessedSchema
/**
* @param SchemaValidator $validator
* @param non-empty-list<SchemaIdentifier> $identifiers
* @param list<SchemaReference> $anchors
* @param list<SchemaAnchor> $anchors
* @param list<SchemaReference> $references
*/
public function __construct(SchemaValidator $validator, array $identifiers, array $anchors, array $references)
Expand Down Expand Up @@ -62,7 +62,7 @@ public function getIdentifiers(): array
}

/**
* @return list<SchemaReference>
* @return list<SchemaAnchor>
*/
public function getAnchors(): array
{
Expand Down
64 changes: 64 additions & 0 deletions src/SchemaAnchor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Yakimun\JsonSchemaValidator;

use Psr\Http\Message\UriInterface;

/**
* @psalm-immutable
*/
final class SchemaAnchor
{
/**
* @var UriInterface
*/
private UriInterface $uri;

/**
* @var bool
*/
private bool $dynamic;

/**
* @var JsonPointer
*/
private JsonPointer $path;

/**
* @param UriInterface $uri
* @param bool $dynamic
* @param JsonPointer $path
*/
public function __construct(UriInterface $uri, bool $dynamic, JsonPointer $path)
{
$this->uri = $uri;
$this->dynamic = $dynamic;
$this->path = $path;
}

/**
* @return UriInterface
*/
public function getUri(): UriInterface
{
return $this->uri;
}

/**
* @return bool
*/
public function isDynamic(): bool
{
return $this->dynamic;
}

/**
* @return JsonPointer
*/
public function getPath(): JsonPointer
{
return $this->path;
}
}
10 changes: 5 additions & 5 deletions src/SchemaContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class SchemaContext
private array $identifiers;

/**
* @var list<SchemaReference>
* @var list<SchemaAnchor>
*/
private array $anchors = [];

Expand Down Expand Up @@ -97,7 +97,7 @@ public function addIdentifier(UriInterface $identifier, string $token): void
}

/**
* @return list<SchemaReference>
* @return list<SchemaAnchor>
* @psalm-mutation-free
*/
public function getAnchors(): array
Expand All @@ -107,11 +107,12 @@ public function getAnchors(): array

/**
* @param UriInterface $anchor
* @param bool $dynamic
* @param string $token
*/
public function addAnchor(UriInterface $anchor, string $token): void
public function addAnchor(UriInterface $anchor, bool $dynamic, string $token): void
{
$this->anchors[] = new SchemaReference($anchor, $this->path->addTokens($token));
$this->anchors[] = new SchemaAnchor($anchor, $dynamic, $this->path->addTokens($token));
}

/**
Expand Down Expand Up @@ -188,7 +189,6 @@ public function createValidator($schema, string ...$tokens): SchemaValidator
* @param string $message
* @param string $token
* @return SchemaException
* @no-named-arguments
* @psalm-mutation-free
*/
public function createException(string $message, string $token): SchemaException
Expand Down
87 changes: 54 additions & 33 deletions src/SchemaProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,52 +34,73 @@ public function __construct(array $keywords)
public function process($schema, array $identifiers, JsonPointer $path): array
{
if (is_object($schema)) {
$properties = get_object_vars($schema);
return $this->processObject($schema, $identifiers, $path);
}

if (is_bool($schema)) {
return $this->processBoolean($schema, $identifiers);
}

if (!$properties) {
$identifier = end($identifiers);
$uri = $identifier->getUri();
$fragment = $identifier->getFragment();
throw new SchemaException(sprintf('The schema must be an object or a boolean. Path: "%s".', (string)$path));
}

/**
* @param object $schema
* @param non-empty-list<SchemaIdentifier> $identifiers
* @param JsonPointer $path
* @return non-empty-list<ProcessedSchema>
*/
public function processObject(object $schema, array $identifiers, JsonPointer $path): array
{
$properties = get_object_vars($schema);

return [new ProcessedSchema(new ObjectSchemaValidator($uri, $fragment, []), $identifiers, [], [])];
}
if (!$properties) {
$identifier = end($identifiers);
$uri = $identifier->getUri();
$fragment = $identifier->getFragment();

$context = new SchemaContext($this, $path, $identifiers);
return [new ProcessedSchema(new ObjectSchemaValidator($uri, $fragment, []), $identifiers, [], [])];
}

foreach (array_intersect_key($this->keywords, $properties) as $keyword) {
$keyword->process($properties, $context);
}
$context = new SchemaContext($this, $path, $identifiers);

$keywordValidators = $context->getKeywordValidators();
foreach (array_intersect_key($this->keywords, $properties) as $keyword) {
$keyword->process($properties, $context);
}

/** @var scalar|object|list<mixed>|null $value */
foreach (array_diff_key($properties, $this->keywords) as $name => $value) {
$keywordValidators[] = new UnknownKeywordValidator($name, $value);
}
$keywordValidators = $context->getKeywordValidators();

$processedIdentifiers = $context->getIdentifiers();
/** @var scalar|object|list<mixed>|null $value */
foreach (array_diff_key($properties, $this->keywords) as $name => $value) {
$keywordValidators[] = new UnknownKeywordValidator($name, $value);
}

$processedIdentifier = end($processedIdentifiers);
$processedUri = $processedIdentifier->getUri();
$processedFragment = $processedIdentifier->getFragment();
$processedIdentifiers = $context->getIdentifiers();

$validator = new ObjectSchemaValidator($processedUri, $processedFragment, $keywordValidators);
$anchors = $context->getAnchors();
$references = $context->getReferences();
$processedIdentifier = end($processedIdentifiers);
$processedUri = $processedIdentifier->getUri();
$processedFragment = $processedIdentifier->getFragment();

$processedSchema = new ProcessedSchema($validator, $processedIdentifiers, $anchors, $references);
$validator = new ObjectSchemaValidator($processedUri, $processedFragment, $keywordValidators);
$anchors = $context->getAnchors();
$references = $context->getReferences();

return [$processedSchema, ...$context->getProcessedSchemas()];
}
$processedSchema = new ProcessedSchema($validator, $processedIdentifiers, $anchors, $references);

if (is_bool($schema)) {
$identifier = end($identifiers);
$uri = $identifier->getUri();
$fragment = $identifier->getFragment();
return [$processedSchema, ...$context->getProcessedSchemas()];
}

return [new ProcessedSchema(new BooleanSchemaValidator($uri, $fragment, $schema), $identifiers, [], [])];
}
/**
* @param bool $schema
* @param non-empty-list<SchemaIdentifier> $identifiers
* @return non-empty-list<ProcessedSchema>
*/
private function processBoolean(bool $schema, array $identifiers): array
{
$identifier = end($identifiers);
$uri = $identifier->getUri();
$fragment = $identifier->getFragment();

throw new SchemaException(sprintf('The schema must be an object or a boolean. Path: "%s".', (string)$path));
return [new ProcessedSchema(new BooleanSchemaValidator($uri, $fragment, $schema), $identifiers, [], [])];
}
}
10 changes: 9 additions & 1 deletion src/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ final class Validator
private array $schemaValidators;

/**
* @var list<string>
*/
private array $dynamicUris;

/**
* @param SchemaValidator $schemaValidator
* @param array<string, SchemaValidator> $schemaValidators
* @param list<string> $dynamicUris
*/
public function __construct(SchemaValidator $schemaValidator, array $schemaValidators)
public function __construct(SchemaValidator $schemaValidator, array $schemaValidators, array $dynamicUris)
{
$this->schemaValidator = $schemaValidator;
$this->schemaValidators = $schemaValidators;
$this->dynamicUris = $dynamicUris;
}
}
21 changes: 13 additions & 8 deletions src/ValidatorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,26 @@ public function createValidator($schema, UriInterface $uri): Validator
}

$schemaValidators = [];
$dynamicUris = [];

foreach (array_intersect_key($validators, $references) as $validatorUri => $validator) {
$schemaValidators[$validatorUri] = $validator[0];

if ($validator[1]) {
$dynamicUris[] = $validatorUri;
}
}

return new Validator(reset($validators)[0], $schemaValidators);
return new Validator(reset($validators)[0], $schemaValidators, $dynamicUris);
}

/**
* @param mixed $schema
* @param UriInterface $uri
* @param array<string, array{SchemaValidator, UriInterface, JsonPointer}> $validators
* @param array<string, array{SchemaValidator, bool, UriInterface, JsonPointer}> $validators
* @param array<string, array{UriInterface, UriInterface, JsonPointer}> $references
* @return array{
* non-empty-array<string, array{SchemaValidator, UriInterface, JsonPointer}>,
* non-empty-array<string, array{SchemaValidator, bool, UriInterface, JsonPointer}>,
* array<string, array{UriInterface, UriInterface, JsonPointer}>
* }
*/
Expand All @@ -134,8 +139,8 @@ private function processSchema($schema, UriInterface $uri, array $validators, ar
if (array_key_exists($identifierUriString, $validators)) {
$uriString = (string)$uri;
$identifierPathString = (string)$identifierPath;
$existingValidatorUriString = (string)$validators[$identifierUriString][1];
$existingValidatorPathString = (string)$validators[$identifierUriString][2];
$existingValidatorUriString = (string)$validators[$identifierUriString][2];
$existingValidatorPathString = (string)$validators[$identifierUriString][3];

$format = 'The schemas "%s" and "%s" must have different identifiers. Paths: "%s" and "%s".';
$message = sprintf(
Expand All @@ -148,7 +153,7 @@ private function processSchema($schema, UriInterface $uri, array $validators, ar
throw new ValidatorFactoryException($message);
}

$validators[$identifierUriString] = [$validator, $uri, $identifierPath];
$validators[$identifierUriString] = [$validator, false, $uri, $identifierPath];
}

foreach ($processedSchema->getAnchors() as $anchor) {
Expand All @@ -158,14 +163,14 @@ private function processSchema($schema, UriInterface $uri, array $validators, ar
if (array_key_exists($anchorUriString, $validators)) {
$uriString = (string)$uri;
$anchorPathString = (string)$anchor->getPath();
$existingValidatorPathString = (string)$validators[$anchorUriString][2];
$existingValidatorPathString = (string)$validators[$anchorUriString][3];

$format = 'The "%s" schema must not contain the same anchors. Paths: "%s" and "%s".';
$message = sprintf($format, $uriString, $existingValidatorPathString, $anchorPathString);
throw new ValidatorFactoryException($message);
}

$validators[$anchorUriString] = [$validator, $uri, $anchorPath];
$validators[$anchorUriString] = [$validator, $anchor->isDynamic(), $uri, $anchorPath];
}

foreach ($processedSchema->getReferences() as $reference) {
Expand Down
2 changes: 1 addition & 1 deletion src/Vocabulary/CoreVocabulary/Keyword/AnchorKeyword.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ public function process(array $properties, SchemaContext $context): void

$identifiers = $context->getIdentifiers();

$context->addAnchor(end($identifiers)->getUri()->withFragment($property), self::NAME);
$context->addAnchor(end($identifiers)->getUri()->withFragment($property), false, self::NAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function process(array $properties, SchemaContext $context): void

$identifiers = $context->getIdentifiers();

$context->addAnchor(end($identifiers)->getUri()->withFragment($property), self::NAME);
$context->addAnchor(end($identifiers)->getUri()->withFragment($property), true, self::NAME);
$context->addKeywordValidator(new DynamicAnchorKeywordValidator($property));
}
}
8 changes: 5 additions & 3 deletions tests/Unit/ProcessedSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
use PHPUnit\Framework\TestCase;
use Yakimun\JsonSchemaValidator\JsonPointer;
use Yakimun\JsonSchemaValidator\ProcessedSchema;
use Yakimun\JsonSchemaValidator\SchemaAnchor;
use Yakimun\JsonSchemaValidator\SchemaIdentifier;
use Yakimun\JsonSchemaValidator\SchemaReference;
use Yakimun\JsonSchemaValidator\SchemaValidator\SchemaValidator;

/**
* @covers \Yakimun\JsonSchemaValidator\ProcessedSchema
* @uses \Yakimun\JsonSchemaValidator\JsonPointer
* @uses \Yakimun\JsonSchemaValidator\SchemaAnchor
* @uses \Yakimun\JsonSchemaValidator\SchemaIdentifier
* @uses \Yakimun\JsonSchemaValidator\SchemaReference
*/
Expand All @@ -31,9 +33,9 @@ final class ProcessedSchemaTest extends TestCase
private SchemaIdentifier $identifier;

/**
* @var SchemaReference
* @var SchemaAnchor
*/
private SchemaReference $anchor;
private SchemaAnchor $anchor;

/**
* @var SchemaReference
Expand All @@ -52,7 +54,7 @@ protected function setUp(): void

$this->validator = $this->createStub(SchemaValidator::class);
$this->identifier = new SchemaIdentifier($uri, $pointer, $pointer);
$this->anchor = new SchemaReference($uri->withFragment('a'), $pointer->addTokens('$anchor'));
$this->anchor = new SchemaAnchor($uri->withFragment('a'), false, $pointer->addTokens('$anchor'));
$this->reference = new SchemaReference($uri, $pointer->addTokens('$ref'));

$anchors = [$this->anchor];
Expand Down
Loading

0 comments on commit 0588fd8

Please sign in to comment.