Skip to content

Commit

Permalink
Add keyword $tag
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominic Tubach committed Mar 21, 2024
1 parent 45653ea commit 98cb47b
Show file tree
Hide file tree
Showing 12 changed files with 603 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ The following additional keywords are provided:
* `maxDate`
* `minDate`
* `precision`
* `$tag` Tagged data can be fetched from a data container after validation.
* `$validations`

See [tests](tests/) for how to use them.

The [`SystopiaValidator`](./src/SystopiaValidator.php) already provides those
keywords. To use them in a different validator class you might want to use
[`SystopiaSchemaParser`](./src/Parsers/SystopiaSchemaParser.php) or
Expand Down
22 changes: 22 additions & 0 deletions src/Exceptions/InvalidArgumentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
/*
* Copyright 2024 SYSTOPIA GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

declare(strict_types=1);

namespace Systopia\JsonSchema\Exceptions;

class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface {}
56 changes: 56 additions & 0 deletions src/KeywordValidators/TagKeywordValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/*
* Copyright 2024 SYSTOPIA GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

declare(strict_types=1);

namespace Systopia\JsonSchema\KeywordValidators;

use Opis\JsonSchema\Errors\ValidationError;
use Opis\JsonSchema\JsonPointer;
use Opis\JsonSchema\KeywordValidators\AbstractKeywordValidator;
use Opis\JsonSchema\ValidationContext;
use Systopia\JsonSchema\Tags\TaggedDataContainerUtil;

final class TagKeywordValidator extends AbstractKeywordValidator
{
/**
* @var array<string, null|mixed>
*/
private array $tags;

/**
* @param array<string, null|mixed> $tags mapping of tag to extra data
*/
public function __construct(array $tags)
{
$this->tags = $tags;
}

public function validate(ValidationContext $context): ?ValidationError
{
// Run other validators first that might change the data.
$error = null === $this->next ? null : $this->next->validate($context);

$data = $context->currentData();
$dataPointer = JsonPointer::pathToString($context->currentDataPath());
foreach ($this->tags as $tag => $extra) {
TaggedDataContainerUtil::getTaggedDataContainer($context)->add($tag, $dataPointer, $data, $extra);
}

return $error;
}
}
3 changes: 3 additions & 0 deletions src/Keywords/SetValueTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public function setValue(ValidationContext $context, callable $transform): void
$path = $context->currentDataPath();
$target = &$this->getDataReference($context, $path);
$target = $transform($target);
if ([] === $path) {
$context->setCurrentData($target);
}
$this->resetCurrentData($context);
}

Expand Down
55 changes: 55 additions & 0 deletions src/Parsers/KeywordValidators/TagKeywordValidatorParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/*
* Copyright 2024 SYSTOPIA GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

declare(strict_types=1);

namespace Systopia\JsonSchema\Parsers\KeywordValidators;

use Opis\JsonSchema\Info\SchemaInfo;
use Opis\JsonSchema\KeywordValidator;
use Opis\JsonSchema\Parsers\KeywordValidatorParser;
use Opis\JsonSchema\Parsers\SchemaParser;
use Systopia\JsonSchema\KeywordValidators\TagKeywordValidator;

final class TagKeywordValidatorParser extends KeywordValidatorParser
{
public function __construct(string $keyword = '$tag')
{
parent::__construct($keyword);
}

public function parse(SchemaInfo $info, SchemaParser $parser, object $shared): ?KeywordValidator
{
if (!$this->keywordExists($info)) {
return null;
}

$tags = (array) $this->keywordValue($info);
$parsedTags = [];
foreach ($tags as $key => $value) {
if (\is_string($key)) {
$parsedTags[$key] = $value;
} elseif (\is_string($value)) {
$parsedTags[$value] = null;
} else {
throw $this->keywordException('Invalid value for keyword {keyword}', $info);
}
}

return [] === $parsedTags ? null : new TagKeywordValidator($parsedTags);
}
}
6 changes: 5 additions & 1 deletion src/Parsers/SystopiaVocabulary.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use Systopia\JsonSchema\Parsers\Keywords\ValidationsKeywordParser;
use Systopia\JsonSchema\Parsers\KeywordValidators\CalculateKeywordValidationParser;
use Systopia\JsonSchema\Parsers\KeywordValidators\CollectErrorsKeywordValidatorParser;
use Systopia\JsonSchema\Parsers\KeywordValidators\TagKeywordValidatorParser;
use Systopia\JsonSchema\Parsers\KeywordValidators\TypeKeywordValidatorParser;

/**
Expand Down Expand Up @@ -58,7 +59,10 @@ public function __construct(array $keywords = [], array $keywordValidators = [],
new TypeKeywordValidatorParser(),
],
$keywordValidators,
[new CalculateKeywordValidationParser()]
[
new CalculateKeywordValidationParser(),
new TagKeywordValidatorParser(),
]
);

parent::__construct($keywords, $keywordValidators, $pragmas);
Expand Down
70 changes: 70 additions & 0 deletions src/Tags/DummyTaggedDataContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
/*
* Copyright 2022 SYSTOPIA GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

declare(strict_types=1);

namespace Systopia\JsonSchema\Tags;

/**
* @codeCoverageIgnore
*/
final class DummyTaggedDataContainer implements TaggedDataContainerInterface
{
private static self $instance;

public static function getInstance(): self
{
return self::$instance ??= new self();
}

public function add(string $tag, string $dataPointer, $data, $extra): void {}

public function get(string $tag, string $dataPointer)
{
return null;
}

public function has(string $tag, string $dataPointer): bool
{
return false;
}

public function getAll(): array
{
return [];
}

public function getByTag(string $tag): array
{
return [];
}

public function hasTag(string $tag): bool
{
return false;
}

public function getExtra(string $tag, string $dataPointer)
{
return null;
}

public function hasExtra(string $tag, string $dataPointer): bool
{
return false;
}
}
80 changes: 80 additions & 0 deletions src/Tags/TaggedDataContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
/*
* Copyright 2022 SYSTOPIA GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

declare(strict_types=1);

namespace Systopia\JsonSchema\Tags;

use Systopia\JsonSchema\Exceptions\InvalidArgumentException;

final class TaggedDataContainer implements TaggedDataContainerInterface
{
/**
* @var array<string, array<string, mixed>>
*/
private array $data = [];

/**
* @var array<string, array<string, mixed>>
*/
private array $extra = [];

public function add(string $tag, string $dataPointer, $data, $extra): void
{
if ($this->has($tag, $dataPointer)) {
throw new InvalidArgumentException(sprintf('Data for tag "%s" at "%s" already exists', $tag, $dataPointer));
}

$this->data[$tag][$dataPointer] = $data;
$this->extra[$tag][$dataPointer] = $extra;
}

public function get(string $tag, string $dataPointer)
{
return $this->data[$tag][$dataPointer] ?? null;
}

public function has(string $tag, string $dataPointer): bool
{
return \array_key_exists($dataPointer, $this->data[$tag] ?? []);
}

public function getAll(): array
{
return $this->data;
}

public function getByTag(string $tag): array
{
return $this->data[$tag] ?? [];
}

public function hasTag(string $tag): bool
{
return isset($this->data[$tag]);
}

public function getExtra(string $tag, string $dataPointer)
{
return $this->extra[$tag][$dataPointer] ?? null;
}

public function hasExtra(string $tag, string $dataPointer): bool
{
return isset($this->extra[$tag][$dataPointer]);
}
}
58 changes: 58 additions & 0 deletions src/Tags/TaggedDataContainerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/*
* Copyright 2022 SYSTOPIA GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

declare(strict_types=1);

namespace Systopia\JsonSchema\Tags;

interface TaggedDataContainerInterface
{
/**
* @param null|mixed $data
* @param null|mixed $extra
*/
public function add(string $tag, string $dataPointer, $data, $extra): void;

/**
* @return null|mixed
*/
public function get(string $tag, string $dataPointer);

/**
* @return bool true if a value (including null) was added for the given tag and pointer
*/
public function has(string $tag, string $dataPointer): bool;

/**
* @return array<string, array<string, null|mixed>> Mapping of tag to a mapping of JSON pointer to data
*/
public function getAll(): array;

/**
* @return array<string, null|mixed> Mapping of JSON pointer to data
*/
public function getByTag(string $tag): array;

public function hasTag(string $tag): bool;

/**
* @return null|mixed
*/
public function getExtra(string $tag, string $dataPointer);

public function hasExtra(string $tag, string $dataPointer): bool;
}
Loading

0 comments on commit 98cb47b

Please sign in to comment.