Skip to content

feat: configurable max page size for ReadChanges#2887

Merged
jpadilla merged 3 commits intomainfrom
read-changes-max-page-size
Jan 22, 2026
Merged

feat: configurable max page size for ReadChanges#2887
jpadilla merged 3 commits intomainfrom
read-changes-max-page-size

Conversation

@jpadilla
Copy link
Copy Markdown
Member

@jpadilla jpadilla commented Jan 21, 2026

Description

What problem is being solved?

The ReadChanges API had a hardcoded max page size of 100 defined at the proto level, making it impossible to customize this limit for different deployment environments. Different deployments may need different max page sizes based on their infrastructure capacity, performance requirements, or specific use cases.

https://github.com/openfga/api/blob/main/openfga/v1/openfga_service.proto#L1682-L1709

How is it being solved?

By moving the max page size validation from the proto definition to server-side validation with configurable limits. The proto-level max validation has been removed (in the openfga/api repository), and server-side validation has been implemented with configuration options via CLI flags and environment variables. The default max remains 100 to maintain backward compatibility with existing deployments.

What changes are made to solve it?

Configuration (pkg/server/config/config.go)

  • Added DefaultReadChangesMaxPageSize = 100 constant
  • Added ReadChangesMaxPageSize field to Config struct
  • Initialized the field in DefaultConfig() with the default value

Server (pkg/server/server.go)

  • Added readChangesMaxPageSize field to Server struct
  • Added WithReadChangesMaxPageSize() option function to configure the limit
  • Initialized the field in NewServerWithOpts() with the default value
  • Added validation to ensure readChangesMaxPageSize > 0 during server initialization

Validation (pkg/server/read_changes.go)

  • Added server-side validation that checks if page_size exceeds the configured max
  • Error message format: "invalid ReadChangesRequest.PageSize: value must be inside range [1, %d]"
  • Validation occurs after proto validation and before authz check for optimal performance
  • Error is automatically mapped to page_size_invalid error code by existing error mapping logic

CLI Integration (cmd/run/run.go)

  • Added --readChanges-max-page-size CLI flag with default value of 100
  • Wired the configuration option to server initialization

Environment Variables (cmd/run/flags.go)

  • Added support for OPENFGA_READ_CHANGES_MAX_PAGE_SIZE environment variable
  • Added support for OPENFGA_READCHANGESMAXPAGESIZE environment variable (alternate format)

Backward Compatibility

  • Default max remains 100 (same as previous proto limit)
  • Existing clients respecting [1, 100] range continue to work without changes
  • No breaking changes for standard usage

References

openfga/api#241

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Server now enforces a maximum page size for ReadChanges requests (default: 100).
    • Max page size is configurable via the --readChanges-max-page-size CLI flag or OPENFGA_READ_CHANGES_MAX_PAGE_SIZE env var.
  • Bug Fixes

    • Requests with page sizes outside the allowed range now return a clear validation error describing the valid range.
  • Tests

    • Added comprehensive tests covering default, boundary, custom, zero, and out-of-range page-size scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 21, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a configurable server-side maximum page size for ReadChanges (default 100); exposed in config and JSON schema, wired to CLI flag/env var, surfaced as a server option, and validated in the ReadChanges handler to reject out-of-range PageSize values early.

Changes

