Skip to content

feat(features): add key and name filters and sort to the features list operation#4204

Merged
tothandras merged 5 commits into
mainfrom
feat/features-filter-name-key-sort
Apr 22, 2026
Merged

feat(features): add key and name filters and sort to the features list operation#4204
tothandras merged 5 commits into
mainfrom
feat/features-filter-name-key-sort

Conversation

@borosr
Copy link
Copy Markdown
Contributor

@borosr borosr commented Apr 22, 2026

Overview

Extend the GET /api/v3/openmeter/features endpoint with a filter[name] and `filter[key] query params, also implement sort query param.

Notes for review

Testing guide:

  1. Create a meter
curl --request POST \
  --url http://localhost:8888/api/v1/meters \
  --header 'Content-Type: application/json' \
  --data '{
  "slug": "local_testing",
  "name": "Local testing",
  "description": "Local testing",
  "aggregation": "COUNT",
  "eventType": "test",
	"groupBy": {
    "model": "$.model",
    "type": "$.type"
  }
}'
  1. Create a feature (at least 3 times with incremented number for sort testing)
curl --request POST \
  --url http://localhost:8888/api/v3/openmeter/features \
  --header 'Content-Type: application/json' \
  --data '{
	"name": "Hello feature 1",
	"description": "Hello feature 1",
	"key": "meterid1",
	"meter": {
		"id": "<meter id from the create meter response>"
	}
}'
  1. List the features (sort)
curl --request GET \
  --url 'http://localhost:8888/api/v3/openmeter/features?sort=key%20asc'
  1. List the features (name and key)
curl --request GET \
  --url 'http://localhost:8888/api/v3/openmeter/features?filter%5Bname%5D%5Bcontains%5D=Hello&filter%5Bkey%5D%5Beq%5D=meterid1'

The response should look like this:

{
	"data": [
		{
			"created_at": "2026-04-22T09:07:37.105283Z",
			"description": "Hello feature 1",
			"id": "01KPT73T0H9A4TZA3TXV1ASYDB",
			"key": "meterid1",
			"labels": {},
			"meter": {
				"id": "01KPQW85CS1ZV4XW1BTAZF6GG0"
			},
			"name": "Hello feature 1",
			"updated_at": "2026-04-22T09:07:37.105283Z"
		},
		{
			"created_at": "2026-04-22T09:07:29.850728Z",
			"description": "Hello feature 2",
			"id": "01KPT73JXT05XS0M0DRHQ26933",
			"key": "meterid2",
			"labels": {},
			"meter": {
				"id": "01KPQW85CS1ZV4XW1BTAZF6GG0"
			},
			"name": "Hello feature 2",
			"updated_at": "2026-04-22T09:07:29.850729Z"
		},
		{
			"created_at": "2026-04-21T11:26:15.615382Z",
			"description": "Hello feature 3",
			"id": "01KPQWMYHZKQE6QKWRQ9PAT7M7",
			"key": "meterid3",
			"labels": {},
			"meter": {
				"id": "01KPQW85CS1ZV4XW1BTAZF6GG0"
			},
			"name": "Hello feature 3",
			"updated_at": "2026-04-21T11:26:15.615383Z"
		}
	],
	"meta": {
		"page": {
			"size": 20,
			"number": 1,
			"total": 3
		}
	}
}

Summary by CodeRabbit

  • New Features

    • Features API now supports filtering by key and name, and optional sorting of feature lists (selectable fields, asc/desc).
  • Bug Fixes / Validation

    • Improved parsing and validation for filter and sort query parameters with clearer invalid-parameter responses.
  • Tests

    • Added tests verifying filtering/search by name and key.

@borosr borosr self-assigned this Apr 22, 2026
@borosr borosr requested a review from a team as a code owner April 22, 2026 09:47
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a740bb44-3315-4196-bb79-0765af76d1f8

📥 Commits

Reviewing files that changed from the base of the PR and between 1cd38c8 and 10412a3.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (6)
  • api/spec/packages/aip/src/features/operations.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/features/list.go
  • openmeter/productcatalog/adapter/feature.go
  • openmeter/productcatalog/adapter/feature_test.go
  • openmeter/productcatalog/feature/connector.go
✅ Files skipped from review due to trivial changes (1)
  • openmeter/productcatalog/adapter/feature.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • api/spec/packages/aip/src/features/operations.tsp
  • openmeter/productcatalog/adapter/feature_test.go
  • openmeter/productcatalog/feature/connector.go
  • api/v3/handlers/features/list.go

📝 Walkthrough

Walkthrough

Adds optional key and name deepObject filters and a sort query parameter to ListFeatures; updates API spec, generated bindings, handler parsing/validation, connector validation, DB adapter filtering, and tests. No response shapes or public routes were changed. (50 words)

Changes

Cohort / File(s) Summary
API spec & generated types
api/spec/packages/aip/src/features/operations.tsp, api/v3/api.gen.go
Added key and name to the ListFeatures filter model and introduced a sort query parameter on generated ListFeaturesParams; updated OpenAPI/swagger spec.
Request handler
api/v3/handlers/features/list.go
Parse/validate filter[key] and filter[name] and the sort query; return structured InvalidParameters on parse errors and map valid sort into request OrderBy/Order.
Connector & validation
openmeter/productcatalog/feature/connector.go
Added Key and Name fields to ListFeaturesParams; added FeatureOrderBy.Validate() and extended ListFeaturesParams.Validate() to validate the new filters and order-by.
Database adapter
openmeter/productcatalog/adapter/feature.go
Apply filter.ApplyToQuery for Key and Name against DB feature fields before existing meter_id filtering, ordering, and pagination.
Tests
openmeter/productcatalog/adapter/feature_test.go
Added sub-tests verifying ListFeatures can filter by name and by key (matching and non-matching cases).

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as Handler (api/v3/handlers/features/list.go)
    participant Validator as Validator (connector.go)
    participant Adapter as Adapter (adapter/feature.go)
    participant DB as Database

    Client->>Handler: ListFeatures(key?, name?, sort?, meter_id?)
    Handler->>Handler: Parse key & name filters
    Handler->>Handler: Parse sort parameter
    Handler->>Validator: Validate params (filters + order_by)
    Validator-->>Handler: Validation result
    alt validation fails
        Handler-->>Client: 400 Bad Request (InvalidParameters)
    else
        Handler->>Adapter: ListFeatures(validated params)
        Adapter->>Adapter: Apply Key filter
        Adapter->>Adapter: Apply Name filter
        Adapter->>Adapter: Apply MeterID filter
        Adapter->>DB: Execute filtered & sorted query
        DB-->>Adapter: Results
        Adapter-->>Handler: Features list
        Handler-->>Client: 200 OK with results
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • tothandras
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title accurately and concisely describes the main changes: adding key and name filters plus sort functionality to the features list operation, which directly matches the changeset across all modified files.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/features-filter-name-key-sort

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/feature Release note: Exciting New Features label Apr 22, 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

Caution

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

⚠️ Outside diff range comments (1)
api/spec/packages/aip/src/features/operations.tsp (1)

50-69: ⚠️ Potential issue | 🟡 Minor

Sort attribute docs don't match the implementation.

Heads up — the doc comment lists supported sort attributes as id, name (default), created_at, but the backend FeatureOrderBy in openmeter/productcatalog/feature/connector.go only accepts key, name, created_at, updated_at. So:

  • sort=id asc will pass through the API layer and then fail validation in the connector ("invalid feature order by: id"), which is a confusing user experience.
  • key and updated_at are actually supported but undocumented.
  • name (default) is misleading — the handler doesn't apply a default order when sort is omitted; no ordering is set on the query at all.

Also, while you're refreshing this filter block, consider mentioning the new filter[key] and filter[name] alongside the existing filter[meter_id] example so the spec is self-describing.

📝 Suggested doc tweak
     /**
      * Sort features returned in the response.
      * Supported sort attributes are:
-     * - `id`
-     * - `name` (default)
+     * - `key`
+     * - `name`
      * - `created_at`
+     * - `updated_at`
      *
      * The `asc` suffix is optional as the default sort order is ascending.
      * The `desc` suffix is used to specify a descending order.
      */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/spec/packages/aip/src/features/operations.tsp` around lines 50 - 69, The
