packages/requirements/src/Config/SchemaValidator.php
1<?php
2
3declare(strict_types=1);
4
5namespace Requirements\Config;
6
7use JsonException;
8use Requirements\Input\InvalidInputException;
9use stdClass;
10
11/**
12 * Validates documents against the bundled JSON Schemas and any local schema they declare.
13 *
14 * A declared $schema is either the bundled schema's published URI, which resolves offline, or
15 * a local JSON Schema path that adds constraints to the bundled schema.
16 */
17final class SchemaValidator
18{
19 /**
20 * The published location of the bundled schemas, accepted as $schema without a download.
21 */
22 public const BASE = 'https://raw.githubusercontent.com/k-kinzal/ztd-query-php/main/packages/requirements/schemas/';
23
24 /**
25 * Validates a document.
26 *
27 * @param mixed $data The decoded document
28 * @param string $kind "config" or "definition", naming the bundled schema
29 * @param string $file The document file
30 *
31 * @throws InvalidInputException When the document breaks a schema, or declares a remote or unreadable one
32 * @throws JsonException When a schema is not JSON
33 */
34 public function validate(mixed $data, string $kind, string $file): void
35 {
36 $schema = new JsonSchemaFile();
37 $schema->validate($data, self::path($kind . '.schema.json'), $file);
38 if ($data instanceof stdClass && isset($data->{'$schema'})) {
39 $declared = $data->{'$schema'};
40 if (!is_string($declared)) {
41 throw new InvalidInputException("$file: \$schema must be a string.");
42 }
43 if ($declared === self::BASE . $kind . '.schema.json' || ($kind === 'definition' && DocumentReader::isMarkdown($file) && $declared === self::BASE . 'definition.document.yaml')) {
44 return;
45 }
46 if (str_contains($declared, '://')) {
47 throw new InvalidInputException("$file: unknown \$schema '$declared'; use the bundled schema URI or a local JSON Schema path.");
48 }
49 $schema->validate($data, str_starts_with($declared, '/') ? $declared : dirname($file) . '/' . $declared, $file);
50 }
51 }
52
53 /**
54 * Returns the path of a bundled schema.
55 *
56 * @param string $name The schema file name
57 *
58 * @return string The path within the package's schemas directory
59 */
60 public static function path(string $name): string
61 {
62 return dirname(__DIR__, 2) . '/schemas/' . $name;
63 }
64}
65