Cohort / File(s) Summary
Configuration
pkg/server/config/config.go, .config-schema.json
Add ReadChangesMaxPageSize uint32 and DefaultReadChangesMaxPageSize = 100; expose readChangesMaxPageSize in JSON schema with env var OPENFGA_READ_CHANGES_MAX_PAGE_SIZE.
Server core
pkg/server/server.go, pkg/server/read_changes.go
Add readChangesMaxPageSize field and WithReadChangesMaxPageSize(max uint32) option; initialize default and enforce PageSize validation in ReadChanges handler (return InvalidArgument for values outside [1, max]).
CLI / Runtime
cmd/run/flags.go, cmd/run/run.go
Add CLI flag --readChanges-max-page-size with env bindings and pass value into server via WithReadChangesMaxPageSize().
Tests
pkg/server/read_changes_test.go
New unit tests covering default/custom max limits, boundary conditions, exceeding limits, zero/negative PageSize behavior, and cleanup.
Changelog / Dependencies
CHANGELOG.md, go.mod
Add changelog entry; update github.com/openfga/api/proto pseudo-version in go.mod.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as CLI/Flags
    participant Config as Config
    participant Server as gRPC Server
    participant Handler as ReadChanges Handler
    participant Datastore as Datastore

    User->>CLI: provide --readChanges-max-page-size
    CLI->>Config: set ReadChangesMaxPageSize
    Config->>Server: initialize with WithReadChangesMaxPageSize()
    Server->>Server: store readChangesMaxPageSize

    User->>Handler: ReadChangesRequest(PageSize=N)
    Handler->>Handler: if PageSize set, validate 1 <= N <= readChangesMaxPageSize
    alt PageSize > max
        Handler-->>User: return InvalidArgument error
    else
        Handler->>Datastore: query changes (PageSize=N)
        Datastore-->>Handler: return changes
        Handler-->>User: return ReadChangesResponse
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • justincoh
  • senojj
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: configurable max page size for ReadChanges' clearly and accurately summarizes the main change—adding configurability for the ReadChanges API's maximum page size.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@jpadilla jpadilla force-pushed the read-changes-max-page-size branch from 8e5a268 to 94726b3 Compare January 21, 2026 21:39
@socket-security
Copy link
Copy Markdown

socket-security bot commented Jan 21, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedgithub.com/​openfga/​api/​proto@​v0.0.0-20250909172242-b4b2a12f5c67 ⏵ v0.0.0-20260122164422-25e22cb1875b98 +1100100100100

View full report

@jpadilla jpadilla force-pushed the read-changes-max-page-size branch from 94726b3 to 33fa8e7 Compare January 21, 2026 21:42
@jpadilla jpadilla marked this pull request as ready for review January 21, 2026 21:42
@jpadilla jpadilla requested a review from a team as a code owner January 21, 2026 21:42
Copilot AI review requested due to automatic review settings January 21, 2026 21:42
@dosubot
Copy link
Copy Markdown

dosubot bot commented Jan 21, 2026

Related Documentation

Checked 5 published document(s) in 0 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a configurable server-side maximum for ReadChanges page size, exposing it via config/CLI/env and enforcing it at request time.

Changes:

  • Introduces ReadChangesMaxPageSize configuration with default 100 and wires it into server construction.
  • Adds server-side validation in ReadChanges to reject requests exceeding the configured max.
  • Adds CLI/env flag wiring and new unit tests covering default/custom max behavior.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/server/server.go Adds server field + option WithReadChangesMaxPageSize and sets default.
