Skip to content

chore(api): update meter query spec by removing explicit special fiel…#3964

Merged
tothandras merged 1 commit into
mainfrom
chore/update-meter-query-api
Mar 18, 2026
Merged

chore(api): update meter query spec by removing explicit special fiel…#3964
tothandras merged 1 commit into
mainfrom
chore/update-meter-query-api

Conversation

@tothandras
Copy link
Copy Markdown
Contributor

@tothandras tothandras commented Mar 17, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new helper function for filtering dimension query results with improved operator validation.
  • Bug Fixes & Improvements

    • Simplified dimensions representation in queries and responses to a uniform map-based structure.
    • Standardized subject and customer_id as reserved dimension names across feature cost and meter queries.
    • Enhanced query filter handling for flexible dimension keys with better validation support.

@tothandras tothandras added the release-note/misc Miscellaneous changes label Mar 17, 2026
@tothandras tothandras requested a review from a team as a code owner March 17, 2026 22:31
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

This PR refactors dimension representations from structured objects with explicit subject and customer_id fields to flat map[string]string types across TypeSpec definitions, generated Go code, and handler implementations. The changes affect feature cost and meter query APIs throughout the stack.

Changes

Cohort / File(s) Summary
TypeSpec Specifications
api/spec/packages/aip/src/features/cost.tsp, api/spec/packages/aip/src/meters/query.tsp
Changed dimensions from nested objects with explicit subject/customer_id fields to generic Record or Record. Added documentation noting subject and customer_id are reserved dimensions.
Generated Go Code
api/v3/api.gen.go
Removed FeatureCostQueryRow_Dimensions, MeterQueryFilters_Dimensions, and MeterQueryRow_Dimensions types. Updated field types to use map[string]string and map[string]QueryFilterStringMapItem instead.
Handler Conversion Logic
api/v3/handlers/featurecost/convert.go, api/v3/handlers/meters/convert.go, api/v3/handlers/meters/query/convert.go
Adapted dimension population from struct field assignment to dynamic map construction. Added new ExtractStringsFromQueryFilterMapItem function to validate and extract string values from filter map items, supporting only Eq and In operators.
Query Parameter Processing
api/v3/handlers/meters/query/params.go
Reworked dimension filter processing to use map-based loops with switch statements. Updated BuildQueryParams to accept api.MeterQueryRequest type. Introduced lazy initialization for FilterGroupBy and new validation logic for arbitrary dimension keys.
Test Updates
api/v3/handlers/meters/query/params_test.go, e2e/e2e_test.go, openmeter/server/server_test.go
Updated test fixtures and assertions to reflect new map-based dimension representations. Tests now use string keys like "subject" and "customer_id" within dimension maps instead of typed struct fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

area/api

Suggested reviewers

  • hekike
  • chrisgacsal
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% 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 references updating the meter query spec by removing explicit special fields, which aligns with the core change of converting structured dimension objects to flat Record/map types across multiple files.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/update-meter-query-api
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can customize the high-level summary generated by CodeRabbit.

Configure the reviews.high_level_summary_instructions setting to provide custom instructions for generating the high-level summary.

@tothandras tothandras enabled auto-merge (squash) March 17, 2026 22:48
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

🧹 Nitpick comments (1)
api/v3/handlers/meters/query/params_test.go (1)

244-258: Please add a reserved-dimension exists/neq rejection case.

This suite only checks unknown operators via AdditionalProperties, but the new map item type also exposes real fields like exists and neq on subject/customer_id. A regression in the special-case rejection path would be easy to miss.

