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.
This commit is contained in:
Dominik Schmidt committed 2026-10-05 12:07:02 +02:00
1 parent 3a702a2e5b
commit 94b02c873b
3 files changed
+31 -18

No files matched your search

+14
View File
@@ -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
}
@@ -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())
@@ -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),
},
}