From ad5a635ffb3e6f2cab213bcd9f97ea132557c0a9 Mon Sep 17 00:00:00 2001 From: Mariem Baccari Date: Wed, 19 Aug 2026 15:50:57 +0200 Subject: [PATCH] [raft/scd] Extract delete constraint --- pkg/scd/actions/constraint.go | 79 ++++++++++++++++++++++++++++++++++ pkg/scd/actions/registry.go | 23 ++++++++++ pkg/scd/constraints_handler.go | 55 ++--------------------- 3 files changed, 105 insertions(+), 52 deletions(-) create mode 100644 pkg/scd/actions/constraint.go diff --git a/pkg/scd/actions/constraint.go b/pkg/scd/actions/constraint.go new file mode 100644 index 000000000..a165bfdb0 --- /dev/null +++ b/pkg/scd/actions/constraint.go @@ -0,0 +1,79 @@ +package actions + +import ( + "context" + + "github.com/golang/geo/s2" + restapi "github.com/interuss/dss/pkg/api/scdv1" + dsserr "github.com/interuss/dss/pkg/errors" + dssmodels "github.com/interuss/dss/pkg/models" + scdmodels "github.com/interuss/dss/pkg/scd/models" + "github.com/interuss/dss/pkg/scd/repos" + dssstore "github.com/interuss/dss/pkg/store" + "github.com/interuss/stacktrace" + "github.com/jackc/pgx/v5" +) + +func init() { + Registry[restapi.DeleteConstraintReferenceOperationID] = dssstore.OperationHandler[repos.Repository]{ + Encode: dssstore.EncodeJSON, + Decode: dssstore.DecodeJSON[*restapi.DeleteConstraintReferenceRequest], + Execute: ExecuteDeleteConstraint, + } +} + +func ExecuteDeleteConstraint(ctx context.Context, repo repos.Repository, request dssstore.OperationRequest) (any, error) { + req, ok := request.(*restapi.DeleteConstraintReferenceRequest) + if !ok { + return nil, stacktrace.NewError("unexpected request type %T for operation %q", request, restapi.DeleteConstraintReferenceOperationID) + } + + // Retrieve Constraint ID + id, err := dssmodels.IDFromString(string(req.Entityid)) + if err != nil { + return nil, stacktrace.NewErrorWithCode(dsserr.BadRequest, "Invalid ID format: `%s`", req.Entityid) + } + + // Make sure deletion request is valid + old, err := repo.GetConstraint(ctx, id) + switch { + case err == pgx.ErrNoRows: + return nil, stacktrace.NewErrorWithCode(dsserr.NotFound, "Constraint %s not found", id.String()) + case err != nil: + return nil, stacktrace.Propagate(err, "Unable to get Constraint from repo") + case old.Manager != dssmodels.Manager(*req.Auth.ClientID): + return nil, stacktrace.NewErrorWithCode(dsserr.PermissionDenied, + "Constraint owned by %s, but %s attempted to delete", old.Manager, *req.Auth.ClientID) + case old.OVN != scdmodels.OVN(req.Ovn): + return nil, stacktrace.NewErrorWithCode(dsserr.VersionMismatch, + "Current version is %s but client specified version %s", old.OVN, scdmodels.OVN(req.Ovn)) + } + + // Delete Constraint in repo + err = repo.DeleteConstraint(ctx, id) + if err != nil { + return nil, stacktrace.Propagate(err, "Unable to delete Constraint from repo") + } + + // Find the Subscriptions interested in Constraints and increment their + // notification indices. + subs, err := repo.IncrementNotificationIndicesForConstraints(ctx, &dssmodels.Volume4D{ + StartTime: old.StartTime, + EndTime: old.EndTime, + SpatialVolume: &dssmodels.Volume3D{ + AltitudeHi: old.AltitudeUpper, + AltitudeLo: old.AltitudeLower, + Footprint: dssmodels.GeometryFunc(func() (s2.CellUnion, error) { + return old.Cells, nil + }), + }}) + if err != nil { + return nil, stacktrace.Propagate(err, "Unable to increment notification indices") + } + + // Return response to client + return &restapi.ChangeConstraintReferenceResponse{ + ConstraintReference: *old.ToRest(), + Subscribers: makeSubscribersToNotify(subs), + }, nil +} diff --git a/pkg/scd/actions/registry.go b/pkg/scd/actions/registry.go index 3f1193ef0..60961f40a 100644 --- a/pkg/scd/actions/registry.go +++ b/pkg/scd/actions/registry.go @@ -1,9 +1,32 @@ package actions import ( + restapi "github.com/interuss/dss/pkg/api/scdv1" + scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" dssstore "github.com/interuss/dss/pkg/store" ) // Registry maps operation IDs to their handlers var Registry = map[string]dssstore.OperationHandler[repos.Repository]{} + +func makeSubscribersToNotify(subscriptions []*scdmodels.Subscription) []restapi.SubscriberToNotify { + result := []restapi.SubscriberToNotify{} + + subscriptionsByURL := map[string][]restapi.SubscriptionState{} + for _, sub := range subscriptions { + subState := restapi.SubscriptionState{ + SubscriptionId: restapi.SubscriptionID(sub.ID.String()), + NotificationIndex: restapi.SubscriptionNotificationIndex(sub.NotificationIndex), + } + subscriptionsByURL[sub.USSBaseURL] = append(subscriptionsByURL[sub.USSBaseURL], subState) + } + for url, states := range subscriptionsByURL { + result = append(result, restapi.SubscriberToNotify{ + UssBaseUrl: restapi.SubscriptionUssBaseURL(url), + Subscriptions: states, + }) + } + + return result +} diff --git a/pkg/scd/constraints_handler.go b/pkg/scd/constraints_handler.go index 851ccb3f7..83465c4da 100644 --- a/pkg/scd/constraints_handler.go +++ b/pkg/scd/constraints_handler.go @@ -22,7 +22,7 @@ func (a *Server) DeleteConstraintReference(ctx context.Context, req *restapi.Del ) restapi.DeleteConstraintReferenceResponseSet { // Retrieve Constraint ID - id, err := dssmodels.IDFromString(string(req.Entityid)) + _, err := dssmodels.IDFromString(string(req.Entityid)) if err != nil { return restapi.DeleteConstraintReferenceResponseSet{Response400: &restapi.ErrorResponse{ Message: dsserr.Handle(ctx, stacktrace.NewErrorWithCode(dsserr.BadRequest, "Invalid ID format: `%s`", req.Entityid))}} @@ -35,61 +35,12 @@ func (a *Server) DeleteConstraintReference(ctx context.Context, req *restapi.Del } // Retrieve OVN - ovn := scdmodels.OVN(req.Ovn) - if ovn == "" { + if req.Ovn == "" { return restapi.DeleteConstraintReferenceResponseSet{Response400: &restapi.ErrorResponse{ Message: dsserr.Handle(ctx, stacktrace.NewErrorWithCode(dsserr.BadRequest, "Missing OVN for constraint to modify"))}} } - var response *restapi.ChangeConstraintReferenceResponse - action := func(ctx context.Context, r repos.Repository) (err error) { - // Make sure deletion request is valid - old, err := r.GetConstraint(ctx, id) - switch { - case err == pgx.ErrNoRows: - return stacktrace.NewErrorWithCode(dsserr.NotFound, "Constraint %s not found", id.String()) - case err != nil: - return stacktrace.Propagate(err, "Unable to get Constraint from repo") - case old.Manager != dssmodels.Manager(*req.Auth.ClientID): - return stacktrace.NewErrorWithCode(dsserr.PermissionDenied, - "Constraint owned by %s, but %s attempted to delete", old.Manager, *req.Auth.ClientID) - case old.OVN != ovn: - return stacktrace.NewErrorWithCode(dsserr.VersionMismatch, - "Current version is %s but client specified version %s", old.OVN, ovn) - } - - // Delete Constraint in repo - err = r.DeleteConstraint(ctx, id) - if err != nil { - return stacktrace.Propagate(err, "Unable to delete Constraint from repo") - } - - // Find the Subscriptions interested in Constraints and increment their - // notification indices. - subs, err := r.IncrementNotificationIndicesForConstraints(ctx, &dssmodels.Volume4D{ - StartTime: old.StartTime, - EndTime: old.EndTime, - SpatialVolume: &dssmodels.Volume3D{ - AltitudeHi: old.AltitudeUpper, - AltitudeLo: old.AltitudeLower, - Footprint: dssmodels.GeometryFunc(func() (s2.CellUnion, error) { - return old.Cells, nil - }), - }}) - if err != nil { - return stacktrace.Propagate(err, "Unable to increment notification indices") - } - - // Return response to client - response = &restapi.ChangeConstraintReferenceResponse{ - ConstraintReference: *old.ToRest(), - Subscribers: makeSubscribersToNotify(subs), - } - - return nil - } - - _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) + response, err := dssstore.TransactWithResult[repos.Repository, *restapi.ChangeConstraintReferenceResponse](ctx, a.Store, req) if err != nil { err = stacktrace.Propagate(err, "Could not delete constraint") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)}