➕ Example test
+func TestBuildQueryParams_RejectsReservedDimensionExistsOperator(t *testing.T) {
+	m := newTestMeter()
+	body := api.MeterQueryRequest{
+		Filters: &api.MeterQueryFilters{
+			Dimensions: &map[string]api.QueryFilterStringMapItem{
+				DimensionSubject: {Exists: lo.ToPtr(true)},
+			},
+		},
+	}
+
+	_, err := BuildQueryParams(context.Background(), m, body, noopCustomerResolver)
+	require.Error(t, err)
+}
As per coding guidelines, "`**/*_test.go`: Make sure the tests are comprehensive and cover the changes."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v3/handlers/meters/query/params_test.go` around lines 244 - 258, Add a
new test that verifies BuildQueryParams rejects reserved-dimension operators
like "exists" and "neq" on DimensionSubject and DimensionCustomerID (e.g.,
create a TestBuildQueryParams_ReservedDimensionExistsNeqRejection) by
constructing api.MeterQueryRequest.Filters with a Dimensions map containing
DimensionSubject and DimensionCustomerID entries that set the struct fields
Exists and Neq (or the corresponding bool/string fields) and assert that
BuildQueryParams(context.Background(), m, body, noopCustomerResolver) returns an
error; this complements the existing
TestBuildQueryParams_UnknownSubjectFilterOperator and ensures the special-case
rejection path in BuildQueryParams handles actual struct fields, not only
AdditionalProperties.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/spec/packages/aip/src/meters/query.tsp`:
- Around line 44-49: The schema currently allows any operator via dimensions?:
Record<Shared.QueryFilterStringMapItem> but the handler only supports eq/in for
reserved keys subject and customer_id, so fix the contract by making those
reserved keys explicit with a restricted filter type (only "eq" and "in") and
keep a separate dimensions map for other keys; update the schema around
dimensions, Shared.QueryFilterStringMapItem, and add explicit properties for
subject and customer_id so generated clients cannot send unsupported operators
for those keys.

In `@api/v3/handlers/featurecost/convert.go`:
- Around line 31-45: The GroupBy loop can overwrite reserved dimension keys
copied from row.Subject and row.CustomerID; update the loop that iterates over
row.GroupBy to skip entries whose key equals query.DimensionSubject or
query.DimensionCustomerID so the dedicated fields (row.Subject and
row.CustomerID) remain authoritative when populating the dimensions map (refer
to dimensions, row.Subject, row.CustomerID, row.GroupBy, query.DimensionSubject,
query.DimensionCustomerID).

---

Nitpick comments:
In `@api/v3/handlers/meters/query/params_test.go`:
- Around line 244-258: Add a new test that verifies BuildQueryParams rejects
reserved-dimension operators like "exists" and "neq" on DimensionSubject and
DimensionCustomerID (e.g., create a
TestBuildQueryParams_ReservedDimensionExistsNeqRejection) by constructing
api.MeterQueryRequest.Filters with a Dimensions map containing DimensionSubject
and DimensionCustomerID entries that set the struct fields Exists and Neq (or
the corresponding bool/string fields) and assert that
BuildQueryParams(context.Background(), m, body, noopCustomerResolver) returns an
error; this complements the existing
TestBuildQueryParams_UnknownSubjectFilterOperator and ensures the special-case
rejection path in BuildQueryParams handles actual struct fields, not only
AdditionalProperties.
🪄 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: 5768f354-2d75-4f85-8284-1ed2bbc8db12

📥 Commits

Reviewing files that changed from the base of the PR and between 3e8da3c and 952e975.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (10)
  • api/spec/packages/aip/src/features/cost.tsp
  • api/spec/packages/aip/src/meters/query.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/featurecost/convert.go
  • api/v3/handlers/meters/convert.go
  • api/v3/handlers/meters/query/convert.go
  • api/v3/handlers/meters/query/params.go
  • api/v3/handlers/meters/query/params_test.go
  • e2e/e2e_test.go
  • openmeter/server/server_test.go

Comment on lines 44 to +49
/**
* Filters to apply to the dimensions of the query.
* For `subject` and `customer_id` only equals ("eq", "in") comparisons are supported.
*/
@maxProperties(10)
dimensions?: {
/**
* Filters to apply to the subject of the query.
* Only equals ("eq", "in") comparisons are supported.
*/
subject?: Shared.QueryFilterString;

/**
* Filters to apply to the customer ID of the query.
* Only equals ("eq", "in") comparisons are supported.
*/
customer_id?: Shared.QueryFilterString;

/**
* Additional dimensions to filter by.
*/
...Record<Shared.QueryFilterStringMapItem>;
};
dimensions?: Record<Shared.QueryFilterStringMapItem>;
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.

⚠️ Potential issue | 🟠 Major

The schema now over-promises reserved-dimension filters.

