From 8fe717ea08b7f3073ed5129a1ba86345f43ffc0c Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Mon, 18 May 2026 15:53:41 +0200 Subject: [PATCH 1/3] feat: add filter and sort to billing profile --- .../src/funcs/billing.ts | 2 + .../aip-client-javascript/src/index.ts | 1 + .../src/models/operations/billing.ts | 6 + .../src/models/schemas.ts | 9 + .../aip-client-javascript/src/models/types.ts | 19 + .../packages/aip/src/billing/operations.tsp | 40 +- api/v3/api.gen.go | 490 ++++++++++-------- api/v3/handlers/billingprofiles/list.go | 31 ++ api/v3/openapi.yaml | 37 ++ openmeter/billing/adapter/profile.go | 19 +- openmeter/billing/httpdriver/profile.go | 2 +- openmeter/billing/profile.go | 51 +- openmeter/billing/service/profile_test.go | 154 ++++++ 13 files changed, 625 insertions(+), 236 deletions(-) create mode 100644 openmeter/billing/service/profile_test.go diff --git a/api/spec/packages/aip-client-javascript/src/funcs/billing.ts b/api/spec/packages/aip-client-javascript/src/funcs/billing.ts index 2e19bfa773..d2d0669d4e 100644 --- a/api/spec/packages/aip-client-javascript/src/funcs/billing.ts +++ b/api/spec/packages/aip-client-javascript/src/funcs/billing.ts @@ -22,6 +22,8 @@ export function listBillingProfiles( ): Promise> { const searchParams = toURLSearchParams({ page: req.page, + sort: encodeSort(req.sort), + filter: req.filter, }) return request(() => http(client) diff --git a/api/spec/packages/aip-client-javascript/src/index.ts b/api/spec/packages/aip-client-javascript/src/index.ts index effbe8e27d..8a7487b6a4 100644 --- a/api/spec/packages/aip-client-javascript/src/index.ts +++ b/api/spec/packages/aip-client-javascript/src/index.ts @@ -191,6 +191,7 @@ export type { LlmCostOverrideCreate, ListCustomersParamsFilter, ListSubscriptionsParamsFilter, + ListBillingProfilesParamsFilter, ListFeatureParamsFilter, ListAddonsParamsFilter, CreateCreditGrantTaxConfig, diff --git a/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts b/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts index fba1ad3818..cda74addcb 100644 --- a/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts +++ b/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts @@ -2,14 +2,20 @@ import { z } from 'zod' import * as schemas from '../schemas.js' import type { CreateBillingProfileRequestInput, + ListBillingProfilesParamsFilter, Profile, ProfilePagePaginatedResponse, + SortQueryInput, UpsertBillingProfileRequestInput, } from '../types.js' export interface ListBillingProfilesQuery { /** Determines which page of the collection to retrieve. */ page?: { size?: number; number?: number } + /** Sort billing profiles returned in the response. Supported sort attributes are: - `id` - `name` - `created_at` (default) - `updated_at` The `asc` suffix is optional as the default sort order is ascending. The `desc` suffix is used to specify a descending order. */ + sort?: SortQueryInput + /** Filter billing profiles returned in the response. To filter billing profiles by name add the following query param: filter[name]=my-profile */ + filter?: ListBillingProfilesParamsFilter } export type ListBillingProfilesRequest = ListBillingProfilesQuery diff --git a/api/spec/packages/aip-client-javascript/src/models/schemas.ts b/api/spec/packages/aip-client-javascript/src/models/schemas.ts index b2782bc2b2..241e3e1c60 100644 --- a/api/spec/packages/aip-client-javascript/src/models/schemas.ts +++ b/api/spec/packages/aip-client-javascript/src/models/schemas.ts @@ -2847,6 +2847,13 @@ export const listSubscriptionsParamsFilter = z }) .describe('Filter options for listing subscriptions.') +export const listBillingProfilesParamsFilter = z + .object({ + id: ulidFieldFilter.optional(), + name: stringFieldFilter.optional(), + }) + .describe('Filter options for listing billing profiles.') + export const listFeatureParamsFilter = z .object({ meter_id: ulidFieldFilter.optional(), @@ -4941,6 +4948,8 @@ export const listBillingProfilesQueryParams = z.object({ }) .optional() .describe('Determines which page of the collection to retrieve.'), + sort: sortQuery.optional(), + filter: listBillingProfilesParamsFilter.optional(), }) export const listBillingProfilesResponse = z.object({ diff --git a/api/spec/packages/aip-client-javascript/src/models/types.ts b/api/spec/packages/aip-client-javascript/src/models/types.ts index 65c876fc40..e4cd580450 100644 --- a/api/spec/packages/aip-client-javascript/src/models/types.ts +++ b/api/spec/packages/aip-client-javascript/src/models/types.ts @@ -1683,6 +1683,25 @@ export interface ListSubscriptionsParamsFilter { plan_key?: string | { eq?: string; oeq?: string[]; neq?: string } } +/** Filter options for listing billing profiles. */ +export interface ListBillingProfilesParamsFilter { + id?: string | { eq?: string; oeq?: string[]; neq?: string } + name?: + | string + | { + eq?: string + neq?: string + contains?: string + ocontains?: string[] + oeq?: string[] + gt?: string + gte?: string + lt?: string + lte?: string + exists?: boolean + } +} + /** Filter options for listing features. */ export interface ListFeatureParamsFilter { meter_id?: string | { eq?: string; oeq?: string[]; neq?: string } diff --git a/api/spec/packages/aip/src/billing/operations.tsp b/api/spec/packages/aip/src/billing/operations.tsp index 288d2c9fb7..3cc78accb1 100644 --- a/api/spec/packages/aip/src/billing/operations.tsp +++ b/api/spec/packages/aip/src/billing/operations.tsp @@ -13,6 +13,17 @@ using TypeSpec.OpenAPI; namespace Billing; +/** + * Filter options for listing billing profiles. + */ +@friendlyName("ListBillingProfilesParamsFilter") +model ListBillingProfilesParamsFilter { + #suppress "@openmeter/api-spec-aip/doc-decorator" "filter field" + id?: Common.ULIDFieldFilter; + #suppress "@openmeter/api-spec-aip/doc-decorator" "filter field" + name?: Common.StringFieldFilter; +} + interface BillingProfilesOperations { /** * List billing profiles. @@ -20,9 +31,32 @@ interface BillingProfilesOperations { @get @operationId("list-billing-profiles") @summary("List billing profiles") - list(...Common.PagePaginationQuery): - | Shared.PagePaginatedResponse - | Common.ErrorResponses; + list( + ...Common.PagePaginationQuery, + + /** + * Sort billing profiles returned in the response. Supported sort attributes are: + * + * - `id` + * - `name` + * - `created_at` (default) + * - `updated_at` + * + * The `asc` suffix is optional as the default sort order is ascending. The `desc` + * suffix is used to specify a descending order. + */ + @query(#{ name: "sort" }) + sort?: Common.SortQuery, + + /** + * Filter billing profiles returned in the response. + * + * To filter billing profiles by name add the following query param: + * filter[name]=my-profile + */ + @query(#{ style: "deepObject", explode: true }) + filter?: ListBillingProfilesParamsFilter, + ): Shared.PagePaginatedResponse | Common.ErrorResponses; /** * Create a new billing profile. diff --git a/api/v3/api.gen.go b/api/v3/api.gen.go index a6d8fafb81..4c94714dc7 100644 --- a/api/v3/api.gen.go +++ b/api/v3/api.gen.go @@ -4597,6 +4597,17 @@ type ListAddonsParamsFilter struct { Status *StringFieldFilterExact `json:"status,omitempty"` } +// ListBillingProfilesParamsFilter Filter options for listing billing profiles. +type ListBillingProfilesParamsFilter struct { + // Id Filters on the given ULID field value by exact match. All properties are + // optional; provide exactly one to specify the comparison. + Id *ULIDFieldFilter `json:"id,omitempty"` + + // Name Filters on the given string field value by either exact or fuzzy match. All + // properties are optional; provide exactly one to specify the comparison. + Name *StringFieldFilter `json:"name,omitempty"` +} + // ListChargesParamsFilter Filter options for listing charges. type ListChargesParamsFilter struct { // Status Filter charges by status. @@ -5835,6 +5846,23 @@ type ListPlanAddonsParams struct { type ListBillingProfilesParams struct { // Page Determines which page of the collection to retrieve. Page *PagePaginationQuery `json:"page,omitempty"` + + // Sort Sort billing profiles returned in the response. Supported sort attributes are: + // + // - `id` + // - `name` + // - `created_at` (default) + // - `updated_at` + // + // The `asc` suffix is optional as the default sort order is ascending. The `desc` + // suffix is used to specify a descending order. + Sort *SortQuery `form:"sort,omitempty" json:"sort,omitempty"` + + // Filter Filter billing profiles returned in the response. + // + // To filter billing profiles by name add the following query param: + // filter[name]=my-profile + Filter *ListBillingProfilesParamsFilter `json:"filter,omitempty"` } // ListSubscriptionsParams defines parameters for ListSubscriptions. @@ -9579,6 +9607,22 @@ func (siw *ServerInterfaceWrapper) ListBillingProfiles(w http.ResponseWriter, r return } + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameterWithOptions("form", false, false, "sort", r.URL.Query(), ¶ms.Sort, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort", Err: err}) + return + } + + // ------------- Optional query parameter "filter" ------------- + + err = filters.Parse(r.URL.Query(), ¶ms.Filter) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListBillingProfiles(w, r, params) })) @@ -10838,228 +10882,230 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v3/handlers/billingprofiles/list.go b/api/v3/handlers/billingprofiles/list.go index ec07803d99..95c893f692 100644 --- a/api/v3/handlers/billingprofiles/list.go +++ b/api/v3/handlers/billingprofiles/list.go @@ -9,6 +9,8 @@ import ( api "github.com/openmeterio/openmeter/api/v3" "github.com/openmeterio/openmeter/api/v3/apierrors" + "github.com/openmeterio/openmeter/api/v3/filters" + "github.com/openmeterio/openmeter/api/v3/request" "github.com/openmeterio/openmeter/api/v3/response" "github.com/openmeterio/openmeter/openmeter/billing" "github.com/openmeterio/openmeter/pkg/framework/commonhttp" @@ -57,6 +59,35 @@ func (h *handler) ListBillingProfiles() ListBillingProfilesHandler { }, } + if params.Sort != nil { + sort, err := request.ParseSortBy(*params.Sort) + if err != nil { + return ListBillingProfilesRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{ + {Field: "sort", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery}, + }) + } + req.OrderBy = billing.OrderBy(sort.Field) + req.Order = sort.Order.ToSortxOrder() + } + + if params.Filter != nil { + id, err := filters.FromAPIFilterULID(params.Filter.Id) + if err != nil { + return ListBillingProfilesRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{ + {Field: "filter[id]", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery}, + }) + } + req.ID = id + + name, err := filters.FromAPIFilterString(params.Filter.Name) + if err != nil { + return ListBillingProfilesRequest{}, apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{ + {Field: "filter[name]", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery}, + }) + } + req.Name = name + } + return req, nil }, func(ctx context.Context, request ListBillingProfilesRequest) (ListBillingProfilesResponse, error) { diff --git a/api/v3/openapi.yaml b/api/v3/openapi.yaml index f1be88a022..43415e93db 100644 --- a/api/v3/openapi.yaml +++ b/api/v3/openapi.yaml @@ -2362,6 +2362,34 @@ paths: description: List billing profiles. parameters: - $ref: '#/components/parameters/PagePaginationQuery' + - name: sort + in: query + required: false + description: |- + Sort billing profiles returned in the response. Supported sort attributes are: + + - `id` + - `name` + - `created_at` (default) + - `updated_at` + + The `asc` suffix is optional as the default sort order is ascending. The `desc` + suffix is used to specify a descending order. + schema: + $ref: '#/components/schemas/SortQuery' + explode: false + style: form + - name: filter + in: query + required: false + description: |- + Filter billing profiles returned in the response. + + To filter billing profiles by name add the following query param: + filter[name]=my-profile + schema: + $ref: '#/components/schemas/ListBillingProfilesParamsFilter' + style: deepObject responses: '200': description: Page paginated response. @@ -8266,6 +8294,15 @@ components: $ref: '#/components/schemas/StringFieldFilterExact' additionalProperties: false description: Filter options for listing add-ons. + ListBillingProfilesParamsFilter: + type: object + properties: + id: + $ref: '#/components/schemas/ULIDFieldFilter' + name: + $ref: '#/components/schemas/StringFieldFilter' + additionalProperties: false + description: Filter options for listing billing profiles. ListChargesParamsFilter: type: object properties: diff --git a/openmeter/billing/adapter/profile.go b/openmeter/billing/adapter/profile.go index f582e3be5a..ae6e55db32 100644 --- a/openmeter/billing/adapter/profile.go +++ b/openmeter/billing/adapter/profile.go @@ -8,7 +8,6 @@ import ( "github.com/samber/lo" - "github.com/openmeterio/openmeter/api" "github.com/openmeterio/openmeter/openmeter/app" "github.com/openmeterio/openmeter/openmeter/billing" "github.com/openmeterio/openmeter/openmeter/customer" @@ -23,6 +22,7 @@ import ( taxcodeadapter "github.com/openmeterio/openmeter/openmeter/taxcode/adapter" "github.com/openmeterio/openmeter/pkg/clock" "github.com/openmeterio/openmeter/pkg/convert" + "github.com/openmeterio/openmeter/pkg/filter" "github.com/openmeterio/openmeter/pkg/framework/entutils" "github.com/openmeterio/openmeter/pkg/models" "github.com/openmeterio/openmeter/pkg/pagination" @@ -160,20 +160,23 @@ func (a *adapter) ListProfiles(ctx context.Context, input billing.ListProfilesIn query = query.Where(billingprofile.DeletedAtIsNil()) } + query = filter.ApplyToQuery(query, input.ID, billingprofile.FieldID) + query = filter.ApplyToQuery(query, input.Name, billingprofile.FieldName) + order := entutils.GetOrdering(sortx.OrderDefault) if !input.Order.IsDefaultValue() { order = entutils.GetOrdering(input.Order) } switch input.OrderBy { - case api.BillingProfileOrderByCreatedAt: - query = query.Order(billingprofile.ByCreatedAt(order...)) - case api.BillingProfileOrderByUpdatedAt: - query = query.Order(billingprofile.ByUpdatedAt(order...)) - case api.BillingProfileOrderByName: + case billing.OrderByID: + query = query.Order(billingprofile.ByID(order...)) + case billing.OrderByName: query = query.Order(billingprofile.ByName(order...)) - case api.BillingProfileOrderByDefault: - query = query.Order(billingprofile.ByDefault(order...)) + case billing.OrderByUpdatedAt: + query = query.Order(billingprofile.ByUpdatedAt(order...)) + case billing.OrderByCreatedAt, billing.OrderByDefault: + fallthrough default: query = query.Order(billingprofile.ByCreatedAt(order...)) } diff --git a/openmeter/billing/httpdriver/profile.go b/openmeter/billing/httpdriver/profile.go index 9842a9357a..ad7870377c 100644 --- a/openmeter/billing/httpdriver/profile.go +++ b/openmeter/billing/httpdriver/profile.go @@ -248,7 +248,7 @@ func (h *handler) ListProfiles() ListProfilesHandler { return ListProfilesRequest{ Namespace: ns, IncludeArchived: lo.FromPtrOr(params.IncludeArchived, DefaultIncludeArchived), - OrderBy: lo.FromPtrOr(params.OrderBy, api.BillingProfileOrderByCreatedAt), + OrderBy: billing.OrderBy(lo.FromPtrOr(params.OrderBy, api.BillingProfileOrderByCreatedAt)), Order: sortx.Order(lo.FromPtrOr(params.Order, api.SortOrderDESC)), Page: pagination.Page{ diff --git a/openmeter/billing/profile.go b/openmeter/billing/profile.go index cc69acf767..4521ad36b3 100644 --- a/openmeter/billing/profile.go +++ b/openmeter/billing/profile.go @@ -12,6 +12,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/app" "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/pkg/datetime" + "github.com/openmeterio/openmeter/pkg/filter" "github.com/openmeterio/openmeter/pkg/models" "github.com/openmeterio/openmeter/pkg/pagination" "github.com/openmeterio/openmeter/pkg/sortx" @@ -366,6 +367,31 @@ type CreateProfileAppsInput = ProfileAppReferences type ListProfilesResult = pagination.Result[Profile] +type OrderBy string + +func (o OrderBy) Values() []OrderBy { + return []OrderBy{ + OrderByCreatedAt, + OrderByUpdatedAt, + OrderByName, + } +} + +func (o OrderBy) Validate() error { + if !slices.Contains(o.Values(), o) { + return fmt.Errorf("invalid order by: %s", o) + } + return nil +} + +const ( + OrderByID OrderBy = "id" + OrderByCreatedAt OrderBy = OrderBy(api.BillingProfileOrderByCreatedAt) + OrderByUpdatedAt OrderBy = OrderBy(api.BillingProfileOrderByUpdatedAt) + OrderByName OrderBy = OrderBy(api.BillingProfileOrderByName) + OrderByDefault OrderBy = OrderBy(api.BillingProfileOrderByDefault) +) + type ListProfilesInput struct { pagination.Page @@ -373,8 +399,11 @@ type ListProfilesInput struct { Namespace string IncludeArchived bool - OrderBy api.BillingProfileOrderBy + OrderBy OrderBy Order sortx.Order + + ID *filter.FilterULID + Name *filter.FilterString } func (i ListProfilesInput) Validate() error { @@ -386,7 +415,25 @@ func (i ListProfilesInput) Validate() error { return fmt.Errorf("error validating expand: %w", err) } - return nil + var errs []error + if i.ID != nil { + if err := i.ID.Validate(); err != nil { + errs = append(errs, err) + } + } + if i.Name != nil { + if err := i.Name.Validate(); err != nil { + errs = append(errs, err) + } + } + + if i.OrderBy != "" { + if err := i.OrderBy.Validate(); err != nil { + errs = append(errs, err) + } + } + + return models.NewNillableGenericValidationError(errors.Join(errs...)) } type ProfileExpand struct { diff --git a/openmeter/billing/service/profile_test.go b/openmeter/billing/service/profile_test.go new file mode 100644 index 0000000000..f534629acc --- /dev/null +++ b/openmeter/billing/service/profile_test.go @@ -0,0 +1,154 @@ +package billingservice + +import ( + "context" + "log/slog" + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/billing" + "github.com/openmeterio/openmeter/pkg/filter" + "github.com/openmeterio/openmeter/pkg/pagination" + "github.com/openmeterio/openmeter/pkg/sortx" +) + +// recordingAdapter is a minimal billing.Adapter test double that captures the +// last ListProfiles call and returns a canned result. +type recordingAdapter struct { + billing.Adapter // nil; panics if any other method is called + + receivedInput billing.ListProfilesInput + result pagination.Result[billing.BaseProfile] + err error +} + +func (a *recordingAdapter) ListProfiles(_ context.Context, input billing.ListProfilesInput) (pagination.Result[billing.BaseProfile], error) { + a.receivedInput = input + return a.result, a.err +} + +func newServiceForProfileTest(adapter *recordingAdapter) *Service { + return &Service{ + adapter: adapter, + logger: slog.Default(), + } +} + +func TestListProfiles(t *testing.T) { + const ns = "test-ns" + + type testCase struct { + name string + input billing.ListProfilesInput + wantErr bool + assertAdapter func(t *testing.T, got billing.ListProfilesInput) + } + + cases := []testCase{ + { + name: "FilterByNameEq", + input: billing.ListProfilesInput{ + Namespace: ns, + Name: &filter.FilterString{Eq: lo.ToPtr("Acme Billing")}, + }, + assertAdapter: func(t *testing.T, got billing.ListProfilesInput) { + t.Helper() + require.NotNil(t, got.Name) + require.Equal(t, lo.ToPtr("Acme Billing"), got.Name.Eq) + }, + }, + { + name: "FilterByNameContains", + input: billing.ListProfilesInput{ + Namespace: ns, + Name: &filter.FilterString{Contains: lo.ToPtr("acme")}, + }, + assertAdapter: func(t *testing.T, got billing.ListProfilesInput) { + t.Helper() + require.NotNil(t, got.Name) + require.Equal(t, lo.ToPtr("acme"), got.Name.Contains) + }, + }, + { + name: "FilterByNameOeq", + input: billing.ListProfilesInput{ + Namespace: ns, + Name: &filter.FilterString{In: lo.ToPtr([]string{"Acme", "Beta"})}, + }, + assertAdapter: func(t *testing.T, got billing.ListProfilesInput) { + t.Helper() + require.NotNil(t, got.Name) + require.Equal(t, lo.ToPtr([]string{"Acme", "Beta"}), got.Name.In) + }, + }, + { + name: "FilterByIDEq", + input: billing.ListProfilesInput{ + Namespace: ns, + ID: &filter.FilterULID{FilterString: filter.FilterString{Eq: lo.ToPtr("01HXYZ1234567890ABCDEFGHJK")}}, + }, + assertAdapter: func(t *testing.T, got billing.ListProfilesInput) { + t.Helper() + require.NotNil(t, got.ID) + require.Equal(t, lo.ToPtr("01HXYZ1234567890ABCDEFGHJK"), got.ID.Eq) + }, + }, + { + name: "SortByNameAsc", + input: billing.ListProfilesInput{ + Namespace: ns, + OrderBy: "name", + Order: sortx.OrderAsc, + }, + assertAdapter: func(t *testing.T, got billing.ListProfilesInput) { + t.Helper() + require.Equal(t, billing.OrderByName, got.OrderBy) + require.Equal(t, sortx.OrderAsc, got.Order) + }, + }, + { + name: "SortByUpdatedAt", + input: billing.ListProfilesInput{ + Namespace: ns, + OrderBy: "updatedAt", + Order: sortx.OrderDesc, + }, + assertAdapter: func(t *testing.T, got billing.ListProfilesInput) { + t.Helper() + require.Equal(t, billing.OrderByUpdatedAt, got.OrderBy) + require.Equal(t, sortx.OrderDesc, got.Order) + }, + }, + { + // both Eq and Contains set — validateSingleOperator returns ErrFilterMultipleOperators + name: "ValidationError_NameMultipleOperators", + input: billing.ListProfilesInput{ + Namespace: ns, + Name: &filter.FilterString{Eq: lo.ToPtr("x"), Contains: lo.ToPtr("y")}, + }, + wantErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Helper() + rec := &recordingAdapter{} + svc := newServiceForProfileTest(rec) + + _, err := svc.ListProfiles(t.Context(), tc.input) + + if tc.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + if tc.assertAdapter != nil { + tc.assertAdapter(t, rec.receivedInput) + } + }) + } +} From fa70d884b3771ba5a8b52f4cc40b17f145a14c0a Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Mon, 18 May 2026 15:58:26 +0200 Subject: [PATCH 2/3] fix: resolve sort format --- .../src/models/operations/billing.ts | 2 +- .../packages/aip/src/billing/operations.tsp | 4 +- api/v3/api.gen.go | 70 +++++++++---------- api/v3/openapi.yaml | 4 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts b/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts index cda74addcb..6afb7edc59 100644 --- a/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts +++ b/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts @@ -12,7 +12,7 @@ import type { export interface ListBillingProfilesQuery { /** Determines which page of the collection to retrieve. */ page?: { size?: number; number?: number } - /** Sort billing profiles returned in the response. Supported sort attributes are: - `id` - `name` - `created_at` (default) - `updated_at` The `asc` suffix is optional as the default sort order is ascending. The `desc` suffix is used to specify a descending order. */ + /** Sort billing profiles returned in the response. Supported sort attributes are: - `id` - `name` - `createdAt` (default) - `updatedAt` The `asc` suffix is optional as the default sort order is ascending. The `desc` suffix is used to specify a descending order. */ sort?: SortQueryInput /** Filter billing profiles returned in the response. To filter billing profiles by name add the following query param: filter[name]=my-profile */ filter?: ListBillingProfilesParamsFilter diff --git a/api/spec/packages/aip/src/billing/operations.tsp b/api/spec/packages/aip/src/billing/operations.tsp index 3cc78accb1..07f9d49bf6 100644 --- a/api/spec/packages/aip/src/billing/operations.tsp +++ b/api/spec/packages/aip/src/billing/operations.tsp @@ -39,8 +39,8 @@ interface BillingProfilesOperations { * * - `id` * - `name` - * - `created_at` (default) - * - `updated_at` + * - `createdAt` (default) + * - `updatedAt` * * The `asc` suffix is optional as the default sort order is ascending. The `desc` * suffix is used to specify a descending order. diff --git a/api/v3/api.gen.go b/api/v3/api.gen.go index 4c94714dc7..aca5c737da 100644 --- a/api/v3/api.gen.go +++ b/api/v3/api.gen.go @@ -5851,8 +5851,8 @@ type ListBillingProfilesParams struct { // // - `id` // - `name` - // - `created_at` (default) - // - `updated_at` + // - `createdAt` (default) + // - `updatedAt` // // The `asc` suffix is optional as the default sort order is ascending. The `desc` // suffix is used to specify a descending order. @@ -11073,39 +11073,39 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v3/openapi.yaml b/api/v3/openapi.yaml index 43415e93db..bb268bd1a9 100644 --- a/api/v3/openapi.yaml +++ b/api/v3/openapi.yaml @@ -2370,8 +2370,8 @@ paths: - `id` - `name` - - `created_at` (default) - - `updated_at` + - `createdAt` (default) + - `updatedAt` The `asc` suffix is optional as the default sort order is ascending. The `desc` suffix is used to specify a descending order. From 448689c172c9c20048e906b0975937a93da2a1aa Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Mon, 18 May 2026 16:15:27 +0200 Subject: [PATCH 3/3] fix: review fixes --- .../src/models/operations/billing.ts | 2 +- .../packages/aip/src/billing/operations.tsp | 2 +- api/v3/api.gen.go | 68 +++++++++---------- api/v3/openapi.yaml | 2 +- openmeter/billing/profile.go | 1 + openmeter/billing/service/profile_test.go | 13 ++++ 6 files changed, 51 insertions(+), 37 deletions(-) diff --git a/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts b/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts index 6afb7edc59..38f15d4399 100644 --- a/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts +++ b/api/spec/packages/aip-client-javascript/src/models/operations/billing.ts @@ -14,7 +14,7 @@ export interface ListBillingProfilesQuery { page?: { size?: number; number?: number } /** Sort billing profiles returned in the response. Supported sort attributes are: - `id` - `name` - `createdAt` (default) - `updatedAt` The `asc` suffix is optional as the default sort order is ascending. The `desc` suffix is used to specify a descending order. */ sort?: SortQueryInput - /** Filter billing profiles returned in the response. To filter billing profiles by name add the following query param: filter[name]=my-profile */ + /** Filter billing profiles returned in the response. To filter billing profiles by name add the following query param: filter[name][eq]=my-profile */ filter?: ListBillingProfilesParamsFilter } diff --git a/api/spec/packages/aip/src/billing/operations.tsp b/api/spec/packages/aip/src/billing/operations.tsp index 07f9d49bf6..09985d807a 100644 --- a/api/spec/packages/aip/src/billing/operations.tsp +++ b/api/spec/packages/aip/src/billing/operations.tsp @@ -52,7 +52,7 @@ interface BillingProfilesOperations { * Filter billing profiles returned in the response. * * To filter billing profiles by name add the following query param: - * filter[name]=my-profile + * filter[name][eq]=my-profile */ @query(#{ style: "deepObject", explode: true }) filter?: ListBillingProfilesParamsFilter, diff --git a/api/v3/api.gen.go b/api/v3/api.gen.go index aca5c737da..26ff29ada0 100644 --- a/api/v3/api.gen.go +++ b/api/v3/api.gen.go @@ -5861,7 +5861,7 @@ type ListBillingProfilesParams struct { // Filter Filter billing profiles returned in the response. // // To filter billing profiles by name add the following query param: - // filter[name]=my-profile + // filter[name][eq]=my-profile Filter *ListBillingProfilesParamsFilter `json:"filter,omitempty"` } @@ -11073,39 +11073,39 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v3/openapi.yaml b/api/v3/openapi.yaml index bb268bd1a9..dd74e60788 100644 --- a/api/v3/openapi.yaml +++ b/api/v3/openapi.yaml @@ -2386,7 +2386,7 @@ paths: Filter billing profiles returned in the response. To filter billing profiles by name add the following query param: - filter[name]=my-profile + filter[name][eq]=my-profile schema: $ref: '#/components/schemas/ListBillingProfilesParamsFilter' style: deepObject diff --git a/openmeter/billing/profile.go b/openmeter/billing/profile.go index 4521ad36b3..505df6e771 100644 --- a/openmeter/billing/profile.go +++ b/openmeter/billing/profile.go @@ -371,6 +371,7 @@ type OrderBy string func (o OrderBy) Values() []OrderBy { return []OrderBy{ + OrderByID, OrderByCreatedAt, OrderByUpdatedAt, OrderByName, diff --git a/openmeter/billing/service/profile_test.go b/openmeter/billing/service/profile_test.go index f534629acc..c801dee0bf 100644 --- a/openmeter/billing/service/profile_test.go +++ b/openmeter/billing/service/profile_test.go @@ -121,6 +121,19 @@ func TestListProfiles(t *testing.T) { require.Equal(t, sortx.OrderDesc, got.Order) }, }, + { + name: "SortByID", + input: billing.ListProfilesInput{ + Namespace: ns, + OrderBy: "id", + Order: sortx.OrderDesc, + }, + assertAdapter: func(t *testing.T, got billing.ListProfilesInput) { + t.Helper() + require.Equal(t, billing.OrderByID, got.OrderBy) + require.Equal(t, sortx.OrderDesc, got.Order) + }, + }, { // both Eq and Contains set — validateSingleOperator returns ErrFilterMultipleOperators name: "ValidationError_NameMultipleOperators",