From 94b02c873b08dd30c4de2a6ae175b0003bbc33e4 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Mon, 5 Oct 2026 12:07:02 +0200 Subject: [PATCH] refactor(search): move the opensearch facet bridge to mapping.ToProto convert.copyFacet rebuilt every facet by hand on the way into the index; mapping.ToProto is the generic counterpart of the FromProto the graph endpoint uses, so both directions share one bridge. --- services/search/pkg/mapping/proto.go | 14 ++++++++++ services/search/pkg/mapping/proto_test.go | 9 +++++++ .../opensearch/internal/convert/opensearch.go | 26 ++++++------------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/services/search/pkg/mapping/proto.go b/services/search/pkg/mapping/proto.go index 64b7262044..338c96c167 100644 --- a/services/search/pkg/mapping/proto.go +++ b/services/search/pkg/mapping/proto.go @@ -35,3 +35,17 @@ func setValueLenient(v reflect.Value, raw any) error { } return err } + +// ToProto builds a *M (a proto facet message) from the equally shaped +// libregraph facet. protojson accepts plain-JSON numbers for int64, so the +// JSON bridge is enough in this direction. +func ToProto[M any, T any](v *T) *M { + if v == nil { + return nil + } + out, err := conversions.To[*M](v) + if err != nil { + return nil + } + return out +} diff --git a/services/search/pkg/mapping/proto_test.go b/services/search/pkg/mapping/proto_test.go index 5e904b4040..2bc920b37c 100644 --- a/services/search/pkg/mapping/proto_test.go +++ b/services/search/pkg/mapping/proto_test.go @@ -42,6 +42,15 @@ var _ = Describe("FromProto", func() { Expect(out.GetTakenDateTime()).To(Equal(mustTime("2023-07-21T10:11:12Z"))) }) + It("round-trips through ToProto", func() { + lg := &libregraph.Audio{Artist: conversions.ToPointer("Motörhead"), Bitrate: conversions.ToPointer(int64(256))} + pb := ToProto[searchmsg.Audio](lg) + Expect(pb).ToNot(BeNil()) + Expect(pb.GetArtist()).To(Equal("Motörhead")) + Expect(pb.GetBitrate()).To(Equal(int64(256))) + Expect(FromProto[libregraph.Audio](pb)).To(Equal(lg)) + }) + It("returns nil for nil input and for an empty message", func() { Expect(FromProto[libregraph.Audio]((*searchmsg.Audio)(nil))).To(BeNil()) Expect(FromProto[libregraph.Audio](&searchmsg.Audio{})).To(BeNil()) diff --git a/services/search/pkg/opensearch/internal/convert/opensearch.go b/services/search/pkg/opensearch/internal/convert/opensearch.go index 81d1b347e2..2f3750d85d 100644 --- a/services/search/pkg/opensearch/internal/convert/opensearch.go +++ b/services/search/pkg/opensearch/internal/convert/opensearch.go @@ -11,20 +11,10 @@ import ( "github.com/opencloud-eu/opencloud/pkg/conversions" searchMessage "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/search/v0" + "github.com/opencloud-eu/opencloud/services/search/pkg/mapping" "github.com/opencloud-eu/opencloud/services/search/pkg/search" ) -// copyFacet converts a typed pointer from the indexed shape (libregraph) to -// the protobuf shape via conversions.To. Returns nil when src is nil so the -// enclosing Match.Entity field stays nil. -func copyFacet[Dst, Src any](src *Src) *Dst { - if src == nil { - return nil - } - dst, _ := conversions.To[*Dst](src) - return dst -} - func OpenSearchHitToMatch(hit opensearchgoAPI.SearchHit) (*searchMessage.Match, error) { resource, err := conversions.To[search.Resource](hit.Source) if err != nil { @@ -79,13 +69,13 @@ func OpenSearchHitToMatch(hit opensearchgoAPI.SearchHit) (*searchMessage.Match, return strings.Join(contentHighlights[:], "; ") }(), - Audio: copyFacet[searchMessage.Audio](resource.Audio), - Image: copyFacet[searchMessage.Image](resource.Image), - Location: copyFacet[searchMessage.GeoCoordinates](resource.Location), - Photo: copyFacet[searchMessage.Photo](resource.Photo), - Video: copyFacet[searchMessage.Video](resource.Video), - MotionPhoto: copyFacet[searchMessage.MotionPhoto](resource.MotionPhoto), - LivePhoto: copyFacet[searchMessage.LivePhoto](resource.LivePhoto), + Audio: mapping.ToProto[searchMessage.Audio](resource.Audio), + Image: mapping.ToProto[searchMessage.Image](resource.Image), + Location: mapping.ToProto[searchMessage.GeoCoordinates](resource.Location), + Photo: mapping.ToProto[searchMessage.Photo](resource.Photo), + Video: mapping.ToProto[searchMessage.Video](resource.Video), + MotionPhoto: mapping.ToProto[searchMessage.MotionPhoto](resource.MotionPhoto), + LivePhoto: mapping.ToProto[searchMessage.LivePhoto](resource.LivePhoto), }, }