Record<Shared.QueryFilterStringMapItem> advertises operators like exists, neq, nin, contains, and, and or for every key, but the handler only accepts eq/in for subject and customer_id. Either keep those reserved keys explicit in the schema again or widen the handler support, otherwise generated clients can send requests the API will reject.

As per coding guidelines, "The declared API should be accurate, in parity with the actual implementation, and easy to understand for the user."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/spec/packages/aip/src/meters/query.tsp` around lines 44 - 49, The schema
currently allows any operator via dimensions?:
Record<Shared.QueryFilterStringMapItem> but the handler only supports eq/in for
reserved keys subject and customer_id, so fix the contract by making those
reserved keys explicit with a restricted filter type (only "eq" and "in") and
keep a separate dimensions map for other keys; update the schema around
dimensions, Shared.QueryFilterStringMapItem, and add explicit properties for
subject and customer_id so generated clients cannot send unsupported operators
for those keys.

Comment on lines +31 to 45
dimensions := make(map[string]string)

if row.Subject != nil {
dimensions[query.DimensionSubject] = *row.Subject
}

if len(row.GroupBy) > 0 {
dimensions.AdditionalProperties = make(map[string]string, len(row.GroupBy))
if row.CustomerID != nil {
dimensions[query.DimensionCustomerID] = *row.CustomerID
}

for key, value := range row.GroupBy {
switch key {
case query.DimensionSubject:
dimensions.Subject = value
case query.DimensionCustomerID:
dimensions.CustomerId = value
default:
if value != nil {
dimensions.AdditionalProperties[key] = *value
}
}
for key, value := range row.GroupBy {
if value != nil {
dimensions[key] = *value
}
}
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.

⚠️ Potential issue | 🟠 Major

Don't let GroupBy overwrite reserved dimensions.

Subject and CustomerID are copied in first, but the generic GroupBy loop can still replace them if those reserved keys show up there. The domain row in openmeter/cost/service.go still models those values separately from GroupBy, so keeping the dedicated fields authoritative avoids silent response drift.

🛠️ Suggested fix
-	for key, value := range row.GroupBy {
-		if value != nil {
-			dimensions[key] = *value
-		}
-	}
+	for key, value := range row.GroupBy {
+		if value == nil {
+			continue
+		}
+		if key == query.DimensionSubject || key == query.DimensionCustomerID {
+			continue
+		}
+		dimensions[key] = *value
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dimensions := make(map[string]string)
if row.Subject != nil {
dimensions[query.DimensionSubject] = *row.Subject
}
if len(row.GroupBy) > 0 {
dimensions.AdditionalProperties = make(map[string]string, len(row.GroupBy))
if row.CustomerID != nil {
dimensions[query.DimensionCustomerID] = *row.CustomerID
}
for key, value := range row.GroupBy {
switch key {
case query.DimensionSubject:
dimensions.Subject = value
case query.DimensionCustomerID:
dimensions.CustomerId = value
default:
if value != nil {
dimensions.AdditionalProperties[key] = *value
}
}
for key, value := range row.GroupBy {
if value != nil {
dimensions[key] = *value
}
}
dimensions := make(map[string]string)
if row.Subject != nil {
dimensions[query.DimensionSubject] = *row.Subject
}
if row.CustomerID != nil {
dimensions[query.DimensionCustomerID] = *row.CustomerID
}
for key, value := range row.GroupBy {
if value == nil {
continue
}
if key == query.DimensionSubject || key == query.DimensionCustomerID {
continue
}
dimensions[key] = *value
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v3/handlers/featurecost/convert.go` around lines 31 - 45, The GroupBy
loop can overwrite reserved dimension keys copied from row.Subject and
row.CustomerID; update the loop that iterates over row.GroupBy to skip entries
whose key equals query.DimensionSubject or query.DimensionCustomerID so the
dedicated fields (row.Subject and row.CustomerID) remain authoritative when
populating the dimensions map (refer to dimensions, row.Subject, row.CustomerID,
row.GroupBy, query.DimensionSubject, query.DimensionCustomerID).

@tothandras tothandras merged commit 325538e into main Mar 18, 2026
38 of 40 checks passed
@tothandras tothandras deleted the chore/update-meter-query-api branch March 18, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants