From cd9d6150f0eeca9f1e6a68dedfd3badda2bbfd20 Mon Sep 17 00:00:00 2001 From: Gustavo Freze Date: Thu, 2 Jul 2026 11:07:43 -0300 Subject: [PATCH] feat: Split query parsing into explicit-schema and default-schema entry points. Criteria::fromQuery now takes a mandatory schema before the request, so the contract is always explicit at the call site. The former request-only case moves to Criteria::fromQueryWithDefaultSchema, which applies the empty default schema. This is a breaking change on both the offset and cursor Criteria. --- .claude/rules/php-library-code-style.md | 5 +- .claude/rules/php-library-tooling.md | 27 ++++-- .claude/skills/tiny-blocks-create/SKILL.md | 5 +- .../assets/config/.gitattributes | 3 + .../assets/config/.gitignore | 4 + .../assets/config/composer.json | 2 + .../assets/config/phpcs.xml | 88 ++++++++++++++++++- .gitattributes | 3 + .gitignore | 4 + README.md | 16 ++-- composer.json | 2 + phpcs.xml | 88 ++++++++++++++++++- src/Clause/Filters.php | 27 ++++-- src/Cursor/Criteria.php | 34 +++++-- src/Cursor/Keyset.php | 2 +- src/Exceptions/PageSizeOutOfRange.php | 1 - src/Internal/AllowedField.php | 19 ++-- src/Internal/Cursor/CursorCodec.php | 26 +++--- src/Internal/FilterClause.php | 2 +- src/Internal/Offset/Offset.php | 2 +- src/Internal/Offset/PageNumber.php | 6 +- src/Internal/Query.php | 6 +- src/Internal/Rsql/FilterParser.php | 2 +- src/Internal/Rsql/Scanner.php | 6 +- src/Limit.php | 2 +- src/Offset/Criteria.php | 35 ++++++-- tests/Unit/Clause/SeekClauseTest.php | 12 +-- tests/Unit/Cursor/CriteriaTest.php | 22 ++--- tests/Unit/Cursor/KeysetTest.php | 26 +++--- tests/Unit/DisjunctionTest.php | 8 +- tests/Unit/EndToEndTest.php | 6 +- tests/Unit/FilterTest.php | 48 +++++----- tests/Unit/LinksTest.php | 12 +-- tests/Unit/Offset/CriteriaTest.php | 24 ++--- tests/Unit/Offset/PageTest.php | 26 +++--- tests/Unit/Offset/SliceTest.php | 32 +++++-- 36 files changed, 453 insertions(+), 180 deletions(-) diff --git a/.claude/rules/php-library-code-style.md b/.claude/rules/php-library-code-style.md index c44dbc1..cc6d3fc 100644 --- a/.claude/rules/php-library-code-style.md +++ b/.claude/rules/php-library-code-style.md @@ -7,8 +7,9 @@ paths: # Code style -Semantic rules for all PHP files in libraries. Formatting rules covered by `PSR-12` are enforced -by `phpcs.xml`. Four formatting rules outside `PSR-12` (single-line signatures within 120 +Semantic rules for all PHP files in libraries. Formatting and structural rules are enforced by +`phpcs.xml` (the `PSR12` ruleset plus curated Squiz, Generic, PSR2, and `SlevomatCodingStandard` +sniffs). Four formatting rules the ruleset does not cover (single-line signatures within 120 characters, no vertical alignment in parameter lists, vertical alignment of `=>` in multi-line match arms and array literals, no trailing comma in multi-line lists) are documented at the end of this file under "Formatting overrides". Complexity rules live in `php-library-modeling.md`. diff --git a/.claude/rules/php-library-tooling.md b/.claude/rules/php-library-tooling.md index 8cf50d2..8f8967f 100644 --- a/.claude/rules/php-library-tooling.md +++ b/.claude/rules/php-library-tooling.md @@ -36,18 +36,26 @@ revise before outputting. 2. `composer.json` exposes exactly five scripts: `configure`, `configure-and-update`, `review`, `test-file`, `tests`. No other public scripts. 3. `composer.json` fixed fields use the canonical values from the skill asset (`license`, `type`, - `minimum-stability`, `prefer-stable`, `authors`, `config`, `require.php`). The five universal + `minimum-stability`, `prefer-stable`, `authors`, `config`, `require.php`). The six universal dev dependencies (`ergebnis/composer-normalize`, `infection/infection`, `phpstan/phpstan`, - `phpunit/phpunit`, `squizlabs/php_codesniffer`) are present. `require-dev` may add libraries - the tests need on top of those five. The asset's caret ranges are the canonical floor, and - the repo `composer.json` matches the asset. To bump, update the asset first, then the repo. + `phpunit/phpunit`, `slevomat/coding-standard`, `squizlabs/php_codesniffer`) are present, and + `config.allow-plugins` enables `dealerdirect/phpcodesniffer-composer-installer` so the Slevomat + sniffs register with phpcs. `require-dev` may add libraries the tests need on top of those six. + The asset's caret ranges are the canonical floor, and the repo `composer.json` matches the + asset. To bump, update the asset first, then the repo. 4. `composer.json` `description` is a single short sentence. Multi-sentence prose belongs in the README Overview, not in Composer metadata. 5. `composer.json` includes a `keywords` array that contains `"tiny-blocks"`. Its position in the array is not constrained. The remaining entries are topic tokens derived from the library's purpose (`psr-7`, `http-client`, `event-sourcing`, etc.). -6. `phpcs.xml` references only the `PSR12` ruleset. No additional sniffs. Formatting rules outside - PSR-12 live in `php-library-code-style.md` under "Formatting overrides". +6. `phpcs.xml` references the `PSR12` ruleset plus curated Squiz, Generic, PSR2, and + `SlevomatCodingStandard` sniffs that mechanically enforce the library's structural and + type-hint conventions (class member order, alphabetically sorted and used-only imports, strict + types, parameter, property, and return type hints, and complexity ceilings). The + `SlevomatCodingStandard.Classes.ClassStructure` groups collapse all methods into one group, so + the `php-ordering-conformance` hook owns the name-length ordering within them. The four + formatting rules the ruleset does not cover live in `php-library-code-style.md` under + "Formatting overrides". 7. `phpunit.xml` sets all five `failOn*` flags to `true` (`failOnDeprecation`, `failOnNotice`, `failOnPhpunitDeprecation`, `failOnRisky`, `failOnWarning`). 8. `phpunit.xml` sets `executionOrder="random"` and `beStrictAboutOutputDuringTests="true"`. @@ -84,9 +92,10 @@ revise before outputting. The committed config files split into two naming conventions on purpose. The split is documented here so it reads as intentional, not accidental. -- **Committed live, no `.dist`:** `phpcs.xml` and `phpunit.xml`. The ruleset (`PSR12` only) and - the test configuration are stable across the whole ecosystem and identical in every library. - There is no per-clone local-override story, so the live file is committed directly. +- **Committed live, no `.dist`:** `phpcs.xml` and `phpunit.xml`. The ruleset (`PSR12` plus the + curated Squiz, Generic, PSR2, and `SlevomatCodingStandard` sniffs) and the test configuration + are stable across the whole ecosystem and identical in every library. There is no per-clone + local-override story, so the live file is committed directly. - **Committed as `.dist`:** `phpstan.neon.dist` and `infection.json.dist`. These are the two tools a contributor may legitimately want to tune locally (a temporary `ignoreErrors` entry, a narrower mutator set while iterating). The `.dist` baseline is committed. A contributor drops a diff --git a/.claude/skills/tiny-blocks-create/SKILL.md b/.claude/skills/tiny-blocks-create/SKILL.md index efcc62b..5f743f7 100644 --- a/.claude/skills/tiny-blocks-create/SKILL.md +++ b/.claude/skills/tiny-blocks-create/SKILL.md @@ -8,8 +8,9 @@ description: Scaffold a new PHP library for the tiny-blocks ecosystem, or restor This skill is the single source of truth for the boilerplate every tiny-blocks PHP library shares: the config files, the CI workflow, and the repository templates. The canonical bodies live in `assets/` as drop-in files. Copy them and substitute the placeholders rather than -regenerating them from memory. The assets already encode the ecosystem's decisions (PSR-12 only, -`level: max`, MSI 100, Docker-wrapped Makefile, the `.dist` naming split, the export-ignore set). +regenerating them from memory. The assets already encode the ecosystem's decisions (the `PSR12` +plus `SlevomatCodingStandard` ruleset, `level: max`, MSI 100, Docker-wrapped Makefile, the +`.dist` naming split, the export-ignore set). The semantic conventions (how to name classes, how to structure `src/`, how to write tests) are **not** in this skill. They live in `.claude/rules/`. This skill produces the skeleton. The rules diff --git a/.claude/skills/tiny-blocks-create/assets/config/.gitattributes b/.claude/skills/tiny-blocks-create/assets/config/.gitattributes index 2bd9baa..369833c 100644 --- a/.claude/skills/tiny-blocks-create/assets/config/.gitattributes +++ b/.claude/skills/tiny-blocks-create/assets/config/.gitattributes @@ -2,6 +2,9 @@ *.php text diff=php +# Mantém os scripts de tooling do Claude fora da estatística de linguagem do GitHub +/.claude/**/*.py linguist-vendored + # Dev-only, excluded from the Packagist tarball /.github export-ignore /tests export-ignore diff --git a/.claude/skills/tiny-blocks-create/assets/config/.gitignore b/.claude/skills/tiny-blocks-create/assets/config/.gitignore index c8f4364..f3626aa 100644 --- a/.claude/skills/tiny-blocks-create/assets/config/.gitignore +++ b/.claude/skills/tiny-blocks-create/assets/config/.gitignore @@ -26,3 +26,7 @@ infection.log Thumbs.db .DS_Store Desktop.ini + +# Cache dos hooks Python do .claude +__pycache__/ +*.pyc diff --git a/.claude/skills/tiny-blocks-create/assets/config/composer.json b/.claude/skills/tiny-blocks-create/assets/config/composer.json index e10a520..85d30fc 100644 --- a/.claude/skills/tiny-blocks-create/assets/config/composer.json +++ b/.claude/skills/tiny-blocks-create/assets/config/composer.json @@ -27,6 +27,7 @@ "infection/infection": "^0.33", "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^13.1", + "slevomat/coding-standard": "^8.30", "squizlabs/php_codesniffer": "^4.0" }, "minimum-stability": "stable", @@ -43,6 +44,7 @@ }, "config": { "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, "ergebnis/composer-normalize": true, "infection/extension-installer": true }, diff --git a/.claude/skills/tiny-blocks-create/assets/config/phpcs.xml b/.claude/skills/tiny-blocks-create/assets/config/phpcs.xml index a52372c..8f95184 100644 --- a/.claude/skills/tiny-blocks-create/assets/config/phpcs.xml +++ b/.claude/skills/tiny-blocks-create/assets/config/phpcs.xml @@ -1,7 +1,93 @@ Code style for the tiny-blocks library. - + src tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitattributes b/.gitattributes index 2bd9baa..369833c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,9 @@ *.php text diff=php +# Mantém os scripts de tooling do Claude fora da estatística de linguagem do GitHub +/.claude/**/*.py linguist-vendored + # Dev-only, excluded from the Packagist tarball /.github export-ignore /tests export-ignore diff --git a/.gitignore b/.gitignore index c8f4364..f3626aa 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ infection.log Thumbs.db .DS_Store Desktop.ini + +# Cache dos hooks Python do .claude +__pycache__/ +*.pyc diff --git a/README.md b/README.md index 99c342a..1b73203 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,13 @@ $schema = Schema::create() # GET /v1/orders?filter=status==paid;total=ge=100&sort=-created_at,id&page[number]=3&page[size]=20 /** @var ServerRequestInterface $request */ -$criteria = Criteria::fromQuery(request: $request, schema: $schema); +$criteria = Criteria::fromQuery(schema: $schema, request: $request); ``` -When `Criteria::fromQuery` receives no schema, an empty contract applies: the default page-size bounds, no filterable -or sortable field, and no default sort. Any incoming filter or sort is then rejected. +For an endpoint that declares no contract, `Criteria::fromQueryWithDefaultSchema` is the request-only entry point. It +applies the default schema, an empty contract: the default page-size bounds, no filterable or sortable field, and no +default sort. Any incoming filter or sort is then rejected. Both `Offset\Criteria` and `Cursor\Criteria` expose it, so +either pagination parses a request without building a schema. | Setting | Default | Meaning | |------------------|---------|-------------------------------------------------| @@ -178,7 +180,7 @@ use TinyBlocks\HttpQuery\Schema; # GET /v1/orders?page[number]=3&page[size]=20 /** @var ServerRequestInterface $request */ -$criteria = Criteria::fromQuery(request: $request, schema: Schema::create()); +$criteria = Criteria::fromQuery(schema: Schema::create(), request: $request); /** @var iterable $items */ $page = $criteria->page(total: 480, items: $items); @@ -202,7 +204,7 @@ use TinyBlocks\HttpQuery\Schema; # GET /v1/orders?page[number]=2&page[size]=20 /** @var ServerRequestInterface $request */ -$criteria = Criteria::fromQuery(request: $request, schema: Schema::create()); +$criteria = Criteria::fromQuery(schema: Schema::create(), request: $request); /** @var iterable $items */ $slice = $criteria->slice(items: $items); @@ -231,7 +233,7 @@ $schema = Schema::create()->sortable(fields: ['created_at', 'id']); # GET /v1/orders?sort=-created_at,id&page[cursor]=BS3RvKY4LqEjYD19mQ0mCpJ&page[size]=20 /** @var ServerRequestInterface $request */ -$keyset = Criteria::fromQuery(request: $request, schema: $schema)->keyset(); +$keyset = Criteria::fromQuery(schema: $schema, request: $request)->keyset(); $keyset->limit()->toInteger(); # The page size, 20. $keyset->orders(); # The list the seek is ordered by. @@ -387,7 +389,7 @@ $schema = Schema::create()->sortable(fields: ['created_at', 'id']); # GET /v1/orders?filter=status==paid&sort=-created_at,id&page[number]=3&page[size]=20 /** @var ServerRequestInterface $request */ -$criteria = Criteria::fromQuery(request: $request, schema: $schema); +$criteria = Criteria::fromQuery(schema: $schema, request: $request); /** @var iterable $items */ $response = $criteria->page(total: 480, items: $items)->toResponse(baseUri: '/v1/orders'); diff --git a/composer.json b/composer.json index 3ed0c04..b2c4a54 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "nyholm/psr7": "^1.8", "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^13.1", + "slevomat/coding-standard": "^8.30", "squizlabs/php_codesniffer": "^4.0" }, "minimum-stability": "stable", @@ -51,6 +52,7 @@ }, "config": { "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, "ergebnis/composer-normalize": true, "infection/extension-installer": true }, diff --git a/phpcs.xml b/phpcs.xml index a52372c..8f95184 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,7 +1,93 @@ Code style for the tiny-blocks library. - + src tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Clause/Filters.php b/src/Clause/Filters.php index 40ebdbd..5a40b50 100644 --- a/src/Clause/Filters.php +++ b/src/Clause/Filters.php @@ -94,6 +94,22 @@ public static function fromTree(Filter $filter, FilterColumns $columns, Operator return new Filters(sql: $fragment->sql(), parameters: $fragment->parameters()); } + /** + * @param list $parts + * @param array $parameters + */ + private static function joinParts(Group $group, array $parts, array $parameters): Fragment + { + if ($parts === []) { + return Fragment::of(sql: '', parameters: []); + } + + $connective = $group->operator() === LogicalOperator::AND ? ' AND ' : ' OR '; + $sql = count($parts) === 1 ? $parts[0] : sprintf('(%s)', implode($connective, $parts)); + + return Fragment::of(sql: $sql, parameters: $parameters); + } + /** * Renders a group, joining its non-empty children with the group connective. * @@ -111,7 +127,7 @@ private static function renderGroup(Group $group, int $offset, FilterColumns $co foreach ($group->filters() as $child) { $fragment = self::render( filter: $child, - offset: $offset + count($parameters), + offset: ($offset + count($parameters)), columns: $columns, renderers: $renderers ); @@ -124,14 +140,7 @@ private static function renderGroup(Group $group, int $offset, FilterColumns $co $parameters = [...$parameters, ...$fragment->parameters()]; } - if ($parts === []) { - return Fragment::of(sql: '', parameters: []); - } - - $connective = $group->operator() === LogicalOperator::AND ? ' AND ' : ' OR '; - $sql = count($parts) === 1 ? $parts[0] : sprintf('(%s)', implode($connective, $parts)); - - return Fragment::of(sql: $sql, parameters: $parameters); + return self::joinParts(group: $group, parts: $parts, parameters: $parameters); } /** diff --git a/src/Cursor/Criteria.php b/src/Cursor/Criteria.php index bbb739f..b8cfa15 100644 --- a/src/Cursor/Criteria.php +++ b/src/Cursor/Criteria.php @@ -40,14 +40,13 @@ private function __construct( } /** - * Creates a Criteria from the request and an optional schema. + * Creates a Criteria from the schema and the request. * - *

