Skip to content

Reject an unusable data frame range where the request arrives - #858

Open
CleanCut wants to merge 7 commits into
mainfrom
cleancut/data-frame-param-validation
Open

Reject an unusable data frame range where the request arrives#858
CleanCut wants to merge 7 commits into
mainfrom
cleancut/data-frame-param-validation

Conversation

@CleanCut

Copy link
Copy Markdown
Contributor

Fix some panics we're seeing in production.

A bad slice, take, item, or add-col value panicked the worker thread rather than returning an error, so a caller could fault a request worker with nothing more than a query string. Two were reachable straight from the slice and take query parameters, and a third from an ordinary page parameter.

Slice ranges are now parsed into a type that cannot hold an unusable range, at the two places untrusted input arrives. Callers that build a range from page arithmetic construct that type directly instead of formatting integers into a string for something deeper to parse again. A page or page_size of zero reads as the first page, matching what the workspace data frame endpoint already did.

A slice that was not shaped like a range, such as "5" or "1..2..3", was previously ignored and the whole frame returned. It is now rejected like any other malformed value. A negative start is rejected too: polars reads it as counting back from the end of the frame, which is not the range the caller named.

The view layer turned these errors straight back into panics by unwrapping the data frame transform, so the diff endpoints kept crashing on a malformed take even once the parse returned cleanly. Those constructors now report the failure to their caller, which answers with a 400.

ENG-1329
Sentry: OXEN-SERVER-28, OXEN-SERVER-29

A bad slice, take, item, or add-col value panicked the worker thread rather than
returning an error, so a caller could fault a request worker with nothing more
than a query string. Two were reachable straight from the slice and take query
parameters, and a third from an ordinary page parameter: page=0 and page_size=0
both produce the range 0..0. That panic reached production.

Slice ranges are now parsed into a type that cannot hold an unusable range, at
the two places untrusted input arrives — the HTTP query parser and the CLI
argument parser — so the read path below them goes back to being infallible.
Callers that build a range from page arithmetic construct that type directly
instead of formatting integers into a string for something deeper to parse
again. A page or page_size of zero reads as the first page, matching what the
workspace data frame endpoint already did.

A slice that was not shaped like a range, such as "5" or "1..2..3", was
previously ignored and the whole frame returned. It is now rejected like any
other malformed value. A negative start is rejected too: polars reads it as
counting back from the end of the frame, which is not the range the caller
named.

The view layer turned these errors straight back into panics by unwrapping the
data frame transform, so the diff endpoints kept crashing on a malformed take
even once the parse returned cleanly. Those constructors now report the failure
to their caller, which answers with a 400.

The message on the range check said "Start must be greater than end" while
firing on start >= end. It now states the rule it enforces.

ENG-1329
Sentry: OXEN-SERVER-28, OXEN-SERVER-29
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added validated row-range slicing with support for empty, malformed, reversed, and overflowing ranges.
    • Improved pagination, including safer page-size handling and reliable metadata for empty data-frame results.
  • Bug Fixes
    • Invalid data-frame query parameters now return actionable client errors instead of server failures.
    • Data-frame views, transformations, downloads, and diffs now surface processing errors reliably.
    • Improved error handling for CLI, server, and Python data-frame slicing operations.

Walkthrough

The PR replaces string-based dataframe slices with validated SliceRange values. It converts dataframe and diff view construction to fallible APIs, propagates errors through clients and HTTP handlers, and returns structured 400 Bad Request responses for invalid dataframe parameters.

Changes

Dataframe validation and error propagation

Layer / File(s) Summary
Validated range and parameter contracts
crates/liboxen/src/opts/df_opts.rs, crates/liboxen/src/error.rs, crates/liboxen/src/opts.rs
SliceRange validates ranges. DFOpts uses typed slices and structured errors. Serialization preserves the wire-format string.
Fallible dataframe and diff views
crates/liboxen/src/core/df/tabular.rs, crates/liboxen/src/view/*.rs, crates/liboxen/src/model/diff/diff_entry.rs
Dataframe transformations, JSON views, and tabular diffs return Result values and propagate errors.
HTTP parsing and handler propagation
crates/oxen-server/src/params/df_opts_query.rs, crates/oxen-server/src/controllers/**/*.rs, crates/oxen-server/src/errors.rs
HTTP parsing validates slice and take parameters. Controllers propagate parsing and view-construction errors. Invalid parameters return structured 400 Bad Request responses.
CLI, Python, and test adoption
crates/oxen-cli/src/cmd/df.rs, crates/oxen-py/src/py_remote_data_frame.rs, crates/liboxen/src/api/client/data_frames.rs, crates/liboxen/src/core/df/tabular.rs
CLI, Python APIs, and tests construct validated SliceRange values and propagate construction errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DataFrameController
  participant QueryParser
  participant JsonDataFrameView
  participant TabularTransform
  Client->>DataFrameController: request with slice or take parameters
  DataFrameController->>QueryParser: parse_opts(query)
  QueryParser-->>DataFrameController: DFOpts or OxenError
  DataFrameController->>JsonDataFrameView: build view from DFOpts
  JsonDataFrameView->>TabularTransform: transform and slice dataframe
  TabularTransform-->>JsonDataFrameView: transformed dataframe or error
  JsonDataFrameView-->>DataFrameController: view or error
  DataFrameController-->>Client: response or 400 Bad Request
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies rejection of unusable data-frame ranges, a central change in the pull request.
Description check ✅ Passed The description directly explains input validation, panic prevention, error propagation, and HTTP 400 responses implemented by the changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cleancut/data-frame-param-validation

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
crates/oxen-server/src/controllers/data_frames.rs (1)

61-77: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Normalize pagination values before use.

At Line 77, page=0 or page_size=0 creates 0..0. SliceRange::new rejects that range, so these requests return 400 instead of using the first page. Large usize values can also overflow the multiplication.

Normalize both values with max(1) before pagination arithmetic. Use the bounded arithmetic already used by the workspace read path. Store the normalized values in response metadata and download options.

  • crates/oxen-server/src/controllers/data_frames.rs#L61-L77: normalize page and page_size before range construction and update page_opts with normalized values.
  • crates/oxen-server/src/controllers/diff.rs#L406-L411: normalize values before constructing the file-diff slice.
  • crates/oxen-server/src/controllers/diff.rs#L835-L835: normalize values before constructing the derived-dataframe pagination slice.
  • crates/oxen-server/src/controllers/workspaces/data_frames.rs#L470-L472: store normalized values in DFOpts for both download handlers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/oxen-server/src/controllers/data_frames.rs` around lines 61 - 77,
Normalize page and page_size with max(1) before pagination arithmetic, using the
workspace read path’s bounded arithmetic to prevent overflow. In
crates/oxen-server/src/controllers/data_frames.rs:61-77, update page_opts and
the constructed slice; in crates/oxen-server/src/controllers/diff.rs:406-411 and
:835-835, normalize values before each file-diff or derived-dataframe slice; and
in crates/oxen-server/src/controllers/workspaces/data_frames.rs:470-472, store
normalized values in DFOpts for both download handlers.
crates/oxen-py/src/py_remote_data_frame.rs (1)

34-40: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Pass opts to the API request.

opts.slice is created at Line 34, but api::client::data_frames::get receives DFOpts::empty() at Line 40. The validated 0..1 slice is discarded. The server therefore applies its default page instead of the requested one. Pass opts, or remove the assignment if size() does not need a one-row view.

