diff --git a/entproto/adapter.go b/entproto/adapter.go index a34ac2766..e9ce36e21 100644 --- a/entproto/adapter.go +++ b/entproto/adapter.go @@ -28,6 +28,7 @@ import ( "github.com/jhump/protoreflect/desc/builder" "google.golang.org/protobuf/types/descriptorpb" _ "google.golang.org/protobuf/types/known/emptypb" + _ "google.golang.org/protobuf/types/known/structpb" _ "google.golang.org/protobuf/types/known/timestamppb" _ "google.golang.org/protobuf/types/known/wrapperspb" // needed to load wkt to global proto registry ) @@ -53,6 +54,9 @@ var ( "google.protobuf.StringValue": "google/protobuf/wrappers.proto", "google.protobuf.BoolValue": "google/protobuf/wrappers.proto", "google.protobuf.BytesValue": "google/protobuf/wrappers.proto", + "google.protobuf.Struct": "google/protobuf/struct.proto", + "google.protobuf.ListValue": "google/protobuf/struct.proto", + "google.protobuf.Value": "google/protobuf/struct.proto", } ) diff --git a/entproto/cmd/protoc-gen-entgrpc/converter.go b/entproto/cmd/protoc-gen-entgrpc/converter.go index 1f12078b7..e7c876c0c 100644 --- a/entproto/cmd/protoc-gen-entgrpc/converter.go +++ b/entproto/cmd/protoc-gen-entgrpc/converter.go @@ -116,6 +116,9 @@ func (g *serviceGenerator) newConverter(fld *entproto.FieldMappingDescriptor) (* case "[]string": case "[]int32", "[]int64", "[]uint32", "[]uint64": out.ToProtoConversion = "" + case "map[string]interface {}", "[]interface {}": + // Handled by convertPbMessageType for google.protobuf.Struct / google.protobuf.ListValue + // ToProtoConstructor and ToEntConstructor are already set default: return nil, fmt.Errorf("entproto: no mapping to ent field type %q", efld.Type.ConstName()) } @@ -179,6 +182,12 @@ func convertPbMessageType(md *desc.MessageDescriptor, entField *gen.Field, conv switch { case md.GetFullyQualifiedName() == "google.protobuf.Timestamp": conv.ToProtoConstructor = protogen.GoImportPath("google.golang.org/protobuf/types/known/timestamppb").Ident("New") + case md.GetFullyQualifiedName() == "google.protobuf.Struct": + conv.ToProtoConstructor = protogen.GoImportPath("entgo.io/contrib/entproto/runtime").Ident("NewStruct") + conv.ToEntConstructor = protogen.GoImportPath("entgo.io/contrib/entproto/runtime").Ident("ExtractStruct") + case md.GetFullyQualifiedName() == "google.protobuf.ListValue": + conv.ToProtoConstructor = protogen.GoImportPath("entgo.io/contrib/entproto/runtime").Ident("NewList") + conv.ToEntConstructor = protogen.GoImportPath("entgo.io/contrib/entproto/runtime").Ident("ExtractList") case isWrapperType(md): fqn := md.GetFullyQualifiedName() typ := strings.Split(fqn, ".")[2] diff --git a/entproto/cmd/protoc-gen-entgrpc/main.go b/entproto/cmd/protoc-gen-entgrpc/main.go index 5e98698ed..406626383 100644 --- a/entproto/cmd/protoc-gen-entgrpc/main.go +++ b/entproto/cmd/protoc-gen-entgrpc/main.go @@ -27,6 +27,7 @@ import ( "entgo.io/ent/entc" "entgo.io/ent/entc/gen" "google.golang.org/protobuf/compiler/protogen" + _ "google.golang.org/protobuf/types/known/structpb" // register google.protobuf.Struct in proto registry ) var ( diff --git a/entproto/runtime/extract.go b/entproto/runtime/extract.go index 6f1700c7f..15cd0761a 100644 --- a/entproto/runtime/extract.go +++ b/entproto/runtime/extract.go @@ -17,6 +17,7 @@ package runtime import ( "time" + "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -24,3 +25,47 @@ import ( func ExtractTime(t *timestamppb.Timestamp) time.Time { return t.AsTime() } + +// NewStruct creates a new *structpb.Struct from a map[string]interface{}. +// Returns nil if the input is nil or conversion fails. +func NewStruct(m map[string]interface{}) *structpb.Struct { + if m == nil { + return nil + } + s, err := structpb.NewStruct(m) + if err != nil { + return nil + } + return s +} + +// ExtractStruct converts a *structpb.Struct to map[string]interface{}. +// Returns nil if the input is nil. +func ExtractStruct(s *structpb.Struct) map[string]interface{} { + if s == nil { + return nil + } + return s.AsMap() +} + +// NewList creates a new *structpb.ListValue from a []interface{}. +// Returns nil if the input is nil or conversion fails. +func NewList(l []interface{}) *structpb.ListValue { + if l == nil { + return nil + } + lv, err := structpb.NewList(l) + if err != nil { + return nil + } + return lv +} + +// ExtractList converts a *structpb.ListValue to []interface{}. +// Returns nil if the input is nil. +func ExtractList(l *structpb.ListValue) []interface{} { + if l == nil { + return nil + } + return l.AsSlice() +}