Skip to content

feat: introduce common interface for feature gate#4401

Open
borosr wants to merge 9 commits into
mainfrom
feat/ff-interface
Open

feat: introduce common interface for feature gate#4401
borosr wants to merge 9 commits into
mainfrom
feat/ff-interface

Conversation

@borosr
Copy link
Copy Markdown
Collaborator

@borosr borosr commented May 20, 2026

What

Introduce common interface to handle feature gate requests.

Why

We want to control specific cases using feature gate.

Summary by CodeRabbit

  • New Features
    • Added feature-gating support with a default no-op gate for safe rollout.
    • Introduced a comprehensive feature-gate interface supporting typed flag evaluations (bool/int/float/string/json) and context/org scoping.
    • Billing worker and API server now accept and propagate feature-gate settings so features can be toggled at runtime.

Review Change Stack

@borosr borosr requested a review from a team as a code owner May 20, 2026 15:12
@borosr borosr self-assigned this May 20, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a FeatureGate interface and noop singleton default, then threads FeatureGate into billing worker construction and v3 API server configuration (defaults wired where callers don’t supply one).

Changes

Feature Gate Infrastructure and Wiring

Layer / File(s) Summary
Feature Gate Interface & Default Implementation
pkg/gatex/gatex.go
New FeatureGate interface with EvaluateBool, EvaluateInt, EvaluateFloat64, EvaluateString, and EvaluateJSON. Adds Org/FGContext interfaces, a package singleton and GetDefault() returning NoopFeatureGate, whose methods return constant defaults.
Billing Worker Feature Gate Wiring
openmeter/billing/worker/worker.go, app/common/openmeter_billingworker.go, cmd/billing-worker/*
Adds FeatureGate to WorkerOptions and Worker; New uses opts.FeatureGate or gatex.GetDefault(). NewBillingWorkerOptions gains a featureGate parameter; Wire files instantiate and bind gatex.NoopFeatureGate and pass it into NewBillingWorkerOptions.
API Server Feature Gate Wiring
api/v3/server/server.go, openmeter/server/server.go
Adds FeatureGate to the v3 API Config and sets it to gatex.GetDefault() during server construction.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels: release-note/misc

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: introduce common interface for feature gate' directly describes the main change—adding a FeatureGate interface and its implementations across the codebase.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ff-interface

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.

@borosr borosr added the release-note/ignore Ignore this change when generating release notes label May 20, 2026
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: 1

🤖 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 `@pkg/gatex/gatex.go`:
- Around line 26-43: The noopFeatureGate methods currently ignore the
caller-provided default and return hardcoded values; update EvaluateBool,
EvaluateInt, EvaluateFloat64, EvaluateString and EvaluateJSON to accept and
return the provided defaultValue parameter (i.e., name the second parameter like
defaultValue bool/int/float64/string/json.RawMessage) and return that
defaultValue with nil error instead of the hardcoded true/0/""/{}, so callers
receive their intended fallback when no real gate is configured.
🪄 Autofix (Beta)

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: CHILL

Plan: Pro

Run ID: d0ed8e6f-7795-40f8-bca7-ec55eac3bf71

📥 Commits

Reviewing files that changed from the base of the PR and between 944c12d and f677e2c.

📒 Files selected for processing (5)
  • api/v3/server/server.go
  • app/common/openmeter_billingworker.go
  • openmeter/billing/worker/worker.go
  • openmeter/server/server.go
  • pkg/gatex/gatex.go

Comment thread pkg/gatex/gatex.go Outdated
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.

♻️ Duplicate comments (1)
pkg/gatex/gatex.go (1)

26-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

NoopFeatureGate should return caller defaults, not hardcoded values.

Right now each method ignores the defaultValue parameter and returns a constant (true, 0, "", {}). That means callers can't control the fallback behavior when no real gate is configured. The interface signature promises that defaultValue will be used, so the noop implementation should honor that.

🔧 Return the provided defaults
-func (n NoopFeatureGate) EvaluateBool(string, bool) (bool, error) {
-	return true, nil
+func (n NoopFeatureGate) EvaluateBool(_ string, defaultValue bool) (bool, error) {
+	return defaultValue, nil
 }
 
-func (n NoopFeatureGate) EvaluateInt(string, int) (int, error) {
-	return 0, nil
+func (n NoopFeatureGate) EvaluateInt(_ string, defaultValue int) (int, error) {
+	return defaultValue, nil
 }
 
-func (n NoopFeatureGate) EvaluateFloat64(string, float64) (float64, error) {
-	return 0, nil
+func (n NoopFeatureGate) EvaluateFloat64(_ string, defaultValue float64) (float64, error) {
+	return defaultValue, nil
 }
 
-func (n NoopFeatureGate) EvaluateString(string, string) (string, error) {
-	return "", nil
+func (n NoopFeatureGate) EvaluateString(_ string, defaultValue string) (string, error) {
+	return defaultValue, nil
 }
 
-func (n NoopFeatureGate) EvaluateJSON(string, json.RawMessage) (json.RawMessage, error) {
-	return json.RawMessage(`{}`), nil
+func (n NoopFeatureGate) EvaluateJSON(_ string, defaultValue json.RawMessage) (json.RawMessage, error) {
+	return slices.Clone(defaultValue), nil
 }

Note: For EvaluateJSON, cloning the slice prevents accidental mutation of the caller's buffer.

🤖 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 `@pkg/gatex/gatex.go` around lines 26 - 44, The NoopFeatureGate methods
currently ignore the provided defaultValue and return hardcoded constants;
update EvaluateBool, EvaluateInt, EvaluateFloat64, and EvaluateString to return
the passed-in defaultValue and nil error (preserving the method signatures), and
change EvaluateJSON to return a cloned copy of the provided json.RawMessage
defaultValue (to avoid sharing/mutation) along with nil error; adjust references
to the parameters in the functions EvaluateBool, EvaluateInt, EvaluateFloat64,
EvaluateString, and EvaluateJSON accordingly so callers receive their supplied
defaults.
🧹 Nitpick comments (2)
pkg/gatex/gatex.go (1)

18-20: 💤 Low value

Redundant nil check in GetDefault().

Since instance is already initialized to NoopFeatureGate{} on line 15, it will never be nil. You can simplify this to just return instance.

♻️ Simplify the singleton getter
 func GetDefault() FeatureGate {
-	if instance == nil {
-		instance = NoopFeatureGate{}
-	}
 	return instance
 }
🤖 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 `@pkg/gatex/gatex.go` around lines 18 - 20, The GetDefault() function contains
a redundant nil check for the package-level variable instance which is already
initialized to NoopFeatureGate{}; remove the conditional block that checks "if
instance == nil { instance = NoopFeatureGate{} }" and simply return instance
from GetDefault(); reference the symbols instance, GetDefault, and
NoopFeatureGate when updating the function.
cmd/billing-worker/wire.go (1)

58-59: 💤 Low value

Consider using wire.Value or a provider function for the zero-value struct.

wire.Struct works fine here, but it's a bit unusual for a struct with no fields. You could simplify to wire.Value(gatex.NoopFeatureGate{}) or—even better—align with the singleton pattern by using gatex.GetDefault as a provider. That way if GetDefault() logic changes, Wire automatically picks it up.

♻️ Alternative Wire patterns

Option 1: Use wire.Value

-		wire.Struct(new(gatex.NoopFeatureGate)),
+		wire.Value(gatex.NoopFeatureGate{}),
 		wire.Bind(new(gatex.FeatureGate), new(gatex.NoopFeatureGate)),

Option 2: Use the singleton (recommended)

-		wire.Struct(new(gatex.NoopFeatureGate)),
-		wire.Bind(new(gatex.FeatureGate), new(gatex.NoopFeatureGate)),
+		gatex.GetDefault,
🤖 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 `@cmd/billing-worker/wire.go` around lines 58 - 59, The current binding uses
wire.Struct(new(gatex.NoopFeatureGate)) for a zero-value struct; replace it with
a clearer pattern—either use wire.Value(gatex.NoopFeatureGate{}) or (preferred)
register the singleton provider gatex.GetDefault as a provider so Wire will use
that implementation automatically; keep the existing
wire.Bind(new(gatex.FeatureGate), ...) entry so the NoopFeatureGate still
satisfies gatex.FeatureGate.
🤖 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.

Duplicate comments:
In `@pkg/gatex/gatex.go`:
- Around line 26-44: The NoopFeatureGate methods currently ignore the provided
defaultValue and return hardcoded constants; update EvaluateBool, EvaluateInt,
EvaluateFloat64, and EvaluateString to return the passed-in defaultValue and nil
error (preserving the method signatures), and change EvaluateJSON to return a
cloned copy of the provided json.RawMessage defaultValue (to avoid
sharing/mutation) along with nil error; adjust references to the parameters in
the functions EvaluateBool, EvaluateInt, EvaluateFloat64, EvaluateString, and
EvaluateJSON accordingly so callers receive their supplied defaults.

---

Nitpick comments:
In `@cmd/billing-worker/wire.go`:
- Around line 58-59: The current binding uses
wire.Struct(new(gatex.NoopFeatureGate)) for a zero-value struct; replace it with
a clearer pattern—either use wire.Value(gatex.NoopFeatureGate{}) or (preferred)
register the singleton provider gatex.GetDefault as a provider so Wire will use
that implementation automatically; keep the existing
wire.Bind(new(gatex.FeatureGate), ...) entry so the NoopFeatureGate still
satisfies gatex.FeatureGate.

In `@pkg/gatex/gatex.go`:
- Around line 18-20: The GetDefault() function contains a redundant nil check
for the package-level variable instance which is already initialized to
NoopFeatureGate{}; remove the conditional block that checks "if instance == nil
{ instance = NoopFeatureGate{} }" and simply return instance from GetDefault();
reference the symbols instance, GetDefault, and NoopFeatureGate when updating
the function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b45179c-af01-4128-8e4d-ed33670e9ae8

📥 Commits

Reviewing files that changed from the base of the PR and between f677e2c and f8218b8.

📒 Files selected for processing (3)
  • cmd/billing-worker/wire.go
  • cmd/billing-worker/wire_gen.go
  • pkg/gatex/gatex.go
✅ Files skipped from review due to trivial changes (1)
  • cmd/billing-worker/wire_gen.go

turip
turip previously approved these changes May 20, 2026
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.

🧹 Nitpick comments (1)
openmeter/server/server.go (1)

144-144: ⚡ Quick win

Avoid hardcoding the no-op gate in the server constructor.

Line 144 always injects gatex.GetDefault(), which blocks passing a real gate through this path when needed.

💡 Suggested wiring tweak
 type Config struct {
 	RouterConfig        router.Config
 	RouterHooks         RouterHooks
 	PostAuthMiddlewares PostAuthMiddlewares
 	ResponseValidation  appconfig.ResponseValidationConfig
+	FeatureGate         gatex.FeatureGate
 }

 func NewServer(config *Config) (*Server, error) {
+	featureGate := config.FeatureGate
+	if featureGate == nil {
+		featureGate = gatex.GetDefault()
+	}
+
 	v3API, err := v3server.NewServer(&v3server.Config{
 		...
-		FeatureGate:              gatex.GetDefault(),
+		FeatureGate:              featureGate,
 	})
🤖 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 `@openmeter/server/server.go` at line 144, The constructor currently hardcodes
FeatureGate to gatex.GetDefault(), preventing callers from injecting a real
gate; update the server creation path that sets FeatureGate (the struct field
named FeatureGate and the call to gatex.GetDefault()) so it accepts an injected
gate parameter (e.g., add a gate parameter to the NewServer/New* constructor or
a functional option) and use the provided gate when non-nil, falling back to
gatex.GetDefault() only if no gate was passed; ensure references to FeatureGate
in the constructor and any related config struct are updated to use the injected
value.
🤖 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.

Nitpick comments:
In `@openmeter/server/server.go`:
- Line 144: The constructor currently hardcodes FeatureGate to
gatex.GetDefault(), preventing callers from injecting a real gate; update the
server creation path that sets FeatureGate (the struct field named FeatureGate
and the call to gatex.GetDefault()) so it accepts an injected gate parameter
(e.g., add a gate parameter to the NewServer/New* constructor or a functional
option) and use the provided gate when non-nil, falling back to
gatex.GetDefault() only if no gate was passed; ensure references to FeatureGate
in the constructor and any related config struct are updated to use the injected
value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3b8f4048-7cd5-4231-86f8-81232d2cacc5

📥 Commits

Reviewing files that changed from the base of the PR and between c1da7be and 8850e72.

📒 Files selected for processing (7)
  • api/v3/server/server.go
  • app/common/openmeter_billingworker.go
  • cmd/billing-worker/wire.go
  • cmd/billing-worker/wire_gen.go
  • openmeter/billing/worker/worker.go
  • openmeter/server/server.go
  • pkg/gatex/gatex.go
✅ Files skipped from review due to trivial changes (1)
  • cmd/billing-worker/wire_gen.go

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.

Caution

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

⚠️ Outside diff range comments (1)
pkg/gatex/gatex.go (1)

41-43: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unnecessary nil check for struct value.

Since instance is initialized to NoopFeatureGate{} (a struct value, not a pointer), it can never be nil. This check will always evaluate to false.

🧹 Suggested simplification
 func GetDefault() FeatureGate {
-	if instance == nil {
-		instance = NoopFeatureGate{}
-	}
 	return instance
 }
🤖 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 `@pkg/gatex/gatex.go` around lines 41 - 43, The nil check on the package-level
variable `instance` is redundant because `instance` is initialized as a
non-pointer `NoopFeatureGate{}` value and can never be nil; remove the
conditional block that checks `if instance == nil { instance = NoopFeatureGate{}
}` and leave the existing initialization of `instance` intact so `instance` is
always the default `NoopFeatureGate{}` value without the unreachable nil-check.
🤖 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 `@pkg/gatex/gatex.go`:
- Around line 41-43: The nil check on the package-level variable `instance` is
redundant because `instance` is initialized as a non-pointer `NoopFeatureGate{}`
value and can never be nil; remove the conditional block that checks `if
instance == nil { instance = NoopFeatureGate{} }` and leave the existing
initialization of `instance` intact so `instance` is always the default
`NoopFeatureGate{}` value without the unreachable nil-check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1a7557f8-7fb8-48d1-949c-a1f9b9835328

📥 Commits

Reviewing files that changed from the base of the PR and between 8850e72 and f521ecc.

📒 Files selected for processing (1)
  • pkg/gatex/gatex.go

Comment thread pkg/gatex/gatex.go Outdated
Comment thread openmeter/billing/worker/worker.go Outdated
Comment thread pkg/gatex/gatex.go Outdated
chrisgacsal
chrisgacsal previously approved these changes May 21, 2026
chrisgacsal
chrisgacsal previously approved these changes May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/ignore Ignore this change when generating release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants