From 9f8c6a02af0f0728f477992b0db560425d9216cc Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Thu, 7 May 2026 11:15:58 +0200 Subject: [PATCH 1/9] feat(subscription-addon-v3): implement get subscription addon v3 endpoint --- .../packages/aip/src/subscriptions/index.tsp | 1 + .../aip/src/subscriptions/operations.tsp | 14 + api/v3/api.gen.go | 461 ++++++++++-------- .../subscriptions/subscriptionaddons/get.go | 54 ++ .../subscriptionaddons/handler.go | 15 +- .../subscriptions/subscriptionaddons/list.go | 2 +- api/v3/openapi.yaml | 35 ++ api/v3/server/routes.go | 8 + api/v3/server/server.go | 4 + openmeter/server/server_test.go | 2 +- openmeter/subscription/addon/http/get.go | 5 +- .../addon/repo/subscriptionaddon.go | 27 +- openmeter/subscription/addon/repository.go | 2 +- openmeter/subscription/addon/service.go | 34 +- .../subscription/addon/service/create_test.go | 4 +- .../subscription/addon/service/service.go | 16 +- .../subscription/workflow/service/addon.go | 4 +- 17 files changed, 454 insertions(+), 234 deletions(-) create mode 100644 api/v3/handlers/subscriptions/subscriptionaddons/get.go diff --git a/api/spec/packages/aip/src/subscriptions/index.tsp b/api/spec/packages/aip/src/subscriptions/index.tsp index 5dc31db5d4..9a7ae7fabf 100644 --- a/api/spec/packages/aip/src/subscriptions/index.tsp +++ b/api/spec/packages/aip/src/subscriptions/index.tsp @@ -2,3 +2,4 @@ import "./subscription.tsp"; import "./subscriptionaddon.tsp"; import "./operations.tsp"; import "./reference.tsp"; +import "./subscriptionaddon.tsp"; diff --git a/api/spec/packages/aip/src/subscriptions/operations.tsp b/api/spec/packages/aip/src/subscriptions/operations.tsp index 092a0e660d..5066663d22 100644 --- a/api/spec/packages/aip/src/subscriptions/operations.tsp +++ b/api/spec/packages/aip/src/subscriptions/operations.tsp @@ -174,4 +174,18 @@ interface SubscriptionAddonOperations { @query(#{ name: "sort" }) sort?: Common.SortQuery, ): Shared.PagePaginatedResponse | Common.NotFound | Common.ErrorResponses; + + /** + * Get an add-on association for a subscription. + */ + @get + @route("/{subscriptionAddonId}") + @operationId("get-subscription-addon") + @summary("Get add-on association for subscription") + @extension(Shared.UnstableExtension, true) + @extension(Shared.InternalExtension, true) + getAddon( + @path subscriptionId: Shared.ULID, + @path subscriptionAddonId: Shared.ULID, + ): Shared.GetResponse | Common.ErrorResponses | Common.NotFound; } diff --git a/api/v3/api.gen.go b/api/v3/api.gen.go index a51baf9a83..993637524e 100644 --- a/api/v3/api.gen.go +++ b/api/v3/api.gen.go @@ -7098,6 +7098,9 @@ type ServerInterface interface { // List subscription addons // (GET /openmeter/subscriptions/{subscriptionId}/addons) ListSubscriptionAddons(w http.ResponseWriter, r *http.Request, subscriptionId ULID, params ListSubscriptionAddonsParams) + // Get add-on association for subscription + // (GET /openmeter/subscriptions/{subscriptionId}/addons/{subscriptionAddonId}) + GetSubscriptionAddon(w http.ResponseWriter, r *http.Request, subscriptionId ULID, subscriptionAddonId ULID) // Cancel subscription // (POST /openmeter/subscriptions/{subscriptionId}/cancel) CancelSubscription(w http.ResponseWriter, r *http.Request, subscriptionId ULID) @@ -7578,6 +7581,12 @@ func (_ Unimplemented) ListSubscriptionAddons(w http.ResponseWriter, r *http.Req w.WriteHeader(http.StatusNotImplemented) } +// Get add-on association for subscription +// (GET /openmeter/subscriptions/{subscriptionId}/addons/{subscriptionAddonId}) +func (_ Unimplemented) GetSubscriptionAddon(w http.ResponseWriter, r *http.Request, subscriptionId ULID, subscriptionAddonId ULID) { + w.WriteHeader(http.StatusNotImplemented) +} + // Cancel subscription // (POST /openmeter/subscriptions/{subscriptionId}/cancel) func (_ Unimplemented) CancelSubscription(w http.ResponseWriter, r *http.Request, subscriptionId ULID) { @@ -9734,6 +9743,40 @@ func (siw *ServerInterfaceWrapper) ListSubscriptionAddons(w http.ResponseWriter, handler.ServeHTTP(w, r) } +// GetSubscriptionAddon operation middleware +func (siw *ServerInterfaceWrapper) GetSubscriptionAddon(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "subscriptionId" ------------- + var subscriptionId ULID + + err = runtime.BindStyledParameterWithOptions("simple", "subscriptionId", chi.URLParam(r, "subscriptionId"), &subscriptionId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "subscriptionId", Err: err}) + return + } + + // ------------- Path parameter "subscriptionAddonId" ------------- + var subscriptionAddonId ULID + + err = runtime.BindStyledParameterWithOptions("simple", "subscriptionAddonId", chi.URLParam(r, "subscriptionAddonId"), &subscriptionAddonId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "subscriptionAddonId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetSubscriptionAddon(w, r, subscriptionId, subscriptionAddonId) + })) + + for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { + handler = siw.HandlerMiddlewares[i](handler) + } + + handler.ServeHTTP(w, r) +} + // CancelSubscription operation middleware func (siw *ServerInterfaceWrapper) CancelSubscription(w http.ResponseWriter, r *http.Request) { @@ -10271,6 +10314,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/openmeter/subscriptions/{subscriptionId}/addons", wrapper.ListSubscriptionAddons) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/openmeter/subscriptions/{subscriptionId}/addons/{subscriptionAddonId}", wrapper.GetSubscriptionAddon) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/openmeter/subscriptions/{subscriptionId}/cancel", wrapper.CancelSubscription) }) @@ -10739,213 +10785,214 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/get.go b/api/v3/handlers/subscriptions/subscriptionaddons/get.go new file mode 100644 index 0000000000..76af122ca9 --- /dev/null +++ b/api/v3/handlers/subscriptions/subscriptionaddons/get.go @@ -0,0 +1,54 @@ +package subscriptionaddons + +import ( + "context" + "net/http" + + apiv3 "github.com/openmeterio/openmeter/api/v3" + subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" + "github.com/openmeterio/openmeter/pkg/framework/commonhttp" + "github.com/openmeterio/openmeter/pkg/framework/transport/httptransport" + "github.com/openmeterio/openmeter/pkg/models" +) + +type ( + GetSubscriptionAddonRequest = subscriptionaddon.GetSubscriptionAddonInput + GetSubscriptionAddonParams struct { + SubscriptionID string + SubscriptionAddonID string + } + GetSubscriptionAddonResponse = apiv3.SubscriptionAddon + GetSubscriptionAddonHandler httptransport.HandlerWithArgs[GetSubscriptionAddonRequest, GetSubscriptionAddonResponse, GetSubscriptionAddonParams] +) + +func (h *handler) GetSubscriptionAddons() GetSubscriptionAddonHandler { + return httptransport.NewHandlerWithArgs( + func(ctx context.Context, r *http.Request, params GetSubscriptionAddonParams) (GetSubscriptionAddonRequest, error) { + ns, err := h.resolveNamespace(ctx) + if err != nil { + return GetSubscriptionAddonRequest{}, err + } + + return GetSubscriptionAddonRequest{ + NamespacedID: models.NamespacedID{ + Namespace: ns, + ID: params.SubscriptionAddonID, + }, + SubscriptionID: params.SubscriptionID, + }, nil + }, + func(ctx context.Context, request GetSubscriptionAddonRequest) (GetSubscriptionAddonResponse, error) { + a, err := h.addonService.Get(ctx, request) + if err != nil { + return GetSubscriptionAddonResponse{}, err + } + + return toAPISubscriptionAddon(*a) + }, + commonhttp.JSONResponseEncoderWithStatus[GetSubscriptionAddonResponse](http.StatusOK), + httptransport.AppendOptions( + h.options, + httptransport.WithOperationName("get-subscription-addon"), + )..., + ) +} diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/handler.go b/api/v3/handlers/subscriptions/subscriptionaddons/handler.go index 4526a172e5..096748caef 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/handler.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/handler.go @@ -9,22 +9,23 @@ import ( type Handler interface { ListSubscriptionAddons() ListSubscriptionAddonsHandler + GetSubscriptionAddons() GetSubscriptionAddonHandler } type handler struct { - resolveNamespace func(ctx context.Context) (string, error) - subscriptionAddonService subscriptionaddon.Service - options []httptransport.HandlerOption + resolveNamespace func(ctx context.Context) (string, error) + addonService subscriptionaddon.Service + options []httptransport.HandlerOption } func New( resolveNamespace func(ctx context.Context) (string, error), - subscriptionAddonService subscriptionaddon.Service, + addonService subscriptionaddon.Service, options ...httptransport.HandlerOption, ) Handler { return &handler{ - resolveNamespace: resolveNamespace, - subscriptionAddonService: subscriptionAddonService, - options: options, + resolveNamespace: resolveNamespace, + addonService: addonService, + options: options, } } diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/list.go b/api/v3/handlers/subscriptions/subscriptionaddons/list.go index 1dd2bab12c..7ab5ea65a2 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/list.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/list.go @@ -85,7 +85,7 @@ func (h *handler) ListSubscriptionAddons() ListSubscriptionAddonsHandler { }, nil }, func(ctx context.Context, req ListSubscriptionAddonsRequest) (ListSubscriptionAddonsResponse, error) { - res, err := h.subscriptionAddonService.List(ctx, req.SubscriptionID.Namespace, req.Input) + res, err := h.addonService.List(ctx, req.SubscriptionID.Namespace, req.Input) if err != nil { return ListSubscriptionAddonsResponse{}, fmt.Errorf("failed to list subscription addons: %w", err) } diff --git a/api/v3/openapi.yaml b/api/v3/openapi.yaml index 05868653db..be09ba61ec 100644 --- a/api/v3/openapi.yaml +++ b/api/v3/openapi.yaml @@ -2648,6 +2648,41 @@ paths: $ref: '#/components/responses/NotFound' tags: - OpenMeter Subscriptions + /openmeter/subscriptions/{subscriptionId}/addons/{subscriptionAddonId}: + get: + operationId: get-subscription-addon + summary: Get add-on association for subscription + description: Get an add-on association for a subscription. + parameters: + - name: subscriptionId + in: path + required: true + schema: + $ref: '#/components/schemas/ULID' + - name: subscriptionAddonId + in: path + required: true + schema: + $ref: '#/components/schemas/ULID' + responses: + '200': + description: SubscriptionAddon response. + content: + application/json: + schema: + $ref: '#/components/schemas/SubscriptionAddon' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + tags: + - OpenMeter Subscriptions + x-internal: true + x-unstable: true /openmeter/subscriptions/{subscriptionId}/cancel: post: operationId: cancel-subscription diff --git a/api/v3/server/routes.go b/api/v3/server/routes.go index 7d5b7063d0..7359a7a7e4 100644 --- a/api/v3/server/routes.go +++ b/api/v3/server/routes.go @@ -109,6 +109,7 @@ func (s *Server) ChangeSubscription(w http.ResponseWriter, r *http.Request, subs s.subscriptionsHandler.ChangeSubscription().With(subscriptionId).ServeHTTP(w, r) } +// Subscription Addons func (s *Server) ListSubscriptionAddons(w http.ResponseWriter, r *http.Request, subscriptionId api.ULID, params api.ListSubscriptionAddonsParams) { s.subscriptionAddonsHandler.ListSubscriptionAddons().With(subscriptionaddonshandler.ListSubscriptionAddonsParams{ SubscriptionID: subscriptionId, @@ -116,6 +117,13 @@ func (s *Server) ListSubscriptionAddons(w http.ResponseWriter, r *http.Request, }).ServeHTTP(w, r) } +func (s *Server) GetSubscriptionAddon(w http.ResponseWriter, r *http.Request, subscriptionId api.ULID, subscriptionAddonId api.ULID) { + s.subscriptionAddonsHandler.GetSubscriptionAddons().With(subscriptionaddonshandler.GetSubscriptionAddonParams{ + SubscriptionID: subscriptionId, + SubscriptionAddonID: subscriptionAddonId, + }).ServeHTTP(w, r) +} + // Apps func (s *Server) ListApps(w http.ResponseWriter, r *http.Request, params api.ListAppsParams) { diff --git a/api/v3/server/server.go b/api/v3/server/server.go index c9bf2e25f7..88b7b62b0e 100644 --- a/api/v3/server/server.go +++ b/api/v3/server/server.go @@ -214,6 +214,10 @@ func (c *Config) Validate() error { errs = append(errs, errors.New("addon service is required")) } + if c.SubscriptionAddonService == nil { + errs = append(errs, errors.New("subscription addon service is required")) + } + return errors.Join(errs...) } diff --git a/openmeter/server/server_test.go b/openmeter/server/server_test.go index 4a1c2d1e3d..cb3aa36b43 100644 --- a/openmeter/server/server_test.go +++ b/openmeter/server/server_test.go @@ -1680,7 +1680,7 @@ func (n NoopSubscriptionAddonService) ChangeQuantity(ctx context.Context, subscr return nil, nil } -func (n NoopSubscriptionAddonService) Get(ctx context.Context, addonId models.NamespacedID) (*subscriptionaddon.SubscriptionAddon, error) { +func (n NoopSubscriptionAddonService) Get(ctx context.Context, params subscriptionaddon.GetSubscriptionAddonInput) (*subscriptionaddon.SubscriptionAddon, error) { return nil, nil } diff --git a/openmeter/subscription/addon/http/get.go b/openmeter/subscription/addon/http/get.go index 5ad14b3178..92db086d4d 100644 --- a/openmeter/subscription/addon/http/get.go +++ b/openmeter/subscription/addon/http/get.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/openmeterio/openmeter/api" + subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" "github.com/openmeterio/openmeter/pkg/framework/commonhttp" "github.com/openmeterio/openmeter/pkg/framework/transport/httptransport" "github.com/openmeterio/openmeter/pkg/models" @@ -38,7 +39,9 @@ func (h *handler) GetSubscriptionAddon() GetSubscriptionAddonHandler { }, nil }, func(ctx context.Context, req GetSubscriptionAddonRequest) (GetSubscriptionAddonResponse, error) { - res, err := h.SubscriptionAddonService.Get(ctx, req.SubscriptionAddonID) + res, err := h.SubscriptionAddonService.Get(ctx, subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: req.SubscriptionAddonID, + }) if err != nil { return GetSubscriptionAddonResponse{}, err } diff --git a/openmeter/subscription/addon/repo/subscriptionaddon.go b/openmeter/subscription/addon/repo/subscriptionaddon.go index 3cb73dca91..2182b63d56 100644 --- a/openmeter/subscription/addon/repo/subscriptionaddon.go +++ b/openmeter/subscription/addon/repo/subscriptionaddon.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/openmeterio/openmeter/openmeter/ent/db" + addondb "github.com/openmeterio/openmeter/openmeter/ent/db/addon" dbsubscriptionaddon "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" dbsubscriptionaddonquantity "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddonquantity" subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" @@ -51,18 +52,28 @@ func (r *subscriptionAddonRepo) Create(ctx context.Context, namespace string, in } // Get retrieves a subscription addon by ID -func (r *subscriptionAddonRepo) Get(ctx context.Context, id models.NamespacedID) (*subscriptionaddon.SubscriptionAddon, error) { +func (r *subscriptionAddonRepo) Get(ctx context.Context, params subscriptionaddon.GetSubscriptionAddonInput) (*subscriptionaddon.SubscriptionAddon, error) { return entutils.TransactingRepo(ctx, r, func(ctx context.Context, repo *subscriptionAddonRepo) (*subscriptionaddon.SubscriptionAddon, error) { - entity, err := querySubscriptionAddon(repo.db.SubscriptionAddon.Query()). - Where( - dbsubscriptionaddon.ID(id.ID), - dbsubscriptionaddon.Namespace(id.Namespace), - ). - Only(ctx) + query := querySubscriptionAddon(repo.db.SubscriptionAddon.Query()) + + if params.ID != "" { + query = query.Where( + dbsubscriptionaddon.ID(params.ID), + dbsubscriptionaddon.Namespace(params.Namespace), + ) + } else { + query = query.Where( + dbsubscriptionaddon.Namespace(params.Namespace), + dbsubscriptionaddon.SubscriptionID(params.SubscriptionID), + dbsubscriptionaddon.HasAddonWith(addondb.Or(addondb.ID(params.AddonIDOrKey), addondb.Key(params.AddonIDOrKey))), + ) + } + + entity, err := query.Only(ctx) if err != nil { if db.IsNotFound(err) { return nil, models.NewGenericNotFoundError( - fmt.Errorf("subscription addon %s not found", id.ID), + fmt.Errorf("subscription addon %s not found", params.ID), ) } diff --git a/openmeter/subscription/addon/repository.go b/openmeter/subscription/addon/repository.go index 6ff8b5a1d0..bd7600ea90 100644 --- a/openmeter/subscription/addon/repository.go +++ b/openmeter/subscription/addon/repository.go @@ -31,7 +31,7 @@ type ListSubscriptionAddonRepositoryInput struct { type SubscriptionAddonRepository interface { Create(ctx context.Context, namespace string, input CreateSubscriptionAddonRepositoryInput) (*models.NamespacedID, error) - Get(ctx context.Context, id models.NamespacedID) (*SubscriptionAddon, error) + Get(ctx context.Context, params GetSubscriptionAddonInput) (*SubscriptionAddon, error) List(ctx context.Context, namespace string, filter ListSubscriptionAddonRepositoryInput) (pagination.Result[SubscriptionAddon], error) } diff --git a/openmeter/subscription/addon/service.go b/openmeter/subscription/addon/service.go index 931e8a2b77..3b0d163e2f 100644 --- a/openmeter/subscription/addon/service.go +++ b/openmeter/subscription/addon/service.go @@ -37,7 +37,7 @@ func (f OrderBy) Validate() error { type Service interface { Create(ctx context.Context, namespace string, input CreateSubscriptionAddonInput) (*SubscriptionAddon, error) - Get(ctx context.Context, id models.NamespacedID) (*SubscriptionAddon, error) + Get(ctx context.Context, id GetSubscriptionAddonInput) (*SubscriptionAddon, error) List(ctx context.Context, namespace string, input ListSubscriptionAddonsInput) (pagination.Result[SubscriptionAddon], error) ChangeQuantity(ctx context.Context, id models.NamespacedID, input CreateSubscriptionAddonQuantityInput) (*SubscriptionAddon, error) @@ -67,3 +67,35 @@ func (i ListSubscriptionAddonsInput) Validate() error { return errors.Join(errs...) } + +// GetSubscriptionAddonInput defines the input parameters for fetching subscription add-on assignment either by SubscriptionAddon.ID or +// by the subscription and add-on identifiers. +type GetSubscriptionAddonInput struct { + models.NamespacedID + + // SubscriptionID + SubscriptionID string `json:"subscriptionIdOrKey"` + + // AddonIDOrKey + AddonIDOrKey string `json:"addonIdOrKey"` +} + +func (i GetSubscriptionAddonInput) Validate() error { + var errs []error + + if err := i.NamespacedID.Validate(); err != nil { + errs = append(errs, err) + } + + if i.ID == "" { + if i.SubscriptionID == "" { + errs = append(errs, errors.New("subscription id or key must be provided if assignment id is not provided")) + } + + if i.AddonIDOrKey == "" { + errs = append(errs, errors.New("add-on id or key must be provided if assignment id is not provided")) + } + } + + return errors.Join(errs...) +} diff --git a/openmeter/subscription/addon/service/create_test.go b/openmeter/subscription/addon/service/create_test.go index 8bd90e7414..ec1418a32a 100644 --- a/openmeter/subscription/addon/service/create_test.go +++ b/openmeter/subscription/addon/service/create_test.go @@ -493,7 +493,9 @@ func TestAddonServiceCreate(t *testing.T) { require.Nil(t, err) // Now, let's fetch the subscription addon - subAdd2, err := deps.SubscriptionAddonService.Get(context.Background(), subAdd1.NamespacedID) + subAdd2, err := deps.SubscriptionAddonService.Get(context.Background(), subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: subAdd1.NamespacedID, + }) require.Nil(t, err) t.Run("Should create addon as specified", func(t *testing.T) { diff --git a/openmeter/subscription/addon/service/service.go b/openmeter/subscription/addon/service/service.go index 0fdfc463e4..7954927fea 100644 --- a/openmeter/subscription/addon/service/service.go +++ b/openmeter/subscription/addon/service/service.go @@ -178,7 +178,9 @@ func (s *service) Create(ctx context.Context, ns string, input subscriptionaddon } // Let's fetch the addon again and return it - subscriptionAddon, err := s.cfg.SubAddRepo.Get(ctx, *subAdd) + subscriptionAddon, err := s.cfg.SubAddRepo.Get(ctx, subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: *subAdd, + }) if err != nil { return nil, fmt.Errorf("failed to get subscription add-on: %w", err) } @@ -198,8 +200,8 @@ func (s *service) Create(ctx context.Context, ns string, input subscriptionaddon } // Get gets a subscription add-on -func (s *service) Get(ctx context.Context, id models.NamespacedID) (*subscriptionaddon.SubscriptionAddon, error) { - return s.cfg.SubAddRepo.Get(ctx, id) +func (s *service) Get(ctx context.Context, params subscriptionaddon.GetSubscriptionAddonInput) (*subscriptionaddon.SubscriptionAddon, error) { + return s.cfg.SubAddRepo.Get(ctx, params) } // List lists subscription add-ons @@ -218,7 +220,9 @@ func (s *service) ChangeQuantity(ctx context.Context, id models.NamespacedID, in return nil, fmt.Errorf("invalid input: %w", err) } - subAdd, err := s.cfg.SubAddRepo.Get(ctx, id) + subAdd, err := s.cfg.SubAddRepo.Get(ctx, subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: id, + }) if err != nil { return nil, fmt.Errorf("failed to get subscription add-on: %w", err) } @@ -292,7 +296,9 @@ func (s *service) ChangeQuantity(ctx context.Context, id models.NamespacedID, in } // Let's fetch the addon and return it - subscriptionAddon, err := s.cfg.SubAddRepo.Get(ctx, id) + subscriptionAddon, err := s.cfg.SubAddRepo.Get(ctx, subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: id, + }) if err != nil { return nil, fmt.Errorf("failed to get subscription add-on: %w", err) } diff --git a/openmeter/subscription/workflow/service/addon.go b/openmeter/subscription/workflow/service/addon.go index 4e63691b71..91f8757e32 100644 --- a/openmeter/subscription/workflow/service/addon.go +++ b/openmeter/subscription/workflow/service/addon.go @@ -123,7 +123,9 @@ func (s *service) ChangeAddonQuantity(ctx context.Context, subscriptionID models return def1, def2, models.NewGenericValidationError(fmt.Errorf("subscription and subscription addon are in different namespaces")) } - subsAdd, err := s.AddonService.Get(ctx, changeInp.SubscriptionAddonID) + subsAdd, err := s.AddonService.Get(ctx, subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: changeInp.SubscriptionAddonID, + }) if err != nil { return def1, def2, fmt.Errorf("failed to get subscription addon: %w", err) } From 42585f2f7e4199c9843693fda91f9b127d8f41e7 Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Thu, 7 May 2026 15:14:09 +0200 Subject: [PATCH 2/9] fix: fix minor issues and add extra checks --- .../subscriptions/subscriptionaddons/convert.go | 8 ++++++-- .../handlers/subscriptions/subscriptionaddons/get.go | 2 +- .../subscriptions/subscriptionaddons/handler.go | 2 +- api/v3/server/routes.go | 2 +- openmeter/subscription/addon/repo/subscriptionaddon.go | 3 +++ openmeter/subscription/addon/service.go | 10 +++++++++- openmeter/subscription/addon/service/service.go | 3 +++ 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/convert.go b/api/v3/handlers/subscriptions/subscriptionaddons/convert.go index 4551ec5880..7d08cc2a48 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/convert.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/convert.go @@ -2,6 +2,8 @@ package subscriptionaddons import ( "errors" + "fmt" + "time" "github.com/samber/lo" @@ -15,8 +17,10 @@ import ( func toAPISubscriptionAddon(addon subscriptionaddon.SubscriptionAddon) (apiv3.SubscriptionAddon, error) { now := clock.Now() - // If no instance is active at `now`, quantity stays 0. - inst, _ := addon.GetInstanceAt(now) + inst, found := addon.GetInstanceAt(now) + if !found { + return apiv3.SubscriptionAddon{}, fmt.Errorf("no instance is active at %s", now.Format(time.RFC3339)) + } pers := lo.Map(addon.GetInstances(), func(i subscriptionaddon.SubscriptionAddonInstance, _ int) timeutil.OpenPeriod { return i.AsPeriod() diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/get.go b/api/v3/handlers/subscriptions/subscriptionaddons/get.go index 76af122ca9..bc525bb823 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/get.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/get.go @@ -21,7 +21,7 @@ type ( GetSubscriptionAddonHandler httptransport.HandlerWithArgs[GetSubscriptionAddonRequest, GetSubscriptionAddonResponse, GetSubscriptionAddonParams] ) -func (h *handler) GetSubscriptionAddons() GetSubscriptionAddonHandler { +func (h *handler) GetSubscriptionAddon() GetSubscriptionAddonHandler { return httptransport.NewHandlerWithArgs( func(ctx context.Context, r *http.Request, params GetSubscriptionAddonParams) (GetSubscriptionAddonRequest, error) { ns, err := h.resolveNamespace(ctx) diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/handler.go b/api/v3/handlers/subscriptions/subscriptionaddons/handler.go index 096748caef..5c21c685ee 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/handler.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/handler.go @@ -9,7 +9,7 @@ import ( type Handler interface { ListSubscriptionAddons() ListSubscriptionAddonsHandler - GetSubscriptionAddons() GetSubscriptionAddonHandler + GetSubscriptionAddon() GetSubscriptionAddonHandler } type handler struct { diff --git a/api/v3/server/routes.go b/api/v3/server/routes.go index 7359a7a7e4..e70399f98a 100644 --- a/api/v3/server/routes.go +++ b/api/v3/server/routes.go @@ -118,7 +118,7 @@ func (s *Server) ListSubscriptionAddons(w http.ResponseWriter, r *http.Request, } func (s *Server) GetSubscriptionAddon(w http.ResponseWriter, r *http.Request, subscriptionId api.ULID, subscriptionAddonId api.ULID) { - s.subscriptionAddonsHandler.GetSubscriptionAddons().With(subscriptionaddonshandler.GetSubscriptionAddonParams{ + s.subscriptionAddonsHandler.GetSubscriptionAddon().With(subscriptionaddonshandler.GetSubscriptionAddonParams{ SubscriptionID: subscriptionId, SubscriptionAddonID: subscriptionAddonId, }).ServeHTTP(w, r) diff --git a/openmeter/subscription/addon/repo/subscriptionaddon.go b/openmeter/subscription/addon/repo/subscriptionaddon.go index 2182b63d56..698ccf0f00 100644 --- a/openmeter/subscription/addon/repo/subscriptionaddon.go +++ b/openmeter/subscription/addon/repo/subscriptionaddon.go @@ -61,6 +61,9 @@ func (r *subscriptionAddonRepo) Get(ctx context.Context, params subscriptionaddo dbsubscriptionaddon.ID(params.ID), dbsubscriptionaddon.Namespace(params.Namespace), ) + if params.SubscriptionID != "" { + query = query.Where(dbsubscriptionaddon.SubscriptionID(params.SubscriptionID)) + } } else { query = query.Where( dbsubscriptionaddon.Namespace(params.Namespace), diff --git a/openmeter/subscription/addon/service.go b/openmeter/subscription/addon/service.go index 3b0d163e2f..da88550c7c 100644 --- a/openmeter/subscription/addon/service.go +++ b/openmeter/subscription/addon/service.go @@ -6,6 +6,8 @@ import ( "fmt" "slices" + "github.com/oklog/ulid/v2" + "github.com/openmeterio/openmeter/pkg/models" "github.com/openmeterio/openmeter/pkg/pagination" "github.com/openmeterio/openmeter/pkg/sortx" @@ -74,7 +76,7 @@ type GetSubscriptionAddonInput struct { models.NamespacedID // SubscriptionID - SubscriptionID string `json:"subscriptionIdOrKey"` + SubscriptionID string `json:"subscriptionId"` // AddonIDOrKey AddonIDOrKey string `json:"addonIdOrKey"` @@ -90,11 +92,17 @@ func (i GetSubscriptionAddonInput) Validate() error { if i.ID == "" { if i.SubscriptionID == "" { errs = append(errs, errors.New("subscription id or key must be provided if assignment id is not provided")) + } else if _, err := ulid.Parse(i.SubscriptionID); err != nil { + errs = append(errs, errors.New("subscription id is not a valid ULID")) } if i.AddonIDOrKey == "" { errs = append(errs, errors.New("add-on id or key must be provided if assignment id is not provided")) } + } else { + if _, err := ulid.Parse(i.ID); err != nil { + errs = append(errs, errors.New("assignment id is not a valid ULID")) + } } return errors.Join(errs...) diff --git a/openmeter/subscription/addon/service/service.go b/openmeter/subscription/addon/service/service.go index 7954927fea..09849227e6 100644 --- a/openmeter/subscription/addon/service/service.go +++ b/openmeter/subscription/addon/service/service.go @@ -201,6 +201,9 @@ func (s *service) Create(ctx context.Context, ns string, input subscriptionaddon // Get gets a subscription add-on func (s *service) Get(ctx context.Context, params subscriptionaddon.GetSubscriptionAddonInput) (*subscriptionaddon.SubscriptionAddon, error) { + if err := params.Validate(); err != nil { + return nil, models.NewGenericValidationError(err) + } return s.cfg.SubAddRepo.Get(ctx, params) } From 731d29fa9abe30900ad280f0544a5f151218a0bd Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Thu, 7 May 2026 15:42:51 +0200 Subject: [PATCH 3/9] fix: review fixes --- api/v3/handlers/subscriptions/subscriptionaddons/get.go | 5 +++++ openmeter/subscription/addon/service.go | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/get.go b/api/v3/handlers/subscriptions/subscriptionaddons/get.go index bc525bb823..2858d1f157 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/get.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/get.go @@ -2,6 +2,7 @@ package subscriptionaddons import ( "context" + "errors" "net/http" apiv3 "github.com/openmeterio/openmeter/api/v3" @@ -43,6 +44,10 @@ func (h *handler) GetSubscriptionAddon() GetSubscriptionAddonHandler { return GetSubscriptionAddonResponse{}, err } + if a == nil { + return GetSubscriptionAddonResponse{}, models.NewGenericNotFoundError(errors.New("subscription addon not found")) + } + return toAPISubscriptionAddon(*a) }, commonhttp.JSONResponseEncoderWithStatus[GetSubscriptionAddonResponse](http.StatusOK), diff --git a/openmeter/subscription/addon/service.go b/openmeter/subscription/addon/service.go index da88550c7c..1471ae3c44 100644 --- a/openmeter/subscription/addon/service.go +++ b/openmeter/subscription/addon/service.go @@ -85,13 +85,13 @@ type GetSubscriptionAddonInput struct { func (i GetSubscriptionAddonInput) Validate() error { var errs []error - if err := i.NamespacedID.Validate(); err != nil { - errs = append(errs, err) + if i.NamespacedID.Namespace == "" { + errs = append(errs, errors.New("namespace is required")) } if i.ID == "" { if i.SubscriptionID == "" { - errs = append(errs, errors.New("subscription id or key must be provided if assignment id is not provided")) + errs = append(errs, errors.New("subscription id must be provided if assignment id is not provided")) } else if _, err := ulid.Parse(i.SubscriptionID); err != nil { errs = append(errs, errors.New("subscription id is not a valid ULID")) } From 7d6dfbdddf1d872b24c7132a3458121fd6db0c18 Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Wed, 13 May 2026 10:14:08 +0200 Subject: [PATCH 4/9] fix: remove AddonIDOrKey --- .../addon/repo/subscriptionaddon.go | 21 +++++----------- openmeter/subscription/addon/service.go | 25 ++++++------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/openmeter/subscription/addon/repo/subscriptionaddon.go b/openmeter/subscription/addon/repo/subscriptionaddon.go index 698ccf0f00..8411428782 100644 --- a/openmeter/subscription/addon/repo/subscriptionaddon.go +++ b/openmeter/subscription/addon/repo/subscriptionaddon.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/openmeterio/openmeter/openmeter/ent/db" - addondb "github.com/openmeterio/openmeter/openmeter/ent/db/addon" dbsubscriptionaddon "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" dbsubscriptionaddonquantity "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddonquantity" subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" @@ -56,20 +55,12 @@ func (r *subscriptionAddonRepo) Get(ctx context.Context, params subscriptionaddo return entutils.TransactingRepo(ctx, r, func(ctx context.Context, repo *subscriptionAddonRepo) (*subscriptionaddon.SubscriptionAddon, error) { query := querySubscriptionAddon(repo.db.SubscriptionAddon.Query()) - if params.ID != "" { - query = query.Where( - dbsubscriptionaddon.ID(params.ID), - dbsubscriptionaddon.Namespace(params.Namespace), - ) - if params.SubscriptionID != "" { - query = query.Where(dbsubscriptionaddon.SubscriptionID(params.SubscriptionID)) - } - } else { - query = query.Where( - dbsubscriptionaddon.Namespace(params.Namespace), - dbsubscriptionaddon.SubscriptionID(params.SubscriptionID), - dbsubscriptionaddon.HasAddonWith(addondb.Or(addondb.ID(params.AddonIDOrKey), addondb.Key(params.AddonIDOrKey))), - ) + query = query.Where( + dbsubscriptionaddon.ID(params.ID), + dbsubscriptionaddon.Namespace(params.Namespace), + ) + if params.SubscriptionID != "" { + query = query.Where(dbsubscriptionaddon.SubscriptionID(params.SubscriptionID)) } entity, err := query.Only(ctx) diff --git a/openmeter/subscription/addon/service.go b/openmeter/subscription/addon/service.go index 1471ae3c44..94801e22a4 100644 --- a/openmeter/subscription/addon/service.go +++ b/openmeter/subscription/addon/service.go @@ -77,31 +77,22 @@ type GetSubscriptionAddonInput struct { // SubscriptionID SubscriptionID string `json:"subscriptionId"` - - // AddonIDOrKey - AddonIDOrKey string `json:"addonIdOrKey"` } func (i GetSubscriptionAddonInput) Validate() error { var errs []error - if i.NamespacedID.Namespace == "" { - errs = append(errs, errors.New("namespace is required")) + if err := i.NamespacedID.Validate(); err != nil { + errs = append(errs, err) } - if i.ID == "" { - if i.SubscriptionID == "" { - errs = append(errs, errors.New("subscription id must be provided if assignment id is not provided")) - } else if _, err := ulid.Parse(i.SubscriptionID); err != nil { - errs = append(errs, errors.New("subscription id is not a valid ULID")) - } + if _, err := ulid.Parse(i.NamespacedID.ID); err != nil { + errs = append(errs, errors.New("assignment id is not a valid ULID")) + } - if i.AddonIDOrKey == "" { - errs = append(errs, errors.New("add-on id or key must be provided if assignment id is not provided")) - } - } else { - if _, err := ulid.Parse(i.ID); err != nil { - errs = append(errs, errors.New("assignment id is not a valid ULID")) + if i.SubscriptionID != "" { + if _, err := ulid.Parse(i.NamespacedID.ID); err != nil { + errs = append(errs, errors.New("subscription id is not a valid ULID")) } } From 84f11ce199c8309a4f812e39adefe2166c287534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20T=C3=B3th?= <4157749+tothandras@users.noreply.github.com> Date: Wed, 13 May 2026 10:20:40 +0200 Subject: [PATCH 5/9] chore: apply suggestion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: András Tóth <4157749+tothandras@users.noreply.github.com> --- openmeter/subscription/addon/service/create_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmeter/subscription/addon/service/create_test.go b/openmeter/subscription/addon/service/create_test.go index ec1418a32a..12e63fd89a 100644 --- a/openmeter/subscription/addon/service/create_test.go +++ b/openmeter/subscription/addon/service/create_test.go @@ -493,9 +493,9 @@ func TestAddonServiceCreate(t *testing.T) { require.Nil(t, err) // Now, let's fetch the subscription addon - subAdd2, err := deps.SubscriptionAddonService.Get(context.Background(), subscriptionaddon.GetSubscriptionAddonInput{ - NamespacedID: subAdd1.NamespacedID, - }) +subAdd2, err := deps.SubscriptionAddonService.Get(t.Context(), subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: subAdd1.NamespacedID, +}) require.Nil(t, err) t.Run("Should create addon as specified", func(t *testing.T) { From aa52b0eba9e06edc4d13b4d56984516a3467dab6 Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Wed, 13 May 2026 11:16:57 +0200 Subject: [PATCH 6/9] fix: review fix, simplify validation --- openmeter/subscription/addon/service.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/openmeter/subscription/addon/service.go b/openmeter/subscription/addon/service.go index 94801e22a4..6e8d4537d0 100644 --- a/openmeter/subscription/addon/service.go +++ b/openmeter/subscription/addon/service.go @@ -6,8 +6,6 @@ import ( "fmt" "slices" - "github.com/oklog/ulid/v2" - "github.com/openmeterio/openmeter/pkg/models" "github.com/openmeterio/openmeter/pkg/pagination" "github.com/openmeterio/openmeter/pkg/sortx" @@ -86,15 +84,5 @@ func (i GetSubscriptionAddonInput) Validate() error { errs = append(errs, err) } - if _, err := ulid.Parse(i.NamespacedID.ID); err != nil { - errs = append(errs, errors.New("assignment id is not a valid ULID")) - } - - if i.SubscriptionID != "" { - if _, err := ulid.Parse(i.NamespacedID.ID); err != nil { - errs = append(errs, errors.New("subscription id is not a valid ULID")) - } - } - - return errors.Join(errs...) + return models.NewGenericValidationError(errors.Join(errs...)) } From 6935d11b97246b3c616c2fd90abe11695bf9f763 Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Wed, 13 May 2026 11:17:44 +0200 Subject: [PATCH 7/9] fix: resolve format issue --- openmeter/subscription/addon/service/create_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmeter/subscription/addon/service/create_test.go b/openmeter/subscription/addon/service/create_test.go index 12e63fd89a..0c62f6c8d8 100644 --- a/openmeter/subscription/addon/service/create_test.go +++ b/openmeter/subscription/addon/service/create_test.go @@ -493,9 +493,9 @@ func TestAddonServiceCreate(t *testing.T) { require.Nil(t, err) // Now, let's fetch the subscription addon -subAdd2, err := deps.SubscriptionAddonService.Get(t.Context(), subscriptionaddon.GetSubscriptionAddonInput{ - NamespacedID: subAdd1.NamespacedID, -}) + subAdd2, err := deps.SubscriptionAddonService.Get(t.Context(), subscriptionaddon.GetSubscriptionAddonInput{ + NamespacedID: subAdd1.NamespacedID, + }) require.Nil(t, err) t.Run("Should create addon as specified", func(t *testing.T) { From d13de4a150e1c532952ad3242b28cc56a17cda0e Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Wed, 13 May 2026 15:24:38 +0200 Subject: [PATCH 8/9] fix: resolve validaton issue, use proper method for wrapping --- openmeter/subscription/addon/repo/subscriptionaddon.go | 6 +++--- openmeter/subscription/addon/service.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openmeter/subscription/addon/repo/subscriptionaddon.go b/openmeter/subscription/addon/repo/subscriptionaddon.go index 8411428782..10139db631 100644 --- a/openmeter/subscription/addon/repo/subscriptionaddon.go +++ b/openmeter/subscription/addon/repo/subscriptionaddon.go @@ -56,8 +56,8 @@ func (r *subscriptionAddonRepo) Get(ctx context.Context, params subscriptionaddo query := querySubscriptionAddon(repo.db.SubscriptionAddon.Query()) query = query.Where( - dbsubscriptionaddon.ID(params.ID), - dbsubscriptionaddon.Namespace(params.Namespace), + dbsubscriptionaddon.ID(params.NamespacedID.ID), + dbsubscriptionaddon.Namespace(params.NamespacedID.Namespace), ) if params.SubscriptionID != "" { query = query.Where(dbsubscriptionaddon.SubscriptionID(params.SubscriptionID)) @@ -67,7 +67,7 @@ func (r *subscriptionAddonRepo) Get(ctx context.Context, params subscriptionaddo if err != nil { if db.IsNotFound(err) { return nil, models.NewGenericNotFoundError( - fmt.Errorf("subscription addon %s not found", params.ID), + fmt.Errorf("subscription addon %s not found", params.NamespacedID.ID), ) } diff --git a/openmeter/subscription/addon/service.go b/openmeter/subscription/addon/service.go index 6e8d4537d0..f0a3de3f7b 100644 --- a/openmeter/subscription/addon/service.go +++ b/openmeter/subscription/addon/service.go @@ -84,5 +84,5 @@ func (i GetSubscriptionAddonInput) Validate() error { errs = append(errs, err) } - return models.NewGenericValidationError(errors.Join(errs...)) + return models.NewNillableGenericValidationError(errors.Join(errs...)) } From 1da6c8c2c6912b11b1b326a54e51cdfcd34bf16f Mon Sep 17 00:00:00 2001 From: Robert Boros Date: Mon, 18 May 2026 16:10:14 +0200 Subject: [PATCH 9/9] fix: resolve error returning --- api/v3/handlers/subscriptions/subscriptionaddons/convert.go | 5 +++-- api/v3/handlers/subscriptions/subscriptionaddons/list.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/convert.go b/api/v3/handlers/subscriptions/subscriptionaddons/convert.go index 7d08cc2a48..744f332b52 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/convert.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/convert.go @@ -11,6 +11,7 @@ import ( "github.com/openmeterio/openmeter/api/v3/labels" subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" "github.com/openmeterio/openmeter/pkg/clock" + "github.com/openmeterio/openmeter/pkg/models" "github.com/openmeterio/openmeter/pkg/timeutil" ) @@ -19,7 +20,7 @@ func toAPISubscriptionAddon(addon subscriptionaddon.SubscriptionAddon) (apiv3.Su inst, found := addon.GetInstanceAt(now) if !found { - return apiv3.SubscriptionAddon{}, fmt.Errorf("no instance is active at %s", now.Format(time.RFC3339)) + return apiv3.SubscriptionAddon{}, models.NewGenericNotFoundError(fmt.Errorf("no instance is active at %s", now.Format(time.RFC3339))) } pers := lo.Map(addon.GetInstances(), func(i subscriptionaddon.SubscriptionAddonInstance, _ int) timeutil.OpenPeriod { @@ -27,7 +28,7 @@ func toAPISubscriptionAddon(addon subscriptionaddon.SubscriptionAddon) (apiv3.Su }) if len(pers) == 0 { - return apiv3.SubscriptionAddon{}, errors.New("no instances found for subscription addon") + return apiv3.SubscriptionAddon{}, models.NewGenericNotFoundError(errors.New("no instances found for subscription addon")) } union := lo.Reduce(pers, func(agg timeutil.OpenPeriod, item timeutil.OpenPeriod, _ int) timeutil.OpenPeriod { diff --git a/api/v3/handlers/subscriptions/subscriptionaddons/list.go b/api/v3/handlers/subscriptions/subscriptionaddons/list.go index 7ab5ea65a2..a06dc57b5a 100644 --- a/api/v3/handlers/subscriptions/subscriptionaddons/list.go +++ b/api/v3/handlers/subscriptions/subscriptionaddons/list.go @@ -87,14 +87,14 @@ func (h *handler) ListSubscriptionAddons() ListSubscriptionAddonsHandler { func(ctx context.Context, req ListSubscriptionAddonsRequest) (ListSubscriptionAddonsResponse, error) { res, err := h.addonService.List(ctx, req.SubscriptionID.Namespace, req.Input) if err != nil { - return ListSubscriptionAddonsResponse{}, fmt.Errorf("failed to list subscription addons: %w", err) + return ListSubscriptionAddonsResponse{}, err } items := make([]apiv3.SubscriptionAddon, 0, len(res.Items)) for _, item := range res.Items { converted, err := toAPISubscriptionAddon(item) if err != nil { - return ListSubscriptionAddonsResponse{}, fmt.Errorf("failed to convert subscription addon: %w", err) + return ListSubscriptionAddonsResponse{}, err } items = append(items, converted) }