Proposed fix
             let response = api::client::data_frames::get(
                 self.repo.repo()?,
                 revision,
                 &self.path,
-                DFOpts::empty(),
+                opts,
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/oxen-py/src/py_remote_data_frame.rs` around lines 34 - 40, Pass the
configured opts, including the 0..1 slice assigned in the surrounding method, to
api::client::data_frames::get instead of DFOpts::empty().
crates/liboxen/src/view/json_data_frame_view.rs (1)

219-222: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Clamp pagination metadata here as well.

from_df_opts clamps page_size and page to a minimum of one at Lines 143-147. from_df_opts_unpaginated does not. With page_size == 0 the response reports page_size: 0 and total_pages: 0, because og_height as f64 / 0.0 is infinite and the cast to usize saturates. Use the same clamp so both constructors report the same pagination domain.

♻️ Proposed clamp
-        let page_size = opts.page_size.unwrap_or(constants::DEFAULT_PAGE_SIZE);
-        let page_number = opts.page.unwrap_or(constants::DEFAULT_PAGE_NUM);
+        // Match `from_df_opts`: pages are 1-based and a page of zero rows names no rows.
+        let page_size = opts
+            .page_size
+            .unwrap_or(constants::DEFAULT_PAGE_SIZE)
+            .max(1);
+        let page_number = opts.page.unwrap_or(constants::DEFAULT_PAGE_NUM).max(1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/liboxen/src/view/json_data_frame_view.rs` around lines 219 - 222,
Clamp the page_size and page values in from_df_opts_unpaginated to a minimum of
one before computing total_pages, matching the existing behavior in
from_df_opts. Ensure the returned pagination metadata never reports zero page
size or page number, and calculate total_pages using the clamped page_size.
crates/liboxen/src/view/tabular_diff_view.rs (1)

52-56: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Follow up on the remaining panics in this now-fallible function.

The signature change makes from_data_frames able to report failures. The diff computations inside it still panic: compute_new_columns_from_dfs, compute_new_rows_proj, compute_new_rows, and the spawn_blocking closure that collects the common-column frames all use .unwrap(). A panic there still faults the request worker, which is the failure mode this PR removes elsewhere. Convert those to ? in a follow-up so the whole path reports instead of panicking.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/liboxen/src/view/tabular_diff_view.rs` around lines 52 - 56, Update
TabularDiffView::from_data_frames to remove all remaining unwrap-based panics:
propagate errors with ? from compute_new_columns_from_dfs,
compute_new_rows_proj, and compute_new_rows, and make the spawn_blocking closure
collecting common-column frames return its collection error so the outer
function can propagate it. Preserve the existing successful computation flow
while ensuring each failure returns OxenError.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/liboxen/src/core/df/tabular.rs`:
- Around line 641-651: Update paginate_df to clamp page_num and page_size to
valid minimums before calculating offsets, treating page_num == 0 as the first
page. Replace direct multiplications and subtraction with saturating arithmetic
so large caller-supplied values cannot overflow, while preserving the existing
slice and collection behavior.

In `@crates/liboxen/src/opts/df_opts.rs`:
- Around line 60-65: Update SliceRange::row_count to saturate the end-start
difference at u32::MAX instead of directly casting, so ranges wider than u32
remain bounded rather than wrapping to zero. Correct the accompanying comment to
describe only the non-negative difference guarantee, preserving existing
behavior for ranges within the u32 limit.

In `@crates/liboxen/src/view/json_data_frame_view.rs`:
- Around line 149-164: Extract the shared page-to-slice arithmetic into a helper
on DFOpts or beside SliceRange, then use it at
crates/liboxen/src/view/json_data_frame_view.rs:149-164 to clamp page to at
least 1, compute start with saturating_mul capped at usize::MAX - page_size, and
set end to start + page_size. Update
crates/liboxen/src/core/df/tabular.rs:641-651 to clamp page_opts.page_num and
page_opts.page_size before subtraction and use saturating multiplication. At
crates/liboxen/src/view/json_data_frame_view.rs:219-222, clamp page_size and
page to at least 1 so reported pagination matches from_df_opts.
- Around line 149-164: Update the pagination calculations in the JSON data-frame
view to cap the page offset using saturating multiplication and limit it to
usize::MAX minus page_size, matching the existing controller logic. Ensure start
remains below end for very large page values and prevent overflow in the
page_size * page computation while preserving normal pagination behavior.

In `@crates/oxen-py/src/py_remote_data_frame.rs`:
- Line 58: Update the row slicing logic around SliceRange::new to convert row
with i64::try_from and compute the exclusive end using checked_add(1),
propagating conversion or overflow failures as OxenError before constructing the
range.

---

Outside diff comments:
In `@crates/liboxen/src/view/json_data_frame_view.rs`:
- Around line 219-222: Clamp the page_size and page values in
from_df_opts_unpaginated to a minimum of one before computing total_pages,
matching the existing behavior in from_df_opts. Ensure the returned pagination
metadata never reports zero page size or page number, and calculate total_pages
using the clamped page_size.

In `@crates/liboxen/src/view/tabular_diff_view.rs`:
- Around line 52-56: Update TabularDiffView::from_data_frames to remove all
remaining unwrap-based panics: propagate errors with ? from
compute_new_columns_from_dfs, compute_new_rows_proj, and compute_new_rows, and
make the spawn_blocking closure collecting common-column frames return its
collection error so the outer function can propagate it. Preserve the existing
successful computation flow while ensuring each failure returns OxenError.

In `@crates/oxen-py/src/py_remote_data_frame.rs`:
- Around line 34-40: Pass the configured opts, including the 0..1 slice assigned
in the surrounding method, to api::client::data_frames::get instead of
DFOpts::empty().

In `@crates/oxen-server/src/controllers/data_frames.rs`:
- Around line 61-77: Normalize page and page_size with max(1) before pagination
arithmetic, using the workspace read path’s bounded arithmetic to prevent
overflow. In crates/oxen-server/src/controllers/data_frames.rs:61-77, update
page_opts and the constructed slice; in
crates/oxen-server/src/controllers/diff.rs:406-411 and :835-835, normalize
values before each file-diff or derived-dataframe slice; and in
crates/oxen-server/src/controllers/workspaces/data_frames.rs:470-472, store
normalized values in DFOpts for both download handlers.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ae093cb-461c-4427-bd75-435b47815ce4

📥 Commits

Reviewing files that changed from the base of the PR and between ebab2cc and 617dae3.

📒 Files selected for processing (19)
  • crates/liboxen/src/api/client/data_frames.rs
  • crates/liboxen/src/core/df/tabular.rs
  • crates/liboxen/src/error.rs
  • crates/liboxen/src/model/diff/diff_entry.rs
  • crates/liboxen/src/opts.rs
  • crates/liboxen/src/opts/df_opts.rs
  • crates/liboxen/src/view/json_data_frame.rs
  • crates/liboxen/src/view/json_data_frame_view.rs
  • crates/liboxen/src/view/tabular_diff_view.rs
  • crates/oxen-cli/src/cmd/df.rs
  • crates/oxen-py/src/py_remote_data_frame.rs
  • crates/oxen-server/src/controllers/data_frames.rs
  • crates/oxen-server/src/controllers/diff.rs
  • crates/oxen-server/src/controllers/workspaces/data_frames.rs
  • crates/oxen-server/src/controllers/workspaces/data_frames/columns.rs
  • crates/oxen-server/src/controllers/workspaces/data_frames/embeddings.rs
  • crates/oxen-server/src/controllers/workspaces/data_frames/rows.rs
  • crates/oxen-server/src/errors.rs
  • crates/oxen-server/src/params/df_opts_query.rs

Comment thread crates/liboxen/src/core/df/tabular.rs Outdated
Comment thread crates/liboxen/src/opts/df_opts.rs
Comment thread crates/liboxen/src/view/json_data_frame_view.rs Outdated
Comment thread crates/oxen-py/src/py_remote_data_frame.rs Outdated
paginate_df underflowed on a page number of zero and multiplied page size by
page number unchecked. Neither function has a call site, so deleting them
removes the defect and the surface it sits on rather than hardening arithmetic
nothing can reach.

ENG-1329

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/liboxen/src/core/df/tabular.rs (1)

631-639: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject slice ranges that exceed u32::MAX rows.

SliceRange::new accepts ranges such as 0..4294967296. SliceRange::row_count() then casts that length to u32, which truncates the value before this slicing path uses it. The request can return an incorrect slice instead of HTTP 400.

Add a maximum-length check in SliceRange::new. Add a regression test for a range longer than u32::MAX.

Proposed fix
 pub fn new(start: i64, end: i64) -> Result<Self, OxenError> {
+    if end - start > u32::MAX as i64 {
+        return invalid("range must not exceed 4294967295 rows");
+    }
     // existing validation
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/liboxen/src/core/df/tabular.rs` around lines 631 - 639, Update
SliceRange::new to reject ranges whose length exceeds u32::MAX, before
row_count() can truncate the value; preserve existing validation for start/end
ordering and valid ranges. Add a regression test constructing a range longer
than u32::MAX and assert that construction is rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/liboxen/src/core/df/tabular.rs`:
- Around line 631-639: Update SliceRange::new to reject ranges whose length
exceeds u32::MAX, before row_count() can truncate the value; preserve existing
validation for start/end ordering and valid ranges. Add a regression test
constructing a range longer than u32::MAX and assert that construction is
rejected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e4706d5e-5763-4da6-8483-1b5bd0bafcb4

📥 Commits

Reviewing files that changed from the base of the PR and between 617dae3 and 6bd2297.

📒 Files selected for processing (1)
  • crates/liboxen/src/core/df/tabular.rs

The page-to-slice arithmetic was written out at five call sites that disagreed
about a degenerate request, and the workspace data frame endpoint capped its
start against usize::MAX before casting to i64, so a large enough page number
produced a negative start and answered a server-side arithmetic problem with a
client error. Pages now convert to a row range through one helper that clamps
page and page size to at least one and cannot overflow, so any page number names
a range the reader can serve.

The unpaginated view reported the page and size it was handed without clamping,
so a page size of zero divided by zero and reported a total page count of
usize::MAX.

ENG-1329
coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/liboxen/src/opts/df_opts.rs (1)

396-402: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject extra item components.

column_at accepts column:1:extra as column:1 because it reads only the first two components. This does not meet the documented col:index format and prevents malformed requests from returning HTTP 400.

Reject the value when split.next().is_some() after reading index.

Proposed fix
 let col = split.next().ok_or_else(malformed)?;
 let index = split.next().ok_or_else(malformed)?;
+if split.next().is_some() {
+    return Err(malformed());
+}
 return Ok(Some(IndexedItem {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/liboxen/src/opts/df_opts.rs` around lines 396 - 402, Update the
parsing logic that constructs IndexedItem to reject values containing more than
two delimiter-separated components: after reading col and index, check
split.next() and return malformed when it is present. Preserve the existing
parsing and error behavior for valid col:index values and invalid indices.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/liboxen/src/opts/df_opts.rs`:
- Around line 296-298: The row-selection path in slice_indices must preserve
invalid row errors instead of converting them to None. Change slice_indices to
return Result<Option<SliceRange>, OxenError>, propagate SliceRange::for_row(row)
with ?, update callers through transform_slice_lazy and slice to handle the
result, and revise the test that currently expects None for an invalid row.

---

Outside diff comments:
In `@crates/liboxen/src/opts/df_opts.rs`:
- Around line 396-402: Update the parsing logic that constructs IndexedItem to
reject values containing more than two delimiter-separated components: after
reading col and index, check split.next() and return malformed when it is
present. Preserve the existing parsing and error behavior for valid col:index
values and invalid indices.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0684620b-e2b9-47d5-ae4e-adfec052ff09

📥 Commits

Reviewing files that changed from the base of the PR and between ebba03a and 3c97c42.

📒 Files selected for processing (2)
  • crates/liboxen/src/opts/df_opts.rs
  • crates/oxen-py/src/py_remote_data_frame.rs

Comment thread crates/liboxen/src/opts/df_opts.rs
An --item value with no colon was ignored and the whole data frame returned
instead of the single value asked for, and one with extra colons silently
dropped everything past the second segment, so "file:1:2" read as row 1 of
"file". Both now report what is wrong with the value, as does an item with an
empty column name, which could never match a column.
An empty data frame answered with a page number and page size of zero while a
non-empty one echoed what the caller asked for, so a reader could not compute
its position from an empty response.
A row index too large to convert into a row range read as no selection at all,
so a request for a single row answered with the entire data frame. It now
reports what is wrong with the index.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/oxen-server/src/controllers/data_frames.rs (1)

69-77: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Construct pagination with validated page bounds.

Lines 69-77 bypass DFOpts::page_bounds and SliceRange::for_page. When page=0, end becomes zero, so SliceRange::new(0, 0) returns an error instead of reading the first page. Large page or page_size values can also overflow during multiplication before range validation.

Use opts.page_bounds() to set page_opts, then use SliceRange::for_page(page, page_size). Add regression coverage for zero values and maximum accepted query values.

Proposed fix
-        let page = query.page.unwrap_or(constants::DEFAULT_PAGE_NUM);
-        let page_size = query.page_size.unwrap_or(constants::DEFAULT_PAGE_SIZE);
+        let (page, page_size) = opts.page_bounds();

         page_opts.page_num = page;
         page_opts.page_size = page_size;

-        let start = if page == 0 { 0 } else { page_size * (page - 1) };
-        let end = page_size * page;
-        opts.slice = Some(SliceRange::new(start as i64, end as i64)?);
+        opts.slice = Some(SliceRange::for_page(page, page_size));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/oxen-server/src/controllers/data_frames.rs` around lines 69 - 77,
Replace the manual page and slice calculations in the controller with validated
pagination: use opts.page_bounds() to populate page_opts and construct
opts.slice via SliceRange::for_page(page, page_size). Preserve the existing
query defaults while ensuring zero values and maximum accepted page/page_size
values are handled without overflow, and add regression coverage for those
boundaries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/oxen-server/src/controllers/data_frames.rs`:
- Around line 69-77: Replace the manual page and slice calculations in the
controller with validated pagination: use opts.page_bounds() to populate
page_opts and construct opts.slice via SliceRange::for_page(page, page_size).
Preserve the existing query defaults while ensuring zero values and maximum
accepted page/page_size values are handled without overflow, and add regression
coverage for those boundaries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 16108a32-d163-4a9d-a9c1-4b8c21b55428

📥 Commits

Reviewing files that changed from the base of the PR and between 76c36dc and 9dd6469.

📒 Files selected for processing (4)
  • crates/liboxen/src/core/df/tabular.rs
  • crates/liboxen/src/opts/df_opts.rs
  • crates/oxen-server/src/controllers/data_frames.rs
  • crates/oxen-server/src/controllers/workspaces/data_frames.rs

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