diff --git a/src/CollectionSchema.php b/src/CollectionSchema.php index d73279f..f6340bc 100644 --- a/src/CollectionSchema.php +++ b/src/CollectionSchema.php @@ -7,16 +7,16 @@ /** * @template I * - * @phpstan-type CollectionSchemaData array + * @type CollectionSchemaData array * - * @extends JsonSchema + * @implements JsonSchemaInterface */ -abstract class CollectionSchema extends JsonSchema +abstract class CollectionSchema implements JsonSchemaInterface { /** - * @param JsonSchema $itemSchema + * @psalm-param JsonSchemaInterface $itemSchema */ - public function __construct(private JsonSchema $itemSchema) + public function __construct(private JsonSchemaInterface $itemSchema) { } @@ -30,6 +30,11 @@ public function getTitle(): string return sprintf('Collection<%s>', $this->itemSchema->getTitle()); } + public function getDescription(): string + { + return sprintf('Collection of %s', $this->itemSchema->getDescription()); + } + protected function getUniqueItems(): ?bool { return null; @@ -50,7 +55,7 @@ protected function getRange(): ?int return null; } - protected function getSchema(): array + public function getSchema(): array { $schema = [ 'type' => 'array', @@ -71,4 +76,24 @@ protected function getSchema(): array return $schema; } + + /** + * {@inheritdoc} + */ + public function jsonSerialize(): array + { + $schema = $this->getSchema(); + + /** + * @var array&array{title: string, description: string, examples: array} + */ + return array_merge( + $schema, + [ + 'title' => $this->getTitle(), + 'description' => $this->getDescription(), + 'examples' => [...$this->getExamples()], + ], + ); + } } diff --git a/src/EnumSchema.php b/src/EnumSchema.php index 2db3285..a310aff 100644 --- a/src/EnumSchema.php +++ b/src/EnumSchema.php @@ -6,16 +6,22 @@ /** * @template E - * @extends JsonSchema + * + * @implements JsonSchemaInterface */ -abstract class EnumSchema extends JsonSchema +abstract class EnumSchema implements JsonSchemaInterface { + /** + * @return iterable + */ + abstract protected function getEnum(): iterable; + public function getExamples(): iterable { return $this->getEnum(); } - protected function getSchema(): array + public function getSchema(): array { return [ 'enum' => [...$this->getEnum()], @@ -23,7 +29,22 @@ protected function getSchema(): array } /** - * @return iterable + * {@inheritdoc} */ - abstract protected function getEnum(): iterable; + public function jsonSerialize(): array + { + $schema = $this->getSchema(); + + /** + * @var array&array{title: string, description: string, examples: array} + */ + return array_merge( + $schema, + [ + 'title' => $this->getTitle(), + 'description' => $this->getDescription(), + 'examples' => [...$this->getExamples()], + ], + ); + } } diff --git a/src/JsonSchema.php b/src/JsonSchema.php index 0227619..e4361d5 100644 --- a/src/JsonSchema.php +++ b/src/JsonSchema.php @@ -4,22 +4,54 @@ namespace Knp\JsonSchema; +use Knp\JsonSchema\Validator\Errors; + /** * @template T of mixed + * + * @implements JsonSchemaInterface */ -abstract class JsonSchema implements JsonSchemaInterface +class JsonSchema implements JsonSchemaInterface { + private function __construct( + protected readonly string $title, + protected readonly string $description, + protected readonly iterable $examples, + protected readonly array $schema + ) { + } + + public function getTitle(): string + { + return $this->title; + } + + public function getDescription(): string + { + return $this->description; + } + + public function getExamples(): iterable + { + yield from $this->examples; + } + + public function getSchema(): array + { + return $this->schema; + } + /** - * @param JsonSchema $schema * @template E of mixed + * @param JsonSchemaInterface $schema * * @return JsonSchema */ - public static function nullable(self $schema): self + public static function nullable(JsonSchemaInterface $schema): self { return self::create( - '', - '', + sprintf('Nullable<%s>', $schema->getTitle()), + $schema->getDescription(), [...$schema->getExamples(), null], ['oneOf' => [self::null(), $schema->jsonSerialize()]], ); @@ -36,64 +68,23 @@ public static function create( string $title, string $description, iterable $examples, - $schema + array $schema ): self { - return new class($title, $description, $examples, $schema) extends JsonSchema { - /** - * @var iterable - */ - private iterable $examples; - - /** - * @param iterable $examples - * @param array $schema - */ - public function __construct( - private string $title, - private string $description, - iterable $examples, - private $schema - ) { - $this->examples = [...$examples]; - } - - public function getTitle(): string - { - return $this->title; - } - - public function getDescription(): string - { - return $this->description; - } - - /** - * @return iterable - */ - public function getExamples(): iterable - { - yield from $this->examples; - } - - public function getSchema(): array - { - return $this->schema; - } - }; + return new self($title, $description, $examples, $schema); } /** * @template I * - * @param JsonSchema $jsonSchema + * @param JsonSchemaInterface $jsonSchema * * @return JsonSchema> */ - public static function collection(self $jsonSchema): self + public static function collection(JsonSchemaInterface $jsonSchema): self { return self::create( sprintf('Collection<%s>', $jsonSchema->getTitle()), - '', + $jsonSchema->getDescription(), [[...$jsonSchema->getExamples()]], [ 'type' => 'array', @@ -103,9 +94,9 @@ public static function collection(self $jsonSchema): self } /** - * @return array&array{title: string, description: string, examples: array} + * {@inheritdoc} */ - public function jsonSerialize(): mixed + public function jsonSerialize(): array { $schema = $this->getSchema(); @@ -122,27 +113,12 @@ public function jsonSerialize(): mixed ); } - /** - * @return iterable - */ - abstract public function getExamples(): iterable; - - public function getTitle(): string - { - return ''; - } - - public function getDescription(): string - { - return ''; - } - /** * @param scalar $value * * @return array */ - protected static function constant($value): array + public static function constant($value): array { return [ 'const' => $value, @@ -152,7 +128,7 @@ protected static function constant($value): array /** * @return array */ - protected static function null(): array + public static function null(): array { return [ 'type' => 'null', @@ -162,7 +138,7 @@ protected static function null(): array /** * @return array */ - protected static function text(): array + public static function text(): array { return [ 'type' => 'string', @@ -173,7 +149,7 @@ protected static function text(): array /** * @return array */ - protected static function boolean(): array + public static function boolean(): array { return [ 'type' => 'boolean', @@ -183,7 +159,7 @@ protected static function boolean(): array /** * @return array */ - protected static function string(?string $format = null): array + public static function string(?string $format = null): array { $result = [ ...self::text(), @@ -200,7 +176,7 @@ protected static function string(?string $format = null): array /** * @return array */ - protected static function integer(): array + public static function integer(): array { return [ 'type' => 'integer', @@ -210,7 +186,7 @@ protected static function integer(): array /** * @return array */ - protected static function number(): array + public static function number(): array { return [ 'type' => 'number', @@ -220,7 +196,7 @@ protected static function number(): array /** * @return array */ - protected static function date(): array + public static function date(): array { return [ 'type' => 'string', @@ -231,7 +207,7 @@ protected static function date(): array /** * @return array */ - protected static function positiveInteger(): array + public static function positiveInteger(): array { return [ ...self::integer(), @@ -244,15 +220,10 @@ protected static function positiveInteger(): array * * @return array{oneOf: array>} */ - protected static function oneOf(...$schemas): array + public static function oneOf(...$schemas): array { return [ 'oneOf' => $schemas, ]; } - - /** - * @return array - */ - abstract protected function getSchema(): array; } diff --git a/src/JsonSchemaInterface.php b/src/JsonSchemaInterface.php index d99c044..039dc34 100644 --- a/src/JsonSchemaInterface.php +++ b/src/JsonSchemaInterface.php @@ -5,7 +5,29 @@ namespace Knp\JsonSchema; use JsonSerializable; +use Knp\JsonSchema\Validator\Errors; +/** + * @template T + */ interface JsonSchemaInterface extends JsonSerializable { + /** + * @return array&array{title: string, description: string, examples: array} + */ + public function jsonSerialize(): array; + + public function getTitle(): string; + + public function getDescription(): string; + + /** + * @return iterable + */ + public function getExamples(): iterable; + + /** + * @return array + */ + public function getSchema(): array; } diff --git a/src/ObjectSchema.php b/src/ObjectSchema.php index 1d8849e..979b76c 100644 --- a/src/ObjectSchema.php +++ b/src/ObjectSchema.php @@ -5,13 +5,14 @@ namespace Knp\JsonSchema; /** - * @template T of array - * @extends JsonSchema + * @template T + * + * @implements JsonSchemaInterface */ -abstract class ObjectSchema extends JsonSchema +abstract class ObjectSchema implements JsonSchemaInterface { /** - * @var array> + * @var array> */ private array $properties = []; @@ -44,11 +45,11 @@ protected function hasAdditionalProperties(): bool } /** - * @template S + * @psalm-template S * - * @param JsonSchema $schema + * @psalm-param JsonSchemaInterface $schema */ - protected function addProperty(string $name, JsonSchema $schema, bool $required = true): void + protected function addProperty(string $name, JsonSchemaInterface $schema, bool $required = true): void { $this->properties[$name] = $schema; @@ -59,7 +60,7 @@ protected function addProperty(string $name, JsonSchema $schema, bool $required } } - protected function getSchema(): array + public function getSchema(): array { return [ 'type' => 'object', @@ -68,4 +69,24 @@ protected function getSchema(): array 'required' => $this->required, ]; } + + /** + * {@inheritdoc} + */ + public function jsonSerialize(): array + { + $schema = $this->getSchema(); + + /** + * @var array&array{title: string, description: string, examples: array} + */ + return array_merge( + $schema, + [ + 'title' => $this->getTitle(), + 'description' => $this->getDescription(), + 'examples' => [...$this->getExamples()], + ], + ); + } } diff --git a/src/Scalar/UuidSchema.php b/src/Scalar/UuidSchema.php index b51e083..27c02c8 100644 --- a/src/Scalar/UuidSchema.php +++ b/src/Scalar/UuidSchema.php @@ -5,11 +5,12 @@ namespace Knp\JsonSchema\Scalar; use Knp\JsonSchema\JsonSchema; +use Knp\JsonSchema\JsonSchemaInterface; /** - * @extends JsonSchema + * @implements JsonSchemaInterface */ -final class UuidSchema extends JsonSchema +final class UuidSchema implements JsonSchemaInterface { private const PATTERN = '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$'; @@ -34,10 +35,10 @@ public function getDescription(): string /** * {@inheritDoc} */ - protected function getSchema(): array + public function getSchema(): array { return array_merge( - self::string(), + JsonSchema::string(), [ 'pattern' => self::PATTERN, 'minLength' => 36, @@ -45,4 +46,24 @@ protected function getSchema(): array ] ); } + + /** + * {@inheritdoc} + */ + public function jsonSerialize(): array + { + $schema = $this->getSchema(); + + /** + * @var array&array{title: string, description: string, examples: array} + */ + return array_merge( + $schema, + [ + 'title' => $this->getTitle(), + 'description' => $this->getDescription(), + 'examples' => [...$this->getExamples()], + ], + ); + } } diff --git a/src/Validator.php b/src/Validator.php index 8d98156..6f364df 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -9,10 +9,10 @@ interface Validator { /** - * @template T + * @template T of array * - * @param T $data - * @param class-string>|JsonSchema $schema + * @param array $data + * @param JsonSchemaInterface $schema */ - public function validate($data, $schema): ?Errors; + public function validate(array $data, JsonSchemaInterface $schema): ?Errors; }