Skip to content

Commit b7c598e

Browse files
committed
Add support for responding with profiles in Content-Type
1 parent df2de0b commit b7c598e

File tree

7 files changed

+44
-7
lines changed

7 files changed

+44
-7
lines changed

docs/context.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class Context
4747
// Links to be returned in the document's links object
4848
public ArrayObject $documentLinks;
4949

50+
// Active JSON:API profile URIs for the current response
51+
public ArrayObject $activeProfiles;
52+
5053
// Get the request method
5154
public function method(): string;
5255

@@ -79,5 +82,8 @@ class Context
7982

8083
// Determine whether a sort field has been requested
8184
public function sortRequested(string $field): bool;
85+
86+
// Activate a JSON:API profile for the current response
87+
public function activateProfile(string $uri): static;
8288
}
8389
```

src/Context.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Context
2424
public ?array $include = null;
2525
public ArrayObject $documentMeta;
2626
public ArrayObject $documentLinks;
27+
public ArrayObject $activeProfiles;
2728
public WeakMap $resourceMeta;
2829

2930
private ?array $body;
@@ -45,6 +46,7 @@ public function __construct(public JsonApi $api, public ServerRequestInterface $
4546

4647
$this->documentMeta = new ArrayObject();
4748
$this->documentLinks = new ArrayObject();
49+
$this->activeProfiles = new ArrayObject();
4850

4951
$this->resourceMeta = new WeakMap();
5052
}
@@ -289,6 +291,13 @@ public function resourceMeta($model, array $meta): static
289291
return $this;
290292
}
291293

294+
public function activateProfile(string $uri): static
295+
{
296+
$this->activeProfiles[$uri] = true;
297+
298+
return $this;
299+
}
300+
292301
public function forModel(array $collections, ?object $model): static
293302
{
294303
$new = clone $this;

src/Exception/Pagination/MaxPageSizeExceededException.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tobyz\JsonApiServer\Exception\Pagination;
44

55
use Tobyz\JsonApiServer\Exception\BadRequestException;
6+
use Tobyz\JsonApiServer\Pagination\CursorPagination;
67

78
class MaxPageSizeExceededException extends BadRequestException
89
{
@@ -11,9 +12,7 @@ public function __construct(private readonly int $maxSize)
1112
parent::__construct('Page size requested is too large');
1213

1314
$this->meta(['page' => ['maxSize' => $this->maxSize]])->links([
14-
'type' => [
15-
'https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded',
16-
],
15+
'type' => [CursorPagination::PROFILE_URI . '/max-size-exceeded'],
1716
]);
1817
}
1918
}

src/Exception/Pagination/RangePaginationNotSupportedException.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tobyz\JsonApiServer\Exception\Pagination;
44

55
use Tobyz\JsonApiServer\Exception\BadRequestException;
6+
use Tobyz\JsonApiServer\Pagination\CursorPagination;
67

78
class RangePaginationNotSupportedException extends BadRequestException
89
{
@@ -11,9 +12,7 @@ public function __construct()
1112
parent::__construct('Range pagination is not supported');
1213

1314
$this->links([
14-
'type' => [
15-
'https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported',
16-
],
15+
'type' => [CursorPagination::PROFILE_URI . '/range-pagination-not-supported'],
1716
]);
1817
}
1918
}

src/JsonApi.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ public function handle(Request $request): Response
166166
throw $e ?? new NotFoundException();
167167
}
168168

169+
if (count($context->activeProfiles)) {
170+
$contentType = $response->getHeaderLine('Content-Type');
171+
172+
if (str_starts_with($contentType, self::MEDIA_TYPE)) {
173+
$profileUris = array_keys(array_filter($context->activeProfiles->getArrayCopy()));
174+
$contentType .= '; profile="' . implode(' ', $profileUris) . '"';
175+
$response = $response->withHeader('Content-Type', $contentType);
176+
}
177+
}
178+
169179
return $response->withAddedHeader('Vary', 'Accept');
170180
}
171181

src/Pagination/CursorPagination.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class CursorPagination implements Pagination
1313
{
1414
use HasSizeParameter;
1515

16+
public const PROFILE_URI = 'https://jsonapi.org/profiles/ethanresnick/cursor-pagination';
17+
1618
public readonly int $size;
1719
public readonly ?string $after;
1820
public readonly ?string $before;
@@ -24,6 +26,8 @@ public function __construct(int $defaultSize = 20, ?int $maxSize = 50)
2426

2527
public function paginate(object $query, Context $context): array
2628
{
29+
$context->activateProfile(self::PROFILE_URI);
30+
2731
$size = $this->getSize($context, 'size');
2832
$after = $this->getCursor($context, 'after');
2933
$before = $this->getCursor($context, 'before');

tests/specification/CursorPaginationTest.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
use Tobyz\JsonApiServer\Endpoint\Index;
66
use Tobyz\JsonApiServer\Exception\BadRequestException;
7-
use Tobyz\JsonApiServer\JsonApi;
87
use Tobyz\JsonApiServer\Exception\Pagination\MaxPageSizeExceededException;
98
use Tobyz\JsonApiServer\Exception\Pagination\RangePaginationNotSupportedException;
9+
use Tobyz\JsonApiServer\JsonApi;
1010
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
1111
use Tobyz\Tests\JsonApiServer\MockResource;
1212

@@ -27,6 +27,16 @@ public function setUp(): void
2727
);
2828
}
2929

30+
public function test_includes_profile_in_content_type(): void
31+
{
32+
$response = $this->api->handle($this->buildRequest('GET', '/articles'));
33+
34+
$this->assertEquals(
35+
'application/vnd.api+json; profile="https://jsonapi.org/profiles/ethanresnick/cursor-pagination"',
36+
$response->getHeaderLine('Content-Type'),
37+
);
38+
}
39+
3040
public function test_first_page_includes_expected_links_and_item_cursors(): void
3141
{
3242
$response = $this->api->handle($this->buildRequest('GET', '/articles'));

0 commit comments

Comments
 (0)