Skip to content
Merged
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
20 changes: 19 additions & 1 deletion src/Factory/V30/FromCebe.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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\Schema;
use Membrane\OpenAPIReader\ValueObject\Partial\Server;
use Membrane\OpenAPIReader\ValueObject\Partial\ServerVariable;
Expand Down Expand Up @@ -210,7 +211,24 @@ private static function createOperation(
return new Operation(
operationId: $operation->operationId,
servers: self::createServers($operation->servers),
parameters: self::createParameters($operation->parameters)
parameters: self::createParameters($operation->parameters),
requestBody: self::createRequestBody($operation->requestBody),
);
}

private static function createRequestBody(
Cebe\Reference|Cebe\RequestBody|null $requestBody
): ?RequestBody {
assert(! $requestBody instanceof Cebe\Reference);

if (is_null($requestBody)) {
return null;
}

return new RequestBody(
$requestBody->description ?? null,
self::createContent($requestBody->content ?? []),
$requestBody->required ?? false,
);
}
}
3 changes: 2 additions & 1 deletion src/ValueObject/Partial/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ final class Operation
* @param Parameter[] $parameters
*/
public function __construct(
public ?string $operationId = null,
public string|null $operationId = null,
public array $servers = [],
public array $parameters = [],
public RequestBody|null $requestBody = null,
) {
}
}
18 changes: 18 additions & 0 deletions src/ValueObject/Partial/RequestBody.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Membrane\OpenAPIReader\ValueObject\Partial;

final class RequestBody
{
/**
* @param MediaType[] $content
*/
public function __construct(
public string|null $description = null,
public array $content = [],
public bool $required = false,
) {
}
}
7 changes: 7 additions & 0 deletions src/ValueObject/Valid/V30/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ private function __construct(
public readonly string $operationId,
public readonly array $servers,
public readonly array $parameters,
public readonly RequestBody|null $requestBody,
) {
parent::__construct($identifier);

Expand All @@ -46,6 +47,7 @@ public function withoutServers(): Operation
$this->operationId,
[new Server($this->getIdentifier(), new Partial\Server('/'))],
$this->parameters,
$this->requestBody,
);
}

Expand Down Expand Up @@ -80,11 +82,16 @@ public static function fromPartial(
$operation->parameters
);

$requestBody = isset($operation->requestBody) ?
new RequestBody($identifier, $operation->requestBody) :
null;

return new Operation(
$identifier,
$operationId,
$servers,
$parameters,
$requestBody,
);
}

Expand Down
62 changes: 62 additions & 0 deletions src/ValueObject/Valid/V30/RequestBody.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Membrane\OpenAPIReader\ValueObject\Valid\V30;

use Membrane\OpenAPIReader\Exception\InvalidOpenAPI;
use Membrane\OpenAPIReader\ValueObject\Partial;
use Membrane\OpenAPIReader\ValueObject\Valid\Identifier;
use Membrane\OpenAPIReader\ValueObject\Valid\Validated;

final class RequestBody extends Validated
{
public readonly string|null $description;

public readonly bool $required;

/**
* REQUIRED
* @var array<string,MediaType>
*/
public readonly array $content;

public function __construct(
Identifier $parentIdentifier,
Partial\RequestBody $requestBody,
) {
$identifier = $parentIdentifier->append('requestBody');
parent::__construct($identifier);

$this->description = $requestBody->description;
$this->required = $requestBody->required;

$this->content = $this->validateContent(
$identifier,
$requestBody->content
);
}

/**
* @param array<Partial\MediaType> $content
* @return array<string, MediaType>
*/
public 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;
}
}
21 changes: 20 additions & 1 deletion tests/MembraneReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use cebe\{openapi\exceptions as CebeException};
use Generator;
use Membrane\OpenAPIReader\Tests\Fixtures\ProvidesPetstoreApi;
use Membrane\OpenAPIReader\{FileFormat, OpenAPIVersion};
use Membrane\OpenAPIReader\Exception\{CannotRead, CannotSupport, InvalidOpenAPI};
use Membrane\OpenAPIReader\Factory\V30\FromCebe;
Expand All @@ -17,7 +18,7 @@
use Membrane\OpenAPIReader\ValueObject\Valid\Identifier;
use Membrane\OpenAPIReader\ValueObject\Valid\V30\OpenAPI;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\Attributes\{CoversClass, DataProvider, Test, TestDox, UsesClass};
use PHPUnit\Framework\Attributes\{CoversClass, DataProvider, DataProviderExternal, Test, TestDox, UsesClass};
use PHPUnit\Framework\TestCase;
use TypeError;

