Skip to content
Merged
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
9 changes: 6 additions & 3 deletions src/devhelm/_pagination.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,7 @@ class Page(Generic[T]):
has_prev: bool = False
total_elements: int | None = None
total_pages: int | None = None
next_cursor: str | None = None


@dataclass
Expand All@@ -40,17 +41,18 @@ class _PageEnvelope(BaseModel):
Items are validated separately via ``parse_list(model_class, ...)`` so the
envelope only describes the surrounding pagination shape; that keeps this
layer P5-clean (no casts) without forcing every model to be expressed
twice. ``extra="forbid"`` so unknown envelope keys surface as a typed
``DevhelmValidationError`` (P1) rather than silently disappearing.
twice. ``extra="ignore"`` so additive response fields (``nextCursor``,
future envelope keys) cannot break ``list()`` — Postel's Law.
"""

model_config = ConfigDict(extra="forbid")
model_config = ConfigDict(extra="ignore")

data: list[Any] = [] # validated separately
hasNext: bool = False
hasPrev: bool = False
totalElements: int | None = None
totalPages: int | None = None
nextCursor: str | None = None


class _CursorPageEnvelope(BaseModel):
Expand DownExpand Up@@ -142,6 +144,7 @@ def fetch_page(
has_prev=envelope.hasPrev,
total_elements=envelope.totalElements,
total_pages=envelope.totalPages,
next_cursor=envelope.nextCursor,
)


Expand Down
34 changes: 34 additions & 0 deletions tests/test_pagination.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
"""Offset-page envelope must tolerate additive TableValueResult fields."""

from __future__ import annotations

from devhelm._pagination import _validate_page


def test_page_envelope_accepts_next_cursor() -> None:
envelope = _validate_page(
{
"data": [],
"hasNext": False,
"hasPrev": False,
"totalElements": 0,
"totalPages": 0,
"nextCursor": None,
}
)
assert envelope.nextCursor is None
assert envelope.data == []


def test_page_envelope_ignores_unknown_keys() -> None:
envelope = _validate_page(
{"data": [], "hasNext": False, "hasPrev": False, "futureField": True}
)
assert envelope.hasNext is False


def test_page_envelope_leaves_items_unparsed() -> None:
envelope = _validate_page(
{"data": [{"id": "not-validated-here"}], "hasNext": False, "hasPrev": False}
)
assert envelope.data[0]["id"] == "not-validated-here"
Loading