diff --git a/Examples/webhook-example/OpenApiSpec.php b/Examples/webhook-example/OpenApiSpec.php
new file mode 100644
index 00000000..5ce29887
--- /dev/null
+++ b/Examples/webhook-example/OpenApiSpec.php
@@ -0,0 +1,34 @@
+
Individual operations can override this definition.
To make security optional, an empty security requirement `({})` can be included in the array.
+ webhooks : array<string,PathItem|string|class-string|object>
+ The available webhooks for the API.
#### Reference
diff --git a/docs/reference/attributes.md b/docs/reference/attributes.md
index c4ebcf0c..ac23680a 100644
--- a/docs/reference/attributes.md
+++ b/docs/reference/attributes.md
@@ -1466,6 +1466,8 @@ The keys inside the array will be prefixed with `x-`.
attachables : Attachable[]|null
Arbitrary attachables for this annotation.
These will be ignored but can be used for custom processing.
+ webhooks : array<string,PathItem|string|class-string|object>|null
+ The available webhooks for the API.
## [Options](https://github.com/zircote/swagger-php/tree/master/src/Attributes/Options.php)
diff --git a/src/Annotations/OpenApi.php b/src/Annotations/OpenApi.php
index 74775276..617fed25 100644
--- a/src/Annotations/OpenApi.php
+++ b/src/Annotations/OpenApi.php
@@ -98,6 +98,13 @@ class OpenApi extends AbstractAnnotation
*/
public $externalDocs = Generator::UNDEFINED;
+ /**
+ * The available webhooks for the API.
+ *
+ * @var Webhook[]
+ */
+ public $webhooks = Generator::UNDEFINED;
+
/**
* @var Analysis
*/
@@ -106,7 +113,7 @@ class OpenApi extends AbstractAnnotation
/**
* @inheritdoc
*/
- public static $_required = ['openapi', 'info', 'paths'];
+ public static $_required = ['openapi', 'info'];
/**
* @inheritdoc
@@ -115,6 +122,7 @@ class OpenApi extends AbstractAnnotation
Info::class => 'info',
Server::class => ['servers'],
PathItem::class => ['paths', 'path'],
+ Webhook::class => ['webhooks'],
Components::class => 'components',
Tag::class => ['tags'],
ExternalDocumentation::class => 'externalDocs',
@@ -143,6 +151,18 @@ public function validate(array $stack = null, array $skip = null, string $ref =
return false;
}
+ if ($this->openapi === self::VERSION_3_0_0 && Generator::isDefault($this->paths)) {
+ $this->_context->logger->warning('Required @OA\PathItem() not found');
+
+ return false;
+ }
+
+ if ($this->openapi === self::VERSION_3_1_0 && Generator::isDefault($this->paths) && Generator::isDefault($this->webhooks) && Generator::isDefault($this->components)) {
+ $this->_context->logger->warning("At least one of 'Required @OA\Info(), @OA\Components() or @OA\Webhook() not found'");
+
+ return false;
+ }
+
return parent::validate([], [], '#', new \stdClass());
}
@@ -230,4 +250,19 @@ private static function resolveRef(string $ref, string $resolved, $container, ar
throw new \Exception('$ref "' . $unresolved . '" not found');
}
+
+ /**
+ * @inheritdoc
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ $data = parent::jsonSerialize();
+
+ if (false === $this->_context->isVersion(OpenApi::VERSION_3_1_0)) {
+ unset($data->webhooks);
+ }
+
+ return $data;
+ }
}
diff --git a/src/Annotations/Webhook.php b/src/Annotations/Webhook.php
new file mode 100644
index 00000000..4fb581f0
--- /dev/null
+++ b/src/Annotations/Webhook.php
@@ -0,0 +1,25 @@
+|null $x
* @param Attachable[]|null $attachables
*/
@@ -27,6 +28,7 @@ public function __construct(
?ExternalDocumentation $externalDocs = null,
?array $paths = null,
?Components $components = null,
+ ?array $webhooks = null,
// annotation
?array $x = null,
?array $attachables = null
@@ -35,7 +37,7 @@ public function __construct(
'openapi' => $openapi,
'security' => $security ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
- 'value' => $this->combine($info, $servers, $tags, $externalDocs, $paths, $components, $attachables),
+ 'value' => $this->combine($info, $servers, $tags, $externalDocs, $paths, $components, $attachables, $webhooks),
]);
}
}
diff --git a/src/Attributes/Webhook.php b/src/Attributes/Webhook.php
new file mode 100644
index 00000000..777ad2f0
--- /dev/null
+++ b/src/Attributes/Webhook.php
@@ -0,0 +1,34 @@
+|null $x
+ * @param Attachable[]|null $attachables
+ */
+ public function __construct(
+ ?string $name = null,
+ ?PathItem $pathItem = null,
+ // annotation
+ ?array $x = null,
+ ?array $attachables = null
+ ) {
+ parent::__construct([
+ 'name' => $name ?? Generator::UNDEFINED,
+ 'pathItem' => $pathItem ?? Generator::UNDEFINED,
+ 'x' => $x ?? Generator::UNDEFINED,
+ 'value' => $this->combine($name, $pathItem, $attachables),
+ ]);
+ }
+}
diff --git a/src/Serializer.php b/src/Serializer.php
index 71f9b2b9..d8b59d35 100644
--- a/src/Serializer.php
+++ b/src/Serializer.php
@@ -58,6 +58,7 @@ class Serializer
OA\Trace::class,
OA\Xml::class,
OA\XmlContent::class,
+ OA\Webhook::class,
];
protected static function isValidAnnotationClass(string $className): bool
diff --git a/tests/Annotations/OpenApiTest.php b/tests/Annotations/OpenApiTest.php
index cf786264..95458d90 100644
--- a/tests/Annotations/OpenApiTest.php
+++ b/tests/Annotations/OpenApiTest.php
@@ -11,9 +11,17 @@
class OpenApiTest extends OpenApiTestCase
{
- public function testValidVersion(): void
+ public function testValidVersion310(): void
+ {
+ $this->assertOpenApiLogEntryContains("At least one of 'Required @OA\Info(), @OA\Components() or @OA\Webhook() not found'");
+
+ $openapi = new OA\OpenApi(['_context' => $this->getContext()]);
+ $openapi->openapi = '3.1.0';
+ $openapi->validate();
+ }
+
+ public function testValidVersion300(): void
{
- $this->assertOpenApiLogEntryContains('Required @OA\Info() not found');
$this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found');
$openapi = new OA\OpenApi(['_context' => $this->getContext()]);
diff --git a/tests/ExamplesTest.php b/tests/ExamplesTest.php
index 239ac243..f9215097 100644
--- a/tests/ExamplesTest.php
+++ b/tests/ExamplesTest.php
@@ -140,6 +140,14 @@ public function exampleDetails(): iterable
[],
];
+ yield 'webhook-example' => [
+ OA\OpenApi::VERSION_3_1_0,
+ 'webhook-example',
+ 'webhook-example.yaml',
+ false,
+ [],
+ ];
+
if (\PHP_VERSION_ID >= 80100) {
yield 'using-links-php81' => [
OA\OpenApi::VERSION_3_0_0,
diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php
index 3dbd1c3f..4cdc653b 100644
--- a/tests/GeneratorTest.php
+++ b/tests/GeneratorTest.php
@@ -38,7 +38,6 @@ public function testScan(string $sourceDir, iterable $sources): void
public function testScanInvalidSource(): void
{
$this->assertOpenApiLogEntryContains('Skipping invalid source: /tmp/__swagger_php_does_not_exist__');
- $this->assertOpenApiLogEntryContains('Required @OA\Info() not found');
$this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found');
(new Generator($this->getTrackingLogger()))