Skip to content

Commit 25b22d4

Browse files
committed
fix(api): v3 sort by fields
1 parent 3d4c7f0 commit 25b22d4

22 files changed

Lines changed: 258 additions & 62 deletions

File tree

api/v3/apierrors/errors_ctors.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,25 @@ func NewBadRequestError(ctx context.Context, err error, invalidFields InvalidPar
212212
}
213213
}
214214

215+
// NewUnsupportedSortFieldError generates a 400 for a `sort` query value naming a
216+
// field the endpoint does not support. v3 list endpoints accept only their
217+
// declared snake_case sort fields; the reason lists the supported ones. Returned
218+
// directly by a resource's sort-field converter so handlers can propagate it
219+
// without re-wrapping.
220+
func NewUnsupportedSortFieldError(ctx context.Context, field string, supported ...string) *BaseAPIError {
221+
err := fmt.Errorf("unsupported sort field: %s", field)
222+
return NewBadRequestError(ctx, err, InvalidParameters{
223+
{
224+
Field: "sort",
225+
Reason: fmt.Sprintf(
226+
"unsupported sort field %q, supported fields: %s",
227+
field, strings.Join(supported, ", "),
228+
),
229+
Source: InvalidParamSourceQuery,
230+
},
231+
})
232+
}
233+
215234
// NewGoneError generates a gone error.
216235
func NewGoneError(ctx context.Context, err error) *BaseAPIError {
217236
return &BaseAPIError{

api/v3/handlers/addons/convert.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package addons
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67

@@ -9,6 +10,7 @@ import (
910
"github.com/samber/lo"
1011

1112
apiv3 "github.com/openmeterio/openmeter/api/v3"
13+
"github.com/openmeterio/openmeter/api/v3/apierrors"
1214
"github.com/openmeterio/openmeter/api/v3/labels"
1315
"github.com/openmeterio/openmeter/openmeter/entitlement"
1416
"github.com/openmeterio/openmeter/openmeter/productcatalog"
@@ -17,6 +19,27 @@ import (
1719
"github.com/openmeterio/openmeter/pkg/models"
1820
)
1921

22+
func FromAPIAddonSortField(ctx context.Context, field string) (addon.OrderBy, error) {
23+
switch field {
24+
case "id":
25+
return addon.OrderByID, nil
26+
case "key":
27+
return addon.OrderByKey, nil
28+
case "version":
29+
return addon.OrderByVersion, nil
30+
case "created_at":
31+
return addon.OrderByCreatedAt, nil
32+
case "updated_at":
33+
return addon.OrderByUpdatedAt, nil
34+
case "name":
35+
return addon.OrderByName, nil
36+
default:
37+
return "", apierrors.NewUnsupportedSortFieldError(
38+
ctx, field, "id", "key", "version", "created_at", "updated_at", "name",
39+
)
40+
}
41+
}
42+
2043
func ToAPILabels(source addon.Addon) *apiv3.Labels {
2144
return labels.FromMetadataAnnotations(source.Metadata, source.Annotations)
2245
}

api/v3/handlers/addons/list.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,11 @@ func (h *handler) ListAddons() ListAddonsHandler {
102102
{Field: "sort", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
103103
})
104104
}
105-
req.OrderBy = addon.OrderBy(sort.Field)
105+
orderBy, err := FromAPIAddonSortField(ctx, sort.Field)
106+
if err != nil {
107+
return ListAddonsRequest{}, err
108+
}
109+
req.OrderBy = orderBy
106110
req.Order = sort.Order.ToSortxOrder()
107111
}
108112

api/v3/handlers/currencies/convert.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
package currencies
22

33
import (
4+
"context"
45
"fmt"
56

67
v3 "github.com/openmeterio/openmeter/api/v3"
8+
"github.com/openmeterio/openmeter/api/v3/apierrors"
79
"github.com/openmeterio/openmeter/openmeter/currencies"
810
)
911

12+
func FromAPICurrencySortField(ctx context.Context, field string) (currencies.OrderBy, error) {
13+
switch field {
14+
case "code":
15+
return currencies.OrderByCode, nil
16+
case "name":
17+
return currencies.OrderByName, nil
18+
default:
19+
return "", apierrors.NewUnsupportedSortFieldError(ctx, field, "code", "name")
20+
}
21+
}
22+
1023
func FromAPIBillingCurrencyType(t v3.BillingCurrencyType) currencies.CurrencyType {
1124
switch t {
1225
case v3.BillingCurrencyTypeCustom:

api/v3/handlers/currencies/list.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (h *handler) ListCurrencies() ListCurrenciesHandler {
5252
})
5353
}
5454

55-
var orderBy string
55+
var orderBy currencies.OrderBy
5656
var order sortx.Order
5757
if params.Sort != nil {
5858
sort, err := request.ParseSortBy(*params.Sort)
@@ -61,14 +61,17 @@ func (h *handler) ListCurrencies() ListCurrenciesHandler {
6161
{Field: "sort", Reason: err.Error(), Source: apierrors.InvalidParamSourceQuery},
6262
})
6363
}
64-
orderBy = sort.Field
64+
orderBy, err = FromAPICurrencySortField(ctx, sort.Field)
65+
if err != nil {
66+
return ListCurrenciesRequest{}, err
67+
}
6568
order = sort.Order.ToSortxOrder()
6669
}
6770

6871
req := ListCurrenciesRequest{
6972
Page: page,
7073
Namespace: ns,
71-
OrderBy: currencies.OrderBy(orderBy),
74+
OrderBy: orderBy,
7275
Order: order,
7376
}
7477

api/v3/handlers/customers/charges/convert.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package charges
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67

78
"github.com/alpacahq/alpacadecimal"
89
"github.com/samber/lo"
910

1011
api "github.com/openmeterio/openmeter/api/v3"
12+
"github.com/openmeterio/openmeter/api/v3/apierrors"
1113
"github.com/openmeterio/openmeter/api/v3/handlers/billingprofiles"
1214
"github.com/openmeterio/openmeter/api/v3/handlers/plans"
1315
"github.com/openmeterio/openmeter/api/v3/labels"
@@ -23,6 +25,20 @@ import (
2325
"github.com/openmeterio/openmeter/pkg/timeutil"
2426
)
2527

28+
// FromAPICustomerChargesSortField validates a v3 (snake_case) customer charges
29+
// sort field, returning it unchanged because the charges adapter matches these
30+
// wire strings directly. Returns a 400 for any unsupported field.
31+
func FromAPICustomerChargesSortField(ctx context.Context, field string) (string, error) {
32+
switch field {
33+
case "id", "created_at", "service_period.from", "billing_period.from":
34+
return field, nil
35+
default:
36+
return "", apierrors.NewUnsupportedSortFieldError(
37+
ctx, field, "id", "created_at", "service_period.from", "billing_period.from",
38+
)
39+
}
40+
}
41+
2642
// ConvertMetadataToLabels converts domain metadata to API labels.
2743
var ConvertMetadataToLabels = labels.FromMetadata[models.Metadata]
2844

api/v3/handlers/customers/charges/list.go

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,11 @@ func (h *handler) ListCustomerCharges() ListCustomerChargesHandler {
8585
},
8686
})
8787
}
88-
if !validChargesSortField(sort.Field) {
89-
return ListCustomerChargesRequest{}, apierrors.NewBadRequestError(ctx, fmt.Errorf("unsupported sort field: %s", sort.Field), apierrors.InvalidParameters{
90-
{
91-
Field: "sort",
92-
Reason: fmt.Sprintf("unsupported sort field %q, supported fields: id, created_at, service_period.from, billing_period.from", sort.Field),
93-
Source: apierrors.InvalidParamSourceQuery,
94-
},
95-
})
88+
orderBy, err := FromAPICustomerChargesSortField(ctx, sort.Field)
89+
if err != nil {
90+
return ListCustomerChargesRequest{}, err
9691
}
97-
req.OrderBy = sort.Field
92+
req.OrderBy = orderBy
9893
req.Order = sort.Order.ToSortxOrder()
9994
}
10095

@@ -161,13 +156,3 @@ func parseChargeStatusFilterSlice(values []string) ([]meta.ChargeStatus, error)
161156