pkg/server/read_changes.go Enforces configured max page size during ReadChanges request handling.
pkg/server/read_changes_test.go Adds tests for default and custom max page size behavior.
pkg/server/config/config.go Adds config field + default for ReadChangesMaxPageSize.
cmd/run/run.go Adds --readChanges-max-page-size flag and applies it to server options.
cmd/run/flags.go Binds flag/env vars to config key readChangesMaxPageSize.
go.mod / go.sum Bumps github.com/openfga/api/proto dependency to pick up validation changes.
CHANGELOG.md Documents the new configurable server-side validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@pkg/server/config/config.go`:
- Around line 339-342: ReadChangesMaxPageSize can be zero which will effectively
block all ReadChanges calls; update VerifyServerSettings (or equivalent config
validation path) to detect when ReadChangesMaxPageSize == 0 and either return a
validation error rejecting the config or normalize the value (e.g., treat 0 as
"unlimited" by setting it to math.MaxUint32 or another sentinel) so runtime
requests are not accidentally locked out; reference ReadChangesMaxPageSize and
VerifyServerSettings when making the change so the check lives in the server
configuration verification logic.

In `@pkg/server/read_changes_test.go`:
- Around line 17-63: The test reuses a single memory datastore (ds :=
memory.New()) across subtests but each subtest calls openfga.Close(), which
closes that shared datastore and leads to flakes; fix by creating and using a
fresh datastore and server per subtest (e.g., call memory.New() and pass it to
MustNewServerWithOpts / WithDatastore inside each t.Run or use the existing
newServer(t, ...) helper), ensure you call t.Cleanup on both the datastore.Close
and openfga.Close within the subtest so each subtest has an isolated, live
datastore/server and does not operate on a closed datastore.

In `@pkg/server/read_changes.go`:
- Around line 32-39: The PageSize validation incorrectly always reads
req.GetPageSize().GetValue() and only checks the upper bound; update the logic
in ReadChangesRequest handling so you first check presence (req.GetPageSize() !=
nil or inspect PageSize proto field) before calling GetValue(), and when present
enforce both lower and upper bounds (pageSize >= 1 && pageSize <=
s.readChangesMaxPageSize) using the same descriptive error via status.Errorf;
keep the variable name pageSize and s.readChangesMaxPageSize to locate and
update the checks and error message so the semantics and message [1, max] align.

In `@pkg/server/server.go`:
- Around line 340-344: Add a validation in NewServerWithOpts after applying all
OpenFGAServiceV1Option funcs to ensure s.readChangesMaxPageSize is a positive,
non-zero int32: check s.readChangesMaxPageSize > 0 and if not return an error
(use fmt.Errorf with a clear message like "readChangesMaxPageSize must be > 0,
got %d"). This guards the WithReadChangesMaxPageSize path which currently
assigns a uint32 into int32 (function WithReadChangesMaxPageSize) and prevents
zero or overflowed negative values from silently disabling or bypassing the
ReadChanges page-size limit.

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.53%. Comparing base (a1e2d3f) to head (6398a6e).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2887      +/-   ##
==========================================
+ Coverage   90.47%   90.53%   +0.06%     
==========================================
  Files         154      154              
  Lines       17516    17535      +19     
==========================================
+ Hits        15846    15873      +27     
+ Misses       1088     1083       -5     
+ Partials      582      579       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jpadilla jpadilla force-pushed the read-changes-max-page-size branch from 33fa8e7 to 142bf66 Compare January 22, 2026 03:07
Signed-off-by: José Padilla <jose.padilla@okta.com>
@jpadilla jpadilla force-pushed the read-changes-max-page-size branch from 142bf66 to 42915ef Compare January 22, 2026 15:25
@jpadilla
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jpadilla
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 22, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.config-schema.json:
- Around line 105-111: The schema allows readChangesMaxPageSize values down to 1
but NewServerWithOpts enforces a minimum of storage.DefaultPageSize (50),
causing config validation mismatch; update the "readChangesMaxPageSize" entry in
.config-schema.json to set "minimum" to 50 (or the value of
storage.DefaultPageSize) and optionally adjust the "description" to note the
server-enforced minimum so config validation aligns with NewServerWithOpts and
storage.DefaultPageSize.

In `@pkg/server/server.go`:
- Around line 931-933: The error message in the check comparing
s.readChangesMaxPageSize and storage.DefaultPageSize has a grammatical mistake
("must be a greater than"); update the fmt.Errorf call (the string produced
where s.readChangesMaxPageSize < storage.DefaultPageSize) to remove the
extraneous "a" and read clearly—e.g. "ReadChanges max page size must be greater
than or equal to %d"—so the message is grammatically correct while still
including storage.DefaultPageSize for context.
♻️ Duplicate comments (2)
pkg/server/read_changes.go (1)

32-41: Enforce the max against the effective page size (omitted PageSize can bypass the limit).
When PageSize is absent/0, downstream pagination defaults (e.g., 50). If readChangesMaxPageSize is configured below that, the limit is bypassed.

🛠️ Suggested fix
@@
 import (
 	"context"
 
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/trace"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 
 	openfgav1 "github.com/openfga/api/proto/openfga/v1"
 
 	"github.com/openfga/openfga/internal/utils/apimethod"
 	"github.com/openfga/openfga/pkg/middleware/validator"
 	"github.com/openfga/openfga/pkg/server/commands"
+	"github.com/openfga/openfga/pkg/storage"
 	"github.com/openfga/openfga/pkg/telemetry"
 )
@@
-	if p := req.GetPageSize(); p != nil {
-		pageSize := p.GetValue()
-		if pageSize < 1 || pageSize > s.readChangesMaxPageSize {
+	pageSize := int32(0)
+	if p := req.GetPageSize(); p != nil {
+		pageSize = p.GetValue()
+	}
+	effectivePageSize := pageSize
+	if effectivePageSize == 0 {
+		effectivePageSize = int32(storage.DefaultPageSize)
+	}
+	if effectivePageSize < 1 || effectivePageSize > s.readChangesMaxPageSize {
 			return nil, status.Errorf(
 				codes.InvalidArgument,
 				"invalid ReadChangesRequest.PageSize: value must be inside range [1, %d]",
 				s.readChangesMaxPageSize,
 			)
-		}
 	}
pkg/server/read_changes_test.go (1)

17-23: Shared datastore across subtests may cause flakiness.

Each subtest's openfga.Close() ultimately closes resources that may affect subsequent subtests. Consider creating a fresh datastore per subtest using a helper function.

Suggested refactor
 func TestReadChangesPageSizeValidation(t *testing.T) {
 	t.Cleanup(func() {
 		goleak.VerifyNone(t)
 	})
-	ds := memory.New()
-	t.Cleanup(ds.Close)
+
+	newServer := func(t *testing.T, opts ...OpenFGAServiceV1Option) *Server {
+		ds := memory.New()
+		t.Cleanup(ds.Close)
+		s := MustNewServerWithOpts(append([]OpenFGAServiceV1Option{WithDatastore(ds)}, opts...)...)
+		t.Cleanup(s.Close)
+		return s
+	}

 	t.Run("page_size_not_provided", func(t *testing.T) {
-		openfga := MustNewServerWithOpts(
-			WithDatastore(ds),
-		)
-		t.Cleanup(openfga.Close)
+		openfga := newServer(t)
🧹 Nitpick comments (1)
pkg/server/server.go (1)

341-350: Option allows values that startup validation rejects.

WithReadChangesMaxPageSize falls back to the default for max == 0 or max > math.MaxInt32, but values between 1 and 49 are accepted here and only rejected later in NewServerWithOpts (lines 931-933). This inconsistency could confuse users—consider also falling back to the default for values below storage.DefaultPageSize:

Suggested fix
 func WithReadChangesMaxPageSize(max uint32) OpenFGAServiceV1Option {
 	return func(s *Server) {
-		if max <= 0 || max > math.MaxInt32 {
+		if max < storage.DefaultPageSize || max > math.MaxInt32 {
 			max = serverconfig.DefaultReadChangesMaxPageSize
 		}

 		s.readChangesMaxPageSize = int32(max)
 	}
 }

Alternatively, remove the startup validation (lines 931-933) and rely solely on the option guard—though keeping both provides defense-in-depth.

@jpadilla
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@jpadilla jpadilla requested a review from willvedd January 22, 2026 16:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 22, 2026

✅ Actions performed

Full review triggered.

adriantam
adriantam previously approved these changes Jan 22, 2026
willvedd
willvedd previously approved these changes Jan 22, 2026
@jpadilla jpadilla changed the title feat: config max page size for ReadChanges feat: configurable max page size for ReadChanges Jan 22, 2026
@jpadilla jpadilla merged commit 055aad1 into main Jan 22, 2026
21 checks passed
@jpadilla jpadilla deleted the read-changes-max-page-size branch January 22, 2026 18:35
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.

4 participants