From 83a2a87746f62b5dddf3d6d8ad13c1918b6e5a9d Mon Sep 17 00:00:00 2001 From: capri-xiyue <52932582+capri-xiyue@users.noreply.github.com> Date: Tue, 24 Jan 2023 10:10:09 -0800 Subject: [PATCH] feat: added Proto conversion util (#56) * added proto util * added proto conversion * fixed lint * added failure case --- go.mod | 2 +- protoutil/conversion.go | 36 ++++++++++++++++ protoutil/conversion_test.go | 84 ++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 protoutil/conversion.go create mode 100644 protoutil/conversion_test.go diff --git a/go.mod b/go.mod index cca8cda5..01d257ef 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( go.uber.org/zap v1.23.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.49.0 + google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -54,5 +55,4 @@ require ( golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect - google.golang.org/protobuf v1.28.1 // indirect ) diff --git a/protoutil/conversion.go b/protoutil/conversion.go new file mode 100644 index 00000000..544eb500 --- /dev/null +++ b/protoutil/conversion.go @@ -0,0 +1,36 @@ +// Copyright 2023 The Authors (see AUTHORS file) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package protoutil provides mechanisms for interacting with proto. +package protoutil + +import ( + "encoding/json" + "fmt" + + "google.golang.org/protobuf/types/known/structpb" +) + +// ToProtoStruct converts v, which must marshal into a JSON object, into a proto struct. +func ToProtoStruct(v any) (*structpb.Struct, error) { + jb, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("json.Marshal: %w", err) + } + x := &structpb.Struct{} + if err := x.UnmarshalJSON(jb); err != nil { + return nil, fmt.Errorf("structpb.Struct.UnmarshalJSON: %w", err) + } + return x, nil +} diff --git a/protoutil/conversion_test.go b/protoutil/conversion_test.go new file mode 100644 index 00000000..1798e107 --- /dev/null +++ b/protoutil/conversion_test.go @@ -0,0 +1,84 @@ +// Copyright 2023 The Authors (see AUTHORS file) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package protoutil provides mechanisms for interacting with proto. +package protoutil + +import ( + "testing" + + "github.com/abcxyz/pkg/testutil" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" +) + +func TestParseProject(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input any + want *structpb.Struct + wantErrSubstr string + }{ + { + name: "success", + input: map[string]any{ + "FieldA": "A", + "FieldB": []string{"B1", "B2"}, + "FieldC": map[string]string{ + "keyC": "ValueC", + }, + }, + want: &structpb.Struct{Fields: map[string]*structpb.Value{ + "FieldA": structpb.NewStringValue("A"), + "FieldB": structpb.NewListValue(&structpb.ListValue{Values: []*structpb.Value{structpb.NewStringValue("B1"), structpb.NewStringValue("B2")}}), + "FieldC": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "keyC": structpb.NewStringValue("ValueC"), + }, + }), + }}, + }, + { + name: "failure_with_json_marshal", + input: map[string]any{ + "FieldA": make(chan int), + }, + wantErrSubstr: "json.Marshal: json: unsupported type: chan int", + }, + { + name: "failure_with_structpb_unmarshal", + input: nil, + wantErrSubstr: "structpb.Struct.UnmarshalJSON: proto:", + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got, gotErr := ToProtoStruct(tc.input) + if diff := testutil.DiffErrString(gotErr, tc.wantErrSubstr); diff != "" { + t.Errorf("Process(%+v) got unexpected error substring: %v", tc.name, diff) + } + if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { + t.Errorf("ToProtoStruct(%+v) got diff (-want, +got): %v", tc.name, diff) + } + }) + } +}