docs for the features list are out of sync with the backend: update the sort and
filter comments so they match the connector's accepted values and
behavior—replace the listed sort attributes (`id`, `name`, `created_at`) with
the actual supported FeatureOrderBy values (`key`, `name`, `created_at`,
`updated_at`), remove the misleading claim that `name` is the default (the
handler does not apply a default order), and note that invalid values (e.g.,
`id`) will be rejected by FeatureOrderBy in
openmeter/productcatalog/feature/connector.go; also expand the filter examples
to document filter[key] and filter[name] alongside the existing filter[meter_id]
usage and ensure the `@query` types (sort?: Common.SortQuery, filter?:
ListFeaturesParamsFilter) remain unchanged.
🧹 Nitpick comments (2)
openmeter/productcatalog/adapter/feature_test.go (1)

253-316: Solid coverage — consider exercising a non-Eq operator too.

The new sub-cases cleanly verify the happy path and empty result for both filters. As a nice-to-have, could you add one case using Contains (e.g., filter[name][contains]=Hello as per the PR description) so the end-to-end filter-operator parsing is exercised in tests, not just equality? Totally optional.

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

In `@openmeter/productcatalog/adapter/feature_test.go` around lines 253 - 316, Add
a small subcase that exercises the non-Eq operator by calling
feature.ListFeatures with feature.ListFeaturesParams where Name (or Key) is set
to &filter.FilterString{Contains: lo.ToPtr("feature")} (or "Hello" per PR
description), then assert the returned Items include the created testFeature
(len>0 and matching Name/Key) and that an unrelated contains value returns zero
items; update the existing test blocks in
openmeter/productcatalog/adapter/feature_test.go (the test using testFeature,
connector.CreateFeature and connector.ListFeatures) to include this Contains
check so operator parsing is covered end-to-end.
api/v3/handlers/features/list.go (1)

