diff --git a/config/services.php b/config/services.php
index fbe7ba13..87dd672d 100644
--- a/config/services.php
+++ b/config/services.php
@@ -29,6 +29,7 @@
use CPSIT\ProjectBuilder\Twig;
use GuzzleHttp\Client as GuzzleClient;
use Nyholm\Psr7;
+use Opis\JsonSchema;
use Psr\Http\Client;
use Psr\Http\Message;
use SebastianFeldmann\Cli;
@@ -80,6 +81,7 @@
// Add external services
$services->set(ExpressionLanguage\ExpressionLanguage::class);
$services->set(Filesystem\Filesystem::class);
+ $services->set(JsonSchema\Validator::class);
$services->set(Slugify\Slugify::class);
$services->set(Client\ClientInterface::class, GuzzleClient::class);
$services->set(Loader\LoaderInterface::class, Loader\FilesystemLoader::class);
diff --git a/docs/development/architecture/components.md b/docs/development/architecture/components.md
index 3f82ef49..f89700aa 100644
--- a/docs/development/architecture/components.md
+++ b/docs/development/architecture/components.md
@@ -69,6 +69,24 @@ Each validator implements [`ValidatorInterface`](https://github.com/CPS-IT/proje
Not all validators can be used for each interaction with the `InputReader`.
```
+## JSON schema validation
+
+While working with JSON files, it's often useful to validate them against a defined
+JSON schema. For this, the [**`Json\SchemaValidator`**](https://github.com/CPS-IT/project-builder/blob/main/src/Json/SchemaValidator.php)
+component can be used.
+
+Example:
+
+```php
+/** @var \CPSIT\ProjectBuilder\Json\SchemaValidator $schemaValidator */
+
+$data = json_decode($json);
+$validationResult = $schemaValidator->validate($data, $schemaFile);
+
+$isValid = $validationResult->isValid(); // Check if JSON is valid
+$error = $validationResult->error(); // Get validation errors
+```
+
## Naming
With the [**`Naming\NameVariantBuilder`**](https://github.com/CPS-IT/project-builder/blob/main/src/Naming/NameVariantBuilder.php)
diff --git a/src/Builder/Config/ConfigFactory.php b/src/Builder/Config/ConfigFactory.php
index 34b98f44..a820afc7 100644
--- a/src/Builder/Config/ConfigFactory.php
+++ b/src/Builder/Config/ConfigFactory.php
@@ -25,6 +25,7 @@
use CPSIT\ProjectBuilder\Exception;
use CPSIT\ProjectBuilder\Helper;
+use CPSIT\ProjectBuilder\Json;
use CPSIT\ProjectBuilder\Paths;
use CuyZ\Valinor\Cache;
use CuyZ\Valinor\Mapper;
@@ -50,7 +51,7 @@ final class ConfigFactory
private function __construct(
private readonly Mapper\TreeMapper $mapper,
- private readonly JsonSchema\Validator $validator,
+ private readonly Json\SchemaValidator $schemaValidator,
) {
}
@@ -65,7 +66,7 @@ public static function create(): self
->mapper()
;
- return new self($mapper, new JsonSchema\Validator());
+ return new self($mapper, new Json\SchemaValidator(new JsonSchema\Validator()));
}
public function buildFromFile(string $file, string $identifier): Config
@@ -87,7 +88,7 @@ public function buildFromFile(string $file, string $identifier): Config
public function buildFromString(string $content, string $identifier, FileType $fileType): Config
{
$parsedContent = $this->parseContent($content, $fileType);
- $validationResult = $this->validateConfig($parsedContent);
+ $validationResult = $this->schemaValidator->validate($parsedContent, Paths::PROJECT_SCHEMA_CONFIG);
if (!$validationResult->isValid()) {
throw Exception\InvalidConfigurationException::forValidationErrors($validationResult->error());
@@ -98,24 +99,6 @@ public function buildFromString(string $content, string $identifier, FileType $f
return $this->mapper->map(Config::class, $source);
}
- private function validateConfig(stdClass $parsedContent): JsonSchema\ValidationResult
- {
- $schemaFile = Filesystem\Path::join(Helper\FilesystemHelper::getProjectRootPath(), Paths::PROJECT_SCHEMA_CONFIG);
- $schemaReference = 'file://'.$schemaFile;
- $schemaResolver = $this->validator->resolver();
-
- // @codeCoverageIgnoreStart
- if (null === $schemaResolver) {
- $schemaResolver = new JsonSchema\Resolvers\SchemaResolver();
- $this->validator->setResolver($schemaResolver);
- }
- // @codeCoverageIgnoreEnd
-
- $schemaResolver->registerFile($schemaReference, $schemaFile);
-
- return $this->validator->validate($parsedContent, $schemaReference);
- }
-
private function generateMapperSource(string $content, string $identifier, FileType $fileType): Mapper\Source\Source
{
$parsedContent = match ($fileType) {
diff --git a/src/Helper/FilesystemHelper.php b/src/Helper/FilesystemHelper.php
index c3faca52..2336aaf2 100644
--- a/src/Helper/FilesystemHelper.php
+++ b/src/Helper/FilesystemHelper.php
@@ -68,4 +68,13 @@ public static function getProjectRootPath(): string
return $rootPath ?? dirname(__DIR__, 2);
}
+
+ public static function resolveRelativePath(string $relativePath): string
+ {
+ if (Filesystem\Path::isAbsolute($relativePath)) {
+ return $relativePath;
+ }
+
+ return Filesystem\Path::makeAbsolute($relativePath, self::getProjectRootPath());
+ }
}
diff --git a/src/Json/SchemaValidator.php b/src/Json/SchemaValidator.php
new file mode 100644
index 00000000..2bbbd8fb
--- /dev/null
+++ b/src/Json/SchemaValidator.php
@@ -0,0 +1,59 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace CPSIT\ProjectBuilder\Json;
+
+use CPSIT\ProjectBuilder\Helper;
+use Opis\JsonSchema;
+
+/**
+ * SchemaValidator.
+ *
+ * @author Elias Häußler
+ * @license GPL-3.0-or-later
+ */
+final class SchemaValidator
+{
+ public function __construct(
+ private readonly JsonSchema\Validator $validator,
+ ) {
+ }
+
+ public function validate(mixed $data, string $schemaFile): JsonSchema\ValidationResult
+ {
+ $schemaFile = Helper\FilesystemHelper::resolveRelativePath($schemaFile);
+ $schemaReference = 'file://'.$schemaFile;
+ $schemaResolver = $this->validator->resolver();
+
+ // @codeCoverageIgnoreStart
+ if (null === $schemaResolver) {
+ $schemaResolver = new JsonSchema\Resolvers\SchemaResolver();
+ $this->validator->setResolver($schemaResolver);
+ }
+ // @codeCoverageIgnoreEnd
+
+ $schemaResolver->registerFile($schemaReference, $schemaFile);
+
+ return $this->validator->validate($data, $schemaReference);
+ }
+}
diff --git a/tests/src/Fixtures/Files/test.schema.json b/tests/src/Fixtures/Files/test.schema.json
new file mode 100644
index 00000000..5f0e02af
--- /dev/null
+++ b/tests/src/Fixtures/Files/test.schema.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema#",
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "foo"
+ ]
+}
diff --git a/tests/src/Helper/FilesystemHelperTest.php b/tests/src/Helper/FilesystemHelperTest.php
index 8ee638c0..c8c4daf2 100644
--- a/tests/src/Helper/FilesystemHelperTest.php
+++ b/tests/src/Helper/FilesystemHelperTest.php
@@ -84,4 +84,25 @@ public function getProjectRootPathReturnsProjectRootPathFromComposerPackageArtif
{
self::assertSame(dirname(__DIR__, 3), Src\Helper\FilesystemHelper::getProjectRootPath());
}
+
+ /**
+ * @test
+ */
+ public function resolveRelativePathReturnsGivenPathIfItIsAnAbsolutePath(): void
+ {
+ $path = '/foo/baz';
+
+ self::assertSame($path, Src\Helper\FilesystemHelper::resolveRelativePath($path));
+ }
+
+ /**
+ * @test
+ */
+ public function resolveRelativePathMakesRelativePathAbsolute(): void
+ {
+ $path = 'foo';
+ $expected = dirname(__DIR__, 3).'/foo';
+
+ self::assertSame($expected, Src\Helper\FilesystemHelper::resolveRelativePath($path));
+ }
}
diff --git a/tests/src/Json/SchemaValidatorTest.php b/tests/src/Json/SchemaValidatorTest.php
new file mode 100644
index 00000000..2356bf73
--- /dev/null
+++ b/tests/src/Json/SchemaValidatorTest.php
@@ -0,0 +1,67 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace CPSIT\ProjectBuilder\Tests\Json;
+
+use CPSIT\ProjectBuilder as Src;
+use CPSIT\ProjectBuilder\Tests;
+use Generator;
+
+use function dirname;
+
+/**
+ * SchemaValidatorTest.
+ *
+ * @author Elias Häußler
+ * @license GPL-3.0-or-later
+ */
+final class SchemaValidatorTest extends Tests\ContainerAwareTestCase
+{
+ private Src\Json\SchemaValidator $subject;
+
+ protected function setUp(): void
+ {
+ $this->subject = self::$container->get(Src\Json\SchemaValidator::class);
+ }
+
+ /**
+ * @test
+ *
+ * @dataProvider validateValidatesJsonDataProvider
+ */
+ public function validateValidatesJson(mixed $data, bool $expected): void
+ {
+ $schemaFile = dirname(__DIR__).'/Fixtures/Files/test.schema.json';
+
+ self::assertSame($expected, $this->subject->validate($data, $schemaFile)->isValid());
+ }
+
+ /**
+ * @return Generator
+ */
+ public function validateValidatesJsonDataProvider(): Generator
+ {
+ yield 'valid json' => [(object) ['foo' => 'baz'], true];
+ yield 'invalid json' => [null, false];
+ }
+}