| Required: | No details available.
contains : array|null
No details available.
+ minContains : int|null
+ @since OpenAPI 3.1.0
+ maxContains : int|null
+ @since OpenAPI 3.1.0
+ prefixItems : array|null
+ @since OpenAPI 3.1.0
patternProperties : array|null
No details available.
unevaluatedProperties : array|null
No details available.
+ unevaluatedItems : mixed|null
+ @since OpenAPI 3.1.0
dependencies : mixed|null
No details available.
+ dependentRequired : array|null
+ @since OpenAPI 3.1.0
+ dependentSchemas : array|null
+ @since OpenAPI 3.1.0
propertyNames : mixed|null
No details available.
const : mixed|null
@since OpenAPI 3.1.0
+ if : mixed|null
+ @since OpenAPI 3.1.0
+ then : mixed|null
+ Applied when `if` succeeds.
+
+@since OpenAPI 3.1.0
+ else : mixed|null
+ Applied when `if` fails.
+
+@since OpenAPI 3.1.0
+ contentSchema : mixed|null
+ Schema for the decoded content described by `contentMediaType`.
+
+@since OpenAPI 3.1.0
x : array<string,mixed>|null
While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
@@ -1233,16 +1259,42 @@ list. | Required: | No details available.
contains : array|null
No details available.
+ minContains : int|null
+ @since OpenAPI 3.1.0
+ maxContains : int|null
+ @since OpenAPI 3.1.0
+ prefixItems : array|null
+ @since OpenAPI 3.1.0
patternProperties : array|null
No details available.
unevaluatedProperties : array|null
No details available.
+ unevaluatedItems : mixed|null
+ @since OpenAPI 3.1.0
dependencies : mixed|null
No details available.
+ dependentRequired : array|null
+ @since OpenAPI 3.1.0
+ dependentSchemas : array|null
+ @since OpenAPI 3.1.0
propertyNames : mixed|null
No details available.
const : mixed|null
@since OpenAPI 3.1.0
+ if : mixed|null
+ @since OpenAPI 3.1.0
+ then : mixed|null
+ Applied when `if` succeeds.
+
+@since OpenAPI 3.1.0
+ else : mixed|null
+ Applied when `if` fails.
+
+@since OpenAPI 3.1.0
+ contentSchema : mixed|null
+ Schema for the decoded content described by `contentMediaType`.
+
+@since OpenAPI 3.1.0
x : array<string,mixed>|null
While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
@@ -1445,16 +1497,42 @@ list. | Required: | No details available.
contains : array|null
No details available.
+ minContains : int|null
+ @since OpenAPI 3.1.0
+ maxContains : int|null
+ @since OpenAPI 3.1.0
+ prefixItems : array|null
+ @since OpenAPI 3.1.0
patternProperties : array|null
No details available.
unevaluatedProperties : array|null
No details available.
+ unevaluatedItems : mixed|null
+ @since OpenAPI 3.1.0
dependencies : mixed|null
No details available.
+ dependentRequired : array|null
+ @since OpenAPI 3.1.0
+ dependentSchemas : array|null
+ @since OpenAPI 3.1.0
propertyNames : mixed|null
No details available.
const : mixed|null
@since OpenAPI 3.1.0
+ if : mixed|null
+ @since OpenAPI 3.1.0
+ then : mixed|null
+ Applied when `if` succeeds.
+
+@since OpenAPI 3.1.0
+ else : mixed|null
+ Applied when `if` fails.
+
+@since OpenAPI 3.1.0
+ contentSchema : mixed|null
+ Schema for the decoded content described by `contentMediaType`.
+
+@since OpenAPI 3.1.0
x : array<string,mixed>|null
While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
@@ -2364,16 +2442,42 @@ list. | Required: | No details available.
contains : array|null
No details available.
+ minContains : int|null
+ @since OpenAPI 3.1.0
+ maxContains : int|null
+ @since OpenAPI 3.1.0
+ prefixItems : array|null
+ @since OpenAPI 3.1.0
patternProperties : array|null
No details available.
unevaluatedProperties : array|null
No details available.
+ unevaluatedItems : mixed|null
+ @since OpenAPI 3.1.0
dependencies : mixed|null
No details available.
+ dependentRequired : array|null
+ @since OpenAPI 3.1.0
+ dependentSchemas : array|null
+ @since OpenAPI 3.1.0
propertyNames : mixed|null
No details available.
const : mixed|null
@since OpenAPI 3.1.0
+ if : mixed|null
+ @since OpenAPI 3.1.0
+ then : mixed|null
+ Applied when `if` succeeds.
+
+@since OpenAPI 3.1.0
+ else : mixed|null
+ Applied when `if` fails.
+
+@since OpenAPI 3.1.0
+ contentSchema : mixed|null
+ Schema for the decoded content described by `contentMediaType`.
+
+@since OpenAPI 3.1.0
x : array<string,mixed>|null
While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
@@ -2937,16 +3041,42 @@ list. | Required: | No details available.
contains : array|null
No details available.
+ minContains : int|null
+ @since OpenAPI 3.1.0
+ maxContains : int|null
+ @since OpenAPI 3.1.0
+ prefixItems : array|null
+ @since OpenAPI 3.1.0
patternProperties : array|null
No details available.
unevaluatedProperties : array|null
No details available.
+ unevaluatedItems : mixed|null
+ @since OpenAPI 3.1.0
dependencies : mixed|null
No details available.
+ dependentRequired : array|null
+ @since OpenAPI 3.1.0
+ dependentSchemas : array|null
+ @since OpenAPI 3.1.0
propertyNames : mixed|null
No details available.
const : mixed|null
@since OpenAPI 3.1.0
+ if : mixed|null
+ @since OpenAPI 3.1.0
+ then : mixed|null
+ Applied when `if` succeeds.
+
+@since OpenAPI 3.1.0
+ else : mixed|null
+ Applied when `if` fails.
+
+@since OpenAPI 3.1.0
+ contentSchema : mixed|null
+ Schema for the decoded content described by `contentMediaType`.
+
+@since OpenAPI 3.1.0
x : array<string,mixed>|null
While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
@@ -3483,16 +3613,42 @@ list. | Required: | No details available.
contains : array|null
No details available.
+ minContains : int|null
+ @since OpenAPI 3.1.0
+ maxContains : int|null
+ @since OpenAPI 3.1.0
+ prefixItems : array|null
+ @since OpenAPI 3.1.0
patternProperties : array|null
No details available.
unevaluatedProperties : array|null
No details available.
+ unevaluatedItems : mixed|null
+ @since OpenAPI 3.1.0
dependencies : mixed|null
No details available.
+ dependentRequired : array|null
+ @since OpenAPI 3.1.0
+ dependentSchemas : array|null
+ @since OpenAPI 3.1.0
propertyNames : mixed|null
No details available.
const : mixed|null
@since OpenAPI 3.1.0
+ if : mixed|null
+ @since OpenAPI 3.1.0
+ then : mixed|null
+ Applied when `if` succeeds.
+
+@since OpenAPI 3.1.0
+ else : mixed|null
+ Applied when `if` fails.
+
+@since OpenAPI 3.1.0
+ contentSchema : mixed|null
+ Schema for the decoded content described by `contentMediaType`.
+
+@since OpenAPI 3.1.0
x : array<string,mixed>|null
While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
diff --git a/docs/reference/spec-attributes.md b/docs/reference/spec-attributes.md
index d238b3cdc..198a0b924 100644
--- a/docs/reference/spec-attributes.md
+++ b/docs/reference/spec-attributes.md
@@ -1523,6 +1523,8 @@ without it the schema has no component key and is reported as missing one.
The media type of string content encoding
contentEncoding : string|null
The encoding used for string content (e.g. base64)
+ contentSchema : Schema|null
+ Schema for the decoded content described by contentMediaType
minimum : int|float|null
Minimum numeric value (inclusive)
maximum : int|float|null
@@ -1657,6 +1659,8 @@ schemas with constrained additional properties:
The media type of string content encoding
contentEncoding : string|null
The encoding used for string content (e.g. base64)
+ contentSchema : Schema|null
+ Schema for the decoded content described by contentMediaType
minimum : int|float|null
Minimum numeric value (inclusive)
maximum : int|float|null
@@ -1799,6 +1803,8 @@ Since Items extends Schema, the implicit `OA\Property` shortcut applies — no e
The media type of string content encoding
contentEncoding : string|null
The encoding used for string content (e.g. base64)
+ contentSchema : Schema|null
+ Schema for the decoded content described by contentMediaType
minimum : int|float|null
Minimum numeric value (inclusive)
maximum : int|float|null
diff --git a/src/Annotations/AbstractAnnotation.php b/src/Annotations/AbstractAnnotation.php
index 760681ef3..eac1c5420 100644
--- a/src/Annotations/AbstractAnnotation.php
+++ b/src/Annotations/AbstractAnnotation.php
@@ -111,6 +111,9 @@ public function __construct(array $properties)
$nestedContext = new Context(['nested' => $this], $this->_context);
foreach ($properties as $property => $value) {
if (property_exists($this, $property)) {
+ if ($value instanceof AbstractAnnotation) {
+ $value = $this->nested($value, $nestedContext);
+ }
$this->{$property} = $value;
if (is_array($value)) {
foreach ($value as $key => $annotation) {
diff --git a/src/Annotations/JsonSchemaTrait.php b/src/Annotations/JsonSchemaTrait.php
index 7acca12c9..efd62c2ac 100644
--- a/src/Annotations/JsonSchemaTrait.php
+++ b/src/Annotations/JsonSchemaTrait.php
@@ -135,6 +135,24 @@ trait JsonSchemaTrait
*/
public $contains = Undefined::UNDEFINED;
+ /**
+ * @var int
+ * @since OpenAPI 3.1.0
+ */
+ public $minContains = Undefined::UNDEFINED;
+
+ /**
+ * @var int
+ * @since OpenAPI 3.1.0
+ */
+ public $maxContains = Undefined::UNDEFINED;
+
+ /**
+ * @var array
+ * @since OpenAPI 3.1.0
+ */
+ public $prefixItems = Undefined::UNDEFINED;
+
/**
* @var array
*/
@@ -145,11 +163,29 @@ trait JsonSchemaTrait
*/
public $unevaluatedProperties = Undefined::UNDEFINED;
+ /**
+ * @var mixed
+ * @since OpenAPI 3.1.0
+ */
+ public $unevaluatedItems = Undefined::UNDEFINED;
+
/**
* @var mixed
*/
public $dependencies = Undefined::UNDEFINED;
+ /**
+ * @var array
+ * @since OpenAPI 3.1.0
+ */
+ public $dependentRequired = Undefined::UNDEFINED;
+
+ /**
+ * @var array
+ * @since OpenAPI 3.1.0
+ */
+ public $dependentSchemas = Undefined::UNDEFINED;
+
/**
* @var mixed
*/
@@ -160,6 +196,36 @@ trait JsonSchemaTrait
* @since OpenAPI 3.1.0
*/
public $const = Undefined::UNDEFINED;
+
+ /**
+ * @var mixed
+ * @since OpenAPI 3.1.0
+ */
+ public $if = Undefined::UNDEFINED;
+
+ /**
+ * Applied when `if` succeeds.
+ *
+ * @var mixed
+ * @since OpenAPI 3.1.0
+ */
+ public $then = Undefined::UNDEFINED;
+
+ /**
+ * Applied when `if` fails.
+ *
+ * @var mixed
+ * @since OpenAPI 3.1.0
+ */
+ public $else = Undefined::UNDEFINED;
+
+ /**
+ * Schema for the decoded content described by `contentMediaType`.
+ *
+ * @var mixed
+ * @since OpenAPI 3.1.0
+ */
+ public $contentSchema = Undefined::UNDEFINED;
}
/*
@@ -189,11 +255,21 @@ trait JsonSchemaTrait
bool|AdditionalProperties|null $additionalProperties = null,
array|null $additionalItems = null,
array|null $contains = null,
+ int|null $minContains = null,
+ int|null $maxContains = null,
+ array|null $prefixItems = null,
array|null $patternProperties = null,
array|null $unevaluatedProperties = null,
+ mixed $unevaluatedItems = Undefined::UNDEFINED,
mixed $dependencies = Undefined::UNDEFINED,
+ array|null $dependentRequired = null,
+ array|null $dependentSchemas = null,
mixed $propertyNames = Undefined::UNDEFINED,
mixed $const = Undefined::UNDEFINED,
+ mixed $if = Undefined::UNDEFINED,
+ mixed $then = Undefined::UNDEFINED,
+ mixed $else = Undefined::UNDEFINED,
+ mixed $contentSchema = Undefined::UNDEFINED,
** PARENT-PARAMS:
@@ -214,10 +290,20 @@ trait JsonSchemaTrait
'additionalProperties' => $additionalProperties ?? Undefined::UNDEFINED,
'additionalItems' => $additionalItems ?? Undefined::UNDEFINED,
'contains' => $contains ?? Undefined::UNDEFINED,
+ 'minContains' => $minContains ?? Undefined::UNDEFINED,
+ 'maxContains' => $maxContains ?? Undefined::UNDEFINED,
+ 'prefixItems' => $prefixItems ?? Undefined::UNDEFINED,
'patternProperties' => $patternProperties ?? Undefined::UNDEFINED,
'unevaluatedProperties' => $unevaluatedProperties ?? Undefined::UNDEFINED,
+ 'unevaluatedItems' => $unevaluatedItems,
'dependencies' => $dependencies,
+ 'dependentRequired' => $dependentRequired ?? Undefined::UNDEFINED,
+ 'dependentSchemas' => $dependentSchemas ?? Undefined::UNDEFINED,
'propertyNames' => $propertyNames,
'const' => $const,
+ 'if' => $if,
+ 'then' => $then,
+ 'else' => $else,
+ 'contentSchema' => $contentSchema,
*/
diff --git a/src/Annotations/Schema.php b/src/Annotations/Schema.php
index 51bb4f6ca..46bfb8163 100644
--- a/src/Annotations/Schema.php
+++ b/src/Annotations/Schema.php
@@ -312,6 +312,9 @@ class Schema extends AbstractAnnotation
'allOf' => '[' . Schema::class . ']',
'oneOf' => '[' . Schema::class . ']',
'anyOf' => '[' . Schema::class . ']',
+ 'prefixItems' => '[' . Schema::class . ']',
+ 'minContains' => 'integer',
+ 'maxContains' => 'integer',
'contentEncoding' => 'string',
'contentMediaType' => 'string',
];
@@ -365,6 +368,14 @@ public function jsonSerialize(): \stdClass
if ($this->_context->isVersion('3.0.x')) {
unset($data->examples);
+ foreach ([
+ 'contains', 'minContains', 'maxContains', 'prefixItems',
+ 'patternProperties', 'propertyNames', 'unevaluatedProperties', 'unevaluatedItems',
+ 'dependentRequired', 'dependentSchemas', 'if', 'then', 'else',
+ 'contentMediaType', 'contentEncoding', 'contentSchema',
+ ] as $keyword) {
+ unset($data->{$keyword});
+ }
if (isset($data->const)) {
$data->enum = [$data->const];
unset($data->const);
@@ -388,7 +399,10 @@ public function validate(?Analysis $analysis = null, string $version = OpenApi::
{
$isValid = parent::validate($analysis, $version, $context);
- if ($this->hasType('array') && Undefined::isDefault($this->items)) {
+ if ($this->hasType('array') && Undefined::isDefault($this->items)
+ && (OpenApi::versionMatch($version, '3.0.x')
+ || (Undefined::isDefault($this->prefixItems) && Undefined::isDefault($this->contains)))) {
+ // 3.1 array schemas may describe their items via prefixItems or contains; 3.0 requires items
$this->_context->logger->warning('@OA\\Items() is required when ' . $this->identity() . ' has type "array" in ' . $this->_context);
$isValid = false;
@@ -399,6 +413,18 @@ public function validate(?Analysis $analysis = null, string $version = OpenApi::
$this->_context->logger->warning(static::shorten(static::class) . '::examples is only allowed as of 3.1.0 in ' . $this->_context);
$isValid = false;
}
+
+ // the same keywords the spec compiler warns about; the rest drop silently
+ foreach (['prefixItems', 'unevaluatedProperties', 'unevaluatedItems'] as $keyword) {
+ if (!Undefined::isDefault($this->{$keyword})) {
+ $this->_context->logger->warning($this->identity() . ': ' . $keyword . ' is not supported in OpenAPI 3.0 in ' . $this->_context);
+ $isValid = false;
+ }
+ }
+ if (!Undefined::isDefault($this->if) || !Undefined::isDefault($this->then) || !Undefined::isDefault($this->else)) {
+ $this->_context->logger->warning($this->identity() . ': if/then/else is not supported in OpenAPI 3.0 in ' . $this->_context);
+ $isValid = false;
+ }
}
// the keyword takes values, so an annotation other than `@OA\Examples` is serialised whole
diff --git a/src/Attributes/AdditionalProperties.php b/src/Attributes/AdditionalProperties.php
index fec93a888..29e263919 100644
--- a/src/Attributes/AdditionalProperties.php
+++ b/src/Attributes/AdditionalProperties.php
@@ -71,11 +71,21 @@ public function __construct(
bool|AdditionalProperties|null $additionalProperties = null,
array|null $additionalItems = null,
array|null $contains = null,
+ int|null $minContains = null,
+ int|null $maxContains = null,
+ array|null $prefixItems = null,
array|null $patternProperties = null,
array|null $unevaluatedProperties = null,
+ mixed $unevaluatedItems = Undefined::UNDEFINED,
mixed $dependencies = Undefined::UNDEFINED,
+ array|null $dependentRequired = null,
+ array|null $dependentSchemas = null,
mixed $propertyNames = Undefined::UNDEFINED,
mixed $const = Undefined::UNDEFINED,
+ mixed $if = Undefined::UNDEFINED,
+ mixed $then = Undefined::UNDEFINED,
+ mixed $else = Undefined::UNDEFINED,
+ mixed $contentSchema = Undefined::UNDEFINED,
// abstract annotation
?array $x = null,
@@ -123,11 +133,21 @@ public function __construct(
'additionalProperties' => $additionalProperties ?? Undefined::UNDEFINED,
'additionalItems' => $additionalItems ?? Undefined::UNDEFINED,
'contains' => $contains ?? Undefined::UNDEFINED,
+ 'minContains' => $minContains ?? Undefined::UNDEFINED,
+ 'maxContains' => $maxContains ?? Undefined::UNDEFINED,
+ 'prefixItems' => $prefixItems ?? Undefined::UNDEFINED,
'patternProperties' => $patternProperties ?? Undefined::UNDEFINED,
'unevaluatedProperties' => $unevaluatedProperties ?? Undefined::UNDEFINED,
+ 'unevaluatedItems' => $unevaluatedItems,
'dependencies' => $dependencies,
+ 'dependentRequired' => $dependentRequired ?? Undefined::UNDEFINED,
+ 'dependentSchemas' => $dependentSchemas ?? Undefined::UNDEFINED,
'propertyNames' => $propertyNames,
'const' => $const,
+ 'if' => $if,
+ 'then' => $then,
+ 'else' => $else,
+ 'contentSchema' => $contentSchema,
// abstract annotation
'x' => $x ?? Undefined::UNDEFINED,
diff --git a/src/Attributes/Items.php b/src/Attributes/Items.php
index b6e3ec057..fdffd7b13 100644
--- a/src/Attributes/Items.php
+++ b/src/Attributes/Items.php
@@ -71,11 +71,21 @@ public function __construct(
bool|AdditionalProperties|null $additionalProperties = null,
array|null $additionalItems = null,
array|null $contains = null,
+ int|null $minContains = null,
+ int|null $maxContains = null,
+ array|null $prefixItems = null,
array|null $patternProperties = null,
array|null $unevaluatedProperties = null,
+ mixed $unevaluatedItems = Undefined::UNDEFINED,
mixed $dependencies = Undefined::UNDEFINED,
+ array|null $dependentRequired = null,
+ array|null $dependentSchemas = null,
mixed $propertyNames = Undefined::UNDEFINED,
mixed $const = Undefined::UNDEFINED,
+ mixed $if = Undefined::UNDEFINED,
+ mixed $then = Undefined::UNDEFINED,
+ mixed $else = Undefined::UNDEFINED,
+ mixed $contentSchema = Undefined::UNDEFINED,
// abstract annotation
?array $x = null,
@@ -123,11 +133,21 @@ public function __construct(
'additionalProperties' => $additionalProperties ?? Undefined::UNDEFINED,
'additionalItems' => $additionalItems ?? Undefined::UNDEFINED,
'contains' => $contains ?? Undefined::UNDEFINED,
+ 'minContains' => $minContains ?? Undefined::UNDEFINED,
+ 'maxContains' => $maxContains ?? Undefined::UNDEFINED,
+ 'prefixItems' => $prefixItems ?? Undefined::UNDEFINED,
'patternProperties' => $patternProperties ?? Undefined::UNDEFINED,
'unevaluatedProperties' => $unevaluatedProperties ?? Undefined::UNDEFINED,
+ 'unevaluatedItems' => $unevaluatedItems,
'dependencies' => $dependencies,
+ 'dependentRequired' => $dependentRequired ?? Undefined::UNDEFINED,
+ 'dependentSchemas' => $dependentSchemas ?? Undefined::UNDEFINED,
'propertyNames' => $propertyNames,
'const' => $const,
+ 'if' => $if,
+ 'then' => $then,
+ 'else' => $else,
+ 'contentSchema' => $contentSchema,
// abstract annotation
'x' => $x ?? Undefined::UNDEFINED,
diff --git a/src/Attributes/JsonContent.php b/src/Attributes/JsonContent.php
index 0eb0a53ea..bfb88b020 100644
--- a/src/Attributes/JsonContent.php
+++ b/src/Attributes/JsonContent.php
@@ -95,11 +95,21 @@ public function __construct(
bool|AdditionalProperties|null $additionalProperties = null,
array|null $additionalItems = null,
array|null $contains = null,
+ int|null $minContains = null,
+ int|null $maxContains = null,
+ array|null $prefixItems = null,
array|null $patternProperties = null,
array|null $unevaluatedProperties = null,
+ mixed $unevaluatedItems = Undefined::UNDEFINED,
mixed $dependencies = Undefined::UNDEFINED,
+ array|null $dependentRequired = null,
+ array|null $dependentSchemas = null,
mixed $propertyNames = Undefined::UNDEFINED,
mixed $const = Undefined::UNDEFINED,
+ mixed $if = Undefined::UNDEFINED,
+ mixed $then = Undefined::UNDEFINED,
+ mixed $else = Undefined::UNDEFINED,
+ mixed $contentSchema = Undefined::UNDEFINED,
// abstract annotation
?array $x = null,
@@ -147,11 +157,21 @@ public function __construct(
'additionalProperties' => $additionalProperties ?? Undefined::UNDEFINED,
'additionalItems' => $additionalItems ?? Undefined::UNDEFINED,
'contains' => $contains ?? Undefined::UNDEFINED,
+ 'minContains' => $minContains ?? Undefined::UNDEFINED,
+ 'maxContains' => $maxContains ?? Undefined::UNDEFINED,
+ 'prefixItems' => $prefixItems ?? Undefined::UNDEFINED,
'patternProperties' => $patternProperties ?? Undefined::UNDEFINED,
'unevaluatedProperties' => $unevaluatedProperties ?? Undefined::UNDEFINED,
+ 'unevaluatedItems' => $unevaluatedItems,
'dependencies' => $dependencies,
+ 'dependentRequired' => $dependentRequired ?? Undefined::UNDEFINED,
+ 'dependentSchemas' => $dependentSchemas ?? Undefined::UNDEFINED,
'propertyNames' => $propertyNames,
'const' => $const,
+ 'if' => $if,
+ 'then' => $then,
+ 'else' => $else,
+ 'contentSchema' => $contentSchema,
// abstract annotation
'x' => $x ?? Undefined::UNDEFINED,
diff --git a/src/Attributes/Property.php b/src/Attributes/Property.php
index 5cd6a1031..08597b122 100644
--- a/src/Attributes/Property.php
+++ b/src/Attributes/Property.php
@@ -74,11 +74,21 @@ public function __construct(
bool|AdditionalProperties|null $additionalProperties = null,
array|null $additionalItems = null,
array|null $contains = null,
+ int|null $minContains = null,
+ int|null $maxContains = null,
+ array|null $prefixItems = null,
array|null $patternProperties = null,
array|null $unevaluatedProperties = null,
+ mixed $unevaluatedItems = Undefined::UNDEFINED,
mixed $dependencies = Undefined::UNDEFINED,
+ array|null $dependentRequired = null,
+ array|null $dependentSchemas = null,
mixed $propertyNames = Undefined::UNDEFINED,
mixed $const = Undefined::UNDEFINED,
+ mixed $if = Undefined::UNDEFINED,
+ mixed $then = Undefined::UNDEFINED,
+ mixed $else = Undefined::UNDEFINED,
+ mixed $contentSchema = Undefined::UNDEFINED,
// abstract annotation
?array $x = null,
@@ -128,11 +138,21 @@ public function __construct(
'additionalProperties' => $additionalProperties ?? Undefined::UNDEFINED,
'additionalItems' => $additionalItems ?? Undefined::UNDEFINED,
'contains' => $contains ?? Undefined::UNDEFINED,
+ 'minContains' => $minContains ?? Undefined::UNDEFINED,
+ 'maxContains' => $maxContains ?? Undefined::UNDEFINED,
+ 'prefixItems' => $prefixItems ?? Undefined::UNDEFINED,
'patternProperties' => $patternProperties ?? Undefined::UNDEFINED,
'unevaluatedProperties' => $unevaluatedProperties ?? Undefined::UNDEFINED,
+ 'unevaluatedItems' => $unevaluatedItems,
'dependencies' => $dependencies,
+ 'dependentRequired' => $dependentRequired ?? Undefined::UNDEFINED,
+ 'dependentSchemas' => $dependentSchemas ?? Undefined::UNDEFINED,
'propertyNames' => $propertyNames,
'const' => $const,
+ 'if' => $if,
+ 'then' => $then,
+ 'else' => $else,
+ 'contentSchema' => $contentSchema,
// abstract annotation
'x' => $x ?? Undefined::UNDEFINED,
diff --git a/src/Attributes/Schema.php b/src/Attributes/Schema.php
index 4e67596c4..2d5b02bbe 100644
--- a/src/Attributes/Schema.php
+++ b/src/Attributes/Schema.php
@@ -71,11 +71,21 @@ public function __construct(
bool|AdditionalProperties|null $additionalProperties = null,
array|null $additionalItems = null,
array|null $contains = null,
+ int|null $minContains = null,
+ int|null $maxContains = null,
+ array|null $prefixItems = null,
array|null $patternProperties = null,
array|null $unevaluatedProperties = null,
+ mixed $unevaluatedItems = Undefined::UNDEFINED,
mixed $dependencies = Undefined::UNDEFINED,
+ array|null $dependentRequired = null,
+ array|null $dependentSchemas = null,
mixed $propertyNames = Undefined::UNDEFINED,
mixed $const = Undefined::UNDEFINED,
+ mixed $if = Undefined::UNDEFINED,
+ mixed $then = Undefined::UNDEFINED,
+ mixed $else = Undefined::UNDEFINED,
+ mixed $contentSchema = Undefined::UNDEFINED,
// abstract annotation
?array $x = null,
@@ -122,11 +132,21 @@ public function __construct(
'additionalProperties' => $additionalProperties ?? Undefined::UNDEFINED,
'additionalItems' => $additionalItems ?? Undefined::UNDEFINED,
'contains' => $contains ?? Undefined::UNDEFINED,
+ 'minContains' => $minContains ?? Undefined::UNDEFINED,
+ 'maxContains' => $maxContains ?? Undefined::UNDEFINED,
+ 'prefixItems' => $prefixItems ?? Undefined::UNDEFINED,
'patternProperties' => $patternProperties ?? Undefined::UNDEFINED,
'unevaluatedProperties' => $unevaluatedProperties ?? Undefined::UNDEFINED,
+ 'unevaluatedItems' => $unevaluatedItems,
'dependencies' => $dependencies,
+ 'dependentRequired' => $dependentRequired ?? Undefined::UNDEFINED,
+ 'dependentSchemas' => $dependentSchemas ?? Undefined::UNDEFINED,
'propertyNames' => $propertyNames,
'const' => $const,
+ 'if' => $if,
+ 'then' => $then,
+ 'else' => $else,
+ 'contentSchema' => $contentSchema,
// abstract annotation
'x' => $x ?? Undefined::UNDEFINED,
diff --git a/src/Attributes/XmlContent.php b/src/Attributes/XmlContent.php
index 0ac828fde..1601973e6 100644
--- a/src/Attributes/XmlContent.php
+++ b/src/Attributes/XmlContent.php
@@ -74,11 +74,21 @@ public function __construct(
bool|AdditionalProperties|null $additionalProperties = null,
array|null $additionalItems = null,
array|null $contains = null,
+ int|null $minContains = null,
+ int|null $maxContains = null,
+ array|null $prefixItems = null,
array|null $patternProperties = null,
array|null $unevaluatedProperties = null,
+ mixed $unevaluatedItems = Undefined::UNDEFINED,
mixed $dependencies = Undefined::UNDEFINED,
+ array|null $dependentRequired = null,
+ array|null $dependentSchemas = null,
mixed $propertyNames = Undefined::UNDEFINED,
mixed $const = Undefined::UNDEFINED,
+ mixed $if = Undefined::UNDEFINED,
+ mixed $then = Undefined::UNDEFINED,
+ mixed $else = Undefined::UNDEFINED,
+ mixed $contentSchema = Undefined::UNDEFINED,
// abstract annotation
?array $x = null,
@@ -126,11 +136,21 @@ public function __construct(
'additionalProperties' => $additionalProperties ?? Undefined::UNDEFINED,
'additionalItems' => $additionalItems ?? Undefined::UNDEFINED,
'contains' => $contains ?? Undefined::UNDEFINED,
+ 'minContains' => $minContains ?? Undefined::UNDEFINED,
+ 'maxContains' => $maxContains ?? Undefined::UNDEFINED,
+ 'prefixItems' => $prefixItems ?? Undefined::UNDEFINED,
'patternProperties' => $patternProperties ?? Undefined::UNDEFINED,
'unevaluatedProperties' => $unevaluatedProperties ?? Undefined::UNDEFINED,
+ 'unevaluatedItems' => $unevaluatedItems,
'dependencies' => $dependencies,
+ 'dependentRequired' => $dependentRequired ?? Undefined::UNDEFINED,
+ 'dependentSchemas' => $dependentSchemas ?? Undefined::UNDEFINED,
'propertyNames' => $propertyNames,
'const' => $const,
+ 'if' => $if,
+ 'then' => $then,
+ 'else' => $else,
+ 'contentSchema' => $contentSchema,
// abstract annotation
'x' => $x ?? Undefined::UNDEFINED,
diff --git a/src/Compiler/OpenApi31Compiler.php b/src/Compiler/OpenApi31Compiler.php
index 15c8ddc9e..268461505 100644
--- a/src/Compiler/OpenApi31Compiler.php
+++ b/src/Compiler/OpenApi31Compiler.php
@@ -546,6 +546,7 @@ protected function compileSchema(OA\Schema|string $schema): array|\stdClass
'pattern' => $schema->pattern,
'contentMediaType' => $schema->contentMediaType,
'contentEncoding' => $schema->contentEncoding,
+ 'contentSchema' => $schema->contentSchema instanceof OA\Schema ? $this->compileSchema($schema->contentSchema) : null,
// Numeric
'minimum' => $this->compileMinimum($schema),
@@ -873,7 +874,7 @@ protected function validateSchemas(Specification $specification): void
foreach ($allSchemas as $schema) {
if ($schema->type !== null && (is_array($schema->type) ? in_array('array', $schema->type, true) : $schema->type === 'array')) {
- if ($schema->items === null) {
+ if ($schema->items === null && $schema->prefixItems === null && $schema->contains === null) {
$this->logger->warning('Schema' . ($schema->schema ? " \"$schema->schema\"" : '') . ' has type "array" but no items in ' . $schema->getSourceLocation());
}
}
diff --git a/src/HybridBridge.php b/src/HybridBridge.php
index 4a19ef417..ffcec8a50 100644
--- a/src/HybridBridge.php
+++ b/src/HybridBridge.php
@@ -661,6 +661,7 @@ protected function convertSchema(OA\Schema $schema): Spec\Schema
pattern: $this->val($schema->pattern),
contentMediaType: $this->val($schema->contentMediaType),
contentEncoding: $this->val($schema->contentEncoding),
+ contentSchema: Undefined::isDefault($schema->contentSchema) ? null : $this->convertSchemaValue($schema->contentSchema),
minimum: $this->val($schema->minimum),
maximum: $this->val($schema->maximum),
exclusiveMinimum: $this->val($schema->exclusiveMinimum),
@@ -670,7 +671,11 @@ protected function convertSchema(OA\Schema $schema): Spec\Schema
minItems: $this->val($schema->minItems),
maxItems: $this->val($schema->maxItems),
uniqueItems: $this->val($schema->uniqueItems),
+ prefixItems: Undefined::isDefault($schema->prefixItems) ? null : array_map($this->convertSchemaValue(...), $schema->prefixItems),
contains: $this->convertSchemaOrBool($schema->contains),
+ minContains: $this->val($schema->minContains),
+ maxContains: $this->val($schema->maxContains),
+ unevaluatedItems: $this->convertSchemaOrBool($schema->unevaluatedItems),
properties: $properties,
required: Undefined::isDefault($schema->required) ? null : $schema->required,
additionalProperties: $this->convertAdditionalProperties($schema),
@@ -683,10 +688,15 @@ protected function convertSchema(OA\Schema $schema): Spec\Schema
propertyNames: Undefined::isDefault($schema->propertyNames)
? null
: $this->convertSchema($schema->propertyNames),
+ dependentRequired: Undefined::isDefault($schema->dependentRequired) ? null : $schema->dependentRequired,
+ dependentSchemas: Undefined::isDefault($schema->dependentSchemas) ? null : array_map($this->convertSchemaValue(...), $schema->dependentSchemas),
allOf: Undefined::isDefault($schema->allOf) ? null : array_map($this->convertSchema(...), $schema->allOf),
anyOf: Undefined::isDefault($schema->anyOf) ? null : array_map($this->convertSchema(...), $schema->anyOf),
oneOf: Undefined::isDefault($schema->oneOf) ? null : array_map($this->convertSchema(...), $schema->oneOf),
not: Undefined::isDefault($schema->not) ? null : $this->convertSchema($schema->not),
+ if: Undefined::isDefault($schema->if) ? null : $this->convertSchemaValue($schema->if),
+ then: Undefined::isDefault($schema->then) ? null : $this->convertSchemaValue($schema->then),
+ else: Undefined::isDefault($schema->else) ? null : $this->convertSchemaValue($schema->else),
enum: Undefined::isDefault($schema->enum) ? null : $schema->enum,
const: Undefined::isDefault($schema->const) ? Undefined::UNDEFINED : $schema->const,
example: Undefined::isDefault($schema->example) ? Undefined::UNDEFINED : $schema->example,
@@ -718,11 +728,26 @@ protected function convertSchemaOrBool(mixed $value): Spec\Schema|bool|null
{
return match (true) {
Undefined::isDefault($value) => null,
- $value instanceof OA\Schema => $this->convertSchema($value),
+ $value instanceof OA\Schema, is_array($value) => $this->convertSchemaValue($value),
default => $value,
};
}
+ /**
+ * Convert a schema-valued keyword; classic docblocks and attributes may carry the
+ * schema as a plain array instead of a Schema annotation.
+ *
+ * @param OA\Schema|array $value
+ */
+ protected function convertSchemaValue(OA\Schema|array $value): Spec\Schema
+ {
+ if (is_array($value)) {
+ $value = new OA\Schema([...$value, '_context' => new Context(['generated' => true])]);
+ }
+
+ return $this->convertSchema($value);
+ }
+
protected function convertProperty(OA\Property $prop): Spec\Property
{
$schema = $this->convertSchema($prop);
diff --git a/src/Spec/Schema.php b/src/Spec/Schema.php
index c8ebdd301..219ce73ab 100644
--- a/src/Spec/Schema.php
+++ b/src/Spec/Schema.php
@@ -68,6 +68,7 @@ class Schema extends AbstractAttribute
* @param string|null $pattern Regular expression pattern the string must match
* @param string|null $contentMediaType The media type of string content encoding
* @param string|null $contentEncoding The encoding used for string content (e.g. base64)
+ * @param Schema|null $contentSchema Schema for the decoded content described by contentMediaType
* @param int|float|null $minimum Minimum numeric value (inclusive)
* @param int|float|null $maximum Maximum numeric value (inclusive)
* @param int|float|bool|null $exclusiveMinimum Exclusive minimum value
@@ -133,6 +134,7 @@ public function __construct(
public ?string $pattern = null,
public ?string $contentMediaType = null,
public ?string $contentEncoding = null,
+ public ?Schema $contentSchema = null,
// Numeric constraints
public int|float|null $minimum = null,
diff --git a/tests/Fixtures/Scratch/SchemaKeywords-spec.php b/tests/Fixtures/Scratch/SchemaKeywords-spec.php
new file mode 100644
index 000000000..78e654134
--- /dev/null
+++ b/tests/Fixtures/Scratch/SchemaKeywords-spec.php
@@ -0,0 +1,97 @@
+ ['billingAddress']],
+ dependentSchemas: ['creditCard' => new OA\Schema(required: ['billingAddress'])],
+)]
+class SchemaKeywordsDependentSpec
+{
+}
+
+#[OA\Schema(
+ schema: 'embedded',
+ type: 'string',
+ contentEncoding: 'base64',
+ contentMediaType: 'application/json',
+ contentSchema: new OA\Schema(type: 'object'),
+)]
+class SchemaKeywordsEmbeddedSpec
+{
+}
+
+#[OA\Schema(schema: 'decoded', type: 'object')]
+class SchemaKeywordsDecodedSpec
+{
+}
+
+// a nested schema slot takes a ref like any other schema position
+#[OA\Schema(
+ schema: 'envelope',
+ type: 'string',
+ contentMediaType: 'application/json',
+ contentSchema: new OA\Schema\Ref(SchemaKeywordsDecodedSpec::class),
+)]
+class SchemaKeywordsEnvelopeSpec
+{
+}
+
+#[OA\Info(title: 'SchemaKeywords', version: '1.0')]
+class SchemaKeywordsControllerSpec
+{
+ #[OA\Operation\Get(path: '/endpoint', operationId: 'endpoint')]
+ #[OA\Response(
+ response: 200,
+ description: 'OK',
+ content: new OA\MediaType\Json(ref: '#/components/schemas/conditional'),
+ )]
+ public function endpoint(): void
+ {
+ }
+}
diff --git a/tests/Fixtures/Scratch/SchemaKeywords.php b/tests/Fixtures/Scratch/SchemaKeywords.php
new file mode 100644
index 000000000..efaa81465
--- /dev/null
+++ b/tests/Fixtures/Scratch/SchemaKeywords.php
@@ -0,0 +1,102 @@
+ 'string'],
+ minContains: 1,
+ maxContains: 3,
+ unevaluatedItems: false,
+ unevaluatedProperties: ['type' => 'string'],
+)]
+class SchemaKeywordsTuple
+{
+}
+
+#[OAT\Schema(
+ schema: 'dependent',
+ type: 'object',
+ dependentRequired: ['creditCard' => ['billingAddress']],
+ dependentSchemas: ['creditCard' => new OAT\Schema(required: ['billingAddress'])],
+)]
+class SchemaKeywordsDependent
+{
+}
+
+#[OAT\Schema(
+ schema: 'embedded',
+ type: 'string',
+ contentEncoding: 'base64',
+ contentMediaType: 'application/json',
+ contentSchema: new OAT\Schema(type: 'object'),
+)]
+class SchemaKeywordsEmbedded
+{
+}
+
+#[OAT\Schema(schema: 'decoded', type: 'object')]
+class SchemaKeywordsDecoded
+{
+}
+
+// a nested schema slot takes a ref like any other schema position
+#[OAT\Schema(
+ schema: 'envelope',
+ type: 'string',
+ contentMediaType: 'application/json',
+ contentSchema: new OAT\Schema(ref: SchemaKeywordsDecoded::class),
+)]
+class SchemaKeywordsEnvelope
+{
+}
+
+#[OAT\Info(title: 'SchemaKeywords', version: '1.0')]
+class SchemaKeywordsController
+{
+ #[OAT\Get(
+ path: '/endpoint',
+ operationId: 'endpoint',
+ responses: [
+ new OAT\Response(
+ response: 200,
+ description: 'OK',
+ content: new OAT\JsonContent(ref: '#/components/schemas/conditional'),
+ ),
+ ],
+ )]
+ public function endpoint(): void
+ {
+ }
+}
diff --git a/tests/Fixtures/Scratch/SchemaKeywords3.0.0.yaml b/tests/Fixtures/Scratch/SchemaKeywords3.0.0.yaml
new file mode 100644
index 000000000..ae923f2c2
--- /dev/null
+++ b/tests/Fixtures/Scratch/SchemaKeywords3.0.0.yaml
@@ -0,0 +1,36 @@
+openapi: 3.0.0
+info:
+ title: SchemaKeywords
+ version: '1.0'
+paths:
+ /endpoint:
+ get:
+ operationId: endpoint
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/conditional'
+components:
+ schemas:
+ conditional:
+ type: object
+ properties:
+ country:
+ type: string
+ postalCode:
+ type: string
+ region:
+ type: string
+ tuple:
+ type: array
+ dependent:
+ type: object
+ embedded:
+ type: string
+ decoded:
+ type: object
+ envelope:
+ type: string
diff --git a/tests/Fixtures/Scratch/SchemaKeywords3.1.0.yaml b/tests/Fixtures/Scratch/SchemaKeywords3.1.0.yaml
new file mode 100644
index 000000000..498b3b07e
--- /dev/null
+++ b/tests/Fixtures/Scratch/SchemaKeywords3.1.0.yaml
@@ -0,0 +1,71 @@
+openapi: 3.1.0
+info:
+ title: SchemaKeywords
+ version: '1.0'
+paths:
+ /endpoint:
+ get:
+ operationId: endpoint
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/conditional'
+components:
+ schemas:
+ conditional:
+ type: object
+ properties:
+ country:
+ type: string
+ postalCode:
+ type: string
+ region:
+ type: string
+ if:
+ required:
+ - country
+ then:
+ required:
+ - postalCode
+ else:
+ required:
+ - region
+ tuple:
+ type: array
+ prefixItems:
+ -
+ type: string
+ -
+ type: integer
+ contains:
+ type: string
+ minContains: 1
+ maxContains: 3
+ unevaluatedItems: false
+ unevaluatedProperties:
+ type: string
+ dependent:
+ type: object
+ dependentRequired:
+ creditCard:
+ - billingAddress
+ dependentSchemas:
+ creditCard:
+ required:
+ - billingAddress
+ embedded:
+ type: string
+ contentMediaType: application/json
+ contentEncoding: base64
+ contentSchema:
+ type: object
+ decoded:
+ type: object
+ envelope:
+ type: string
+ contentMediaType: application/json
+ contentSchema:
+ $ref: '#/components/schemas/decoded'
diff --git a/tests/Fixtures/Scratch/SchemaKeywords3.2.0.yaml b/tests/Fixtures/Scratch/SchemaKeywords3.2.0.yaml
new file mode 100644
index 000000000..1475eeeb1
--- /dev/null
+++ b/tests/Fixtures/Scratch/SchemaKeywords3.2.0.yaml
@@ -0,0 +1,71 @@
+openapi: 3.2.0
+info:
+ title: SchemaKeywords
+ version: '1.0'
+paths:
+ /endpoint:
+ get:
+ operationId: endpoint
+ responses:
+ 200:
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/conditional'
+components:
+ schemas:
+ conditional:
+ type: object
+ properties:
+ country:
+ type: string
+ postalCode:
+ type: string
+ region:
+ type: string
+ if:
+ required:
+ - country
+ then:
+ required:
+ - postalCode
+ else:
+ required:
+ - region
+ tuple:
+ type: array
+ prefixItems:
+ -
+ type: string
+ -
+ type: integer
+ contains:
+ type: string
+ minContains: 1
+ maxContains: 3
+ unevaluatedItems: false
+ unevaluatedProperties:
+ type: string
+ dependent:
+ type: object
+ dependentRequired:
+ creditCard:
+ - billingAddress
+ dependentSchemas:
+ creditCard:
+ required:
+ - billingAddress
+ embedded:
+ type: string
+ contentMediaType: application/json
+ contentEncoding: base64
+ contentSchema:
+ type: object
+ decoded:
+ type: object
+ envelope:
+ type: string
+ contentMediaType: application/json
+ contentSchema:
+ $ref: '#/components/schemas/decoded'
diff --git a/tests/ScratchTest.php b/tests/ScratchTest.php
index bd506e095..b57aae1ed 100644
--- a/tests/ScratchTest.php
+++ b/tests/ScratchTest.php
@@ -28,6 +28,15 @@ public static function scratchTestCases(): iterable
// for a diagnostic only one mode raises. Both keys contribute when both are present.
$expectedLogs = [
'Auth-3.0.0' => ['mutualTLS security schemes are not supported in OpenAPI 3.0 and will be omitted'],
+ 'SchemaKeywords-3.0.0' => [
+ 'prefixItems is not supported in OpenAPI 3.0',
+ 'unevaluatedProperties is not supported in OpenAPI 3.0',
+ 'unevaluatedItems is not supported in OpenAPI 3.0',
+ 'if/then/else is not supported in OpenAPI 3.0',
+ ],
+ 'SchemaKeywords-3.0.0-classic' => ['@OA\Items() is required when @OA\Schema() has type "array"'],
+ 'SchemaKeywords-3.0.0-spec' => ['has type "array" but no items'],
+ 'SchemaKeywords-3.0.0-hybrid' => ['has type "array" but no items'],
'Examples-3.0.0-classic' => ['@OA\Schema::examples is only allowed as of 3.1.0'],
'Examples-3.0.0-spec' => ['examples array is not supported in OpenAPI 3.0'],
'Examples-3.0.0-hybrid' => ['examples array is not supported in OpenAPI 3.0'],
|
|
|
|
|
|