Skip to content
Open
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.6.1

- Bumped `Version::API_SPEC_VERSION` to the Apify OpenAPI spec `v2-2026-09-10T091137Z`. This
version formally documents the `X-Apify-Pagination-*` response headers (including the
previously-undocumented `X-Apify-Pagination-Desc`) on all offset-paginated list endpoints and
the `offset`/`limit`/`desc` query parameters on the webhook dispatches list; this client already
implemented all of those.
- `DatasetClient::listItems()` now prefers the `X-Apify-Pagination-Desc` response header over the
requested `desc` option when reporting `PaginationList::isDesc()`, matching the reference JS
client's `_createPaginationList` and the header newly documented in the spec above.

## 0.6.0

- Synced to Apify OpenAPI spec `v2-2026-09-02T154542Z`.
Expand Down
11 changes: 10 additions & 1 deletion src/Resource/DatasetClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ public function listItems(?DatasetListItemsOptions $options = null): PaginationL
$this->headerInt($response, 'X-Apify-Pagination-Offset', 0),
$this->headerInt($response, 'X-Apify-Pagination-Limit', $count),
$count,
$options->desc ?? false,
// Prefer the server-reported X-Apify-Pagination-Desc header (matches the reference JS
// client's `_createPaginationList`); fall back to the requested option when the header
// is absent, e.g. against an older API version that predates it.
$this->headerBool($response, 'X-Apify-Pagination-Desc', $options->desc ?? false),
);
}

Expand Down Expand Up @@ -215,4 +218,10 @@ private function headerInt(ResponseInterface $response, string $name, int $fallb
$value = $response->getHeaderLine($name);
return $value === '' ? $fallback : (int) $value;
}

private function headerBool(ResponseInterface $response, string $name, bool $fallback): bool
{
$value = $response->getHeaderLine($name);
return $value === '' ? $fallback : $value === 'true';
}
}
4 changes: 2 additions & 2 deletions src/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ final class Version
* The semantic version of this client library (see https://semver.org/).
* Changes to the public interface other than additive ones are considered breaking changes.
*/
public const CLIENT_VERSION = '0.6.0';
public const CLIENT_VERSION = '0.6.1';

/**
* The version of the Apify OpenAPI specification this client was generated and verified
* against. Corresponds to the {@code info.version} field of the Apify OpenAPI document.
*/
public const API_SPEC_VERSION = 'v2-2026-09-02T154542Z';
public const API_SPEC_VERSION = 'v2-2026-09-10T091137Z';

private function __construct()
{
Expand Down
2 changes: 2 additions & 0 deletions tests/Integration/DatasetIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public function testDatasetCrudFlow(): void
self::assertSame(3, $page->getCount());
self::assertCount(3, $page->getItems());
self::assertSame(1, $page->getItems()[0]['n']);
// isDesc() is sourced from the server's X-Apify-Pagination-Desc response header.
self::assertFalse($page->isDesc());

$csv = $dataset->downloadItems(DownloadItemsFormat::CSV, new DatasetDownloadOptions(bom: true));
self::assertStringContainsString('url', $csv);
Expand Down
39 changes: 39 additions & 0 deletions tests/Unit/HttpClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Apify\Client\ApifyClient;
use Apify\Client\Exception\ApifyApiException;
use Apify\Client\Internal\Json;
use Apify\Client\Options\DatasetListItemsOptions;
use PHPUnit\Framework\TestCase;

final class HttpClientTest extends TestCase
Expand Down Expand Up @@ -129,6 +130,44 @@ public function testDatasetItemsUseHeaderPagination(): void
self::assertSame(42, $page->getTotal());
self::assertSame(3, $page->getCount());
self::assertSame(1, $page->getItems()[0]['n']);
self::assertFalse($page->isDesc());
}

public function testDatasetItemsDescHeaderTakesPrecedenceOverOption(): void
{
// The server-reported X-Apify-Pagination-Desc header must win over the requested option
// (matches the reference JS client), so a page always reflects what the server actually did.
$transport = (new MockTransport())->queueResponse(
200,
Json::encode([['n' => 1]]),
['X-Apify-Pagination-Desc' => 'true']
);
$page = $this->client($transport)->dataset('ds1')->listItems(new DatasetListItemsOptions(desc: false));

self::assertTrue($page->isDesc());
}

public function testDatasetItemsDescHeaderFalseTakesPrecedenceOverOption(): void
{
// Symmetric case: the header must be genuinely *parsed*, not just checked for presence.
// A "false" header must win over a contradicting `desc: true` option, so this fails if
// headerBool() ever regresses to presence-only ("header is set, so trust the option").
$transport = (new MockTransport())->queueResponse(
200,
Json::encode([['n' => 1]]),
['X-Apify-Pagination-Desc' => 'false']
);
$page = $this->client($transport)->dataset('ds1')->listItems(new DatasetListItemsOptions(desc: true));

self::assertFalse($page->isDesc());
}

public function testDatasetItemsDescFallsBackToOptionWhenHeaderMissing(): void
{
$transport = (new MockTransport())->queueResponse(200, Json::encode([['n' => 1]]));
$page = $this->client($transport)->dataset('ds1')->listItems(new DatasetListItemsOptions(desc: true));

self::assertTrue($page->isDesc());
}

public function testValidateInputParsesBareObject(): void
Expand Down
Loading