Skip to content

Refuse page numbers and values that overflow or break their kind. - #12

Merged
gustavofreze merged 3 commits into
mainfrom
fix/harden-query-value-kinds
Oct 14, 2026
Merged

gustavofreze merged 3 commits into
mainfrom
fix/harden-query-value-kinds

Conversation

@gustavofreze

Copy link
Copy Markdown
Member

Summary

Four inputs reached a consumer as a 500 where the contract is a 422, because the library either threw an error it does
not translate or accepted a value the store then refused. All four were reproduced in the services that consume the
library, and each service worked around them at its own edge.

  • page[number] of 9223372036854775807 made Offset::fromPage compute (page - 1) * limit as a float, and the
    int constructor then raised a TypeError. A page above the largest one whose offset still fits an int now raises
    PageNumberOutOfRange, and an offset of PHP_INT_MAX raises OffsetOutOfRange, both through new aboveMaximum
    factories, so the consumers that already translate those exceptions answer 422. The ceiling depends on the page size:
    intdiv(PHP_INT_MAX - 1, size) + 1, so no page is refused for an arbitrary reason and every navigation link stays
    representable. PageCount computed the page count through a float that loses precision above 2^53 and now uses
    integer arithmetic.
  • The UUID pattern ended in $ without the D modifier, so <uuid>\n matched, in a filter and in the id of a
    cursor, and reached UUID_TO_BIN as an invalid value. The INTEGER pattern had the same flaw and accepted a value
    that does not fit an int. INTEGER now compares the digits as text against PHP_INT_MAX and PHP_INT_MIN
    without converting the value, and leading zeros and -0 stay valid.
  • DATETIME checked only the shape of the value. Iso8601::isValid now also refuses a calendar that does not exist
    (2026-02-30, month 13, year 0000), a clock outside 00:00:00 to 23:59:59, an offset outside ±23:59, a
    fraction of more than nine digits, and an instant that falls outside the years 0001 to 9999 in UTC, such as
    9999-12-31T22:00:00-05:00. The clock ranges live in the pattern, checkdate covers the calendar, and the range is
    checked once against two bounds with DateTimeImmutable. A date alone, and a date-time without an offset, read as
    UTC, stay valid.

Public API: backward compatible. PageNumberOutOfRange::aboveMaximum and OffsetOutOfRange::aboveMaximum are the
additions, and values that were never valid are now refused, so the release is 2.6.0.

Consumers can drop their own guards after composer update: the D modifier in their regexes, the validation of the
cursor id, and the check for a date-time outside the years 0001 to 9999. They keep a cap on page[number] when any
layer handles the offset as a 32-bit integer, since the ceiling of the library is the 64-bit one.

Related issue

None.

Checklist

  • Tests added or updated. Pagination at the ceiling and beyond it, with sizes from 1 to PHP_INT_MAX; the
    integers 9223372036854775807, 9223372036854775808, -9223372036854775808 and -9223372036854775809; a UUID
    followed by a line break; impossible dates, clock values, offsets and fractions; and both edges of the range.
  • Documentation updated when applicable. README: the table of kinds, what a date-time accepts, and the ceiling of
    the page number.
  • make review passes.
  • make tests passes. 399 tests, lines 100% (976/976), MSI 100% (767 of 767 killed).

@gustavofreze
gustavofreze merged commit 6cbb5db into main Oct 14, 2026
5 checks passed
@gustavofreze
gustavofreze deleted the fix/harden-query-value-kinds branch October 14, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant