chore(api): update meter query spec by removing explicit special fiel…#3964
Conversation
…ds from dimensions
📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment Tip You can customize the high-level summary generated by CodeRabbit.Configure the |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
api/v3/handlers/meters/query/params_test.go (1)
244-258: Please add a reserved-dimensionexists/neqrejection case.This suite only checks unknown operators via
AdditionalProperties, but the new map item type also exposes real fields likeexistsandneqonsubject/customer_id. A regression in the special-case rejection path would be easy to miss.As per coding guidelines, "`**/*_test.go`: Make sure the tests are comprehensive and cover the changes."➕ 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) +}🤖 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
⛔ Files ignored due to path filters (1)
api/v3/openapi.yamlis excluded by!**/openapi.yaml
📒 Files selected for processing (10)
api/spec/packages/aip/src/features/cost.tspapi/spec/packages/aip/src/meters/query.tspapi/v3/api.gen.goapi/v3/handlers/featurecost/convert.goapi/v3/handlers/meters/convert.goapi/v3/handlers/meters/query/convert.goapi/v3/handlers/meters/query/params.goapi/v3/handlers/meters/query/params_test.goe2e/e2e_test.goopenmeter/server/server_test.go
| /** | ||
| * 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>; |
There was a problem hiding this comment.
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.
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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).
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes & Improvements