220 lines
6.9 KiB
PHP
220 lines
6.9 KiB
PHP
<?php declare(strict_types=1);
|
|
|
|
/**
|
|
* @license Apache 2.0
|
|
*/
|
|
|
|
namespace OpenApi\Annotations;
|
|
|
|
use OpenApi\Analysis;
|
|
use OpenApi\Generator;
|
|
use OpenApi\Logger;
|
|
use OpenApi\Util;
|
|
|
|
/**
|
|
* @Annotation
|
|
* This is the root document object for the API specification.
|
|
*
|
|
* A "OpenApi Object": https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#openapi-object
|
|
*/
|
|
class OpenApi extends AbstractAnnotation
|
|
{
|
|
/**
|
|
* The semantic version number of the OpenAPI Specification version that the OpenAPI document uses.
|
|
* The openapi field should be used by tooling specifications and clients to interpret the OpenAPI document.
|
|
* This is not related to the API info.version string.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $openapi = '3.0.0';
|
|
|
|
/**
|
|
* Provides metadata about the API. The metadata may be used by tooling as required.
|
|
*
|
|
* @var Info
|
|
*/
|
|
public $info = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* An array of Server Objects, which provide connectivity information to a target server.
|
|
* If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /.
|
|
*
|
|
* @var Server[]
|
|
*/
|
|
public $servers = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* The available paths and operations for the API.
|
|
*
|
|
* @var PathItem[]
|
|
*/
|
|
public $paths = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* An element to hold various components for the specification.
|
|
*
|
|
* @var Components
|
|
*/
|
|
public $components = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* Lists the required security schemes to execute this operation.
|
|
* The name used for each property must correspond to a security scheme declared
|
|
* in the Security Schemes under the Components Object.
|
|
* Security Requirement Objects that contain multiple schemes require that
|
|
* all schemes must be satisfied for a request to be authorized.
|
|
* This enables support for scenarios where multiple query parameters or
|
|
* HTTP headers are required to convey security information.
|
|
* When a list of Security Requirement Objects is defined on the Open API object or
|
|
* Operation Object, only one of Security Requirement Objects in the list needs to
|
|
* be satisfied to authorize the request.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $security = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* A list of tags used by the specification with additional metadata.
|
|
* The order of the tags can be used to reflect on their order by the parsing tools.
|
|
* Not all tags that are used by the Operation Object must be declared.
|
|
* The tags that are not declared may be organized randomly or based on the tools' logic.
|
|
* Each tag name in the list must be unique.
|
|
*
|
|
* @var Tag[]
|
|
*/
|
|
public $tags = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* Additional external documentation.
|
|
*
|
|
* @var ExternalDocumentation
|
|
*/
|
|
public $externalDocs = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* @var Analysis
|
|
*/
|
|
public $_analysis = Generator::UNDEFINED;
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public static $_blacklist = ['_context', '_unmerged', '_analysis'];
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public static $_required = ['openapi', 'info', 'paths'];
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public static $_nested = [
|
|
Info::class => 'info',
|
|
Server::class => ['servers'],
|
|
PathItem::class => ['paths', 'path'],
|
|
Components::class => 'components',
|
|
Tag::class => ['tags'],
|
|
ExternalDocumentation::class => 'externalDocs',
|
|
];
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public static $_types = [];
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
public function validate(array $parents = null, array $skip = null, string $ref = ''): bool
|
|
{
|
|
if ($parents !== null || $skip !== null || $ref !== '') {
|
|
Logger::notice('Nested validation for ' . $this->identity() . ' not allowed');
|
|
|
|
return false;
|
|
}
|
|
|
|
return parent::validate([], [], '#');
|
|
}
|
|
|
|
/**
|
|
* Save the OpenAPI documentation to a file.
|
|
*/
|
|
public function saveAs(string $filename, string $format = 'auto'): void
|
|
{
|
|
if ($format === 'auto') {
|
|
$format = strtolower(substr($filename, -5)) === '.json' ? 'json' : 'yaml';
|
|
}
|
|
if (strtolower($format) === 'json') {
|
|
$content = $this->toJson();
|
|
} else {
|
|
$content = $this->toYaml();
|
|
}
|
|
if (file_put_contents($filename, $content) === false) {
|
|
throw new \Exception('Failed to saveAs("' . $filename . '", "' . $format . '")');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Look up an annotation with a $ref url.
|
|
*
|
|
* @param string $ref The $ref value, for example: "#/components/schemas/Product"
|
|
*/
|
|
public function ref(string $ref)
|
|
{
|
|
if (substr($ref, 0, 2) !== '#/') {
|
|
// @todo Add support for external (http) refs?
|
|
throw new \Exception('Unsupported $ref "' . $ref . '", it should start with "#/"');
|
|
}
|
|
|
|
return $this->resolveRef($ref, '#/', $this, []);
|
|
}
|
|
|
|
/**
|
|
* Recursive helper for ref().
|
|
*/
|
|
private static function resolveRef(string $ref, string $resolved, $container, array $mapping)
|
|
{
|
|
if ($ref === $resolved) {
|
|
return $container;
|
|
}
|
|
$path = substr($ref, strlen($resolved));
|
|
$slash = strpos($path, '/');
|
|
|
|
$subpath = $slash === false ? $path : substr($path, 0, $slash);
|
|
$property = Util::refDecode($subpath);
|
|
$unresolved = $slash === false ? $resolved . $subpath : $resolved . $subpath . '/';
|
|
|
|
if (is_object($container)) {
|
|
if (property_exists($container, $property) === false) {
|
|
throw new \Exception('$ref "' . $ref . '" not found');
|
|
}
|
|
if ($slash === false) {
|
|
return $container->$property;
|
|
}
|
|
$mapping = [];
|
|
if ($container instanceof AbstractAnnotation) {
|
|
foreach ($container::$_nested as $nestedClass => $nested) {
|
|
if (is_string($nested) === false && count($nested) === 2 && $nested[0] === $property) {
|
|
$mapping[$nestedClass] = $nested[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
return self::resolveRef($ref, $unresolved, $container->$property, $mapping);
|
|
} elseif (is_array($container)) {
|
|
if (array_key_exists($property, $container)) {
|
|
return self::resolveRef($ref, $unresolved, $container[$property], []);
|
|
}
|
|
foreach ($mapping as $nestedClass => $keyField) {
|
|
foreach ($container as $key => $item) {
|
|
if (is_numeric($key) && is_object($item) && $item instanceof $nestedClass && (string) $item->$keyField === $property) {
|
|
return self::resolveRef($ref, $unresolved, $item, []);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
throw new \Exception('$ref "' . $unresolved . '" not found');
|
|
}
|
|
}
|