84-94: Parsing is clean — consider validating the sort field here for a nicer 400.

Tiny suggestion: if a user passes e.g. sort=id asc, request.ParseSortBy succeeds, the handler stores feature.FeatureOrderBy("id"), and the failure only surfaces deeper via ListFeaturesParams.Validate() as a generic validation error. If you validate up front, you can return the same structured apierrors.InvalidParameters{Field: "sort", ...} shape you already use for the other params, which is easier for API consumers to reason about.

💡 Optional tweak
 				req.OrderBy = feature.FeatureOrderBy(sort.Field)
+				if err := req.OrderBy.Validate(); err != nil {
+					return ListFeaturesRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
+						{Field: "sort", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
+					})
+				}
 				req.Order = sort.Order.ToSortxOrder()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v3/handlers/features/list.go` around lines 84 - 94, After parsing sort
via request.ParseSortBy, validate the parsed sort.Field before assigning to
req.OrderBy: check that the value maps to a known feature.FeatureOrderBy (e.g.,
via an existing enum/validator or a switch/lookup of allowed fields) and if it
is invalid return the same structured bad-request (apierrors.NewBadRequestError
with apierrors.InvalidParameters{Field: "sort", Reason: err.Error(), Source:
apierrors.InvalidParamSourceQuery}) instead of deferring to
ListFeaturesParams.Validate(); update the block around params.Sort handling
(where request.ParseSortBy is called and req.OrderBy =
feature.FeatureOrderBy(...)) to perform this early validation and produce the
consistent 400 response.
🤖 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/v3/api.gen.go`:
- Around line 5102-5110: The generated doc for the Sort field is missing "key"
as a supported sort attribute; update the source API spec (OpenAPI/Swagger or
the generator input that defines the features list/sort description) to include
`key` in the supported sort attributes (and any enum or examples that drive
SortQuery), then re-run the code generator to regenerate api.gen.go so the Sort
*SortQuery comment and any related validation/enums include `key` (target
symbols: the Sort field on the features request model and the SortQuery
definition).

---

Outside diff comments:
In `@api/spec/packages/aip/src/features/operations.tsp`:
- Around line 50-69: The docs for the features list are out of sync with the
backend: update the sort and filter comments so they match the connector's
accepted values and behavior—replace the listed sort attributes (`id`, `name`,
`created_at`) with the actual supported FeatureOrderBy values (`key`, `name`,
`created_at`, `updated_at`), remove the misleading claim that `name` is the
default (the handler does not apply a default order), and note that invalid
values (e.g., `id`) will be rejected by FeatureOrderBy in
openmeter/productcatalog/feature/connector.go; also expand the filter examples
to document filter[key] and filter[name] alongside the existing filter[meter_id]
usage and ensure the `@query` types (sort?: Common.SortQuery, filter?:
ListFeaturesParamsFilter) remain unchanged.

---