When the schema is omitted, an empty contract applies: the default page-size bounds, no - * filterable or sortable field, and no default sort. Any incoming filter or sort is then - * rejected. The pagination always carries the incoming cursor token and the page size.

+ *

It parses the request query string and validates it against the schema. The pagination + * always carries the incoming cursor token and the page size.

* + * @param Schema $schema The query contract the request is validated against. * @param ServerRequestInterface $request The incoming PSR-7 server request. - * @param Schema|null $schema The query contract, or null for the empty contract. * @return Criteria The criteria carrying the validated comparisons, the effective sort, and the pagination. * @throws FilterExpressionIsInvalid If the filter expression cannot be parsed. * @throws SortExpressionIsInvalid If the sort expression cannot be parsed. @@ -58,9 +57,9 @@ private function __construct( * @throws FilterValueNotAllowed If a compared value falls outside the permitted set or kind. * @throws SortFieldNotAllowed If the sort orders by a field that was never declared sortable. */ - public static function fromQuery(ServerRequestInterface $request, ?Schema $schema = null): Criteria + public static function fromQuery(Schema $schema, ServerRequestInterface $request): Criteria { - $query = Query::from(schema: $schema ?? Schema::default(), request: $request); + $query = Query::from(schema: $schema, request: $request); $cursor = Token::from(token: $query->cursorToken()); return new Criteria( @@ -72,6 +71,27 @@ public static function fromQuery(ServerRequestInterface $request, ?Schema $schem ); } + /** + * Creates a Criteria from the request under the default schema. + * + *

The default schema is an empty contract: the default page-size bounds, no filterable or + * sortable field, and no default sort. Any incoming filter or sort is then rejected, and the + * pagination always carries the incoming cursor token and the page size.

+ * + * @param ServerRequestInterface $request The incoming PSR-7 server request. + * @return Criteria The criteria carrying the validated comparisons, the effective sort, and the pagination. + * @throws FilterExpressionIsInvalid If the filter expression cannot be parsed. + * @throws SortExpressionIsInvalid If the sort expression cannot be parsed. + * @throws PageSizeOutOfRange If the page size falls outside the valid range. + * @throws FilterShapeNotSupported If the filter is not a comparison or an AND group of comparisons. + * @throws FilterFieldNotAllowed If a comparison targets a field that was never allowed. + * @throws SortFieldNotAllowed If the sort orders by a field that was never declared sortable. + */ + public static function fromQueryWithDefaultSchema(ServerRequestInterface $request): Criteria + { + return Criteria::fromQuery(schema: Schema::default(), request: $request); + } + /** * Returns the effective sort. * diff --git a/src/Cursor/Keyset.php b/src/Cursor/Keyset.php index c9f8ea3..dc952bf 100644 --- a/src/Cursor/Keyset.php +++ b/src/Cursor/Keyset.php @@ -61,7 +61,7 @@ public function page(iterable $items, ?Closure $keysOf = null): Page sort: $this->submittedSort, items: $items, filter: $this->filter, - keysOf: $keysOf ?? SortKeys::from(sort: $this->sort), + keysOf: ($keysOf ?? SortKeys::from(sort: $this->sort)), pagination: $this->pagination ); } diff --git a/src/Exceptions/PageSizeOutOfRange.php b/src/Exceptions/PageSizeOutOfRange.php index a05d5bb..cbd564e 100644 --- a/src/Exceptions/PageSizeOutOfRange.php +++ b/src/Exceptions/PageSizeOutOfRange.php @@ -5,7 +5,6 @@ namespace TinyBlocks\HttpQuery\Exceptions; use InvalidArgumentException; -use TinyBlocks\HttpQuery\Schema; /** * Raised when a requested page size falls outside the valid range. diff --git a/src/Internal/AllowedField.php b/src/Internal/AllowedField.php index 9e87a06..5849059 100644 --- a/src/Internal/AllowedField.php +++ b/src/Internal/AllowedField.php @@ -31,13 +31,7 @@ public function permit(Comparison $comparison): Comparison } foreach ($comparison->values() as $value) { - if (!is_null($this->values) && !in_array($value, $this->values, true)) { - throw FilterValueNotAllowed::notPermitted(field: $this->field, value: $value); - } - - if (!is_null($this->kind) && !$this->kind->matches(value: $value)) { - throw FilterValueNotAllowed::kindMismatch(kind: $this->kind, field: $this->field, value: $value); - } + $this->permitValue(value: $value); } return $comparison; @@ -47,4 +41,15 @@ public function hasField(string $field): bool { return $this->field === $field; } + + private function permitValue(string $value): void + { + if (!is_null($this->values) && !in_array($value, $this->values, true)) { + throw FilterValueNotAllowed::notPermitted(field: $this->field, value: $value); + } + + if (!is_null($this->kind) && !$this->kind->matches(value: $value)) { + throw FilterValueNotAllowed::kindMismatch(kind: $this->kind, field: $this->field, value: $value); + } + } } diff --git a/src/Internal/Cursor/CursorCodec.php b/src/Internal/Cursor/CursorCodec.php index 5da62d0..b481a2e 100644 --- a/src/Internal/Cursor/CursorCodec.php +++ b/src/Internal/Cursor/CursorCodec.php @@ -15,23 +15,12 @@ private function __construct() public static function decode(string $token): array { $decoded = base64_decode(strtr($token, '-_', '+/'), true); + $keys = $decoded === false ? null : json_decode($decoded, true); - if ($decoded === false) { + if (!is_array($keys) || !array_is_list($keys) || !CursorCodec::hasOnlyScalarsOrNulls(keys: $keys)) { throw CursorIsInvalid::from(token: $token); } - $keys = json_decode($decoded, true); - - if (!is_array($keys) || !array_is_list($keys)) { - throw CursorIsInvalid::from(token: $token); - } - - foreach ($keys as $value) { - if (!is_scalar($value) && !is_null($value)) { - throw CursorIsInvalid::from(token: $token); - } - } - return $keys; } @@ -44,4 +33,15 @@ public static function encode(array $keys): string |> (static fn(string $encoded): string => strtr($encoded, '+/', '-_')) |> (static fn(string $encoded): string => rtrim($encoded, '=')); } + + private static function hasOnlyScalarsOrNulls(array $keys): bool + { + foreach ($keys as $value) { + if (!is_scalar($value) && !is_null($value)) { + return false; + } + } + + return true; + } } diff --git a/src/Internal/FilterClause.php b/src/Internal/FilterClause.php index dc20e06..38fe908 100644 --- a/src/Internal/FilterClause.php +++ b/src/Internal/FilterClause.php @@ -26,7 +26,7 @@ public static function from(FilterColumn $column, int $offset, Comparison $compa { $values = array_map($column->normalize(...), $comparison->values()); $names = array_map( - static fn(int $index): string => sprintf('filter_%d', $offset + $index), + static fn(int $index): string => sprintf('filter_%d', ($offset + $index)), array_keys($values) ); $parameters = array_combine($names, $values); diff --git a/src/Internal/Offset/Offset.php b/src/Internal/Offset/Offset.php index 39f13fa..370fa9b 100644 --- a/src/Internal/Offset/Offset.php +++ b/src/Internal/Offset/Offset.php @@ -24,7 +24,7 @@ public static function from(int $value): Offset public static function fromPage(PageNumber $page, Limit $limit): Offset { - return new Offset(value: ($page->value() - 1) * $limit->value()); + return new Offset(value: (($page->value() - 1) * $limit->value())); } public function value(): int diff --git a/src/Internal/Offset/PageNumber.php b/src/Internal/Offset/PageNumber.php index 4c7aaff..69b4c4f 100644 --- a/src/Internal/Offset/PageNumber.php +++ b/src/Internal/Offset/PageNumber.php @@ -24,12 +24,12 @@ public static function from(int $value): PageNumber public static function fromOffset(Limit $limit, Offset $offset): PageNumber { - return new PageNumber(value: intdiv($offset->value(), $limit->value()) + 1); + return new PageNumber(value: (intdiv($offset->value(), $limit->value()) + 1)); } public function next(): PageNumber { - return new PageNumber(value: $this->value + 1); + return new PageNumber(value: ($this->value + 1)); } public function value(): int @@ -44,6 +44,6 @@ public function isFirst(): bool public function previous(): ?PageNumber { - return $this->isFirst() ? null : new PageNumber(value: $this->value - 1); + return $this->isFirst() ? null : new PageNumber(value: ($this->value - 1)); } } diff --git a/src/Internal/Query.php b/src/Internal/Query.php index c4311cd..67d39b5 100644 --- a/src/Internal/Query.php +++ b/src/Internal/Query.php @@ -31,9 +31,9 @@ public static function from(Schema $schema, ServerRequestInterface $request): Qu $parameters = QueryParameters::from(request: $request); $page = $parameters->get(key: 'page')->toArray(); - $size = Attribute::from(value: $page['size'] ?? null); - $cursor = Attribute::from(value: $page['cursor'] ?? null); - $number = Attribute::from(value: $page['number'] ?? null); + $size = Attribute::from(value: ($page['size'] ?? null)); + $cursor = Attribute::from(value: ($page['cursor'] ?? null)); + $number = Attribute::from(value: ($page['number'] ?? null)); $expression = $parameters->get(key: 'filter')->toString(); $filter = $expression === '' ? Group::none() : FilterParser::from(input: $expression)->parse(); diff --git a/src/Internal/Rsql/FilterParser.php b/src/Internal/Rsql/FilterParser.php index 7484727..ceea706 100644 --- a/src/Internal/Rsql/FilterParser.php +++ b/src/Internal/Rsql/FilterParser.php @@ -64,7 +64,7 @@ private function constraint(int $depth): Filter } $this->scanner->expect(character: '('); - $group = $this->disjunction(depth: $depth + 1); + $group = $this->disjunction(depth: ($depth + 1)); $this->scanner->expect(character: ')'); return $group; diff --git a/src/Internal/Rsql/Scanner.php b/src/Internal/Rsql/Scanner.php index c9dfba8..e6b3594 100644 --- a/src/Internal/Rsql/Scanner.php +++ b/src/Internal/Rsql/Scanner.php @@ -24,7 +24,7 @@ public static function from(string $input): Scanner public function peek(): string { - return $this->input[$this->position] ?? ''; + return ($this->input[$this->position] ?? ''); } public function value(): string @@ -58,7 +58,7 @@ private function quoted(string $quote): string $character = $this->input[$this->position]; if ($character === '\\') { - $characters[] = $this->input[$this->position + 1] ?? ''; + $characters[] = ($this->input[($this->position + 1)] ?? ''); $this->position += 2; continue; } @@ -107,7 +107,7 @@ public function unreserved(): string $this->position++; } - $token = substr($this->input, $start, $this->position - $start); + $token = substr($this->input, $start, ($this->position - $start)); if ($token === '') { throw FilterExpressionIsInvalid::from(expression: $this->input); diff --git a/src/Limit.php b/src/Limit.php index f42c1cf..7d0f47a 100644 --- a/src/Limit.php +++ b/src/Limit.php @@ -32,7 +32,7 @@ public static function of(int $size): Limit */ public function plus(int $extra): Limit { - return new Limit(size: $this->size + $extra); + return new Limit(size: ($this->size + $extra)); } /** diff --git a/src/Offset/Criteria.php b/src/Offset/Criteria.php index cf19643..a41f2df 100644 --- a/src/Offset/Criteria.php +++ b/src/Offset/Criteria.php @@ -42,14 +42,13 @@ private function __construct( } /** - * Creates a Criteria from the request and an optional schema. + * Creates a Criteria from the schema and the request. * - *

When the schema is omitted, an empty contract applies: the default page-size bounds, no - * filterable or sortable field, and no default sort. Any incoming filter or sort is then - * rejected. The pagination derives its offset from the one-based page number and the page size.

+ *

It parses the request query string and validates it against the schema. The pagination + * derives its offset from the one-based page number and the page size.

* + * @param Schema $schema The query contract the request is validated against. * @param ServerRequestInterface $request The incoming PSR-7 server request. - * @param Schema|null $schema The query contract, or null for the empty contract. * @return Criteria The criteria carrying the validated comparisons, the effective sort, and the pagination. * @throws FilterExpressionIsInvalid If the filter expression cannot be parsed. * @throws SortExpressionIsInvalid If the sort expression cannot be parsed. @@ -61,9 +60,9 @@ private function __construct( * @throws FilterValueNotAllowed If a compared value falls outside the permitted set or kind. * @throws SortFieldNotAllowed If the sort orders by a field that was never declared sortable. */ - public static function fromQuery(ServerRequestInterface $request, ?Schema $schema = null): Criteria + public static function fromQuery(Schema $schema, ServerRequestInterface $request): Criteria { - $query = Query::from(schema: $schema ?? Schema::default(), request: $request); + $query = Query::from(schema: $schema, request: $request); return new Criteria( sort: $query->sort(), @@ -74,6 +73,28 @@ public static function fromQuery(ServerRequestInterface $request, ?Schema $schem ); } + /** + * Creates a Criteria from the request under the default schema. + * + *

The default schema is an empty contract: the default page-size bounds, no filterable or + * sortable field, and no default sort. Any incoming filter or sort is then rejected, and the + * pagination derives its offset from the one-based page number and the page size.

+ * + * @param ServerRequestInterface $request The incoming PSR-7 server request. + * @return Criteria The criteria carrying the validated comparisons, the effective sort, and the pagination. + * @throws FilterExpressionIsInvalid If the filter expression cannot be parsed. + * @throws SortExpressionIsInvalid If the sort expression cannot be parsed. + * @throws PageNumberOutOfRange If the page number is less than 1. + * @throws PageSizeOutOfRange If the page size falls outside the valid range. + * @throws FilterShapeNotSupported If the filter is not a comparison or an AND group of comparisons. + * @throws FilterFieldNotAllowed If a comparison targets a field that was never allowed. + * @throws SortFieldNotAllowed If the sort orders by a field that was never declared sortable. + */ + public static function fromQueryWithDefaultSchema(ServerRequestInterface $request): Criteria + { + return Criteria::fromQuery(schema: Schema::default(), request: $request); + } + /** * Creates a Page from the total element count and the items. * diff --git a/tests/Unit/Clause/SeekClauseTest.php b/tests/Unit/Clause/SeekClauseTest.php index e40805b..c69527e 100644 --- a/tests/Unit/Clause/SeekClauseTest.php +++ b/tests/Unit/Clause/SeekClauseTest.php @@ -18,8 +18,8 @@ public function testFromWhenNoIncomingCursorThenYieldsAnEmptyPredicate(): void { /** @Given a keyset over a sort with no incoming cursor */ $keyset = Criteria::fromQuery( - request: Query::from(parameters: ['sort' => '-created_at,id', 'page' => ['size' => '2']]), - schema: Schema::create()->sortable(fields: ['id', 'created_at']) + schema: Schema::create()->sortable(fields: ['id', 'created_at']), + request: Query::from(parameters: ['sort' => '-created_at,id', 'page' => ['size' => '2']]) )->keyset(); /** @When the seek predicate is assembled */ @@ -43,10 +43,10 @@ public function testFromWhenMultipleOrdersThenRendersTheRowValueComparison(): vo /** @And a keyset descending by creation time then ascending by identifier following the cursor */ $keyset = Criteria::fromQuery( + schema: Schema::create()->sortable(fields: ['id', 'created_at']), request: Query::from( parameters: ['sort' => '-created_at,id', 'page' => ['cursor' => $token, 'size' => '2']] - ), - schema: Schema::create()->sortable(fields: ['id', 'created_at']) + ) )->keyset(); /** @When the seek predicate is assembled */ @@ -81,10 +81,10 @@ public function testFromWhenSingleDescendingOrderThenSelectsRowsBelowTheCursor() /** @And a keyset descending by that single field following the cursor */ $keyset = Criteria::fromQuery( + schema: Schema::create()->sortable(fields: ['created_at']), request: Query::from( parameters: ['sort' => '-created_at', 'page' => ['cursor' => $token, 'size' => '2']] - ), - schema: Schema::create()->sortable(fields: ['created_at']) + ) )->keyset(); /** @When the seek predicate is assembled */ diff --git a/tests/Unit/Cursor/CriteriaTest.php b/tests/Unit/Cursor/CriteriaTest.php index 9c762a1..afd3184 100644 --- a/tests/Unit/Cursor/CriteriaTest.php +++ b/tests/Unit/Cursor/CriteriaTest.php @@ -24,7 +24,7 @@ public function testFromQueryWhenEmptyThenSortIsEmpty(): void $query = Query::from(parameters: []); /** @When building the criteria from the query */ - $criteria = Criteria::fromQuery(request: $query); + $criteria = Criteria::fromQueryWithDefaultSchema(request: $query); /** @Then the effective sort is empty */ self::assertTrue($criteria->sort()->isEmpty()); @@ -36,7 +36,7 @@ public function testFromQueryWhenEmptyThenComparisonsAreEmpty(): void $query = Query::from(parameters: []); /** @When building the criteria from the query */ - $criteria = Criteria::fromQuery(request: $query); + $criteria = Criteria::fromQueryWithDefaultSchema(request: $query); /** @Then there is no comparison */ self::assertSame([], $criteria->comparisons()); @@ -48,7 +48,7 @@ public function testKeysetWhenBuiltWithoutCursorThenBuildsCursorPage(): void $schema = Schema::create()->defaultSort(sort: Sort::fromExpression(expression: 'id')); /** @And a criteria parsed from a request carrying a page size of two and no cursor */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['size' => '2']]), schema: $schema); + $criteria = Criteria::fromQuery(schema: $schema, request: Query::from(parameters: ['page' => ['size' => '2']])); /** @When building a cursor page through the keyset view over the array rows fetched */ $page = $criteria->keyset()->page(items: [['id' => 10], ['id' => 20], ['id' => 30]]); @@ -70,8 +70,8 @@ public function testKeysetWhenIncomingCursorPresentThenBuildsCursorPage(): void /** @And a criteria parsed from a request carrying that cursor and a page size of two */ $criteria = Criteria::fromQuery( - request: Query::from(parameters: ['page' => ['cursor' => $token, 'size' => '2']]), - schema: $schema + schema: $schema, + request: Query::from(parameters: ['page' => ['cursor' => $token, 'size' => '2']]) ); /** @When building a cursor page through the keyset view over the items fetched */ @@ -87,7 +87,7 @@ public function testKeysetWhenIncomingCursorPresentThenBuildsCursorPage(): void public function testKeysetWhenEffectiveSortIsEmptyThenThrowsSortIsRequired(): void { /** @Given a criteria parsed from a request carrying no sort and no schema default */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: [])); + $criteria = Criteria::fromQueryWithDefaultSchema(request: Query::from(parameters: [])); /** @Then an exception indicating a deterministic order is required is raised */ $this->expectException(SortIsRequired::class); @@ -105,7 +105,7 @@ public function testFromQueryWhenCustomSchemaGivenThenAppliesItsDefaultPageSize( ->defaultSort(sort: Sort::fromExpression(expression: 'id')); /** @When building the keyset view from a query carrying no page size and the schema */ - $keyset = Criteria::fromQuery(request: Query::from(parameters: []), schema: $schema)->keyset(); + $keyset = Criteria::fromQuery(schema: $schema, request: Query::from(parameters: []))->keyset(); /** @Then the keyset carries the schema default page size */ self::assertSame(5, $keyset->limit()->toInteger()); @@ -121,8 +121,8 @@ public function testFromQueryWhenCursorPresentThenKeysetCarriesPageSizeAndCursor /** @And a criteria parsed from a request carrying that cursor and a page size of ten */ $criteria = Criteria::fromQuery( - request: Query::from(parameters: ['page' => ['cursor' => $token, 'size' => '10']]), - schema: $schema + schema: $schema, + request: Query::from(parameters: ['page' => ['cursor' => $token, 'size' => '10']]) ); /** @When building the keyset view */ @@ -145,7 +145,7 @@ public function testFromQueryWhenPerPageAboveMaximumThenThrowsPageSizeOutOfRange $this->expectExceptionMessage('Page size'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query); + Criteria::fromQueryWithDefaultSchema(request: $query); } public function testFromQueryWhenFilterAndSortGivenThenEachSpecificationIsValidated(): void @@ -159,7 +159,7 @@ public function testFromQueryWhenFilterAndSortGivenThenEachSpecificationIsValida $query = Query::from(parameters: ['sort' => '-created_at', 'filter' => 'status==paid']); /** @When building the criteria from the query and the schema */ - $criteria = Criteria::fromQuery(request: $query, schema: $schema); + $criteria = Criteria::fromQuery(schema: $schema, request: $query); /** @Then the validated comparisons carry the filtered field and value */ self::assertEquals( diff --git a/tests/Unit/Cursor/KeysetTest.php b/tests/Unit/Cursor/KeysetTest.php index ca7eb2a..1edf869 100644 --- a/tests/Unit/Cursor/KeysetTest.php +++ b/tests/Unit/Cursor/KeysetTest.php @@ -30,8 +30,8 @@ public function testLimitThenReturnsThePageSize(): void { /** @Given a keyset view built from a request carrying a sort and a page size of fifteen */ $keyset = Criteria::fromQuery( - request: Query::from(parameters: ['sort' => 'id', 'page' => ['size' => '15']]), - schema: $this->schema + schema: $this->schema, + request: Query::from(parameters: ['sort' => 'id', 'page' => ['size' => '15']]) )->keyset(); /** @When reading the page size */ @@ -44,7 +44,7 @@ public function testLimitThenReturnsThePageSize(): void public function testOrdersWhenSortGivenThenReturnsItsOrders(): void { /** @Given a keyset view built from a descending sort over a single field */ - $keyset = Criteria::fromQuery(request: Query::from(parameters: ['sort' => '-name']), schema: $this->schema) + $keyset = Criteria::fromQuery(schema: $this->schema, request: Query::from(parameters: ['sort' => '-name'])) ->keyset(); /** @When reading the orders */ @@ -58,8 +58,8 @@ public function testPageWhenKeysOfGivenThenUsesTheExtractor(): void { /** @Given a keyset view built from a request carrying a sort and a page size of two */ $keyset = Criteria::fromQuery( - request: Query::from(parameters: ['sort' => 'id', 'page' => ['size' => '2']]), - schema: $this->schema + schema: $this->schema, + request: Query::from(parameters: ['sort' => 'id', 'page' => ['size' => '2']]) )->keyset(); /** @When building the page from the items and an explicit key extractor */ @@ -76,8 +76,8 @@ public function testCursorWhenNoIncomingCursorThenEverySortFieldIsNull(): void { /** @Given a keyset view built from a request carrying a sort over two fields */ $keyset = Criteria::fromQuery( - request: Query::from(parameters: ['sort' => 'created_at,id', 'page' => ['size' => '2']]), - schema: $this->schema + schema: $this->schema, + request: Query::from(parameters: ['sort' => 'created_at,id', 'page' => ['size' => '2']]) )->keyset(); /** @When reading the incoming cursor key values */ @@ -91,8 +91,8 @@ public function testPageWhenNoKeysOfGivenThenDerivesKeysFromTheSortFields(): voi { /** @Given a keyset view built from a request carrying a sort and a page size of two */ $keyset = Criteria::fromQuery( - request: Query::from(parameters: ['sort' => 'id', 'page' => ['size' => '2']]), - schema: $this->schema + schema: $this->schema, + request: Query::from(parameters: ['sort' => 'id', 'page' => ['size' => '2']]) )->keyset(); /** @When building the page from array rows without a key extractor */ @@ -112,10 +112,10 @@ public function testCursorWhenIncomingCursorGivenThenKeysValuesBySortField(): vo /** @And a keyset view built from a request carrying that cursor and a sort over two fields */ $keyset = Criteria::fromQuery( + schema: $this->schema, request: Query::from( parameters: ['sort' => 'created_at,id', 'page' => ['cursor' => $token, 'size' => '2']] - ), - schema: $this->schema + ) )->keyset(); /** @When reading the incoming cursor key values */ @@ -132,10 +132,10 @@ public function testCursorWhenDecodedCountMismatchesThenThrowsCursorIsInvalid(): /** @And a keyset view whose sort carries two fields */ $keyset = Criteria::fromQuery( + schema: $this->schema, request: Query::from( parameters: ['sort' => 'created_at,id', 'page' => ['cursor' => $token, 'size' => '2']] - ), - schema: $this->schema + ) )->keyset(); /** @Then an exception indicating the cursor is invalid is raised */ diff --git a/tests/Unit/DisjunctionTest.php b/tests/Unit/DisjunctionTest.php index 9e54873..c85aebf 100644 --- a/tests/Unit/DisjunctionTest.php +++ b/tests/Unit/DisjunctionTest.php @@ -29,7 +29,7 @@ public function testCursorWhenDisjunctionAllowedThenExposesTheOrTree(): void $query = Query::from(parameters: ['filter' => 'a==1,b==2']); /** @When building the cursor criteria */ - $criteria = CursorCriteria::fromQuery(request: $query, schema: $schema); + $criteria = CursorCriteria::fromQuery(schema: $schema, request: $query); /** @Then the filter tree is the OR group */ self::assertEquals( @@ -57,7 +57,7 @@ public function testWhenDisjunctionAllowedThenValidatesEveryNestedLeaf(): void $query = Query::from(parameters: ['filter' => '(a==1,b==2);c==3']); /** @When building the criteria */ - $criteria = OffsetCriteria::fromQuery(request: $query, schema: $schema); + $criteria = OffsetCriteria::fromQuery(schema: $schema, request: $query); /** @Then every nested leaf is validated and returned in tree order */ self::assertEquals( @@ -85,7 +85,7 @@ public function testWhenDisjunctionAllowedAndFieldNotAllowedThenStillThrows(): v $this->expectExceptionMessage('Filter field is not allowed.'); /** @When building the criteria */ - OffsetCriteria::fromQuery(request: $query, schema: $schema); + OffsetCriteria::fromQuery(schema: $schema, request: $query); } public function testOffsetWhenDisjunctionAllowedThenAcceptsTheOrGroupAndExposesTheTree(): void @@ -100,7 +100,7 @@ public function testOffsetWhenDisjunctionAllowedThenAcceptsTheOrGroupAndExposesT $query = Query::from(parameters: ['filter' => 'a==1,b==2']); /** @When building the offset criteria */ - $criteria = OffsetCriteria::fromQuery(request: $query, schema: $schema); + $criteria = OffsetCriteria::fromQuery(schema: $schema, request: $query); /** @Then the filter tree is the OR group and the comparisons carry every validated leaf */ self::assertEquals( diff --git a/tests/Unit/EndToEndTest.php b/tests/Unit/EndToEndTest.php index ab5a113..13fb227 100644 --- a/tests/Unit/EndToEndTest.php +++ b/tests/Unit/EndToEndTest.php @@ -30,7 +30,7 @@ public function testPipelineWhenOffsetRequestGivenThenPageRendersTheResponse(): ]); /** @And the criteria parsed from those parameters */ - $criteria = OffsetCriteria::fromQuery(request: $query, schema: $schema); + $criteria = OffsetCriteria::fromQuery(schema: $schema, request: $query); /** @And the base URI the navigation links render against */ $base = '/v1/orders?filter=status==paid;total=ge=100&sort=-created_at,id'; @@ -74,7 +74,7 @@ public function testPipelineWhenCursorRequestGivenThenCursorPageRendersTheRespon $query = Query::from(parameters: ['sort' => 'id', 'page' => ['cursor' => $token, 'size' => '2']]); /** @And a cursor page built through the keyset view over the array rows fetched */ - $page = CursorCriteria::fromQuery(request: $query, schema: $schema) + $page = CursorCriteria::fromQuery(schema: $schema, request: $query) ->keyset() ->page(items: [['id' => 10], ['id' => 20], ['id' => 30]]); @@ -114,7 +114,7 @@ public function testPipelineWhenOffsetRequestGivenThenLinkHeaderFoldsEveryRelati ]); /** @And the criteria parsed from those parameters */ - $criteria = OffsetCriteria::fromQuery(request: $query, schema: $schema); + $criteria = OffsetCriteria::fromQuery(schema: $schema, request: $query); /** @And the base URI the relations render against */ $base = '/v1/orders?filter=status==paid;total=ge=100&sort=-created_at,id'; diff --git a/tests/Unit/FilterTest.php b/tests/Unit/FilterTest.php index 0cf97bd..3438ba6 100644 --- a/tests/Unit/FilterTest.php +++ b/tests/Unit/FilterTest.php @@ -39,7 +39,7 @@ public function testFromQueryWhenNoFilterThenComparisonsAreEmpty(): void $query = Query::from(parameters: []); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then there is no comparison */ self::assertSame([], $comparisons); @@ -51,7 +51,7 @@ public function testFromQueryWhenAndGroupThenComparisonsCarryEveryLeaf(): void $query = Query::from(parameters: ['filter' => 'a==1;b==2']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the comparisons carry both leaves in order */ self::assertEquals([ @@ -66,7 +66,7 @@ public function testFromQueryWhenInListThenComparisonCarriesEveryValue(): void $query = Query::from(parameters: ['filter' => 'role=in=(admin,user)']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the only comparison is an IN comparison carrying every listed value in order */ self::assertEquals( @@ -81,7 +81,7 @@ public function testFromQueryWhenNotInListThenComparisonCarriesEveryValue(): voi $query = Query::from(parameters: ['filter' => 'role=out=(a,b)']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the only comparison is a NOT_IN comparison carrying every listed value in order */ self::assertEquals( @@ -100,7 +100,7 @@ public function testFromQueryWhenOrGroupThenThrowsFilterShapeNotSupported(): voi $this->expectExceptionMessage('Filter shape is not supported.'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $this->schema); + Criteria::fromQuery(schema: $this->schema, request: $query); } public function testFromQueryWhenDoubleQuotedValueThenComparisonStripsQuotes(): void @@ -109,7 +109,7 @@ public function testFromQueryWhenDoubleQuotedValueThenComparisonStripsQuotes(): $query = Query::from(parameters: ['filter' => 'name=="John Doe"']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the comparison carries the value with the surrounding quotes stripped */ self::assertEquals( @@ -124,7 +124,7 @@ public function testFromQueryWhenSingleQuotedValueThenComparisonStripsQuotes(): $query = Query::from(parameters: ['filter' => "name=='John Doe'"]); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the comparison carries the value with the surrounding quotes stripped */ self::assertEquals( @@ -143,7 +143,7 @@ public function testFromQueryWhenNestedGroupThenThrowsFilterShapeNotSupported(): $this->expectExceptionMessage('Filter shape <(a==1,b==2);c==3> is not supported.'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $this->schema); + Criteria::fromQuery(schema: $this->schema, request: $query); } public function testFromQueryWhenEscapedQuoteInValueThenComparisonKeepsTheQuote(): void @@ -152,7 +152,7 @@ public function testFromQueryWhenEscapedQuoteInValueThenComparisonKeepsTheQuote( $query = Query::from(parameters: ['filter' => 'name=="a\\"b"']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the comparison carries the value with the escaped quote unescaped */ self::assertEquals( @@ -174,7 +174,7 @@ public function testFromQueryWhenFieldNotAllowedThenThrowsFilterFieldNotAllowed( $this->expectExceptionMessage('Filter field is not allowed.'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $schema); + Criteria::fromQuery(schema: $schema, request: $query); } public function testFromQueryWhenValueBreaksKindThenThrowsFilterValueNotAllowed(): void @@ -194,7 +194,7 @@ public function testFromQueryWhenValueBreaksKindThenThrowsFilterValueNotAllowed( $this->expectExceptionMessage('does not match the datetime kind'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $schema); + Criteria::fromQuery(schema: $schema, request: $query); } public function testFromQueryWhenParenthesizedComparisonThenComparisonIsReturned(): void @@ -203,7 +203,7 @@ public function testFromQueryWhenParenthesizedComparisonThenComparisonIsReturned $query = Query::from(parameters: ['filter' => '(status==paid)']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the only comparison is the unwrapped equality */ self::assertEquals( @@ -225,7 +225,7 @@ public function testFromQueryWhenDateTimeValueMatchesKindThenComparisonIsReturne $query = Query::from(parameters: ['filter' => 'created_at=gt=2023-01-15T10:30:00Z']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $schema, request: $query)->comparisons(); /** @Then the only comparison carries the date-time value */ self::assertEquals([Comparison::of( @@ -245,7 +245,7 @@ public function testFromQueryWhenMixedPrecedenceThenThrowsFilterShapeNotSupporte $this->expectExceptionMessage('is not supported'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $this->schema); + Criteria::fromQuery(schema: $this->schema, request: $query); } public function testFromQueryWhenValueNotPermittedThenThrowsFilterValueNotAllowed(): void @@ -265,7 +265,7 @@ public function testFromQueryWhenValueNotPermittedThenThrowsFilterValueNotAllowe $this->expectExceptionMessage('Value is not permitted for filter field .'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $schema); + Criteria::fromQuery(schema: $schema, request: $query); } public function testFromQueryWhenParenthesizedAndGroupThenComparisonsCarryEveryLeaf(): void @@ -274,7 +274,7 @@ public function testFromQueryWhenParenthesizedAndGroupThenComparisonsCarryEveryL $query = Query::from(parameters: ['filter' => '(a==1;b==2)']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the comparisons carry both leaves in order */ self::assertEquals([ @@ -289,7 +289,7 @@ public function testFromQueryWhenSingleComparisonThenComparisonCarriesFieldAndVa $query = Query::from(parameters: ['filter' => 'status==paid']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the only comparison is an equality on the named field with its value */ self::assertEquals( @@ -307,7 +307,7 @@ public function testFromQueryWhenComparisonOperatorThenComparisonCarriesThatOper $query = Query::from(parameters: ['filter' => $expression]); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the only comparison carries the expected operator */ self::assertEquals([Comparison::of(field: 'a', values: ['b'], operator: $expected)], $comparisons); @@ -326,7 +326,7 @@ public function testFromQueryWhenOperatorNotAllowedThenThrowsFilterOperatorNotAl $this->expectExceptionMessage('Operator is not allowed for filter field .'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $schema); + Criteria::fromQuery(schema: $schema, request: $query); } public function testFromQueryWhenFilterNestedToTheMaximumDepthThenComparisonIsReturned(): void @@ -340,7 +340,7 @@ public function testFromQueryWhenFilterNestedToTheMaximumDepthThenComparisonIsRe ]); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the deeply nested parentheses collapse to the single unwrapped comparison */ self::assertEquals( @@ -355,7 +355,7 @@ public function testFromQueryWhenEscapedBackslashInValueThenComparisonKeepsTheBa $query = Query::from(parameters: ['filter' => 'name=="a\\\\b"']); /** @When reading the validated comparisons */ - $comparisons = Criteria::fromQuery(request: $query, schema: $this->schema)->comparisons(); + $comparisons = Criteria::fromQuery(schema: $this->schema, request: $query)->comparisons(); /** @Then the comparison carries the value with the escaped backslash unescaped */ self::assertEquals( @@ -376,7 +376,7 @@ public function testFromQueryWhenMalformedExpressionThenThrowsFilterExpressionIs $this->expectExceptionMessage('could not be parsed'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $this->schema); + Criteria::fromQuery(schema: $this->schema, request: $query); } public function testFromQueryWhenInListHasDisallowedValueThenThrowsFilterValueNotAllowed(): void @@ -395,7 +395,7 @@ public function testFromQueryWhenInListHasDisallowedValueThenThrowsFilterValueNo $this->expectException(FilterValueNotAllowed::class); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $schema); + Criteria::fromQuery(schema: $schema, request: $query); } public function testFromQueryWhenFilterNestedBeyondTheMaximumDepthThenThrowsFilterExpressionIsInvalid(): void @@ -413,7 +413,7 @@ public function testFromQueryWhenFilterNestedBeyondTheMaximumDepthThenThrowsFilt $this->expectExceptionMessage('could not be parsed'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $this->schema); + Criteria::fromQuery(schema: $this->schema, request: $query); } public static function comparisonOperatorCases(): array diff --git a/tests/Unit/LinksTest.php b/tests/Unit/LinksTest.php index 8c943f6..c832fb7 100644 --- a/tests/Unit/LinksTest.php +++ b/tests/Unit/LinksTest.php @@ -46,7 +46,7 @@ public function testToArrayWhenReservedCharacterInValueThenQuotesIt(): void public function testToArrayWhenPageIsTheLastThenOmitsTheNextRelation(): void { /** @Given a criteria pointing at the twenty-fourth page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '24', 'size' => '20']]) ); @@ -173,7 +173,7 @@ public function testToArrayWhenOrGroupNestedInAndThenWrapsItInParentheses(): voi public function testToArrayWhenPageIsTheFirstThenOmitsThePreviousRelation(): void { /** @Given a criteria pointing at the first page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '20']]) ); @@ -204,7 +204,7 @@ public function testToArrayWhenPageIsTheFirstThenOmitsThePreviousRelation(): voi public function testToArrayWhenSliceMiddleThenExposesSelfFirstPrevAndNext(): void { /** @Given a criteria at a middle page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -256,7 +256,7 @@ public function testToArrayWhenOrGroupFilterThenJoinsChildrenWithTheOrToken(): v public function testToArrayWhenSliceMiddleThenCarriesAFirstButNoLastRelation(): void { /** @Given a criteria pointing at a middle page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -318,7 +318,7 @@ public function testConstructorWhenInvokedThroughReflectionThenInstantiatesTheSt public function testToHeaderWhenPageInTheMiddleThenFoldsEveryRelationIntoOneCommaJoinedValue(): void { /** @Given a criteria at a middle page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -347,7 +347,7 @@ public function testToHeaderWhenPageInTheMiddleThenFoldsEveryRelationIntoOneComm public function testToArrayWhenPageInTheMiddleThenExposesEveryRelationPreservingFilterAndSort(): void { /** @Given a criteria at a middle page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); diff --git a/tests/Unit/Offset/CriteriaTest.php b/tests/Unit/Offset/CriteriaTest.php index 1b51aaf..c8c512e 100644 --- a/tests/Unit/Offset/CriteriaTest.php +++ b/tests/Unit/Offset/CriteriaTest.php @@ -24,7 +24,7 @@ public function testSortWhenNoClientSortAndNoDefaultThenIsEmpty(): void $schema = Schema::create(); /** @When reading the effective sort from a query carrying no sort */ - $sort = Criteria::fromQuery(request: Query::from(parameters: []), schema: $schema)->sort(); + $sort = Criteria::fromQuery(schema: $schema, request: Query::from(parameters: []))->sort(); /** @Then the effective sort is empty */ self::assertTrue($sort->isEmpty()); @@ -33,7 +33,7 @@ public function testSortWhenNoClientSortAndNoDefaultThenIsEmpty(): void public function testPageWhenBuiltFromRequestThenReturnsOffsetPage(): void { /** @Given a criteria parsed from a request carrying a page size of three */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema(request: Query::from(parameters: ['page' => ['size' => '3']])); /** @When building an offset page from the total element count and the items */ $page = $criteria->page(items: ['a', 'b', 'c'], total: 30); @@ -51,7 +51,7 @@ public function testFromQueryWhenEmptyThenOffsetAndLimitAreDefaults(): void $query = Query::from(parameters: []); /** @When building the criteria from the query */ - $criteria = Criteria::fromQuery(request: $query); + $criteria = Criteria::fromQueryWithDefaultSchema(request: $query); /** @Then the offset starts at zero */ self::assertSame(0, $criteria->offset()); @@ -63,7 +63,7 @@ public function testFromQueryWhenEmptyThenOffsetAndLimitAreDefaults(): void public function testSliceWhenBuiltFromRequestThenReturnsOffsetSlice(): void { /** @Given a criteria parsed from a request carrying a page size of three */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema(request: Query::from(parameters: ['page' => ['size' => '3']])); /** @When building an offset slice from the items fetched for the page size plus one */ $slice = $criteria->slice(items: ['a', 'b', 'c', 'd']); @@ -81,7 +81,7 @@ public function testFromQueryWhenPerPageAtMaximumThenLimitIsAccepted(): void $query = Query::from(parameters: ['page' => ['size' => '100']]); /** @When building the criteria from the query */ - $criteria = Criteria::fromQuery(request: $query); + $criteria = Criteria::fromQueryWithDefaultSchema(request: $query); /** @Then the limit carries the maximum page size */ self::assertSame(100, $criteria->limit()->toInteger()); @@ -93,7 +93,7 @@ public function testSortWhenClientSortsAllowedFieldsThenReturnsTheClientSort(): $schema = Schema::create()->sortable(fields: ['created_at', 'id']); /** @When reading the effective sort from a client sort over both fields */ - $sort = Criteria::fromQuery(request: Query::from(parameters: ['sort' => '-created_at,id']), schema: $schema) + $sort = Criteria::fromQuery(schema: $schema, request: Query::from(parameters: ['sort' => '-created_at,id'])) ->sort(); /** @Then the effective sort is the client sort */ @@ -106,7 +106,7 @@ public function testSortWhenNoClientSortAndDefaultGivenThenReturnsTheDefault(): $schema = Schema::create()->defaultSort(sort: Sort::fromExpression(expression: '-created_at')); /** @When reading the effective sort from a query carrying no sort */ - $sort = Criteria::fromQuery(request: Query::from(parameters: []), schema: $schema)->sort(); + $sort = Criteria::fromQuery(schema: $schema, request: Query::from(parameters: []))->sort(); /** @Then the effective sort is the schema default */ self::assertEquals(Sort::fromExpression(expression: '-created_at'), $sort); @@ -121,7 +121,7 @@ public function testFromQueryWhenCustomSchemaGivenThenAppliesItsDefaultPageSize( $schema = Schema::create()->defaultPerPage(defaultPerPage: 5); /** @When building the criteria from the query and the schema */ - $criteria = Criteria::fromQuery(request: $query, schema: $schema); + $criteria = Criteria::fromQuery(schema: $schema, request: $query); /** @Then the offset is derived from the requested page and the schema default page size */ self::assertSame(5, $criteria->offset()); @@ -140,7 +140,7 @@ public function testFromQueryWhenPerPageAboveMaximumThenThrowsPageSizeOutOfRange $this->expectExceptionMessage('Page size'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query); + Criteria::fromQueryWithDefaultSchema(request: $query); } public function testSortWhenClientSortsDisallowedFieldThenThrowsSortFieldNotAllowed(): void @@ -156,7 +156,7 @@ public function testSortWhenClientSortsDisallowedFieldThenThrowsSortFieldNotAllo $this->expectExceptionMessage('Sort field is not allowed.'); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $schema); + Criteria::fromQuery(schema: $schema, request: $query); } public function testFromQueryWhenFilterSortAndPageGivenThenEachSpecificationIsValidated(): void @@ -174,7 +174,7 @@ public function testFromQueryWhenFilterSortAndPageGivenThenEachSpecificationIsVa ]); /** @When building the criteria from the query and the schema */ - $criteria = Criteria::fromQuery(request: $query, schema: $schema); + $criteria = Criteria::fromQuery(schema: $schema, request: $query); /** @Then the validated comparisons carry the filtered field and value */ self::assertEquals( @@ -204,6 +204,6 @@ public function testSortWhenServerControlledAndClientSortsThenThrowsSortFieldNot $this->expectException(SortFieldNotAllowed::class); /** @When building the criteria from the query */ - Criteria::fromQuery(request: $query, schema: $schema); + Criteria::fromQuery(schema: $schema, request: $query); } } diff --git a/tests/Unit/Offset/PageTest.php b/tests/Unit/Offset/PageTest.php index e4eb121..7dce5b5 100644 --- a/tests/Unit/Offset/PageTest.php +++ b/tests/Unit/Offset/PageTest.php @@ -18,7 +18,7 @@ final class PageTest extends TestCase public function testNavigationWhenTotalIsZeroThenThereIsNoPage(): void { /** @Given a criteria on the first page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '20']]) ); @@ -51,7 +51,7 @@ public function testNavigationWhenTotalIsZeroThenThereIsNoPage(): void public function testNavigationWhenLastPageGivenThenHasNoNextPage(): void { /** @Given a criteria on the last page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '24', 'size' => '20']]) ); @@ -74,7 +74,7 @@ public function testNavigationWhenLastPageGivenThenHasNoNextPage(): void public function testItemsWhenPageGivenThenCarriesTheProvidedItems(): void { /** @Given a criteria on the third page with a page size of twenty */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -88,7 +88,7 @@ public function testItemsWhenPageGivenThenCarriesTheProvidedItems(): void public function testFromWhenTotalIsNegativeThenThrowsTotalIsNegative(): void { /** @Given a criteria on the first page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '20']]) ); @@ -103,7 +103,7 @@ public function testFromWhenTotalIsNegativeThenThrowsTotalIsNegative(): void public function testNavigationWhenFirstPageGivenThenHasNoPreviousPage(): void { /** @Given a criteria on the first page */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '20']]) ); @@ -132,7 +132,7 @@ public function testNavigationWhenFirstPageGivenThenHasNoPreviousPage(): void public function testTotalWhenPageGivenThenReturnsTheTotalElementCount(): void { /** @Given a criteria on the first page with a page size of twenty */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '20']]) ); @@ -146,7 +146,7 @@ public function testTotalWhenPageGivenThenReturnsTheTotalElementCount(): void public function testTotalPagesWhenTotalIsNotAMultipleOfPerPageThenRoundsUp(): void { /** @Given a criteria on the first page with a page size of twenty */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '20']]) ); @@ -160,7 +160,7 @@ public function testTotalPagesWhenTotalIsNotAMultipleOfPerPageThenRoundsUp(): vo public function testMetadataWhenMiddlePageGivenThenCarriesEveryFlagAndCount(): void { /** @Given a criteria on a middle page with a page size of twenty */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -181,7 +181,7 @@ public function testMetadataWhenMiddlePageGivenThenCarriesEveryFlagAndCount(): v public function testNavigationWhenFirstPageGivenThenOmitsThePreviousRelation(): void { /** @Given a criteria on the first page of a multi-page result */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '20']]) ); @@ -200,7 +200,7 @@ public function testNavigationWhenFirstPageGivenThenOmitsThePreviousRelation(): public function testToResponseWhenMiddlePageGivenThenRendersBodyAndLinkHeader(): void { /** @Given a criteria on the third page with a page size of twenty */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -243,7 +243,7 @@ public function testToResponseWhenMiddlePageGivenThenRendersBodyAndLinkHeader(): public function testNavigationWhenMiddlePageGivenThenExposesEverySurroundingPage(): void { /** @Given a criteria on a middle page with a page size of twenty */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -275,7 +275,7 @@ public function testNavigationWhenMiddlePageGivenThenExposesEverySurroundingPage public function testNavigationWhenMiddlePageGivenThenTheFirstTargetPointsAtTheFirstPage(): void { /** @Given a criteria on a middle page of a multi-page result */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); @@ -295,7 +295,7 @@ public function testNavigationWhenMiddlePageGivenThenTheFirstTargetPointsAtTheFi public function testNavigationWhenMiddlePageGivenThenListsFirstPreviousNextAndLastTargets(): void { /** @Given a criteria on a middle page of a multi-page result */ - $criteria = Criteria::fromQuery( + $criteria = Criteria::fromQueryWithDefaultSchema( request: Query::from(parameters: ['page' => ['number' => '3', 'size' => '20']]) ); diff --git a/tests/Unit/Offset/SliceTest.php b/tests/Unit/Offset/SliceTest.php index 0dca7a2..5d7ddfe 100644 --- a/tests/Unit/Offset/SliceTest.php +++ b/tests/Unit/Offset/SliceTest.php @@ -17,7 +17,9 @@ final class SliceTest extends TestCase public function testToResponseWhenSliceGivenThenRendersBodyAndLinkHeader(): void { /** @Given a criteria on the second page with a page size of three */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']]) + ); /** @And a slice built from items fetched for the page size plus one */ $slice = $criteria->slice(items: ['a', 'b', 'c', 'd']); @@ -54,7 +56,9 @@ public function testToResponseWhenSliceGivenThenRendersBodyAndLinkHeader(): void public function testOffsetWhenSecondPageGivenThenReturnsTheZeroBasedOffset(): void { /** @Given a criteria on the second page with a page size of three */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']]) + ); /** @When building the slice from the items fetched for the page size plus one */ $slice = $criteria->slice(items: ['a', 'b', 'c', 'd']); @@ -66,7 +70,9 @@ public function testOffsetWhenSecondPageGivenThenReturnsTheZeroBasedOffset(): vo public function testNavigationWhenExtraElementFetchedThenHasNextAndTrimsItems(): void { /** @Given a criteria on the second page with a page size of three */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']]) + ); /** @When building the slice from the items fetched for the page size plus one */ $slice = $criteria->slice(items: ['a', 'b', 'c', 'd']); @@ -104,7 +110,9 @@ public function testNavigationWhenExtraElementFetchedThenHasNextAndTrimsItems(): public function testNavigationWhenNoExtraElementOnFirstPageThenNoNextAndNoPrevious(): void { /** @Given a criteria on the first page with a page size of three */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '3']]) + ); /** @When building the slice from items fetched within the page size */ $slice = $criteria->slice(items: ['a', 'b', 'c']); @@ -131,7 +139,9 @@ public function testNavigationWhenNoExtraElementOnFirstPageThenNoNextAndNoPrevio public function testNavigationWhenMiddlePageGivenThenListsFirstPreviousAndNextRelations(): void { /** @Given a criteria on a middle page fetched for the page size plus one */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']]) + ); /** @And a slice on that middle page */ $slice = $criteria->slice(items: ['a', 'b', 'c', 'd']); @@ -148,7 +158,9 @@ public function testNavigationWhenMiddlePageGivenThenListsFirstPreviousAndNextRe public function testNavigationWhenMiddlePageGivenThenTheFirstTargetPointsAtTheFirstPage(): void { /** @Given a criteria on a middle page fetched for the page size plus one */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']]) + ); /** @And a slice on that middle page */ $slice = $criteria->slice(items: ['a', 'b', 'c', 'd']); @@ -166,7 +178,9 @@ public function testNavigationWhenMiddlePageGivenThenTheFirstTargetPointsAtTheFi public function testNavigationWhenMiddlePageGivenThenTheNextTargetPointsAtTheFollowingPage(): void { /** @Given a criteria on a middle page fetched for the page size plus one */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '2', 'size' => '3']]) + ); /** @And a slice on that middle page */ $slice = $criteria->slice(items: ['a', 'b', 'c', 'd']); @@ -184,7 +198,9 @@ public function testNavigationWhenMiddlePageGivenThenTheNextTargetPointsAtTheFol public function testNavigationWhenFirstPageWithoutExtraElementThenListsOnlyTheFirstRelation(): void { /** @Given a criteria on the first page fetched within the page size */ - $criteria = Criteria::fromQuery(request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '3']])); + $criteria = Criteria::fromQueryWithDefaultSchema( + request: Query::from(parameters: ['page' => ['number' => '1', 'size' => '3']]) + ); /** @And a slice on that first page */ $slice = $criteria->slice(items: ['a', 'b', 'c']);