diff --git a/src/Analysis.php b/src/Analysis.php index 7f6954a2d..c52f074e9 100644 --- a/src/Analysis.php +++ b/src/Analysis.php @@ -423,12 +423,47 @@ public function process($processors = null): void public function validate(): bool { - if ($this->openapi instanceof OA\OpenApi) { - return $this->openapi->validate(); + if (!$this->openapi instanceof OA\OpenApi) { + $this->context->logger->warning('No openapi target set. Run the MergeIntoOpenApi processor before validate()'); + + return false; + } + + $isValid = true; + $version = $this->openapi->openapi; + $context = new \stdClass(); + + foreach ($this->collectAnnotations($this->openapi) as $annotation) { + $isValid = $annotation->validate($this, $version, $context) && $isValid; } - $this->context->logger->warning('No openapi target set. Run the MergeIntoOpenApi processor before validate()'); + return $isValid; + + } + + /** + * @return array + */ + protected function collectAnnotations(OA\AbstractAnnotation $root): array + { + $annotations = [$root]; + + foreach (get_object_vars($root) as $field => $value) { + if (null === $value || Generator::isDefault($value) || is_scalar($value) || in_array($field, $root::$_blacklist)) { + continue; + } - return false; + if ($value instanceof OA\AbstractAnnotation) { + $annotations = array_merge($annotations, $this->collectAnnotations($value)); + } elseif (is_array($value)) { + foreach ($value as $item) { + if ($item instanceof OA\AbstractAnnotation) { + $annotations = array_merge($annotations, $this->collectAnnotations($item)); + } + } + } + } + + return $annotations; } } diff --git a/src/Annotations/AbstractAnnotation.php b/src/Annotations/AbstractAnnotation.php index a1e5383c4..a0d7fa011 100644 --- a/src/Annotations/AbstractAnnotation.php +++ b/src/Annotations/AbstractAnnotation.php @@ -6,6 +6,7 @@ namespace OpenApi\Annotations; +use OpenApi\Analysis; use OpenApi\Annotations as OA; use OpenApi\Context; use OpenApi\Generator; @@ -450,28 +451,62 @@ public function jsonSerialize() } /** - * Validate annotation tree, and log notices & warnings. - * - * @param array $stack the path of annotations above this annotation in the tree - * @param array $skip (prevent stack overflow, when traversing an infinite dependency graph) - * @param string $ref Current ref path? - * @param object $context a free-form context contains + * Validate a given value against a `_$type` definition. */ - public function validate(array $stack = [], array $skip = [], string $ref = '', ?object $context = null): bool + private function validateValueType(string $type, mixed $value): bool { - if (in_array($this, $skip, true)) { + if (str_starts_with($type, '[') && str_ends_with($type, ']')) { + // $value must be an array + if (!$this->validateValueType('array', $value)) { + return false; + } + + $itemType = substr($type, 1, -1); + foreach ($value as $item) { + if (!$this->validateValueType($itemType, $item)) { + return false; + } + } + return true; } - $valid = true; + if (is_subclass_of($type, AbstractAnnotation::class)) { + $type = 'object'; + } + + $isValidType = fn (string $type, mixed $value): bool => match ($type) { + 'string' => is_string($value), + 'boolean' => is_bool($value), + 'integer' => is_int($value), + 'number' => is_numeric($value), + 'object' => is_object($value), + 'array' => is_array($value) && array_is_list($value), + 'scheme' => in_array($value, ['http', 'https', 'ws', 'wss'], true), + default => throw new OpenApiException('Invalid type "' . $type . '"'), + }; + + foreach (explode('|', $type) as $tt) { + if ($isValidType(trim($tt), $value)) { + return true; + } + } + + return false; + } - // Report orphaned annotations + public function validate(?Analysis $analysis = null, string $version = OpenApi::DEFAULT_VERSION, ?object $context = null): bool + { + $isValid = true; + + // validate unmerged foreach ($this->_unmerged as $annotation) { if (!is_object($annotation)) { $this->_context->logger->warning('Unexpected type: "' . gettype($annotation) . '" in ' . $this->identity() . '->_unmerged, expecting a Annotation object'); break; } +<<<<<<< HEAD /** @var class-string $class */ $class = get_class($annotation); if ($details = $this->matchNested($annotation)) { @@ -485,14 +520,28 @@ public function validate(array $stack = [], array $skip = [], string $ref = '', $message = 'Unexpected ' . $annotation->identity(); if ($class::$_parents) { $message .= ', expected to be inside ' . implode(', ', Util::shorten($class::$_parents)); +======= + if ($details = $this->matchNested($annotation)) { + $property = $details->value; + if (is_array($property)) { + $this->_context->logger->warning('Only one ' . $annotation->identity([]) . ' allowed for ' . $this->identity() . ' multiple found, skipped: ' . $annotation->_context); + } else { + $this->_context->logger->warning('Only one ' . $annotation->identity([]) . ' allowed for ' . $this->identity() . " multiple found in:\n Using: " . $this->{$property}->_context . "\n Skipped: " . $annotation->_context); + } + } elseif ($annotation instanceof AbstractAnnotation) { + $message = 'Unexpected ' . $annotation->identity(); + if ($annotation::$_parents) { + $message .= ', expected to be inside ' . implode(', ', AbstractAnnotation::shorten($annotation::$_parents)); +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) } $this->_context->logger->warning($message . ' in ' . $annotation->_context); } - $valid = false; + + $isValid = false; } - // Report conflicting key - foreach (static::$_nested as $annotationClass => $nested) { + // validate conflicting keys + foreach ($this::$_nested as $annotationClass => $nested) { if (is_string($nested) || count($nested) === 1) { continue; } @@ -502,9 +551,15 @@ public function validate(array $stack = [], array $skip = [], string $ref = '', } $keys = []; $keyField = $nested[1]; + /** @var AbstractAnnotation $item */ foreach ($this->{$property} as $key => $item) { +<<<<<<< HEAD if (is_array($item) && is_numeric($key) === false) { $this->_context->logger->warning($this->identity() . '->' . $property . ' is an object literal, use nested ' . Util::shorten($annotationClass) . '() annotation(s) in ' . $this->_context); +======= + if (is_array($item) && !is_numeric($key)) { + $this->_context->logger->warning($this->identity() . '->' . $property . ' is an object literal, use nested ' . AbstractAnnotation::shorten($annotationClass) . '() annotation(s) in ' . $this->_context); +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) $keys[$key] = $item; } elseif (Generator::isDefault($item->{$keyField})) { $this->_context->logger->error($item->identity() . ' is missing key-field: "' . $keyField . '" in ' . $item->_context); @@ -516,29 +571,46 @@ public function validate(array $stack = [], array $skip = [], string $ref = '', } } +<<<<<<< HEAD if (property_exists($this, 'ref') && !Generator::isDefault($this->ref) && is_string($this->ref)) { if (substr($this->ref, 0, 2) === '#/' && $stack !== [] && $stack[0] instanceof OpenApi) { // Internal reference +======= + // validate refs + if ($analysis?->openapi && property_exists($this, 'ref') && !Generator::isDefault($this->ref) && is_string($this->ref)) { + if (str_starts_with($this->ref, '#/')) { +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) try { - $stack[0]->ref($this->ref); + $analysis->openapi->ref($this->ref); } catch (\Exception $e) { $this->_context->logger->warning($e->getMessage() . ' for ' . $this->identity() . ' in ' . $this->_context, ['exception' => $e]); + $isValid = false; } } - } else { - // Report missing required fields (when not a $ref) - foreach (static::$_required as $property) { + } + + // validate required properties + if (!property_exists($this, 'ref') || Generator::isDefault($this->ref) || !is_string($this->ref)) { + foreach ($this::$_required as $property) { if (Generator::isDefault($this->{$property})) { $message = 'Missing required field "' . $property . '" for ' . $this->identity() . ' in ' . $this->_context; - foreach (static::$_nested as $class => $nested) { + foreach ($this::$_nested as $class => $nested) { $nestedProperty = is_array($nested) ? $nested[0] : $nested; if ($property === $nestedProperty) { if ($this instanceof OpenApi) { +<<<<<<< HEAD $message = 'Required ' . Util::shorten($class) . '() not found'; } elseif (is_array($nested)) { $message = $this->identity() . ' requires at least one ' . Util::shorten($class) . '() in ' . $this->_context; } else { $message = $this->identity() . ' requires a ' . Util::shorten($class) . '() in ' . $this->_context; +======= + $message = 'Required ' . AbstractAnnotation::shorten($class) . '() not found'; + } elseif (is_array($nested)) { + $message = $this->identity() . ' requires at least one ' . AbstractAnnotation::shorten($class) . '() in ' . $this->_context; + } else { + $message = $this->identity() . ' requires a ' . AbstractAnnotation::shorten($class) . '() in ' . $this->_context; +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) } break; } @@ -548,73 +620,36 @@ public function validate(array $stack = [], array $skip = [], string $ref = '', } } - // Report invalid types - foreach (static::$_types as $property => $type) { + // validate types + foreach ($this::$_types as $property => $type) { $value = $this->{$property}; if (Generator::isDefault($value) || $value === null) { continue; } if (is_string($type)) { - if ($this->validateType($type, $value) === false) { - $valid = false; + if (!$this->validateValueType($type, $value)) { $this->_context->logger->warning($this->identity() . '->' . $property . ' is a "' . gettype($value) . '", expecting a "' . $type . '" in ' . $this->_context); + $isValid = false; } } elseif (is_array($type)) { // enum? - if (in_array($value, $type) === false) { + if (!in_array($value, $type)) { $this->_context->logger->warning($this->identity() . '->' . $property . ' "' . $value . '" is invalid, expecting "' . implode('", "', $type) . '" in ' . $this->_context); } } else { throw new OpenApiException('Invalid ' . get_class($this) . '::$_types[' . $property . ']'); } } - $stack[] = $this; + // validate example/examples 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; - } - /** - * Recursively validate all annotation properties. - * - * @param array|object $fields - */ - private static function _validate($fields, array $stack, array $skip, string $baseRef, ?object $context): bool - { - $valid = true; - $blacklist = []; - if (is_object($fields)) { - if (in_array($fields, $skip, true)) { - return true; - } - $skip[] = $fields; - $blacklist = property_exists($fields, '_blacklist') ? $fields::$_blacklist : []; - } - - foreach ($fields as $field => $value) { - if ($value === null || is_scalar($value) || in_array($field, $blacklist)) { - continue; - } - $ref = $baseRef !== '' ? $baseRef . '/' . urlencode((string) $field) : urlencode((string) $field); - if (is_object($value)) { - if (method_exists($value, 'validate')) { - if (!$value->validate($stack, $skip, $ref, $context)) { - $valid = false; - } - } elseif (!self::_validate($value, $stack, $skip, $ref, $context)) { - $valid = false; - } - } elseif (is_array($value) && !self::_validate($value, $stack, $skip, $ref, $context)) { - $valid = false; + $isValid = false; } } - return $valid; + return $isValid; } /** @@ -652,7 +687,7 @@ public function identity(?array $properties = null): string } /** - * Check if $other can be nested and if so return details about where/how. + * Check if $other can be nested, and if so, return details about where/how. * * @param AbstractAnnotation $other the other annotation * @@ -691,10 +726,11 @@ public function getRoot(): string /** * Match the annotation root. * - * @param class-string $rootClass the root class to match + * @param class-string $thisClass the root class to match */ - public function isRoot(string $rootClass): bool + public function isRoot(string $thisClass): bool { +<<<<<<< HEAD return get_class($this) === $rootClass || $this->getRoot() === $rootClass; } @@ -784,6 +820,9 @@ private function validateArrayType($value): bool } return true; +======= + return static::class === $thisClass || $this->getRoot() === $thisClass; +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) } /** diff --git a/src/Annotations/Items.php b/src/Annotations/Items.php index af125feb1..13b246526 100644 --- a/src/Annotations/Items.php +++ b/src/Annotations/Items.php @@ -37,6 +37,7 @@ class Items extends Schema XmlContent::class, Items::class, ]; +<<<<<<< HEAD /** * @inheritdoc @@ -58,4 +59,6 @@ public function validate(array $stack = [], array $skip = [], string $ref = '', return $valid; } +======= +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) } diff --git a/src/Annotations/License.php b/src/Annotations/License.php index 4c4405791..85adf5762 100644 --- a/src/Annotations/License.php +++ b/src/Annotations/License.php @@ -6,6 +6,7 @@ namespace OpenApi\Annotations; +use OpenApi\Analysis; use OpenApi\Generator; /** @@ -84,20 +85,18 @@ public function jsonSerialize() return $data; } - /** - * @inheritdoc - */ - public function validate(array $stack = [], array $skip = [], string $ref = '', ?object $context = null): bool + #[\Override] + public function validate(?Analysis $analysis = null, string $version = OpenApi::DEFAULT_VERSION, ?object $context = null): bool { - $valid = parent::validate($stack, $skip, $ref, $context); + $isValid = parent::validate($analysis, $version, $context); - if (!$this->_context->isVersion('3.0.x')) { + if (!OpenApi::versionMatch($version, '3.0.x')) { if (!Generator::isDefault($this->url) && !Generator::isDefault($this->identifier)) { - $this->_context->logger->warning($this->identity() . ' url and identifier are mutually exclusive'); - $valid = false; + $this->_context->logger->warning($this->identity() . ' url and identifier are mutually exclusive in ' . $this->_context); + $isValid = false; } } - return $valid; + return $isValid; } } diff --git a/src/Annotations/OpenApi.php b/src/Annotations/OpenApi.php index 174083887..38d18b199 100644 --- a/src/Annotations/OpenApi.php +++ b/src/Annotations/OpenApi.php @@ -145,39 +145,51 @@ class OpenApi extends AbstractAnnotation */ public static $_types = []; +<<<<<<< HEAD /** * @inheritdoc */ public function validate(?array $stack = null, ?array $skip = null, string $ref = '', $context = null): bool +======= + public function __construct(array $properties) { - if ($stack !== null || $skip !== null || $ref !== '') { - $this->_context->logger->warning('Nested validation for ' . $this->identity() . ' not allowed'); + parent::__construct($properties); - return false; + if ($this->_context->root()->version) { + // override via `Generator::setVersion()` + $this->openapi = $this->_context->root()->version; + } else { + $this->_context->root()->version = $this->openapi; } + } - if (!in_array($this->openapi, self::SUPPORTED_VERSIONS)) { - $this->_context->logger->warning('Unsupported OpenAPI version "' . $this->openapi . '". Allowed versions are: ' . implode(', ', self::SUPPORTED_VERSIONS)); + #[\Override] + public function validate(?Analysis $analysis = null, string $version = OpenApi::DEFAULT_VERSION, ?object $context = null): bool +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) + { + $isValid = parent::validate($analysis, $version, $context); - return false; + if (!in_array($this->openapi, OpenApi::SUPPORTED_VERSIONS)) { + $this->_context->logger->warning('Unsupported OpenAPI version "' . $this->openapi . '". Allowed versions are: ' . implode(', ', OpenApi::SUPPORTED_VERSIONS)); + $isValid = false; } /* paths is optional in 3.1.x */ - if (self::versionMatch($this->openapi, '3.0.x') && Generator::isDefault($this->paths)) { + if (OpenApi::versionMatch($version, '3.0.x') && Generator::isDefault($this->paths)) { $this->_context->logger->warning('Required @OA\PathItem() not found'); + $isValid = false; } - if (self::versionMatch($this->openapi, '3.1.x') + if (OpenApi::versionMatch($version, '3.1.x') && Generator::isDefault($this->paths) && Generator::isDefault($this->webhooks) && Generator::isDefault($this->components) ) { - $this->_context->logger->warning("At least one of 'Required @OA\PathItem(), @OA\Components() or @OA\Webhook() not found'"); - - return false; + $this->_context->logger->warning('At least one of @OA\PathItem(), @OA\Components() or @OA\Webhook() required'); + $isValid = false; } - return parent::validate([], [], '#', new \stdClass()); + return $isValid; } /** diff --git a/src/Annotations/Operation.php b/src/Annotations/Operation.php index 81363734d..99d388d42 100644 --- a/src/Annotations/Operation.php +++ b/src/Annotations/Operation.php @@ -6,6 +6,7 @@ namespace OpenApi\Annotations; +use OpenApi\Analysis; use OpenApi\Annotations as OA; use OpenApi\Generator; @@ -211,39 +212,33 @@ public function jsonSerialize() return $data; } - /** - * @inheritdoc - */ - public function validate(array $stack = [], array $skip = [], string $ref = '', ?object $context = null): bool + #[\Override] + public function validate(?Analysis $analysis = null, string $version = OpenApi::DEFAULT_VERSION, ?object $context = null): bool { - if (in_array($this, $skip, true)) { - return true; - } - - $valid = parent::validate($stack, $skip, $ref, $context); + $isValid = parent::validate($analysis, $version, $context); if (!Generator::isDefault($this->responses)) { foreach ($this->responses as $response) { if (!Generator::isDefault($response->response) && $response->response !== 'default' && preg_match('/^([12345]{1}\d{2})|([12345]{1}XX)$/', (string) $response->response) === 0) { $this->_context->logger->warning('Invalid value "' . $response->response . '" for ' . $response->identity([]) . '->response, expecting "default", a HTTP Status Code or HTTP Status Code range definition in ' . $response->_context); - $valid = false; + $isValid = false; } } } - if (is_object($context) && !Generator::isDefault($this->operationId)) { + if (!Generator::isDefault($this->operationId)) { if (!property_exists($context, 'operationIds')) { $context->operationIds = []; } if (in_array($this->operationId, $context->operationIds)) { $this->_context->logger->warning('operationId must be unique. Duplicate value found: "' . $this->operationId . '"'); - $valid = false; + $isValid = false; } $context->operationIds[] = $this->operationId; } - return $valid; + return $isValid; } } diff --git a/src/Annotations/Parameter.php b/src/Annotations/Parameter.php index dcb5549ff..0ef552118 100644 --- a/src/Annotations/Parameter.php +++ b/src/Annotations/Parameter.php @@ -6,6 +6,7 @@ namespace OpenApi\Annotations; +use OpenApi\Analysis; use OpenApi\Generator; /** @@ -266,27 +267,21 @@ class Parameter extends AbstractAnnotation Trace::class, ]; - /** - * @inheritdoc - */ - public function validate(array $stack = [], array $skip = [], string $ref = '', ?object $context = null): bool + #[\Override] + public function validate(?Analysis $analysis = null, string $version = OpenApi::DEFAULT_VERSION, ?object $context = null): bool { - if (in_array($this, $skip, true)) { - return true; - } - - $valid = parent::validate($stack, $skip, $ref, $context); + $isValid = parent::validate($analysis, $version, $context); if (Generator::isDefault($this->ref)) { if ($this->in === 'body') { if (Generator::isDefault($this->schema)) { $this->_context->logger->warning('Field "schema" is required when ' . $this->identity() . ' is in "' . $this->in . '" in ' . $this->_context); - $valid = false; + $isValid = false; } } } - return $valid; + return $isValid; } #[\Override] diff --git a/src/Annotations/Response.php b/src/Annotations/Response.php index 2534796a1..53f31985b 100644 --- a/src/Annotations/Response.php +++ b/src/Annotations/Response.php @@ -6,6 +6,7 @@ namespace OpenApi\Annotations; +use OpenApi\Analysis; use OpenApi\Generator; /** @@ -113,18 +114,16 @@ class Response extends AbstractAnnotation Trace::class, ]; - /** - * @inheritdoc - */ - public function validate(array $stack = [], array $skip = [], string $ref = '', ?object $context = null): bool + #[\Override] + public function validate(?Analysis $analysis = null, string $version = OpenApi::DEFAULT_VERSION, ?object $context = null): bool { - $valid = parent::validate($stack, $skip, $ref, $context); + $isValid = parent::validate($analysis, $version, $context); if (Generator::isDefault($this->description) && Generator::isDefault($this->ref)) { - $this->_context->logger->warning($this->identity() . ' One of description or ref is required in ' . $this->_context->getDebugLocation()); - $valid = false; + $this->_context->logger->warning($this->identity() . ' One of description or ref is required in ' . $this->_context); + $isValid = false; } - return $valid; + return $isValid; } } diff --git a/src/Annotations/Schema.php b/src/Annotations/Schema.php index 6f572ae5b..afdfc7aab 100644 --- a/src/Annotations/Schema.php +++ b/src/Annotations/Schema.php @@ -6,6 +6,7 @@ namespace OpenApi\Annotations; +use OpenApi\Analysis; use OpenApi\Generator; /** @@ -541,25 +542,24 @@ public function jsonSerialize() return $data; } - /** - * @inheritdoc - */ - public function validate(array $stack = [], array $skip = [], string $ref = '', ?object $context = null): bool + #[\Override] + public function validate(?Analysis $analysis = null, string $version = OpenApi::DEFAULT_VERSION, ?object $context = null): bool { + $isValid = parent::validate($analysis, $version, $context); + if ($this->hasType('array') && Generator::isDefault($this->items)) { $this->_context->logger->warning('@OA\\Items() is required when ' . $this->identity() . ' has type "array" in ' . $this->_context); - return false; + $isValid = false; } - if ($this->_context->isVersion('3.0.x')) { + if (OpenApi::versionMatch($version, '3.0.x')) { if (!Generator::isDefault($this->examples)) { - $this->_context->logger->warning($this->identity() . ' is only allowed as of 3.1.0'); - - return false; + $this->_context->logger->warning(static::shorten(static::class) . '::examples is only allowed as of 3.1.0 in ' . $this->_context); + $isValid = false; } } - return parent::validate($stack, $skip, $ref, $context); + return $isValid; } } diff --git a/tests/Annotations/AbstractAnnotationTest.php b/tests/Annotations/AbstractAnnotationTest.php index ea6b0746c..7d770c65e 100644 --- a/tests/Annotations/AbstractAnnotationTest.php +++ b/tests/Annotations/AbstractAnnotationTest.php @@ -110,6 +110,7 @@ public static function nestedMatches(): iterable { $parameterMatch = (object) ['key' => OA\Parameter::class, 'value' => ['parameters']]; +<<<<<<< HEAD return [ 'simple-match' => [OA\Parameter::class, $parameterMatch], 'invalid-annotation' => [OA\Schema::class, null], @@ -117,6 +118,13 @@ public static function nestedMatches(): iterable 'sub-sub-annotation' => [SubSubParameter::class, $parameterMatch], 'sub-invalid' => [SubSchema::class, null], ]; +======= + yield 'simple-match' => [OA\Parameter::class, $parameterMatch]; + yield 'invalid-annotation' => [OA\Schema::class, null]; + yield 'sub-annotation' => [SubParameter::class, $parameterMatch]; + yield 'sub-sub-annotation' => [SubSubParameter::class, $parameterMatch]; + yield 'sub-invalid' => [SubSchema::class, null]; +>>>>>>> e7fa8bb (Refactor annotation validation (#1971)) } /** @@ -157,8 +165,8 @@ public function testValidateExamples(): void ->getProcessorPipeline() ->process($analysis); - $this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found'); $this->assertOpenApiLogEntryContains('Required @OA\Info() not found'); + $this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found'); $this->assertOpenApiLogEntryContains('"example" and "examples" are mutually exclusive'); $analysis->validate(); diff --git a/tests/Annotations/LicenseTest.php b/tests/Annotations/LicenseTest.php index 658b4f363..c343fc5cc 100644 --- a/tests/Annotations/LicenseTest.php +++ b/tests/Annotations/LicenseTest.php @@ -21,6 +21,6 @@ public function testValidation3_1_0(): void $this->assertOpenApiLogEntryContains('@OA\License() url and identifier are mutually exclusive'); $annotations = $this->annotationsFromDocBlockParser('@OA\License(name="MIT", identifier="MIT", url="http://localhost")', [], '3.1.1'); - $annotations[0]->validate(); + $annotations[0]->validate(version: '3.1.1'); } } diff --git a/tests/Annotations/OpenApiTest.php b/tests/Annotations/OpenApiTest.php index 27ecb74c3..96aca26f4 100644 --- a/tests/Annotations/OpenApiTest.php +++ b/tests/Annotations/OpenApiTest.php @@ -13,30 +13,32 @@ class OpenApiTest extends OpenApiTestCase { public function testValidVersion(): void { - $this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found'); $this->assertOpenApiLogEntryContains('Required @OA\Info() not found'); + $this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found'); $openapi = new OA\OpenApi(['_context' => $this->getContext()]); $openapi->openapi = '3.0.3'; $openapi->validate(); } - public function testValidVersion310(): void + public function testValidVersion3_1_0(): void { - $this->assertOpenApiLogEntryContains("At least one of 'Required @OA\PathItem(), @OA\Components() or @OA\Webhook() not found'"); + $this->assertOpenApiLogEntryContains('Required @OA\Info() not found'); + $this->assertOpenApiLogEntryContains('At least one of @OA\PathItem(), @OA\Components() or @OA\Webhook() required'); $openapi = new OA\OpenApi(['_context' => $this->getContext()]); $openapi->openapi = '3.1.1'; - $openapi->validate(); + $openapi->validate(version: '3.1.1'); } public function testInvalidVersion(): void { + $this->assertOpenApiLogEntryContains('Required @OA\Info() not found'); $this->assertOpenApiLogEntryContains('Unsupported OpenAPI version "2". Allowed versions are:'); $openapi = new OA\OpenApi(['_context' => $this->getContext()]); $openapi->openapi = '2'; - $openapi->validate(); + $openapi->validate(version: '2'); } public function testSerialize(): void diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php index 65507c595..5e02ed64c 100644 --- a/tests/GeneratorTest.php +++ b/tests/GeneratorTest.php @@ -60,8 +60,8 @@ public function testScan(string $name, iterable $sources): void public function testScanInvalidSource(): void { $this->assertOpenApiLogEntryContains('Skipping invalid source: /tmp/__swagger_php_does_not_exist__'); - $this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found'); $this->assertOpenApiLogEntryContains('Required @OA\Info() not found'); + $this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found'); (new Generator($this->getTrackingLogger())) ->setAnalyser($this->getAnalyzer())