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

Webhooks #1462

Closed
wants to merge 7 commits into from
Closed
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
34 changes: 34 additions & 0 deletions Examples/webhook-example/OpenApiSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace OpenApi\Examples\WebhookExample;

use OpenApi\Annotations as OA;

/**
* @OA\OpenApi(
* @OA\Info(
* version="1.0.0",
* title="Webhook Example",
* ),
* webhooks={
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs updating I think?

* "newPet": @OA\PathItem(
* @OA\Post(
* @OA\RequestBody(
* description="Information about a new pet in the system",
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(ref="#/components/schemas/Pet")
* ),
* ),
* @OA\Response(
* response=200,
* description="Return a 200 status to indicate that the data was received successfully"
* )
* )
* )
* }
* )
*/
class OpenApiSpec
{
}
34 changes: 34 additions & 0 deletions Examples/webhook-example/Pet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace OpenApi\Examples\WebhookExample;

use OpenApi\Annotations as OA;

/**
* @OA\Schema(required={"id", "name"})
*/
final class Pet
{
/**
* @OA\Property(format="int64")
*
* @var int
*/
public $id;

/**
* @OA\Property
*
* @var string
*/
public $name;

/**
* @OA\Property
*
* @var string
*/
public $tag;
}
31 changes: 31 additions & 0 deletions Examples/webhook-example/webhook-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
openapi: 3.1.0
info:
title: 'Webhook Example'
version: 1.0.0
components:
schemas:
Pet:
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
type: object
webhooks:
newPet:
post:
requestBody:
description: 'Information about a new pet in the system'
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
responses:
'200':
description: 'Return a 200 status to indicate that the data was received successfully'
2 changes: 2 additions & 0 deletions docs/reference/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,8 @@ The list of values includes alternative security requirement objects that can be
Only one of the security requirement objects need to be satisfied to authorize a request.<br />
Individual operations can override this definition.<br />
To make security optional, an empty security requirement `({})` can be included in the array.</p></dd>
<dt><strong>webhooks</strong> : <span style="font-family: monospace;">array&lt;string,PathItem|string|class-string|object&gt;</span></dt>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs need refresh - ideally done right before merge

<dd><p>The available webhooks for the API.</p></dd>
</dl>

#### Reference
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,8 @@ The keys inside the array will be prefixed with `x-`.</p></dd>
<dt><strong>attachables</strong> : <span style="font-family: monospace;">Attachable[]|null</span></dt>
<dd><p>Arbitrary attachables for this annotation.<br />
These will be ignored but can be used for custom processing.</p></dd>
<dt><strong>webhooks</strong> : <span style="font-family: monospace;">array&lt;string,PathItem|string|class-string|object&gt;|null</span></dt>
<dd><p>The available webhooks for the API.</p></dd>
</dl>

## [Options](https://github.com/zircote/swagger-php/tree/master/src/Attributes/Options.php)
Expand Down
37 changes: 36 additions & 1 deletion src/Annotations/OpenApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -106,7 +113,7 @@ class OpenApi extends AbstractAnnotation
/**
* @inheritdoc
*/
public static $_required = ['openapi', 'info', 'paths'];
public static $_required = ['openapi', 'info'];
DerManoMann marked this conversation as resolved.
Show resolved Hide resolved

/**
* @inheritdoc
Expand All @@ -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',
Expand Down Expand Up @@ -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'");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be @OA\PathItem instead of @OA\Info


return false;
}

return parent::validate([], [], '#', new \stdClass());
}

Expand Down Expand Up @@ -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;
}
}
25 changes: 25 additions & 0 deletions src/Annotations/Webhook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);

/**
* @license Apache 2.0
*/

namespace OpenApi\Annotations;

use OpenApi\Generator;

/**
* @Annotation
*/
class Webhook extends AbstractAnnotation
{
/**
* @var string
*/
public $name = Generator::UNDEFINED;

/**
* @var PathItem
*/
public $pathItem = Generator::UNDEFINED;
}
4 changes: 3 additions & 1 deletion src/Attributes/OpenApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class OpenApi extends \OpenApi\Annotations\OpenApi
* @param Server[]|null $servers
* @param Tag[]|null $tags
* @param PathItem[]|null $paths
* @param Webhook[]|null $webhooks
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
Expand All @@ -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
Expand All @@ -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),
]);
}
}
34 changes: 34 additions & 0 deletions src/Attributes/Webhook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);

/**
* @license Apache 2.0
*/

namespace OpenApi\Attributes;

use OpenApi\Generator;

#[\Attribute(\Attribute::TARGET_CLASS)]
class Webhook extends \OpenApi\Annotations\Webhook
{
/**
* @param string|null $name
* @param PathItem|null $pathItem
* @param array<string,mixed>|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),
]);
}
}
1 change: 1 addition & 0 deletions src/Serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 10 additions & 2 deletions tests/Annotations/OpenApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()]);
Expand Down
8 changes: 8 additions & 0 deletions tests/ExamplesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion tests/GeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is surprising.

$this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found');

(new Generator($this->getTrackingLogger()))
Expand Down
Loading