Expand Down Expand Up @@ -347,6 +348,24 @@ public function itReadsFromString(OpenAPI $expected, string $openApi): void
self::assertEquals($expected, $actual);
}

#[Test]
#[DataProviderExternal(ProvidesPetstoreApi::class, 'provideOperations')]
public function itReadsRealExamples(
string $filepath,
string $path,
Method $method,
Valid\V30\Operation $expected
): void {
$sut = new MembraneReader([OpenAPIVersion::Version_3_0]);

$api = $sut->readFromAbsoluteFilePath($filepath);

$actual = $api->paths[$path]?->getOperations()[$method->value];

self::assertEquals($expected, $actual);
}


public static function provideInvalidFormatting(): Generator
{
yield 'Empty string to be interpreted as json' => ['', FileFormat::Json];
Expand Down
102 changes: 102 additions & 0 deletions tests/fixtures/ProvidesPetstoreApi.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Membrane\OpenAPIReader\Tests\Fixtures;

use Generator;
use Membrane\OpenAPIReader\ValueObject\Partial;
use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method;
use Membrane\OpenAPIReader\ValueObject\Valid\Identifier;
use Membrane\OpenAPIReader\ValueObject\Valid\V30;

final class ProvidesPetstoreApi
{
private const API = __DIR__ . '/petstore.yaml';

/**
* @return \Generator<array{
* 0: string,
* 1: string,
* 2: Method,
* 3: V30\Operation,
* }>
*/
public static function provideOperations(): Generator
{
yield 'listPets' => [self::API, '/pets', Method::GET, self::listPets()];

yield 'createPets' => [self::API, '/pets', Method::POST, self::createPets()];

yield 'showPetById' => [self::API, '/pets/{petId}', Method::GET, self::showPetById()];
}

private static function listPets(): V30\Operation
{
return V30\Operation::fromPartial(
parentIdentifier: new Identifier('Swagger Petstore(1.0.0)', '/pets'),
pathServers: [new V30\Server(
new Identifier('Swagger Petstore(1.0.0)'),
new Partial\Server(url: 'http://petstore.swagger.io/v1'),
)],
pathParameters: [],
method: Method::GET,
operation: new Partial\Operation('listPets', [], [
new Partial\Parameter(
name: 'limit',
in: 'query',
schema: new Partial\Schema(type: 'integer', maximum: 100, format: 'int32'),
),
])
);
}

private static function createPets(): V30\Operation
{
return V30\Operation::fromPartial(
parentIdentifier: new Identifier('Swagger Petstore(1.0.0)', '/pets'),
pathServers: [new V30\Server(
new Identifier('Swagger Petstore(1.0.0)'),
new Partial\Server(url: 'http://petstore.swagger.io/v1'),
)],
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,
)),
);
}

private static function showPetById(): V30\Operation
{
return V30\Operation::fromPartial(
parentIdentifier: new Identifier('Swagger Petstore(1.0.0)', '/pets/{petId}'),
pathServers: [new V30\Server(
new Identifier('Swagger Petstore(1.0.0)'),
new Partial\Server(url: 'http://petstore.swagger.io/v1'),
)],
pathParameters: [],
method: Method::GET,
operation: new Partial\Operation('showPetById', [], [
new Partial\Parameter(
name: 'petId',
in: 'path',
required: true,
schema: new Partial\Schema(type: 'string'),
),
])
);
}
}
12 changes: 6 additions & 6 deletions tests/fixtures/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ paths:
operationId: createPets
tags:
- pets
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
'201':
description: Null response
Expand All @@ -67,12 +73,6 @@ paths:
description: The id of the pet to retrieve
schema:
type: string
- name: petId
in: query
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
Expand Down