From f62c24a2d7f42f2ccaeb90348ea3c5fe9a8cfb6f Mon Sep 17 00:00:00 2001 From: Michel Davit Date: Wed, 1 Jul 2026 19:04:35 +0200 Subject: [PATCH] [scd] Move scd conversions to scd package --- pkg/models/geo.go | 34 ++---------- pkg/scd/constraints_handler.go | 8 +-- pkg/scd/models/constraints.go | 4 +- .../models/conversions.go} | 52 +++++++++++-------- .../models/conversions_test.go} | 27 +++++----- pkg/scd/models/operational_intents.go | 4 +- pkg/scd/models/subscriptions.go | 4 +- pkg/scd/operational_intents_handler.go | 10 ++-- pkg/scd/subscriptions_handler.go | 4 +- 9 files changed, 65 insertions(+), 82 deletions(-) rename pkg/{models/scd_conversions.go => scd/models/conversions.go} (77%) rename pkg/{models/scd_conversions_test.go => scd/models/conversions_test.go} (88%) diff --git a/pkg/models/geo.go b/pkg/models/geo.go index d132c450e..f52c5ae0d 100644 --- a/pkg/models/geo.go +++ b/pkg/models/geo.go @@ -9,38 +9,12 @@ import ( ) const ( - // TimeFormatRFC3339 is the string used for RFC3339 - TimeFormatRFC3339 = "RFC3339" - minLat = -90.0 - maxLat = 90.0 - minLng = -180.0 - maxLng = 180.0 - UnitsM = "M" - ReferenceW84 = "W84" + minLat = -90.0 + maxLat = 90.0 + minLng = -180.0 + maxLng = 180.0 ) -var ( - unitToMeterMultiplicativeFactors = map[unit]float32{ - unitMeter: 1, - } - - altitudeReferenceWGS84 altitudeReference = "W84" - unitMeter unit = "M" -) - -type ( - altitudeReference string - unit string -) - -func (ar altitudeReference) String() string { - return string(ar) -} - -func (u unit) String() string { - return string(u) -} - func float32p(v float32) *float32 { return &v } diff --git a/pkg/scd/constraints_handler.go b/pkg/scd/constraints_handler.go index 0688176c7..530091aee 100644 --- a/pkg/scd/constraints_handler.go +++ b/pkg/scd/constraints_handler.go @@ -369,10 +369,10 @@ func validateAndReturnConstraintUpsertParams( // Start and end times are required for each volume // The end time may not be in the past - valid.uExtent, err = dssmodels.UnionVolumes4DFromSCDRest( + valid.uExtent, err = scdmodels.UnionVolumes4DFromSCDRest( params.Extents, - dssmodels.WithRequireTimeBounds(), - dssmodels.WithRequireEndTimeAfter(now), + scdmodels.WithRequireTimeBounds(), + scdmodels.WithRequireEndTimeAfter(now), ) if err != nil { return nil, stacktrace.Propagate(err, "Invalid extents") @@ -404,7 +404,7 @@ func (a *Server) QueryConstraintReferences(ctx context.Context, req *restapi.Que } // Parse area of interest to common Volume4D - vol4, err := dssmodels.Volume4DFromSCDRest(aoi) + vol4, err := scdmodels.Volume4DFromSCDRest(aoi) if err != nil { return restapi.QueryConstraintReferencesResponseSet{Response400: &restapi.ErrorResponse{ Message: dsserr.Handle(ctx, stacktrace.PropagateWithCode(err, dsserr.BadRequest, "Failed to convert to internal geometry model"))}} diff --git a/pkg/scd/models/constraints.go b/pkg/scd/models/constraints.go index 5f7fdebca..5908d30c0 100644 --- a/pkg/scd/models/constraints.go +++ b/pkg/scd/models/constraints.go @@ -38,14 +38,14 @@ func (c *Constraint) ToRest() *restapi.ConstraintReference { if c.StartTime != nil { result.TimeStart = restapi.Time{ Value: c.StartTime.Format(time.RFC3339Nano), - Format: dssmodels.TimeFormatRFC3339, + Format: TimeFormatRFC3339, } } if c.EndTime != nil { result.TimeEnd = restapi.Time{ Value: c.EndTime.Format(time.RFC3339Nano), - Format: dssmodels.TimeFormatRFC3339, + Format: TimeFormatRFC3339, } } diff --git a/pkg/models/scd_conversions.go b/pkg/scd/models/conversions.go similarity index 77% rename from pkg/models/scd_conversions.go rename to pkg/scd/models/conversions.go index fad5ae357..e0bdd01a3 100644 --- a/pkg/models/scd_conversions.go +++ b/pkg/scd/models/conversions.go @@ -4,13 +4,21 @@ import ( "time" restapi "github.com/interuss/dss/pkg/api/scdv1" + dssmodels "github.com/interuss/dss/pkg/models" "github.com/interuss/stacktrace" ) -type Volume4DValidator func(*Volume4D) error +const ( + // TimeFormatRFC3339 is the string used for RFC3339 + TimeFormatRFC3339 = "RFC3339" + UnitsM = "M" + ReferenceW84 = "W84" +) + +type Volume4DValidator func(*dssmodels.Volume4D) error func WithRequireTimeBounds() Volume4DValidator { - return func(v *Volume4D) error { + return func(v *dssmodels.Volume4D) error { if v.StartTime == nil { return stacktrace.NewError("Missing start time") } @@ -22,7 +30,7 @@ func WithRequireTimeBounds() Volume4DValidator { } func WithRequireEndTimeAfter(now time.Time) Volume4DValidator { - return func(v *Volume4D) error { + return func(v *dssmodels.Volume4D) error { if v.EndTime != nil && v.EndTime.Before(now) { return stacktrace.NewError("End time may not be in the past") } @@ -31,7 +39,7 @@ func WithRequireEndTimeAfter(now time.Time) Volume4DValidator { } func WithRequireAltitudeBounds() Volume4DValidator { - return func(v *Volume4D) error { + return func(v *dssmodels.Volume4D) error { if v.SpatialVolume.AltitudeLo == nil { return stacktrace.NewError("Missing lower altitude") } @@ -44,8 +52,8 @@ func WithRequireAltitudeBounds() Volume4DValidator { // UnionVolumes4DFromSCDRest converts a slice of vol4 SCD v1 REST model to a single bounding Volume4D // Validation is applied on the resulting volume union -func UnionVolumes4DFromSCDRest(vol4s []restapi.Volume4D, validators ...Volume4DValidator) (*Volume4D, error) { - volumes := make([]*Volume4D, len(vol4s)) +func UnionVolumes4DFromSCDRest(vol4s []restapi.Volume4D, validators ...Volume4DValidator) (*dssmodels.Volume4D, error) { + volumes := make([]*dssmodels.Volume4D, len(vol4s)) for idx, vol4 := range vol4s { volume, err := Volume4DFromSCDRest(&vol4) if err != nil { @@ -53,7 +61,7 @@ func UnionVolumes4DFromSCDRest(vol4s []restapi.Volume4D, validators ...Volume4DV } volumes[idx] = volume } - union, err := UnionVolumes4D(volumes...) + union, err := dssmodels.UnionVolumes4D(volumes...) if err != nil { return nil, stacktrace.Propagate(err, "Failed to union volumes") } @@ -68,7 +76,7 @@ func UnionVolumes4DFromSCDRest(vol4s []restapi.Volume4D, validators ...Volume4DV } // Volume4DFromSCDRest converts vol4 SCD v1 REST model to a Volume4D -func Volume4DFromSCDRest(vol4 *restapi.Volume4D) (*Volume4D, error) { +func Volume4DFromSCDRest(vol4 *restapi.Volume4D) (*dssmodels.Volume4D, error) { vol3, err := Volume3DFromSCDRest(&vol4.Volume) if err != nil { return nil, err // No need to Propagate this error as this stack layer does not add useful information @@ -96,7 +104,7 @@ func Volume4DFromSCDRest(vol4 *restapi.Volume4D) (*Volume4D, error) { return nil, stacktrace.NewError("Start time cannot be after end time") } - volume := &Volume4D{ + volume := &dssmodels.Volume4D{ SpatialVolume: vol3, StartTime: startTime, EndTime: endTime, @@ -106,7 +114,7 @@ func Volume4DFromSCDRest(vol4 *restapi.Volume4D) (*Volume4D, error) { } // Volume3DFromSCDRest converts a vol3 SCD v1 REST model to a Volume3D -func Volume3DFromSCDRest(vol3 *restapi.Volume3D) (*Volume3D, error) { +func Volume3DFromSCDRest(vol3 *restapi.Volume3D) (*dssmodels.Volume3D, error) { if vol3 == nil { return nil, nil } @@ -119,7 +127,7 @@ func Volume3DFromSCDRest(vol3 *restapi.Volume3D) (*Volume3D, error) { if vol3.AltitudeLower.Reference != ReferenceW84 { return nil, stacktrace.NewError("Invalid lower altitude reference") } - altLo = float32p(float32(vol3.AltitudeLower.Value)) + altLo = new(float32(vol3.AltitudeLower.Value)) } var altHi *float32 @@ -130,7 +138,7 @@ func Volume3DFromSCDRest(vol3 *restapi.Volume3D) (*Volume3D, error) { if vol3.AltitudeUpper.Reference != ReferenceW84 { return nil, stacktrace.NewError("Invalid upper altitude reference") } - altHi = float32p(float32(vol3.AltitudeUpper.Value)) + altHi = new(float32(vol3.AltitudeUpper.Value)) } if altLo != nil && altHi != nil && *altLo > *altHi { @@ -141,36 +149,36 @@ func Volume3DFromSCDRest(vol3 *restapi.Volume3D) (*Volume3D, error) { case vol3.OutlineCircle != nil && vol3.OutlinePolygon != nil: return nil, stacktrace.NewError("Both circle and polygon specified in outline geometry") case vol3.OutlinePolygon != nil: - return &Volume3D{ + return &dssmodels.Volume3D{ Footprint: GeoPolygonFromSCDRest(vol3.OutlinePolygon), AltitudeLo: altLo, AltitudeHi: altHi, }, nil case vol3.OutlineCircle != nil: - return &Volume3D{ + return &dssmodels.Volume3D{ Footprint: GeoCircleFromSCDRest(vol3.OutlineCircle), AltitudeLo: altLo, AltitudeHi: altHi, }, nil } - return &Volume3D{ + return &dssmodels.Volume3D{ AltitudeLo: altLo, AltitudeHi: altHi, }, nil } // GeoCircleFromSCDRest converts a circle SCD v1 REST model to a GeoCircle -func GeoCircleFromSCDRest(c *restapi.Circle) *GeoCircle { - return &GeoCircle{ +func GeoCircleFromSCDRest(c *restapi.Circle) *dssmodels.GeoCircle { + return &dssmodels.GeoCircle{ Center: *LatLngPointFromSCDRest(c.Center), - RadiusMeter: unitToMeterMultiplicativeFactors[unit(c.Radius.Units)] * c.Radius.Value, + RadiusMeter: c.Radius.Value, } } // GeoPolygonFromSCDRest converts a polygon SCD v1 REST model to a GeoPolygon -func GeoPolygonFromSCDRest(p *restapi.Polygon) *GeoPolygon { - result := &GeoPolygon{} +func GeoPolygonFromSCDRest(p *restapi.Polygon) *dssmodels.GeoPolygon { + result := &dssmodels.GeoPolygon{} for _, ltlng := range p.Vertices { result.Vertices = append(result.Vertices, LatLngPointFromSCDRest(<lng)) } @@ -179,8 +187,8 @@ func GeoPolygonFromSCDRest(p *restapi.Polygon) *GeoPolygon { } // LatLngPointFromSCDRest converts a point SCD v1 REST model to a latlngpoint -func LatLngPointFromSCDRest(p *restapi.LatLngPoint) *LatLngPoint { - return &LatLngPoint{ +func LatLngPointFromSCDRest(p *restapi.LatLngPoint) *dssmodels.LatLngPoint { + return &dssmodels.LatLngPoint{ Lat: float64(p.Lat), Lng: float64(p.Lng), } diff --git a/pkg/models/scd_conversions_test.go b/pkg/scd/models/conversions_test.go similarity index 88% rename from pkg/models/scd_conversions_test.go rename to pkg/scd/models/conversions_test.go index cab30fbdf..8dcbd994a 100644 --- a/pkg/models/scd_conversions_test.go +++ b/pkg/scd/models/conversions_test.go @@ -5,6 +5,7 @@ import ( "time" restapi "github.com/interuss/dss/pkg/api/scdv1" + dssmodels "github.com/interuss/dss/pkg/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,7 +31,7 @@ func TestUnionVolumes4DFromSCDRest(t *testing.T) { name string validators []Volume4DValidator rest []restapi.Volume4D - want *Volume4D + want *dssmodels.Volume4D wantErr bool }{ { @@ -43,7 +44,7 @@ func TestUnionVolumes4DFromSCDRest(t *testing.T) { {TimeStart: newRestTime(timeStart), TimeEnd: newRestTime(timeMid)}, {TimeStart: newRestTime(timeStart), TimeEnd: newRestTime(timeEnd)}, }, - want: &Volume4D{SpatialVolume: &Volume3D{}, StartTime: &timeStart, EndTime: &timeEnd}, + want: &dssmodels.Volume4D{SpatialVolume: &dssmodels.Volume3D{}, StartTime: &timeStart, EndTime: &timeEnd}, }, { name: "TimeEndExpired", @@ -79,7 +80,7 @@ func TestUnionVolumes4DFromSCDRest(t *testing.T) { {Volume: restapi.Volume3D{AltitudeLower: newRestAlt(altLo), AltitudeUpper: newRestAlt(altMid)}}, {Volume: restapi.Volume3D{AltitudeLower: newRestAlt(altMid), AltitudeUpper: newRestAlt(altHi)}}, }, - want: &Volume4D{SpatialVolume: &Volume3D{AltitudeLo: &altLo, AltitudeHi: &altHi}}, + want: &dssmodels.Volume4D{SpatialVolume: &dssmodels.Volume3D{AltitudeLo: &altLo, AltitudeHi: &altHi}}, }, { name: "MissingLowerAltitude", @@ -122,18 +123,18 @@ func TestVolume4DFromSCDRest(t *testing.T) { testCases := []struct { name string rest *restapi.Volume4D - want *Volume4D + want *dssmodels.Volume4D wantErr bool }{ { name: "Empty", rest: &restapi.Volume4D{}, - want: &Volume4D{SpatialVolume: &Volume3D{}}, + want: &dssmodels.Volume4D{SpatialVolume: &dssmodels.Volume3D{}}, }, { name: "Times", rest: &restapi.Volume4D{TimeStart: newRestTime(start), TimeEnd: newRestTime(end)}, - want: &Volume4D{SpatialVolume: &Volume3D{}, StartTime: &start, EndTime: &end}, + want: &dssmodels.Volume4D{SpatialVolume: &dssmodels.Volume3D{}, StartTime: &start, EndTime: &end}, }, { name: "InvalidTimeStart", @@ -173,21 +174,21 @@ func TestVolume3DFromSCDRest(t *testing.T) { testCases := []struct { name string rest *restapi.Volume3D - want *Volume3D + want *dssmodels.Volume3D wantErr bool }{ { name: "Empty", rest: &restapi.Volume3D{}, - want: &Volume3D{}, + want: &dssmodels.Volume3D{}, }, { name: "Polygon", rest: &restapi.Volume3D{ OutlinePolygon: &restapi.Polygon{}, }, - want: &Volume3D{ - Footprint: &GeoPolygon{}, + want: &dssmodels.Volume3D{ + Footprint: &dssmodels.GeoPolygon{}, }, }, { @@ -198,14 +199,14 @@ func TestVolume3DFromSCDRest(t *testing.T) { Radius: &restapi.Radius{}, }, }, - want: &Volume3D{ - Footprint: &GeoCircle{}, + want: &dssmodels.Volume3D{ + Footprint: &dssmodels.GeoCircle{}, }, }, { name: "Altitudes", rest: &restapi.Volume3D{AltitudeLower: newRestAlt(lo), AltitudeUpper: newRestAlt(hi)}, - want: &Volume3D{AltitudeLo: &lo, AltitudeHi: &hi}, + want: &dssmodels.Volume3D{AltitudeLo: &lo, AltitudeHi: &hi}, }, { name: "InvalidLowerAltitude", diff --git a/pkg/scd/models/operational_intents.go b/pkg/scd/models/operational_intents.go index 8f92d6bb3..f6ef0c2dc 100644 --- a/pkg/scd/models/operational_intents.go +++ b/pkg/scd/models/operational_intents.go @@ -113,14 +113,14 @@ func (o *OperationalIntent) ToRest() *restapi.OperationalIntentReference { if o.StartTime != nil { result.TimeStart = restapi.Time{ Value: o.StartTime.Format(time.RFC3339Nano), - Format: dssmodels.TimeFormatRFC3339, + Format: TimeFormatRFC3339, } } if o.EndTime != nil { result.TimeEnd = restapi.Time{ Value: o.EndTime.Format(time.RFC3339Nano), - Format: dssmodels.TimeFormatRFC3339, + Format: TimeFormatRFC3339, } } diff --git a/pkg/scd/models/subscriptions.go b/pkg/scd/models/subscriptions.go index aa66ecb5f..73b042470 100644 --- a/pkg/scd/models/subscriptions.go +++ b/pkg/scd/models/subscriptions.go @@ -53,14 +53,14 @@ func (s *Subscription) ToRest(dependentOperationalIntents []dssmodels.ID) (*rest if s.StartTime != nil { result.TimeStart = &restapi.Time{ Value: s.StartTime.Format(time.RFC3339Nano), - Format: dssmodels.TimeFormatRFC3339, + Format: TimeFormatRFC3339, } } if s.EndTime != nil { result.TimeEnd = &restapi.Time{ Value: s.EndTime.Format(time.RFC3339Nano), - Format: dssmodels.TimeFormatRFC3339, + Format: TimeFormatRFC3339, } } diff --git a/pkg/scd/operational_intents_handler.go b/pkg/scd/operational_intents_handler.go index f611797d0..44802cf30 100644 --- a/pkg/scd/operational_intents_handler.go +++ b/pkg/scd/operational_intents_handler.go @@ -252,7 +252,7 @@ func (a *Server) QueryOperationalIntentReferences(ctx context.Context, req *rest } // Parse area of interest to common Volume4D - vol4, err := dssmodels.Volume4DFromSCDRest(aoi) + vol4, err := scdmodels.Volume4DFromSCDRest(aoi) if err != nil { return restapi.QueryOperationalIntentReferencesResponseSet{Response400: &restapi.ErrorResponse{ Message: dsserr.Handle(ctx, stacktrace.PropagateWithCode(err, dsserr.BadRequest, "Error parsing geometry"))}} @@ -479,11 +479,11 @@ func validateAndReturnOIRUpsertParams( // Start and end times, as well as lower and upper altitudes, are required for each volume // The end time may not be in the past. - valid.uExtent, err = dssmodels.UnionVolumes4DFromSCDRest( + valid.uExtent, err = scdmodels.UnionVolumes4DFromSCDRest( params.Extents, - dssmodels.WithRequireTimeBounds(), - dssmodels.WithRequireAltitudeBounds(), - dssmodels.WithRequireEndTimeAfter(now), + scdmodels.WithRequireTimeBounds(), + scdmodels.WithRequireAltitudeBounds(), + scdmodels.WithRequireEndTimeAfter(now), ) if err != nil { return nil, stacktrace.Propagate(err, "Invalid extents") diff --git a/pkg/scd/subscriptions_handler.go b/pkg/scd/subscriptions_handler.go index 98ab9fed2..d7e2a63d5 100644 --- a/pkg/scd/subscriptions_handler.go +++ b/pkg/scd/subscriptions_handler.go @@ -104,7 +104,7 @@ func (a *Server) PutSubscription(ctx context.Context, manager string, subscripti // Parse extents // If end time is not specified, the value will be chosen automatically by the DSS. // If start time is not specified, it will default to the time the request is processed. - extents, err := dssmodels.Volume4DFromSCDRest(¶ms.Extents) + extents, err := scdmodels.Volume4DFromSCDRest(¶ms.Extents) if err != nil { return nil, stacktrace.PropagateWithCode(err, dsserr.BadRequest, "Unable to parse extents") } @@ -378,7 +378,7 @@ func (a *Server) QuerySubscriptions(ctx context.Context, req *restapi.QuerySubsc } // Parse area of interest to common Volume4D - vol4, err := dssmodels.Volume4DFromSCDRest(aoi) + vol4, err := scdmodels.Volume4DFromSCDRest(aoi) if err != nil { return restapi.QuerySubscriptionsResponseSet{Response400: &restapi.ErrorResponse{ Message: dsserr.Handle(ctx, stacktrace.PropagateWithCode(err, dsserr.BadRequest, "Failed to convert to internal geometry model"))}}