Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validation rule that example/examples are mutually exclusive #1561

Merged
merged 8 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@
"testlegacy": "Run tests using the legacy TokenAnalyser",
"testall": "Run all tests (test + testlegacy)",
"analyse": "Run static analysis (phpstan/psalm)",
"spectral": "Run spectral lint over all .yaml files in the Examples folder",
"spectral-examples": "Run spectral lint over all .yaml files in the Examples folder",
"spectral-scratch": "Run spectral lint over all .yaml files in the tests/Fixtures/Scratch folder",
"spectral": "Run all spectral tests",
"docs:gen": "Rebuild reference documentation",
"docs:dev": "Run dev server for local development of gh-pages",
"docs:build": "Re-build static gh-pages"
Expand All @@ -105,7 +107,12 @@
"export XDEBUG_MODE=off && phpstan analyse --memory-limit=2G",
"export XDEBUG_MODE=off && psalm"
],
"spectral": "for ff in `find Examples -name '*.yaml'`; do spectral lint $ff; done",
"spectral-examples": "for ff in `find Examples -name '*.yaml'`; do spectral lint $ff; done",
"spectral-scratch": "for ff in `find tests/Fixtures/Scratch -name '*.yaml'`; do spectral lint $ff; done",
"spectral": [
"@spectral-examples",
"@spectral-scratch"
],
"docs:gen": [
"@php tools/refgen.php",
"@php tools/procgen.php"
Expand Down
4 changes: 2 additions & 2 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ parameters:
path: src/Processors/DocBlockDescriptions.php

-
message: "#^Property OpenApi\\\\Annotations\\\\JsonContent\\:\\:\\$examples \\(array\\<string, OpenApi\\\\Annotations\\\\Examples\\>\\) does not accept string\\.$#"
message: "#^Property OpenApi\\\\Annotations\\\\Schema\\:\\:\\$examples \\(array\\<OpenApi\\\\Annotations\\\\Examples\\>\\) does not accept string\\.$#"
count: 1
path: src/Processors/MergeJsonContent.php

-
message: "#^Property OpenApi\\\\Annotations\\\\XmlContent\\:\\:\\$examples \\(array\\<string, OpenApi\\\\Annotations\\\\Examples\\>\\) does not accept string\\.$#"
message: "#^Property OpenApi\\\\Annotations\\\\Schema\\:\\:\\$examples \\(array\\<OpenApi\\\\Annotations\\\\Examples\\>\\) does not accept string\\.$#"
count: 1
path: src/Processors/MergeXmlContent.php

Expand Down
34 changes: 31 additions & 3 deletions src/Annotations/AbstractAnnotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ public function __set(string $property, $value): void
$this->_context->logger->warning('Ignoring unexpected property "' . $property . '" for ' . $this->identity() . ', expecting "' . implode('", "', array_keys($fields)) . '" in ' . $this->_context);
}

/**
* Check if one of the given version numbers matches the current OpenAPI version.
*
* @param string|array $versions One or more version numbers
*/
public function isOpenApiVersion($versions): bool
{
return $this->_context->isVersion($versions);
}

/**
* Merge given annotations to their mapped properties configured in static::$_nested.
*
Expand Down Expand Up @@ -350,7 +360,7 @@ public function jsonSerialize()
if (isset($data->ref)) {
// Only specific https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#reference-object
$ref = ['$ref' => $data->ref];
if ($this->_context->version === OpenApi::VERSION_3_1_0) {
if ($this->isOpenApiVersion(OpenApi::VERSION_3_1_0)) {
foreach (['summary', 'description'] as $prop) {
if (property_exists($this, $prop)) {
if (!Generator::isDefault($this->{$prop})) {
Expand All @@ -361,7 +371,7 @@ public function jsonSerialize()
}
if (property_exists($this, 'nullable') && $this->nullable === true) {
$ref = ['oneOf' => [$ref]];
if ($this->_context->version == OpenApi::VERSION_3_1_0) {
if ($this->isOpenApiVersion(OpenApi::VERSION_3_1_0)) {
$ref['oneOf'][] = ['type' => 'null'];
} else {
$ref['nullable'] = $data->nullable;
Expand All @@ -381,7 +391,18 @@ public function jsonSerialize()
$data = (object) $ref;
}

if ($this->_context->version === OpenApi::VERSION_3_1_0) {
if ($this->isOpenApiVersion(OpenApi::VERSION_3_0_0)) {
if (isset($data->exclusiveMinimum) && is_numeric($data->exclusiveMinimum)) {
$data->minimum = $data->exclusiveMinimum;
$data->exclusiveMinimum = true;
}
if (isset($data->exclusiveMaximum) && is_numeric($data->exclusiveMaximum)) {
$data->maximum = $data->exclusiveMaximum;
$data->exclusiveMaximum = true;
}
}

if ($this->isOpenApiVersion(OpenApi::VERSION_3_1_0)) {
if (isset($data->nullable)) {
if (true === $data->nullable) {
if (isset($data->oneOf)) {
Expand Down Expand Up @@ -540,6 +561,13 @@ public function validate(array $stack = [], array $skip = [], string $ref = '',
}
$stack[] = $this;

if (property_exists($this, 'example') && property_exists($this, 'examples')) {
if (!Generator::isDefault($this->example) && !Generator::isDefault($this->examples)) {
$valid = false;
$this->_context->logger->warning($this->identity() . ': "example" and "examples" are mutually exclusive');
}
}

return self::_validate($this, $stack, $skip, $ref, $context) ? $valid : false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Annotations/Components.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class Components extends AbstractAnnotation
/**
* Reusable Examples.
*
* @var Examples[]
* @var array<Examples>
*/
public $examples = Generator::UNDEFINED;

Expand Down
1 change: 1 addition & 0 deletions src/Annotations/Examples.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Examples extends AbstractAnnotation

public static $_parents = [
Components::class,
Schema::class,
Parameter::class,
PathParameter::class,
MediaType::class,
Expand Down
2 changes: 2 additions & 0 deletions src/Annotations/HeaderParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

namespace OpenApi\Annotations;

use OpenApi\Annotations as OA;

/**
* A `@OA\Request` header parameter.
*
Expand Down
12 changes: 1 addition & 11 deletions src/Annotations/JsonContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace OpenApi\Annotations;

use OpenApi\Generator;
use OpenApi\Annotations as OA;

/**
* Shorthand for a json response.
Expand All @@ -17,16 +17,6 @@
*/
class JsonContent extends Schema
{
/**
* An associative array of Examples attributes.
*
* The keys represent the name of the example and the values are instances of the Examples attribute.
* Each example is used to show how the content of the request or response should look like.
*
* @var array<string,Examples>
*/
public $examples = Generator::UNDEFINED;

/**
* @inheritdoc
*/
Expand Down
4 changes: 2 additions & 2 deletions src/Annotations/License.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function jsonSerialize()
{
$data = parent::jsonSerialize();

if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) {
if ($this->isOpenApiVersion(OpenApi::VERSION_3_0_0)) {
unset($data->identifier);
}

Expand All @@ -90,7 +90,7 @@ public function validate(array $stack = [], array $skip = [], string $ref = '',
{
$valid = parent::validate($stack, $skip, $ref, $context);

if ($this->_context->isVersion(OpenApi::VERSION_3_1_0)) {
if ($this->isOpenApiVersion(OpenApi::VERSION_3_1_0)) {
if (!Generator::isDefault($this->url) && $this->identifier !== Generator::UNDEFINED) {
$this->_context->logger->warning($this->identity() . ' url and identifier are mutually exclusive');
$valid = false;
Expand Down
8 changes: 3 additions & 5 deletions src/Annotations/MediaType.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,11 @@ class MediaType extends AbstractAnnotation
/**
* Examples of the media type.
*
* Each example object should match the media type and specified schema if present.
* Each example should contain a value in the correct format as specified in the parameter encoding.
* The examples object is mutually exclusive of the example object.
* Furthermore, if referencing a schema which contains an example, the examples value shall override the example provided by the schema.
*
* Furthermore, if referencing a schema which contains an example,
* the examples value shall override the example provided by the schema.
*
* @var array<string,Examples>
* @var array<Examples>
*/
public $examples = Generator::UNDEFINED;

Expand Down
2 changes: 1 addition & 1 deletion src/Annotations/OpenApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public function jsonSerialize()
{
$data = parent::jsonSerialize();

if (false === $this->_context->isVersion(OpenApi::VERSION_3_1_0)) {
if (false === $this->isOpenApiVersion(OpenApi::VERSION_3_1_0)) {
unset($data->webhooks);
}

Expand Down
1 change: 1 addition & 0 deletions src/Annotations/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
namespace OpenApi\Annotations;

use OpenApi\Generator;
use OpenApi\Annotations as OA;

/**
* Base class for `@OA\Get`, `@OA\Post`, `@OA\Put`, etc.
Expand Down
4 changes: 2 additions & 2 deletions src/Annotations/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,13 @@ class Parameter extends AbstractAnnotation
public $example = Generator::UNDEFINED;

/**
* Examples of the media type.
* Examples of the parameter.
*
* Each example should contain a value in the correct format as specified in the parameter encoding.
* The examples object is mutually exclusive of the example object.
* Furthermore, if referencing a schema which contains an example, the examples value shall override the example provided by the schema.
*
* @var array<string,Examples>
* @var array<Examples>
*/
public $examples = Generator::UNDEFINED;

Expand Down
2 changes: 2 additions & 0 deletions src/Annotations/PathParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

namespace OpenApi\Annotations;

use OpenApi\Annotations as OA;

/**
* A `@OA\Request` path parameter.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Annotations/QueryParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

namespace OpenApi\Annotations;

use OpenApi\Annotations as OA;

/**
* A `@OA\Request` query parameter.
*
Expand Down
54 changes: 42 additions & 12 deletions src/Annotations/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,17 @@ class Schema extends AbstractAnnotation

/**
* The maximum number of properties allowed in an object instance.
* An object instance is valid against this property if its number of properties is less than, or equal to, the value of this attribute.
* An object instance is valid against this property if its number of properties is less than, or equal to, the
* value of this attribute.
*
* @var int
*/
public $maxProperties = Generator::UNDEFINED;

/**
* The minimum number of properties allowed in an object instance.
* An object instance is valid against this property if its number of properties is greater than, or equal to, the value of this attribute.
* An object instance is valid against this property if its number of properties is greater than, or equal to, the
* value of this attribute.
*
* @var int
*/
Expand Down Expand Up @@ -121,9 +123,9 @@ class Schema extends AbstractAnnotation
* - ssv: space separated values foo bar.
* - tsv: tab separated values foo\tbar.
* - pipes: pipe separated values foo|bar.
* - multi: corresponds to multiple parameter instances instead of multiple values for a single instance foo=bar&foo=baz.
* This is valid only for parameters of type <code>query</code> or <code>formData</code>.
* Default value is csv.
* - multi: corresponds to multiple parameter instances instead of multiple values for a single instance
* foo=bar&foo=baz. This is valid only for parameters of type <code>query</code> or <code>formData</code>. Default
* value is csv.
*
* @var string
*/
Expand Down Expand Up @@ -179,7 +181,8 @@ class Schema extends AbstractAnnotation
/**
* The maximum length of a string property.
*
* A string instance is valid against this property if its length is less than, or equal to, the value of this attribute.
* A string instance is valid against this property if its length is less than, or equal to, the value of this
* attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor26)
*
Expand All @@ -190,7 +193,8 @@ class Schema extends AbstractAnnotation
/**
* The minimum length of a string property.
*
* A string instance is valid against this property if its length is greater than, or equal to, the value of this attribute.
* A string instance is valid against this property if its length is greater than, or equal to, the value of this
* attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor29)
*
Expand All @@ -208,7 +212,8 @@ class Schema extends AbstractAnnotation
/**
* The maximum number of items allowed in an array property.
*
* An array instance is valid against this property if its number of items is less than, or equal to, the value of this attribute.
* An array instance is valid against this property if its number of items is less than, or equal to, the value of
* this attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor42)
*
Expand All @@ -219,7 +224,8 @@ class Schema extends AbstractAnnotation
/**
* The minimum number of items allowed in an array property.
*
* An array instance is valid against this property if its number of items is greater than, or equal to, the value of this attribute.
* An array instance is valid against this property if its number of items is greater than, or equal to, the value
* of this attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor45)
*
Expand All @@ -241,7 +247,8 @@ class Schema extends AbstractAnnotation
/**
* A collection of allowable values for a property.
*
* A property instance is valid against this attribute if its value is one of the values specified in this collection.
* A property instance is valid against this attribute if its value is one of the values specified in this
* collection.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor76)
*
Expand Down Expand Up @@ -319,6 +326,19 @@ class Schema extends AbstractAnnotation
*/
public $example = Generator::UNDEFINED;

/**
* Examples of the schema.
*
* Each example should contain a value in the correct format as specified in the parameter encoding.
* The examples object is mutually exclusive of the example object.
* Furthermore, if referencing a schema which contains an example, the examples value shall override the example provided by the schema.
*
* @since 3.1.0
*
* @var array<Examples>
*/
public $examples = Generator::UNDEFINED;

/**
* Allows sending a null value for the defined schema.
* Default value is false.
Expand Down Expand Up @@ -439,6 +459,7 @@ class Schema extends AbstractAnnotation
Items::class => 'items',
Property::class => ['properties', 'property'],
ExternalDocumentation::class => 'externalDocs',
Examples::class => ['examples', 'example'],
Xml::class => 'xml',
AdditionalProperties::class => 'additionalProperties',
Attachable::class => ['attachables'],
Expand All @@ -463,8 +484,9 @@ public function jsonSerialize()
{
$data = parent::jsonSerialize();

if (isset($data->const)) {
if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) {
if ($this->isOpenApiVersion(OpenApi::VERSION_3_0_0)) {
unset($data->examples);
if (isset($data->const)) {
$data->enum = [$data->const];
unset($data->const);
}
Expand All @@ -484,6 +506,14 @@ public function validate(array $stack = [], array $skip = [], string $ref = '',
return false;
}

if ($this->isOpenApiVersion(OpenApi::VERSION_3_0_0)) {
if (!Generator::isDefault($this->examples)) {
$this->_context->logger->warning($this->identity() . ' is only allowed for ' . OpenApi::VERSION_3_1_0);

return false;
}
}

return parent::validate($stack, $skip, $ref, $context);
}
}
Loading