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

Allow attributes' enums to be specified with an enum class string #1303

Merged
merged 21 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Annotations/Schema.php
Expand Up @@ -201,7 +201,7 @@ class Schema extends AbstractAnnotation
/**
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor76)
*
* @var string[]|int[]|float[]
* @var string[]|int[]|float[]|class-string
*/
public $enum = Generator::UNDEFINED;

Expand Down
2 changes: 1 addition & 1 deletion src/Annotations/ServerVariable.php
Expand Up @@ -27,7 +27,7 @@ class ServerVariable extends AbstractAnnotation
/**
* An enumeration of values to be used if the substitution options are from a limited set.
*
* @var string[]|int[]|float[]
* @var string[]|int[]|float[]|class-string
*/
public $enum = Generator::UNDEFINED;

Expand Down
4 changes: 2 additions & 2 deletions src/Attributes/AdditionalProperties.php
Expand Up @@ -16,7 +16,7 @@ class AdditionalProperties extends \OpenApi\Annotations\AdditionalProperties
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[] $enum
* @param string[]|int[]|float[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
Expand Down Expand Up @@ -46,7 +46,7 @@ public function __construct(
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
?array $enum = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
Expand Down
4 changes: 2 additions & 2 deletions src/Attributes/Items.php
Expand Up @@ -16,7 +16,7 @@ class Items extends \OpenApi\Annotations\Items
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[] $enum
* @param string[]|int[]|float[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
Expand Down Expand Up @@ -46,7 +46,7 @@ public function __construct(
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
?array $enum = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
Expand Down
4 changes: 2 additions & 2 deletions src/Attributes/JsonContent.php
Expand Up @@ -17,7 +17,7 @@ class JsonContent extends \OpenApi\Annotations\JsonContent
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[] $enum
* @param string[]|int[]|float[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
Expand Down Expand Up @@ -48,7 +48,7 @@ public function __construct(
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
?array $enum = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
Expand Down
4 changes: 2 additions & 2 deletions src/Attributes/Property.php
Expand Up @@ -16,7 +16,7 @@ class Property extends \OpenApi\Annotations\Property
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[] $enum
* @param string[]|int[]|float[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
Expand Down Expand Up @@ -47,7 +47,7 @@ public function __construct(
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
?array $enum = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
Expand Down
4 changes: 2 additions & 2 deletions src/Attributes/Schema.php
Expand Up @@ -16,7 +16,7 @@ class Schema extends \OpenApi\Annotations\Schema
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[] $enum
* @param string[]|int[]|float[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
Expand Down Expand Up @@ -47,7 +47,7 @@ public function __construct(
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
?array $enum = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
Expand Down
8 changes: 4 additions & 4 deletions src/Attributes/ServerVariable.php
Expand Up @@ -12,15 +12,15 @@
class ServerVariable extends \OpenApi\Annotations\ServerVariable
{
/**
* @param string[]|int[]|float[]|null $enum
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
* @param string[]|int[]|float[]|class-string|null $enum
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $serverVariable = null,
?string $description = null,
?string $default = null,
?array $enum = null,
array|string|null $enum = null,
?array $variables = null,
// annotation
?array $x = null,
Expand Down
4 changes: 2 additions & 2 deletions src/Attributes/XmlContent.php
Expand Up @@ -17,7 +17,7 @@ class XmlContent extends \OpenApi\Annotations\XmlContent
* @param int|float $maximum
* @param int|float $minimum
* @param Property[] $properties
* @param string[]|int[]|float[] $enum
* @param string[]|int[]|float[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
Expand Down Expand Up @@ -48,7 +48,7 @@ public function __construct(
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
?array $enum = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
Expand Down
72 changes: 71 additions & 1 deletion src/Processors/ExpandEnums.php
@@ -1,4 +1,5 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);
DerManoMann marked this conversation as resolved.
Show resolved Hide resolved

/**
* @license Apache 2.0
Expand All @@ -7,8 +8,20 @@
namespace OpenApi\Processors;

use OpenApi\Analysis;
use OpenApi\Annotations\Items as AnnotationsItems;
use OpenApi\Annotations\JsonContent as AnnotationsJsonContent;
use OpenApi\Annotations\Property as AnnotationsProperty;
use OpenApi\Annotations\Schema as AnnotationSchema;
use OpenApi\Annotations\XmlContent as AnnotationsXmlContent;
use OpenApi\Attributes\AdditionalProperties as AttributesAdditionalProperties;
use OpenApi\Annotations\AdditionalProperties as AnnotationsAdditionalProperties;
use OpenApi\Attributes\Items as AttributesItems;
use OpenApi\Attributes\JsonContent as AttributesJsonContent;
use OpenApi\Attributes\Property as AttributesProperty;
use OpenApi\Attributes\Schema as AttributeSchema;
use OpenApi\Attributes\ServerVariable as AttributesServerVariable;
use OpenApi\Annotations\ServerVariable as AnnotationsServerVariable;
use OpenApi\Attributes\XmlContent as AttributesXmlContent;
use OpenApi\Generator;

/**
Expand All @@ -20,12 +33,28 @@ class ExpandEnums
{
use Concerns\TypesTrait;

/**
* @param Analysis $analysis
DerManoMann marked this conversation as resolved.
Show resolved Hide resolved
*
* @throws \ReflectionException
*/
public function __invoke(Analysis $analysis)
{
if (!class_exists('\\ReflectionEnum')) {
return;
}

$this->expandContextEnum($analysis);
$this->expandSchemaEnum($analysis);
}

/**
* @param Analysis $analysis
*
* @throws \ReflectionException
*/
private function expandContextEnum(Analysis $analysis): void
{
/** @var AnnotationSchema[] $schemas */
$schemas = $analysis->getAnnotationsOfType([AnnotationSchema::class, AttributeSchema::class], true);

Expand Down Expand Up @@ -55,4 +84,45 @@ public function __invoke(Analysis $analysis)
}
}
}

/**
* @param Analysis $analysis
*/
private function expandSchemaEnum(Analysis $analysis): void
{
/** @var AnnotationSchema[] $schemas */
$schemas = $analysis->getAnnotationsOfType([
DerManoMann marked this conversation as resolved.
Show resolved Hide resolved
AnnotationsAdditionalProperties::class,
AttributesAdditionalProperties::class,
AnnotationsItems::class,
AttributesItems::class,
AnnotationsJsonContent::class,
AttributesJsonContent::class,
AnnotationsProperty::class,
AttributesProperty::class,
AnnotationSchema::class,
AttributeSchema::class,
AttributesServerVariable::class,
AnnotationsServerVariable::class,
AnnotationsXmlContent::class,
AttributesXmlContent::class,
], true);

foreach ($schemas as $schema) {
if ($schema->enum !== Generator::UNDEFINED && is_string($schema->enum)) {
$source = $schema->enum;
// Convert to Enum value if it is an Enum class string
if (is_a($schema->enum, 'UnitEnum', true)) {
$enums = [];
foreach ($source::cases() as $case) {
$enums[] = $case->value ?? $case->name;
}

$schema->enum = $enums;
} else {
throw new \InvalidArgumentException("Unexpected enum value, requires specifying the Enum class string: $source");
}
}
}
}
}
50 changes: 50 additions & 0 deletions tests/Fixtures/PHP/ReferencesEnum.php
@@ -0,0 +1,50 @@
<?php declare(strict_types=1);

/**
* @license Apache 2.0
*/

namespace OpenApi\Tests\Fixtures\PHP;

use OpenApi\Attributes\Property;
use OpenApi\Attributes\Schema;
use OpenApi\Attributes\Items;

#[Schema()]
class ReferencesEnum
{
#[Property(title: 'statusEnum', description: 'Status enum', type: 'string', enum: StatusEnum::class, nullable: false)]
public string $statusEnum;

/**
* @OA\Property(title="statusEnumBacked",
* description="Status enum backend",
* type="int",
* enum="\OpenApi\Tests\Fixtures\PHP\StatusEnumBacked",
* nullable="false"
* )
*/
public int $statusEnumBacked;

#[Property(title: 'statusEnumIntegerBacked', description: 'Status enum integer backend', type: 'int', enum: StatusEnumIntegerBacked::class, nullable: true)]
public ?int $statusEnumIntegerBacked;

/**
* @OA\Property(title="statusEnumStringBacked",
* description="Status enum string backend",
* type="string",
* enum="\OpenApi\Tests\Fixtures\PHP\StatusEnumStringBacked",
* nullable="true"
* )
*/
public ?string $statusEnumStringBacked;

/** @var list<string> StatusEnumStringBacked array */
#[Property(
title: 'statusEnums',
description: 'StatusEnumStringBacked array',
type: 'array',
items: new Items(title: 'itemsStatusEnumStringBacked', type: 'string', enum: StatusEnumStringBacked::class)
)]
public array $statusEnums;
}
53 changes: 53 additions & 0 deletions tests/Processors/ExpandEnumsTest.php
Expand Up @@ -7,6 +7,10 @@
namespace OpenApi\Tests\Processors;

use OpenApi\Analysers\TokenAnalyser;
use OpenApi\Annotations\Items;
use OpenApi\Annotations\Property as AnnotationsProperty;
use OpenApi\Attributes\Property as AttributesProperty;
use OpenApi\Generator;
use OpenApi\Processors\ExpandEnums;
use OpenApi\Tests\Fixtures\PHP\StatusEnum;
use OpenApi\Tests\Fixtures\PHP\StatusEnumBacked;
Expand Down Expand Up @@ -60,4 +64,53 @@ public function testExpandBackedStringEnum(): void

self::assertEquals(['draft', 'published', 'archived'], $schema->enum);
}

/**
* @requires PHP >= 8.1
*/
public function testExpandEnumClassString(): void
{
$analysis = $this->analysisFromFixtures(['PHP/ReferencesEnum.php']);
$analysis->process([new ExpandEnums()]);
$schemas = $analysis->getAnnotationsOfType([AnnotationsProperty::class, AttributesProperty::class, Items::class], true);

$expected = [
DerManoMann marked this conversation as resolved.
Show resolved Hide resolved
'statusEnum' => $this->convertEnumNames(StatusEnum::cases()),
'statusEnumBacked' => $this->convertEnumValues(StatusEnumBacked::cases()),
'statusEnumIntegerBacked' => $this->convertEnumValues(StatusEnumIntegerBacked::cases()),
'statusEnumStringBacked' => $this->convertEnumValues(StatusEnumStringBacked::cases()),
'statusEnums' => Generator::UNDEFINED,
'itemsStatusEnumStringBacked' => $this->convertEnumValues(StatusEnumStringBacked::cases()),
];

foreach ($schemas as $schema) {
if ($schema instanceof AnnotationsProperty || $schema instanceof Items) {
self::assertEquals($expected[$schema->title], $schema->enum);
}
}
}

/**
* @param list<\UnitEnum> $enums
*
* @return list<string>
*/
private function convertEnumNames(array $enums): array
{
return array_map(function ($c) {
return $c->name;
}, $enums);
}

/**
* @param list<StatusEnumBacked|StatusEnumIntegerBacked|StatusEnumStringBacked> $enums
*
* @return list<string|int>
*/
private function convertEnumValues(array $enums): array
{
return array_map(function ($c) {
return $c->value;
}, $enums);
}
}