Nitpick comments:
In `@api/v3/handlers/features/list.go`:
- Around line 84-94: After parsing sort via request.ParseSortBy, validate the
parsed sort.Field before assigning to req.OrderBy: check that the value maps to
a known feature.FeatureOrderBy (e.g., via an existing enum/validator or a
switch/lookup of allowed fields) and if it is invalid return the same structured
bad-request (apierrors.NewBadRequestError with
apierrors.InvalidParameters{Field: "sort", Reason: err.Error(), Source:
apierrors.InvalidParamSourceQuery}) instead of deferring to
ListFeaturesParams.Validate(); update the block around params.Sort handling
(where request.ParseSortBy is called and req.OrderBy =
feature.FeatureOrderBy(...)) to perform this early validation and produce the
consistent 400 response.

In `@openmeter/productcatalog/adapter/feature_test.go`:
- Around line 253-316: Add a small subcase that exercises the non-Eq operator by
calling feature.ListFeatures with feature.ListFeaturesParams where Name (or Key)
is set to &filter.FilterString{Contains: lo.ToPtr("feature")} (or "Hello" per PR
description), then assert the returned Items include the created testFeature
(len>0 and matching Name/Key) and that an unrelated contains value returns zero
items; update the existing test blocks in
openmeter/productcatalog/adapter/feature_test.go (the test using testFeature,
connector.CreateFeature and connector.ListFeatures) to include this Contains
check so operator parsing is covered end-to-end.
🪄 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: 46fc0fed-af53-4efe-bf19-218b5046cfa3

📥 Commits

Reviewing files that changed from the base of the PR and between 605f03f and 01ae48d.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (6)
  • api/spec/packages/aip/src/features/operations.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/features/list.go
  • openmeter/productcatalog/adapter/feature.go
  • openmeter/productcatalog/adapter/feature_test.go
  • openmeter/productcatalog/feature/connector.go

Comment thread api/v3/api.gen.go
Comment on lines +21 to +34
/**
* Filter features by meter ID.
*/
meter_id?: Common.ULIDFieldFilter;

/**
* Filter features by key.
*/
key?: Common.StringFieldFilter;

/**
* Filter features by name.
*/
name?: Common.StringFieldFilter;
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.

We should add back these, not to end up with allOf

Suggested change
/**
* Filter features by meter ID.
*/
meter_id?: Common.ULIDFieldFilter;
/**
* Filter features by key.
*/
key?: Common.StringFieldFilter;
/**
* Filter features by name.
*/
name?: Common.StringFieldFilter;
#suppress "@openmeter/api-spec-aip/doc-decorator" "shared model"
meter_id?: Common.ULIDFieldFilter;
#suppress "@openmeter/api-spec-aip/doc-decorator" "shared model"
key?: Common.StringFieldFilter;
#suppress "@openmeter/api-spec-aip/doc-decorator" "shared model"
name?: Common.StringFieldFilter;

}
req.MeterIDs = meterIDs

key, err := filters.FromAPIFilterString(params.Filter.Key)
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.

params.Filter.Key nil check is missing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

hey, the filters.FromAPIFilterString is doing an early exit if the params.Filter.Key is nil

})
}

req.OrderBy = feature.FeatureOrderBy(sort.Field)
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.

Should we validate the supported order by's here? I know we'll return NewGenericValidationError from the service, just wondering.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm validating this field inside the ListFeaturesParams.Validate() and I didn't see the reason to validate it in both layers.

@borosr borosr force-pushed the feat/features-filter-name-key-sort branch from 1cd38c8 to 10412a3 Compare April 22, 2026 10:57
@borosr borosr requested a review from tothandras April 22, 2026 10:57
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 the current code and only fix it if needed.

Inline comments:
In `@api/spec/packages/aip/src/features/operations.tsp`:
- Around line 23-26: The operation docs currently list only the meter_id filter
while the filter model now exposes key?: Common.StringFieldFilter and name?:
Common.StringFieldFilter; update the operation documentation text that
references the filter model (the sections showing meter_id) to include both key
and name alongside meter_id, and mirror this change in the other occurrence
noted (around the second block at lines ~56-60); ensure the docs describe that
key and name accept the same StringFieldFilter options as meter_id so generated
API docs accurately reflect the implemented filters.
🪄 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: db9f3451-44d5-4ca7-b1b2-060db7c75e61

📥 Commits

Reviewing files that changed from the base of the PR and between 2eb48b5 and 1cd38c8.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (2)
  • api/spec/packages/aip/src/features/operations.tsp
  • api/v3/api.gen.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/v3/api.gen.go

Comment thread api/spec/packages/aip/src/features/operations.tsp
@tothandras tothandras merged commit 1c3c904 into main Apr 22, 2026
29 of 30 checks passed
@tothandras tothandras deleted the feat/features-filter-name-key-sort branch April 22, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants