From fa6d7e06fb12f1969668a1111a50f5448fa67989 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 25 Nov 2024 11:28:45 +0000 Subject: [PATCH] Add responses to 3.0 --- src/Exception/InvalidOpenAPI.php | 32 +++- src/Factory/V30/FromCebe.php | 35 ++++ src/ValueObject/Partial/Header.php | 21 +++ src/ValueObject/Partial/Operation.php | 2 + src/ValueObject/Partial/Response.php | 19 +++ src/ValueObject/Valid/Parameter.php | 19 +++ src/ValueObject/Valid/V30/Header.php | 123 ++++++++++++++ src/ValueObject/Valid/V30/Operation.php | 35 ++++ src/ValueObject/Valid/V30/Parameter.php | 31 ++-- src/ValueObject/Valid/V30/Response.php | 86 ++++++++++ tests/MembraneReaderTest.php | 16 +- tests/ReaderTest.php | 16 +- tests/ValueObject/Valid/V30/ParameterTest.php | 2 +- tests/fixtures/Helper/OpenAPIProvider.php | 16 +- tests/fixtures/Helper/PartialHelper.php | 19 ++- tests/fixtures/ProvidesPetstoreApi.php | 159 +++++++++++++++--- 16 files changed, 572 insertions(+), 59 deletions(-) create mode 100644 src/ValueObject/Partial/Header.php create mode 100644 src/ValueObject/Partial/Response.php create mode 100644 src/ValueObject/Valid/Parameter.php create mode 100644 src/ValueObject/Valid/V30/Header.php create mode 100644 src/ValueObject/Valid/V30/Response.php diff --git a/src/Exception/InvalidOpenAPI.php b/src/Exception/InvalidOpenAPI.php index 4283ab9..2869e5f 100644 --- a/src/Exception/InvalidOpenAPI.php +++ b/src/Exception/InvalidOpenAPI.php @@ -12,6 +12,16 @@ */ final class InvalidOpenAPI extends RuntimeException { + public static function missingRequiredField( + Identifier $identifier, + string $requiredField, + ): self { + return new InvalidOpenAPI(<<responses ?? [] as $code => $response) { + $responses[$code] = self::createResponse($response); + } + + return new Operation( operationId: $operation->operationId, servers: self::createServers($operation->servers), parameters: self::createParameters($operation->parameters), requestBody: self::createRequestBody($operation->requestBody), + responses: $responses, ); } @@ -233,4 +243,29 @@ private static function createRequestBody( $requestBody->required ?? false, ); } + + public static function createHeader( + Cebe\Reference|Cebe\Header $header + ): Header { + return new Header( + description: $header->description ?? null, + style: $header->style ?? 'simple', + explode: $header->explode ?? false, + required: $header->required ?? false, + schema: isset($header->schema) ? self::createSchema($header->schema) : null, + content: isset($header->content) ? self::createContent($header->content) : [], + ); + } + + private static function createResponse( + Cebe\Reference|Cebe\Response $response, + ): Response { + assert(! $response instanceof Cebe\Reference); + + return new Response( + description: $response->description, + headers: array_map(fn($h) => self::createHeader($h), $response->headers), + content: self::createContent($response->content ?? []), + ); + } } diff --git a/src/ValueObject/Partial/Header.php b/src/ValueObject/Partial/Header.php new file mode 100644 index 0000000..c4dbfc5 --- /dev/null +++ b/src/ValueObject/Partial/Header.php @@ -0,0 +1,21 @@ + $content + */ + public function __construct( + public string|null $description = null, + public string|null $style = null, + public bool|null $explode = null, + public bool|null $required = null, + public Schema|null $schema = null, + public array $content = [], + ) { + } +} diff --git a/src/ValueObject/Partial/Operation.php b/src/ValueObject/Partial/Operation.php index faa142a..cab0008 100644 --- a/src/ValueObject/Partial/Operation.php +++ b/src/ValueObject/Partial/Operation.php @@ -9,12 +9,14 @@ final class Operation /** * @param Server[] $servers * @param Parameter[] $parameters + * @param Response[] $responses */ public function __construct( public string|null $operationId = null, public array $servers = [], public array $parameters = [], public RequestBody|null $requestBody = null, + public array $responses = [], ) { } } diff --git a/src/ValueObject/Partial/Response.php b/src/ValueObject/Partial/Response.php new file mode 100644 index 0000000..a214d8b --- /dev/null +++ b/src/ValueObject/Partial/Response.php @@ -0,0 +1,19 @@ + $headers, + * @param MediaType[] $content + */ + public function __construct( + public string|null $description = null, + public array $headers = [], + public array $content = [], + ) { + } +} diff --git a/src/ValueObject/Valid/Parameter.php b/src/ValueObject/Valid/Parameter.php new file mode 100644 index 0000000..7b33c95 --- /dev/null +++ b/src/ValueObject/Valid/Parameter.php @@ -0,0 +1,19 @@ +getMediaType() + * @phpstan-assert-if-false null $this->getMediaType() + */ + public function hasMediaType(): bool; + public function getMediaType(): ?string; +} diff --git a/src/ValueObject/Valid/V30/Header.php b/src/ValueObject/Valid/V30/Header.php new file mode 100644 index 0000000..2190f84 --- /dev/null +++ b/src/ValueObject/Valid/V30/Header.php @@ -0,0 +1,123 @@ + + */ + public readonly array $content; + + public function __construct( + Identifier $identifier, + Partial\Header $header + ) { + parent::__construct($identifier); + + $this->style = $this->validateStyle($identifier, $header->style); + $this->required = $header->required ?? false; + $this->explode = $header->explode ?? Style::Simple->defaultExplode(); + + + isset($header->schema) === empty($header->content) ?: + throw InvalidOpenAPI::mustHaveSchemaXorContent($identifier); + + if (isset($header->schema)) { + $this->content = []; + $this->schema = new Schema( + $this->appendedIdentifier('schema'), + $header->schema + ); + } else { + $this->schema = null; + + $this->content = $this->validateContent( + $this->getIdentifier(), + $header->content + ); + } + } + + public function getSchema(): Schema + { + if (isset($this->schema)) { + return $this->schema; + } else { + assert(array_values($this->content)[0]->schema !== null); + return array_values($this->content)[0]->schema; + } + } + + public function hasMediaType(): bool + { + return !empty($this->content); + } + + public function getMediaType(): ?string + { + return array_key_first($this->content); + } + + private function validateStyle(Identifier $identifier, ?string $style): Style + { + $defaultStyle = Style::default(In::Header); + + if ($style !== null && Style::from($style) !== $defaultStyle) { + throw InvalidOpenAPI::parameterIncompatibleStyle($identifier); + } + + return $defaultStyle; + } + + /** + * @param array $content + * @return array + */ + private function validateContent(Identifier $identifier, array $content): array + { + if (count($content) !== 1) { + throw InvalidOpenAPI::parameterContentCanOnlyHaveOneEntry($identifier); + } + + if (!isset($content[0]->contentType)) { + throw InvalidOpenAPI::contentMissingMediaType($identifier); + } + + if (!isset($content[0]->schema)) { + throw InvalidOpenAPI::mustHaveSchemaXorContent($identifier); + } + + return [ + $content[0]->contentType => new MediaType( + $this->appendedIdentifier($content[0]->contentType), + $content[0] + ), + ]; + } +} diff --git a/src/ValueObject/Valid/V30/Operation.php b/src/ValueObject/Valid/V30/Operation.php index 2eb9bae..b424f56 100644 --- a/src/ValueObject/Valid/V30/Operation.php +++ b/src/ValueObject/Valid/V30/Operation.php @@ -12,6 +12,7 @@ use Membrane\OpenAPIReader\ValueObject\Valid\Validated; use Membrane\OpenAPIReader\ValueObject\Valid\Warning; +//TODO valid operation needs to validateResponses final class Operation extends Validated { /** @@ -26,6 +27,8 @@ final class Operation extends Validated * @param string $operationId * Required by Membrane * MUST be unique, value is case-sensitive. + * + * @param Response[] $responses */ private function __construct( Identifier $identifier, @@ -33,6 +36,7 @@ private function __construct( public readonly array $servers, public readonly array $parameters, public readonly RequestBody|null $requestBody, + public readonly array $responses, ) { parent::__construct($identifier); @@ -48,6 +52,7 @@ public function withoutServers(): Operation [new Server($this->getIdentifier(), new Partial\Server('/'))], $this->parameters, $this->requestBody, + $this->responses, ); } @@ -86,12 +91,15 @@ public static function fromPartial( new RequestBody($identifier, $operation->requestBody) : null; + $responses = self::validateResponses($identifier, $operation->responses); + return new Operation( $identifier, $operationId, $servers, $parameters, $requestBody, + $responses, ); } @@ -214,4 +222,31 @@ private static function mergeParameters( return array_values($result); } + + /** + * @param Partial\Response[] $responses + * @return array + */ + private static function validateResponses( + Identifier $identifier, + array $responses, + ): array { + $result = []; + + foreach ($responses as $code => $response) { + if (! is_numeric($code) && $code !== 'default') { + throw InvalidOpenAPI::responseCodeMustBeNumericOrDefault( + $identifier, + (string) $code, + ); + } + + $result[(string) $code] = new Response( + $identifier->append('response', (string) $code), + $response, + ); + } + + return $result; + } } diff --git a/src/ValueObject/Valid/V30/Parameter.php b/src/ValueObject/Valid/V30/Parameter.php index c4ec69f..ee8c56c 100644 --- a/src/ValueObject/Valid/V30/Parameter.php +++ b/src/ValueObject/Valid/V30/Parameter.php @@ -47,14 +47,14 @@ final class Parameter extends Validated public function __construct( Identifier $parentIdentifier, - Partial\Parameter $parameter + Partial\Parameter $header ) { - $this->name = $parameter->name ?? + $this->name = $header->name ?? throw InvalidOpenAPI::parameterMissingName($parentIdentifier); $this->in = $this->validateIn( $parentIdentifier, - $parameter->in, + $header->in, ); $identifier = $parentIdentifier->append($this->name, $this->in->value); @@ -63,25 +63,24 @@ public function __construct( $this->required = $this->validateRequired( $identifier, $this->in, - $parameter->required + $header->required ); - isset($parameter->schema) === empty($parameter->content) ?: - throw InvalidOpenAPI::mustHaveSchemaXorContent($parameter->name); + isset($header->schema) === empty($header->content) ?: + throw InvalidOpenAPI::mustHaveSchemaXorContent($identifier); - if (isset($parameter->schema)) { + if (isset($header->schema)) { $this->content = []; $this->schema = new Schema( $this->appendedIdentifier('schema'), - $parameter->schema + $header->schema ); } else { $this->schema = null; $this->content = $this->validateContent( $this->getIdentifier(), - $parameter->name, - $parameter->content + $header->content ); } @@ -89,10 +88,10 @@ public function __construct( $identifier, $this->getSchema(), $this->in, - $parameter->style, + $header->style, ); - $this->explode = $parameter->explode ?? $this->style->defaultExplode(); + $this->explode = $header->explode ?? $this->style->defaultExplode(); } public function getSchema(): Schema @@ -117,7 +116,8 @@ public function getMediaType(): ?string public function isIdentical(Parameter $other): bool { - return $this->name === $other->name && $this->in === $other->in; + return $this->name === $other->name + && $this->in === $other->in; } public function isSimilar(Parameter $other): bool @@ -226,11 +226,10 @@ private function validateStyle( */ private function validateContent( Identifier $identifier, - string $name, array $content, ): array { if (count($content) !== 1) { - throw InvalidOpenAPI::parameterContentCanOnlyHaveOneEntry($this->getIdentifier()); + throw InvalidOpenAPI::parameterContentCanOnlyHaveOneEntry($identifier); } if (!isset($content[0]->contentType)) { @@ -238,7 +237,7 @@ private function validateContent( } if (!isset($content[0]->schema)) { - throw InvalidOpenAPI::mustHaveSchemaXorContent($name); + throw InvalidOpenAPI::mustHaveSchemaXorContent($identifier); } return [ diff --git a/src/ValueObject/Valid/V30/Response.php b/src/ValueObject/Valid/V30/Response.php new file mode 100644 index 0000000..13cbc6a --- /dev/null +++ b/src/ValueObject/Valid/V30/Response.php @@ -0,0 +1,86 @@ + */ + public readonly array $headers; + + /** @var array */ + public readonly array $content; + + public function __construct( + Identifier $identifier, + Partial\Response $response, + ) { + parent::__construct($identifier); + + $this->description = $response->description + ?? throw InvalidOpenAPI::missingRequiredField($identifier, 'description'); + + $this->headers = $this->validateHeaders($identifier, $response->headers); + + $this->content = $this->validateContent($identifier, $response->content); + } + + /** + * @param array $headers + * @return array + */ + private function validateHeaders( + Identifier $identifier, + array $headers + ): array { + $result = []; + foreach ($headers as $headerName => $header) { + if ($this->headerShouldBeIgnored($headerName)) { + continue; + } + + $result[$headerName] = new Header( + $identifier->append('header', $headerName), + $header, + ); + } + + return $result; + } + + private function headerShouldBeIgnored(string $headerName): bool + { + return mb_strtolower($headerName) === 'content-type'; + } + + /** + * @param array $content + * @return array + */ + private function validateContent( + Identifier $identifier, + array $content + ): array { + $result = []; + foreach ($content as $mediaType) { + if (!isset($mediaType->contentType)) { + throw InvalidOpenAPI::contentMissingMediaType($identifier); + } + + $result[$mediaType->contentType] = new MediaType( + $identifier->append($mediaType->contentType), + $mediaType, + ); + } + + return $result; + } +} diff --git a/tests/MembraneReaderTest.php b/tests/MembraneReaderTest.php index e8ac3d2..d28ca6c 100644 --- a/tests/MembraneReaderTest.php +++ b/tests/MembraneReaderTest.php @@ -443,7 +443,9 @@ public static function provideInvalidOpenAPIs(): Generator $openAPIArray['paths'] = ['/firstpath' => ['get' => $path]]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'get-first-path(get)', 'param(query)') + ), ]; yield 'path with parameter both schema and content' => [ @@ -460,7 +462,9 @@ public static function provideInvalidOpenAPIs(): Generator ],]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'param(query)') + ), ]; yield 'path with operation missing both schema and content' => [ @@ -472,7 +476,9 @@ public static function provideInvalidOpenAPIs(): Generator ]]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'param(query)') + ), ]; yield 'path with operation both schema and content' => [ @@ -488,7 +494,9 @@ public static function provideInvalidOpenAPIs(): Generator $openAPIArray['paths'] = ['/firstpath' => ['get' => $path],]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'get-first-path(get)', 'param(query)') + ), ]; } diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index 4b3e061..8b1d73f 100644 --- a/tests/ReaderTest.php +++ b/tests/ReaderTest.php @@ -265,7 +265,9 @@ public static function provideInvalidOpenAPIs(): Generator $openAPIArray['paths'] = ['/firstpath' => ['get' => $path]]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'get-first-path(get)', 'param(query)') + ), ]; yield 'path with parameter both schema and content' => [ @@ -282,7 +284,9 @@ public static function provideInvalidOpenAPIs(): Generator ],]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'param(query)') + ), ]; yield 'path with operation missing both schema and content' => [ @@ -294,7 +298,9 @@ public static function provideInvalidOpenAPIs(): Generator ]]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'param(query)') + ), ]; yield 'path with operation both schema and content' => [ @@ -310,7 +316,9 @@ public static function provideInvalidOpenAPIs(): Generator $openAPIArray['paths'] = ['/firstpath' => ['get' => $path],]; return json_encode($openAPIArray); })(), - InvalidOpenAPI::mustHaveSchemaXorContent('param'), + InvalidOpenAPI::mustHaveSchemaXorContent( + new Identifier('(1.0.0)', '/firstpath', 'get-first-path(get)', 'param(query)') + ), ]; } diff --git a/tests/ValueObject/Valid/V30/ParameterTest.php b/tests/ValueObject/Valid/V30/ParameterTest.php index 0e93b41..8674814 100644 --- a/tests/ValueObject/Valid/V30/ParameterTest.php +++ b/tests/ValueObject/Valid/V30/ParameterTest.php @@ -307,7 +307,7 @@ public static function provideInvalidPartialParameters(): Generator foreach ($schemaXorContentCases as $schemaXorContentCase => $data) { yield $schemaXorContentCase => $case( - InvalidOpenAPI::mustHaveSchemaXorContent($name), + InvalidOpenAPI::mustHaveSchemaXorContent($identifier), $data, ); } diff --git a/tests/fixtures/Helper/OpenAPIProvider.php b/tests/fixtures/Helper/OpenAPIProvider.php index 5d91592..b0ff2fb 100644 --- a/tests/fixtures/Helper/OpenAPIProvider.php +++ b/tests/fixtures/Helper/OpenAPIProvider.php @@ -5,6 +5,7 @@ namespace Membrane\OpenAPIReader\Tests\Fixtures\Helper; use cebe\openapi\spec as Cebe; +use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\V30\OpenAPI; final class OpenAPIProvider @@ -239,7 +240,10 @@ enum: ['2.0', '2.1', '2.2'], ) ] ) - ] + ], + responses: [ + '200' => new Partial\Response(description: 'Successful Response'), + ], ) ), PartialHelper::createPathItem( @@ -283,7 +287,10 @@ enum: ['2.0', '2.1', '2.2'], ) ] ) - ] + ], + responses: [ + '200' => new Partial\Response(description: 'Successful Response'), + ], ), put: PartialHelper::createOperation( operationId: 'second-put', @@ -299,7 +306,10 @@ enum: ['2.0', '2.1', '2.2'], type: 'object' ) ) - ] + ], + responses: [ + '200' => new Partial\Response(description: 'Successful Response'), + ], ) ) ] diff --git a/tests/fixtures/Helper/PartialHelper.php b/tests/fixtures/Helper/PartialHelper.php index cda0cba..1b26539 100644 --- a/tests/fixtures/Helper/PartialHelper.php +++ b/tests/fixtures/Helper/PartialHelper.php @@ -9,6 +9,8 @@ use Membrane\OpenAPIReader\ValueObject\Partial\Operation; use Membrane\OpenAPIReader\ValueObject\Partial\Parameter; use Membrane\OpenAPIReader\ValueObject\Partial\PathItem; +use Membrane\OpenAPIReader\ValueObject\Partial\RequestBody; +use Membrane\OpenAPIReader\ValueObject\Partial\Response; use Membrane\OpenAPIReader\ValueObject\Partial\Schema; use Membrane\OpenAPIReader\ValueObject\Partial\Server; use Membrane\OpenAPIReader\ValueObject\Partial\ServerVariable; @@ -103,15 +105,24 @@ public static function createParameter( ); } + /** + * @param Server[] $servers + * @param Parameter[] $parameters + * @param Response[] $responses + */ public static function createOperation( ?string $operationId = 'test-id', array $servers = [], - array $parameters = [] + array $parameters = [], + RequestBody $requestBody = null, + array $responses = [], ): Operation { return new Operation( - $operationId, - $servers, - $parameters + operationId: $operationId, + servers: $servers, + parameters: $parameters, + requestBody: $requestBody, + responses: $responses, ); } diff --git a/tests/fixtures/ProvidesPetstoreApi.php b/tests/fixtures/ProvidesPetstoreApi.php index ac9a452..660cc93 100644 --- a/tests/fixtures/ProvidesPetstoreApi.php +++ b/tests/fixtures/ProvidesPetstoreApi.php @@ -41,13 +41,61 @@ private static function listPets(): V30\Operation )], pathParameters: [], method: Method::GET, - operation: new Partial\Operation('listPets', [], [ - new Partial\Parameter( + operation: new Partial\Operation( + operationId: 'listPets', + servers: [], + parameters: [new Partial\Parameter( name: 'limit', in: 'query', schema: new Partial\Schema(type: 'integer', maximum: 100, format: 'int32'), - ), - ]) + )], + responses: [ + '200' => new Partial\Response( + description: 'A paged array of pets', + headers: ['x-next' => new Partial\Header( + description: 'hi', + schema: new Partial\Schema('string'), + )], + content: [new Partial\MediaType( + contentType: 'application/json', + schema: new Partial\Schema( + type: 'array', + maxItems: 100, + items: new Partial\Schema( + type: 'object', + required: ['id', 'name'], + properties: [ + 'id' => new Partial\Schema( + type: 'integer', + format: 'int64', + ), + 'name' => new Partial\Schema( + type: 'string', + ), + 'tag' => new Partial\Schema( + type: 'string', + ), + ], + ), + ) + )], + ), + 'default' => new Partial\Response( + description: 'unexpected error', + content: [new Partial\MediaType( + contentType: 'application/json', + schema: new Partial\Schema( + type: 'object', + required: ['code', 'message'], + properties: [ + 'code' => new Partial\Schema(type: 'integer', format: 'int32'), + 'message' => new Partial\Schema(type: 'string'), + ] + ) + )] + ), + ] + ), ); } @@ -61,21 +109,46 @@ private static function createPets(): V30\Operation )], pathParameters: [], method: Method::POST, - operation: new Partial\Operation('createPets', [], [], new Partial\RequestBody( - content: [new Partial\MediaType( - contentType: 'application/json', - schema: new Partial\Schema( - type: 'object', - required: ['id', 'name'], - properties: [ - 'id' => new Partial\Schema(type: 'integer', format: 'int64'), - 'name' => new Partial\Schema(type: 'string'), - 'tag' => new Partial\Schema(type: 'string'), - ], - ) - )], - required: true, - )), + operation: new Partial\Operation( + 'createPets', + [], + [], + new Partial\RequestBody( + content: [new Partial\MediaType( + contentType: 'application/json', + schema: new Partial\Schema( + type: 'object', + required: ['id', 'name'], + properties: [ + 'id' => new Partial\Schema(type: 'integer', format: 'int64'), + 'name' => new Partial\Schema(type: 'string'), + 'tag' => new Partial\Schema(type: 'string'), + ], + ) + )], + required: true, + ), + responses: [ + '201' => new Partial\Response( + description: 'Null response', + content: [] + ), + 'default' => new Partial\Response( + description: 'unexpected error', + content: [new Partial\MediaType( + contentType: 'application/json', + schema: new Partial\Schema( + type: 'object', + required: ['code', 'message'], + properties: [ + 'code' => new Partial\Schema(type: 'integer', format: 'int32'), + 'message' => new Partial\Schema(type: 'string'), + ] + ) + )] + ), + ]), + ); } @@ -89,14 +162,54 @@ private static function showPetById(): V30\Operation )], pathParameters: [], method: Method::GET, - operation: new Partial\Operation('showPetById', [], [ - new Partial\Parameter( + operation: new Partial\Operation( + operationId: 'showPetById', + servers: [], + parameters: [new Partial\Parameter( name: 'petId', in: 'path', required: true, schema: new Partial\Schema(type: 'string'), - ), - ]) + )], + responses: [ + '200' => new Partial\Response( + description: 'Expected response to a valid request', + content: [new Partial\MediaType( + contentType: 'application/json', + schema: new Partial\Schema( + type: 'object', + required: ['id', 'name'], + properties: [ + 'id' => new Partial\Schema( + type: 'integer', + format: 'int64', + ), + 'name' => new Partial\Schema( + type: 'string', + ), + 'tag' => new Partial\Schema( + type: 'string', + ), + ], + ), + )] + ), + 'default' => new Partial\Response( + description: 'unexpected error', + content: [new Partial\MediaType( + contentType: 'application/json', + schema: new Partial\Schema( + type: 'object', + required: ['code', 'message'], + properties: [ + 'code' => new Partial\Schema(type: 'integer', format: 'int32'), + 'message' => new Partial\Schema(type: 'string'), + ] + ) + )] + ), + ] + ) ); } }