Skip to content

Commit fc86012

Browse files
committed
Add controllers and models for order management API
1 parent 0351844 commit fc86012

10 files changed

Lines changed: 491 additions & 0 deletions
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\Fixtures\App\Controller;
6+
7+
use OpenSolid\Api\Routing\Attribute\Post;
8+
use OpenSolid\Api\Tests\Fixtures\App\Model\CreateOrderPayload;
9+
use OpenSolid\Api\Tests\Fixtures\App\Model\OrderView;
10+
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
11+
12+
#[Post(
13+
path: '/orders',
14+
name: 'api_create_order',
15+
description: 'Create an Order',
16+
summary: 'Creates a new order resource.',
17+
tags: ['Order'],
18+
)]
19+
final readonly class CreateOrderController
20+
{
21+
public function __invoke(#[MapRequestPayload] CreateOrderPayload $payload): OrderView
22+
{
23+
throw new \LogicException('Not implemented.');
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\Fixtures\App\Controller;
6+
7+
use OpenSolid\Api\Controller\Model\Paginator\Paginator;
8+
use OpenSolid\Api\Routing\Attribute\GetCollection;
9+
use OpenSolid\Api\Tests\Fixtures\App\Model\FindOrdersByProductQuery;
10+
use OpenSolid\Api\Tests\Fixtures\App\Model\OrderView;
11+
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
12+
13+
#[GetCollection(
14+
path: '/orders/by-product',
15+
name: 'api_find_orders_by_product',
16+
description: 'Find Orders by Product',
17+
summary: 'Retrieves orders filtered by product.',
18+
tags: ['Order'],
19+
)]
20+
final readonly class FindOrdersByProductController
21+
{
22+
/**
23+
* @return Paginator<OrderView>
24+
*/
25+
public function __invoke(#[MapQueryString] ?FindOrdersByProductQuery $query = null): Paginator
26+
{
27+
throw new \LogicException('Not implemented.');
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\Fixtures\App\Controller;
6+
7+
use OpenSolid\Api\Controller\Model\Paginator\Paginator;
8+
use OpenSolid\Api\Routing\Attribute\GetCollection;
9+
use OpenSolid\Api\Tests\Fixtures\App\Model\FindOrdersQuery;
10+
use OpenSolid\Api\Tests\Fixtures\App\Model\OrderView;
11+
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
12+
13+
#[GetCollection(
14+
path: '/orders',
15+
name: 'api_find_orders',
16+
description: 'Find Orders',
17+
summary: 'Retrieves a paginated collection of order resources.',
18+
tags: ['Order'],
19+
)]
20+
final readonly class FindOrdersController
21+
{
22+
/**
23+
* @return Paginator<OrderView>
24+
*/
25+
public function __invoke(#[MapQueryString] ?FindOrdersQuery $query = null): Paginator
26+
{
27+
throw new \LogicException('Not implemented.');
28+
}
29+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\Fixtures\App\Model;
6+
7+
use OpenApi\Attributes as OA;
8+
use Symfony\Component\Validator\Constraints as Assert;
9+
10+
#[OA\Schema]
11+
final class CreateOrderPayload
12+
{
13+
#[Assert\NotBlank]
14+
#[OA\Property(description: 'The external system ID')]
15+
public string $externalId;
16+
17+
#[Assert\Positive]
18+
#[OA\Property(description: 'The total amount in cents', example: 5000)]
19+
public int $total;
20+
21+
#[Assert\Choice(choices: ['USD', 'EUR', 'GBP'])]
22+
#[OA\Property(description: 'The currency code', example: 'USD')]
23+
public string $currency = 'USD';
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\Fixtures\App\Model;
6+
7+
use OpenApi\Attributes as OA;
8+
use OpenSolid\Api\Controller\Model\Paginator\PaginationParams;
9+
10+
final class FindOrdersByProductQuery
11+
{
12+
use PaginationParams;
13+
14+
#[OA\QueryParameter(description: 'Filter by external system ID')]
15+
public ?string $externalId = null;
16+
17+
#[OA\QueryParameter(description: 'Filter by product ID', schema: new OA\Schema(format: 'uuid'))]
18+
public ?string $productId = null;
19+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\Fixtures\App\Model;
6+
7+
use OpenApi\Attributes as OA;
8+
use OpenSolid\Api\Controller\Model\Paginator\PaginationParams;
9+
10+
final class FindOrdersQuery
11+
{
12+
use PaginationParams;
13+
14+
#[OA\QueryParameter(description: 'Filter by external system ID')]
15+
public ?string $externalId = null;
16+
17+
#[OA\QueryParameter(description: 'Filter by order status')]
18+
public ?string $status = null;
19+
20+
#[OA\QueryParameter(description: 'Filter by currency code')]
21+
public ?string $currency = null;
22+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\Fixtures\App\Model;
6+
7+
use OpenApi\Attributes as OA;
8+
9+
#[OA\Schema]
10+
final readonly class OrderView
11+
{
12+
public function __construct(
13+
#[OA\Property(description: 'The order ID', format: 'uuid', example: '019d0121-5df2-77df-be75-8933613d53ab')]
14+
public string $id,
15+
16+
#[OA\Property(description: 'The external system ID')]
17+
public string $externalId,
18+
19+
#[OA\Property(description: 'The order status', example: 'pending')]
20+
public string $status,
21+
22+
#[OA\Property(description: 'The total amount in cents', example: 5000)]
23+
public int $total,
24+
25+
#[OA\Property(description: 'The currency code', example: 'USD')]
26+
public string $currency,
27+
) {
28+
}
29+
}

tests/Fixtures/App/OpenApiSpec.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
)]
1414
#[OA\License(name: 'MIT', url: 'https://opensource.org/licenses/MIT')]
1515
#[OA\Server(url: 'https://127.0.0.1:8000', description: 'Production server (uses live data)')]
16+
#[OA\Tag(name: 'Order', description: 'The order resource')]
1617
#[OA\Tag(name: 'Product', description: 'The product resource')]
1718
class OpenApiSpec
1819
{
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Api\Tests\OpenApi;
6+
7+
use OpenSolid\Api\OpenApi\OpenApiGenerator;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use Psr\Log\AbstractLogger;
10+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
11+
12+
class OpenApiValidationTest extends KernelTestCase
13+
{
14+
protected function tearDown(): void
15+
{
16+
parent::tearDown();
17+
restore_exception_handler();
18+
}
19+
20+
#[Test]
21+
public function generationProducesNoWarningsOrErrors(): void
22+
{
23+
$generator = self::getContainer()->get(OpenApiGenerator::class);
24+
$openApi = $generator->generate();
25+
26+
$logs = [];
27+
$logger = new class($logs) extends AbstractLogger {
28+
public function __construct(private array &$logs)
29+
{
30+
}
31+
32+
public function log($level, \Stringable|string $message, array $context = []): void
33+
{
34+
$this->logs[] = ['level' => (string) $level, 'message' => (string) $message];
35+
}
36+
};
37+
38+
$openApi->_context->logger = $logger;
39+
$openApi->validate();
40+
41+
$issues = array_filter($logs, static fn (array $log): bool => in_array($log['level'], ['warning', 'error'], true));
42+
43+
self::assertSame([], $issues, 'OpenAPI validation should produce no warnings or errors');
44+
}
45+
}

0 commit comments

Comments
 (0)