162157
return statuses, nil
163158
}
164-
165-
// validChargesSortField reports whether field is a supported sort attribute for charges.
166-
func validChargesSortField(field string) bool {
167-
switch field {
168-
case "id", "created_at", "service_period.from", "billing_period.from":
169-
return true
170-
default:
171-
return false
172-
}
173-
}

api/v3/handlers/customers/convert.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,31 @@
22
package customers
33

44
import (
5+
"context"
6+
57
"github.com/samber/lo"
68

79
api "github.com/openmeterio/openmeter/api/v3"
10+
"github.com/openmeterio/openmeter/api/v3/apierrors"
811
"github.com/openmeterio/openmeter/api/v3/labels"
912
"github.com/openmeterio/openmeter/api/v3/response"
1013
"github.com/openmeterio/openmeter/openmeter/customer"
1114
"github.com/openmeterio/openmeter/pkg/models"
1215
)
1316

17+
func FromAPICustomerSortField(ctx context.Context, field string) (string, error) {
18+
switch field {
19+
case "id":
20+
return "id", nil
21+
case "created_at":
22+
return "createdAt", nil
23+
case "name":
24+
return "name", nil
25+
default:
26+
return "", apierrors.NewUnsupportedSortFieldError(ctx, field, "id", "created_at", "name")
27+
}
28+
}
29+
1430
// goverter:variables
1531
// goverter:skipCopySameType
1632
// goverter:output:file ./convert.gen.go

api/v3/handlers/customers/list.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ func (h *handler) ListCustomers() ListCustomersHandler {
6565
},
6666
})
6767
}
68-
orderBy = sort.Field
68+
orderBy, err = FromAPICustomerSortField(ctx, sort.Field)
69+
if err != nil {
70+
return ListCustomersRequest{}, err
71+
}
6972
order = sort.Order.ToSortxOrder()
7073
}
7174

api/v3/handlers/events/convert.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ func fromAPICustomerIDFilter(ctx context.Context, f *api.ULIDFieldFilter) (*filt
115115
return &filter.FilterString{In: &values}, nil
116116
}
117117

118+
func FromAPIEventSortField(ctx context.Context, field string) (streaming.EventSortField, error) {
119+
switch field {
120+
case "time":
121+
return streaming.EventSortFieldTime, nil
122+
case "ingested_at":
123+
return streaming.EventSortFieldIngestedAt, nil
124+
case "stored_at":
125+
return streaming.EventSortFieldStoredAt, nil
126+
default:
127+
return "", apierrors.NewUnsupportedSortFieldError(
128+
ctx, field, "time", "ingested_at", "stored_at",
129+
)
130+
}
131+
}
132+
118133
// fromAPIEventSort resolves the public sort query into a backend sort field and direction.
119134
func fromAPIEventSort(ctx context.Context, sort *api.SortQuery) (streaming.EventSortField, sortx.Order, error) {
120135
if lo.FromPtr(sort) == "" {
@@ -132,23 +147,9 @@ func fromAPIEventSort(ctx context.Context, sort *api.SortQuery) (streaming.Event
132147
})
133148
}
134149

135-
var field streaming.EventSortField
136-
switch parsed.Field {
137-
case string(streaming.EventSortFieldTime):
138-
field = streaming.EventSortFieldTime
139-
case string(streaming.EventSortFieldIngestedAt):
140-
field = streaming.EventSortFieldIngestedAt
141-
case string(streaming.EventSortFieldStoredAt):
142-
field = streaming.EventSortFieldStoredAt
143-
default:
144-
err := fmt.Errorf("unsupported sort field: %q", parsed.Field)
145-
return "", "", apierrors.NewBadRequestError(ctx, err, apierrors.InvalidParameters{
146-
{
147-
Field: "sort",
148-
Reason: err.Error(),
149-
Source: apierrors.InvalidParamSourceQuery,
150-
},
151-
})
150+
field, err := FromAPIEventSortField(ctx, parsed.Field)
151+
if err != nil {
152+
return "", "", err
152153
}
153154

154155
// If the caller did not supply an explicit asc/desc suffix, default to

0 commit comments

Comments
 (0)