From 9891d37db201c1a32af4dd6f1d0db7854dd216df Mon Sep 17 00:00:00 2001 From: John Charman Date: Thu, 14 Nov 2024 12:08:35 +0000 Subject: [PATCH 1/6] Add format --- src/Factory/V30/FromCebe.php | 1 + src/ValueObject/Partial/Schema.php | 6 ++++++ src/ValueObject/Valid/V30/Schema.php | 3 +++ 3 files changed, 10 insertions(+) diff --git a/src/Factory/V30/FromCebe.php b/src/Factory/V30/FromCebe.php index 2de2bc6..b185b07 100644 --- a/src/Factory/V30/FromCebe.php +++ b/src/Factory/V30/FromCebe.php @@ -185,6 +185,7 @@ enum: $schema->enum ?? null, $schema->additionalProperties : self::createSchema($schema->additionalProperties) ?? true) : true, + format: $schema->format ?? null, ); } diff --git a/src/ValueObject/Partial/Schema.php b/src/ValueObject/Partial/Schema.php index ffcbfc0..24665b7 100644 --- a/src/ValueObject/Partial/Schema.php +++ b/src/ValueObject/Partial/Schema.php @@ -120,6 +120,12 @@ public function __construct( */ public bool|Schema $unevaluatedItems = true, public bool|Schema $unevaluatedProperties = true, + + /** + * Keywords that MAY provide additional validation, depending on tool + * https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00#section-7 + */ + public string|null $format = null, ) { } } diff --git a/src/ValueObject/Valid/V30/Schema.php b/src/ValueObject/Valid/V30/Schema.php index 0b63b34..f628653 100644 --- a/src/ValueObject/Valid/V30/Schema.php +++ b/src/ValueObject/Valid/V30/Schema.php @@ -55,6 +55,7 @@ final class Schema extends Validated implements Valid\Schema public readonly array|null $oneOf; public readonly Schema|null $not; + public readonly string|null $format; /** @var Type[] */ private readonly array $typesItCanBe; @@ -101,6 +102,8 @@ public function __construct( new Schema($this->getIdentifier()->append('not'), $schema->not) : null; + $this->format = $schema->format; + $this->typesItCanBe = array_map( fn($t) => Type::from($t), $this->typesItCanBe() From f60dd33ebe62d89483282f8a09e43e56b8c6586a Mon Sep 17 00:00:00 2001 From: John Charman Date: Thu, 14 Nov 2024 15:16:17 +0000 Subject: [PATCH 2/6] Simplify several keywords OpenAPI 3.0 specifically states items must be a Schema (if specified). OpenAPI 3.1 (by default) uses a more recent Json Schema; - items must be a Schema (if specified). Both OpenAPI versions allow properties to be treated as an empty array. Only OpenAPI 3.1 has prefixItems, which cna be treated as an empty array. --- src/ValueObject/Partial/Schema.php | 9 ++++----- src/ValueObject/Valid/V30/Schema.php | 8 ++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/ValueObject/Partial/Schema.php b/src/ValueObject/Partial/Schema.php index 24665b7..38c2fe1 100644 --- a/src/ValueObject/Partial/Schema.php +++ b/src/ValueObject/Partial/Schema.php @@ -98,17 +98,16 @@ public function __construct( * 3.1 https://json-schema.org/draft/2020-12/json-schema-core#name-keywords-for-applying-subschema */ /** @var array */ - public array|null $prefixItems = null, - /** @var array|Schema|null */ - public array|Schema|null $items = null, + public array $prefixItems = [], + public Schema|null $items = null, public Schema|null $contains = null, /** * Keywords for applying subschemas to arrays * 3.0 https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00#section-5.22 * 3.1 https://json-schema.org/draft/2020-12/json-schema-core#name-keywords-for-applying-subschemas */ - /** @var array|null */ - public array|null $properties = [], + /** @var array */ + public array $properties = [], /** @var array */ public array $patternProperties = [], public bool|Schema $additionalProperties = true, diff --git a/src/ValueObject/Valid/V30/Schema.php b/src/ValueObject/Valid/V30/Schema.php index f628653..ea58d20 100644 --- a/src/ValueObject/Valid/V30/Schema.php +++ b/src/ValueObject/Valid/V30/Schema.php @@ -33,8 +33,8 @@ final class Schema extends Validated implements Valid\Schema public readonly int $minLength; public readonly string|null $pattern; - /** @var array|Schema|null */ - public readonly array|Schema|null $items; + /** @var Schema|null */ + public readonly Schema|null $items; public readonly int|null $maxItems; public readonly int $minItems; public readonly bool $uniqueItems; @@ -317,10 +317,6 @@ private function validateItems( return $items; } - if (is_array($items)) { - return $this->validateSubSchemas('items', $items); - } - return new Schema($this->getIdentifier()->append('items'), $items); } } From bd16de097f81be6e517fd169b7c16a9c50ab7128 Mon Sep 17 00:00:00 2001 From: John Charman Date: Thu, 14 Nov 2024 15:21:05 +0000 Subject: [PATCH 3/6] Update 3.0 PartialHelper to allow all keywords --- tests/fixtures/Helper/PartialHelper.php | 54 +++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/tests/fixtures/Helper/PartialHelper.php b/tests/fixtures/Helper/PartialHelper.php index d77eafa..40b3e63 100644 --- a/tests/fixtures/Helper/PartialHelper.php +++ b/tests/fixtures/Helper/PartialHelper.php @@ -12,6 +12,7 @@ use Membrane\OpenAPIReader\ValueObject\Partial\Schema; use Membrane\OpenAPIReader\ValueObject\Partial\Server; use Membrane\OpenAPIReader\ValueObject\Partial\ServerVariable; +use Membrane\OpenAPIReader\ValueObject\Value; final class PartialHelper { @@ -125,22 +126,67 @@ public static function createMediaType( } /** + * @param null|Value[] $enum * @param null|Schema[] $allOf * @param null|Schema[] $anyOf * @param null|Schema[] $oneOf + * @param Schema[] $properties + * @param null|string[] $required * @return Schema */ public static function createSchema( - ?string $type = null, - ?array $allOf = null, - ?array $anyOf = null, - ?array $oneOf = null, + string|null $type = null, + bool $nullable = false, + array|null $enum = null, + Value|null $default = null, + float|int|null $multipleOf = null, + float|int|null $maximum = null, + float|int|null $minimum = null, + bool $exclusiveMaximum = false, + bool $exclusiveMinimum = false, + int|null $maxLength = null, + int $minLength = 0, + string|null $pattern = null, + int|null $maxItems = null, + int $minItems = 0, + bool $uniqueItems = false, + int|null $maxProperties = null, + int $minProperties = 0, + array|null $required = null, + Schema|null $items = null, + array $properties = [], + array|null $allOf = null, + array|null $anyOf = null, + array|null $oneOf = null, + Schema|null $not = null, + string|null $format = null, ): Schema { return new Schema( type: $type, + enum: $enum, + default: $default, + nullable: $nullable, + multipleOf: $multipleOf, + exclusiveMaximum: $exclusiveMaximum, + exclusiveMinimum: $exclusiveMinimum, + maximum: $maximum, + minimum: $minimum, + maxLength: $maxLength, + minLength: $minLength, + pattern: $pattern, + maxItems: $maxItems, + minItems: $minItems, + uniqueItems: $uniqueItems, + maxProperties: $maxProperties, + minProperties: $minProperties, + required: $required, allOf: $allOf, anyOf: $anyOf, oneOf: $oneOf, + not: $not, + items: $items, + properties: $properties, + format: $format, ); } } From 34f8c62db1cf453315dcd255161694077a1d07e3 Mon Sep 17 00:00:00 2001 From: John Charman Date: Thu, 14 Nov 2024 15:38:40 +0000 Subject: [PATCH 4/6] Test V30 getRelevantMinMax methods --- src/Factory/V30/FromCebe.php | 7 +- src/ValueObject/Partial/Schema.php | 1 - src/ValueObject/Valid/V30/Schema.php | 10 +- tests/ValueObject/Valid/V30/SchemaTest.php | 160 +++++++++++++++++++++ 4 files changed, 164 insertions(+), 14 deletions(-) diff --git a/src/Factory/V30/FromCebe.php b/src/Factory/V30/FromCebe.php index b185b07..968f47b 100644 --- a/src/Factory/V30/FromCebe.php +++ b/src/Factory/V30/FromCebe.php @@ -176,11 +176,8 @@ enum: $schema->enum ?? null, dependentSchemas: isset($schema->dependentSchemas) ? $createSchemas($schema->dependentSchemas) : null, - items: isset($schema->items) ? (is_array($schema->items) ? - $createSchemas($schema->items) : - self::createSchema($schema->items)) : - null, - properties: isset($schema->properties) ? $createSchemas($schema->properties) : null, + items: isset($schema->items) ? self::createSchema($schema->items) : null, + properties: isset($schema->properties) ? $createSchemas($schema->properties) : [], additionalProperties: isset($schema->additionalProperties) ? (is_bool($schema->additionalProperties) ? $schema->additionalProperties : self::createSchema($schema->additionalProperties) ?? true) : diff --git a/src/ValueObject/Partial/Schema.php b/src/ValueObject/Partial/Schema.php index 38c2fe1..c26812c 100644 --- a/src/ValueObject/Partial/Schema.php +++ b/src/ValueObject/Partial/Schema.php @@ -119,7 +119,6 @@ public function __construct( */ public bool|Schema $unevaluatedItems = true, public bool|Schema $unevaluatedProperties = true, - /** * Keywords that MAY provide additional validation, depending on tool * https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00#section-7 diff --git a/src/ValueObject/Valid/V30/Schema.php b/src/ValueObject/Valid/V30/Schema.php index ea58d20..9f60a75 100644 --- a/src/ValueObject/Valid/V30/Schema.php +++ b/src/ValueObject/Valid/V30/Schema.php @@ -300,14 +300,8 @@ private function validateProperties(?array $subSchemas): array return $result; } - /** - * @param array|Partial\Schema|null $items - * @return array|Schema|null - */ - private function validateItems( - Type|null $type, - array|Partial\Schema|null $items, - ): array|Schema|null { + private function validateItems(Type|null $type, Partial\Schema|null $items): Schema|null + { if (is_null($items)) { //@todo update tests to support this validation //if ($type == Type::Array) { diff --git a/tests/ValueObject/Valid/V30/SchemaTest.php b/tests/ValueObject/Valid/V30/SchemaTest.php index 917def5..04d027a 100644 --- a/tests/ValueObject/Valid/V30/SchemaTest.php +++ b/tests/ValueObject/Valid/V30/SchemaTest.php @@ -8,6 +8,7 @@ use Membrane\OpenAPIReader\Exception\InvalidOpenAPI; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\OpenAPIReader\Tests\Fixtures\Helper\PartialHelper; +use Membrane\OpenAPIReader\ValueObject\Limit; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Valid\Identifier; @@ -18,6 +19,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; @@ -117,6 +119,26 @@ public function itWarnsAgainstImpossibleSchemas( ); } + #[Test] + #[TestDox('It determines the relevant numeric inclusive|exclusive maximum, if there is one')] + #[DataProvider('provideSchemasWithMax')] + public function itGetsRelevantMaximum(?Limit $expected, Partial\Schema $schema): void + { + $sut = new Schema(new Identifier(''), $schema); + + self::assertEquals($expected, $sut->getRelevantMaximum()); + } + + #[Test] + #[TestDox('It determines the relevant numeric inclusive|exclusive minimum, if there is one')] + #[DataProvider('provideSchemasWithMin')] + public function itGetsRelevantMinimum(?Limit $expected, Partial\Schema $schema): void + { + $sut = new Schema(new Identifier(''), $schema); + + self::assertEquals($expected, $sut->getRelevantMinimum()); + } + public static function provideInvalidComplexSchemas(): Generator { $xOfs = [ @@ -291,4 +313,142 @@ public static function provideSchemasAcceptNoTypes(): Generator ]; } } + + /** + * @return Generator + */ + public static function provideSchemasWithMax(): Generator + { + yield 'no min or max' => [ + null, + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: false, + maximum: null, + minimum: null, + ), + ]; + + yield 'inclusive min' => [ + null, + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: false, + maximum: null, + minimum: 1, + ), + ]; + + yield 'exclusive min' => [ + null, + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: true, + maximum: null, + minimum: 1, + ), + ]; + + yield 'inclusive max' => [ + new Limit(1, false), + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: false, + maximum: 1, + minimum: null, + ), + ]; + + yield 'exclusive max' => [ + new Limit(1, true), + PartialHelper::createSchema( + exclusiveMaximum: true, + exclusiveMinimum: false, + maximum: 1, + minimum: null, + ), + ]; + + yield 'inclusive max and exclusive min' => [ + new Limit(5, false), + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: true, + maximum: 5, + minimum: 1, + ), + ]; + } + + /** + * @return Generator + */ + public static function provideSchemasWithMin(): Generator + { + yield 'no min or max' => [ + null, + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: false, + maximum: null, + minimum: null, + ), + ]; + + yield 'inclusive min' => [ + new Limit(1, false), + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: false, + maximum: null, + minimum: 1, + ), + ]; + + yield 'exclusive min' => [ + new Limit(1, true), + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: true, + maximum: null, + minimum: 1, + ), + ]; + + yield 'inclusive max' => [ + null, + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: false, + maximum: 1, + minimum: null, + ), + ]; + + yield 'exclusive max' => [ + null, + PartialHelper::createSchema( + exclusiveMaximum: true, + exclusiveMinimum: false, + maximum: 1, + minimum: null, + ), + ]; + + yield 'inclusive max and exclusive min' => [ + new Limit(1, true), + PartialHelper::createSchema( + exclusiveMaximum: false, + exclusiveMinimum: true, + maximum: 5, + minimum: 1, + ), + ]; + } } From 493cc373029a9fd1965b61823d0191032ee6f912 Mon Sep 17 00:00:00 2001 From: John Charman Date: Thu, 14 Nov 2024 17:12:53 +0000 Subject: [PATCH 5/6] Simplify V30 Factory --- src/Factory/V30/FromCebe.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Factory/V30/FromCebe.php b/src/Factory/V30/FromCebe.php index 968f47b..736fb8c 100644 --- a/src/Factory/V30/FromCebe.php +++ b/src/Factory/V30/FromCebe.php @@ -146,7 +146,6 @@ private static function createSchema( return new Schema( type: $schema->type, enum: $schema->enum ?? null, - const: $schema->const ?? null, default: isset($schema->default) ? new Value($schema->default) : null, nullable: $schema->nullable ?? false, multipleOf: $schema->multipleOf ?? null, @@ -160,22 +159,13 @@ enum: $schema->enum ?? null, maxItems: $schema->maxItems ?? null, minItems: $schema->minItems ?? 0, uniqueItems: $schema->uniqueItems ?? false, - maxContains: $schema->maxContains ?? null, - minContains: $schema->minContains ?? null, maxProperties: $schema->maxProperties ?? null, minProperties: $schema->minProperties ?? 0, required: $schema->required ?? null, - dependentRequired: $schema->dependentRequired ?? null, allOf: isset($schema->allOf) ? $createSchemas($schema->allOf) : null, anyOf: isset($schema->anyOf) ? $createSchemas($schema->anyOf) : null, oneOf: isset($schema->oneOf) ? $createSchemas($schema->oneOf) : null, not: isset($schema->not) ? self::createSchema($schema->not) : null, - if: isset($schema->if) ? self::createSchema($schema->if) : null, - then: isset($schema->then) ? self::createSchema($schema->then) : null, - else: isset($schema->else) ? self::createSchema($schema->else) : null, - dependentSchemas: isset($schema->dependentSchemas) ? - $createSchemas($schema->dependentSchemas) : - null, items: isset($schema->items) ? self::createSchema($schema->items) : null, properties: isset($schema->properties) ? $createSchemas($schema->properties) : [], additionalProperties: isset($schema->additionalProperties) ? (is_bool($schema->additionalProperties) ? From abd5ee4672f543183fe6631f580a3148f60101bb Mon Sep 17 00:00:00 2001 From: John Charman Date: Thu, 14 Nov 2024 17:13:00 +0000 Subject: [PATCH 6/6] Fix V30 enum construction in Factory --- src/Factory/V30/FromCebe.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Factory/V30/FromCebe.php b/src/Factory/V30/FromCebe.php index 736fb8c..f474d9b 100644 --- a/src/Factory/V30/FromCebe.php +++ b/src/Factory/V30/FromCebe.php @@ -145,7 +145,9 @@ private static function createSchema( return new Schema( type: $schema->type, - enum: $schema->enum ?? null, + enum: isset($schema->enum) ? + array_map(fn($e) => new Value($e), $schema->enum) : + null, default: isset($schema->default) ? new Value($schema->default) : null, nullable: $schema->nullable ?? false, multipleOf: $schema->multipleOf ?? null,