Skip to content

Commit

Permalink
Add new messagemagic (#5)
Browse files Browse the repository at this point in the history
* feat: support messagemagic

* test: fix testdata
  • Loading branch information
wolfogre authored Sep 11, 2024
1 parent 139774d commit 4cb495b
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 20 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/gochore/protomagic
go 1.23

require (
github.com/gochore/pt v1.3.0
github.com/stretchr/testify v1.9.0
google.golang.org/protobuf v1.34.2
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gochore/pt v1.3.0 h1:E/cLFk84WyNDCwFvgGZezLo656KEJNMUAznFcb5NYP0=
github.com/gochore/pt v1.3.0/go.mod h1:VgAadJxZUjqIPhLPrfC8BgTWQzi1tVIrQOefxBC64PQ=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
32 changes: 32 additions & 0 deletions messagemagic/messagemagic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package messagemagic

import (
"google.golang.org/protobuf/proto"
)

// Patch patches the message with the patch.
// It copies all not optional fields from the patch to the message.
// For optional fields, it copies the value only if the patch has the field.
// Otherwise, it keeps the value in the message unchanged.

func Patch[T proto.Message](msg, patch T) T {
dst := proto.Clone(msg).(T)

dstRefl, srcRefl := dst.ProtoReflect(), patch.ProtoReflect()
for i := range srcRefl.Descriptor().Fields().Len() {
fd := srcRefl.Descriptor().Fields().Get(i)
if fd.HasOptionalKeyword() {
if srcRefl.Has(fd) {
dstRefl.Set(fd, srcRefl.Get(fd))
}
} else {
if srcRefl.Has(fd) {
dstRefl.Set(fd, srcRefl.Get(fd))
} else {
dstRefl.Clear(fd)
}
}
}

return dst
}
174 changes: 174 additions & 0 deletions messagemagic/messagemagic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package messagemagic

import (
"encoding/json"
"testing"

dummyv1 "github.com/gochore/protomagic/testdata/gen/dummy/v1"

"github.com/gochore/pt"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/proto"
)

func assertMessageEqual(t *testing.T, expected, actual proto.Message) {
if !assert.True(t, proto.Equal(expected, actual)) {
expectedJson, _ := json.Marshal(expected)
actualJson, _ := json.Marshal(actual)
assert.JSONEq(t, string(expectedJson), string(actualJson)) // to show the diff
}
}

func TestPatch(t *testing.T) {
t.Run("regular", func(t *testing.T) {
msg := &dummyv1.DummyA{
Name: "name",
Value: 1,
Values: []string{"a", "b"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_FOO,
ConfigA: &dummyv1.DummyConfigA{Name: "name"},
OName: pt.P("o_name"),
OValue: pt.P[int32](2),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR),
OConfigA: &dummyv1.DummyConfigA{Name: "name"},
}
patch := &dummyv1.DummyA{
Name: "name2",
Value: 2,
Values: []string{"c", "d"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR,
ConfigA: &dummyv1.DummyConfigA{Name: "name2"},
OName: pt.P("o_name2"),
OValue: pt.P[int32](3),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_FOO),
OConfigA: &dummyv1.DummyConfigA{Name: "name2"},
}
want := &dummyv1.DummyA{
Name: "name2",
Value: 2,
Values: []string{"c", "d"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR,
ConfigA: &dummyv1.DummyConfigA{Name: "name2"},
OName: pt.P("o_name2"),
OValue: pt.P[int32](3),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_FOO),
OConfigA: &dummyv1.DummyConfigA{Name: "name2"},
}
got := Patch(msg, patch)
assertMessageEqual(t, want, got)
})

t.Run("empty patch", func(t *testing.T) {
msg := &dummyv1.DummyA{
Name: "name",
Value: 1,
Values: []string{"a", "b"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_FOO,
ConfigA: &dummyv1.DummyConfigA{Name: "name"},
OName: pt.P("o_name"),
OValue: pt.P[int32](2),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR),
OConfigA: &dummyv1.DummyConfigA{Name: "name"},
}
patch := &dummyv1.DummyA{
Name: "",
Value: 0,
Values: nil,
TestType: 0,
ConfigA: nil,
OName: nil,
OValue: nil,
OTestType: nil,
OConfigA: nil,
}
want := &dummyv1.DummyA{
Name: "",
Value: 0,
Values: nil,
TestType: 0,
ConfigA: nil,
OName: pt.P("o_name"),
OValue: pt.P[int32](2),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR),
OConfigA: &dummyv1.DummyConfigA{Name: "name"},
}
got := Patch(msg, patch)
assertMessageEqual(t, want, got)
})

t.Run("empty msg", func(t *testing.T) {
msg := &dummyv1.DummyA{
Name: "",
Value: 0,
Values: nil,
TestType: 0,
ConfigA: nil,
OName: nil,
OValue: nil,
OTestType: nil,
OConfigA: nil,
}
patch := &dummyv1.DummyA{
Name: "name2",
Value: 2,
Values: []string{"c", "d"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR,
ConfigA: &dummyv1.DummyConfigA{Name: "name2"},
OName: pt.P("o_name2"),
OValue: pt.P[int32](3),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_FOO),
OConfigA: &dummyv1.DummyConfigA{Name: "name2"},
}
want := &dummyv1.DummyA{
Name: "name2",
Value: 2,
Values: []string{"c", "d"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR,
ConfigA: &dummyv1.DummyConfigA{Name: "name2"},
OName: pt.P("o_name2"),
OValue: pt.P[int32](3),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_FOO),
OConfigA: &dummyv1.DummyConfigA{Name: "name2"},
}
got := Patch(msg, patch)
assertMessageEqual(t, want, got)
})

t.Run("zero patch", func(t *testing.T) {
msg := &dummyv1.DummyA{
Name: "name",
Value: 1,
Values: []string{"a", "b"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_FOO,
ConfigA: &dummyv1.DummyConfigA{Name: "name"},
OName: pt.P("o_name"),
OValue: pt.P[int32](2),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR),
OConfigA: &dummyv1.DummyConfigA{Name: "name"},
}
patch := &dummyv1.DummyA{
Name: "name2",
Value: 2,
Values: []string{"c", "d"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR,
ConfigA: &dummyv1.DummyConfigA{Name: "name2"},
OName: pt.P(""),
OValue: pt.P[int32](0),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_UNSPECIFIED),
OConfigA: &dummyv1.DummyConfigA{},
}
want := &dummyv1.DummyA{
Name: "name2",
Value: 2,
Values: []string{"c", "d"},
TestType: dummyv1.TestEnumType_TEST_ENUM_TYPE_BAR,
ConfigA: &dummyv1.DummyConfigA{Name: "name2"},
OName: pt.P(""),
OValue: pt.P[int32](0),
OTestType: pt.P(dummyv1.TestEnumType_TEST_ENUM_TYPE_UNSPECIFIED),
OConfigA: &dummyv1.DummyConfigA{},
}
got := Patch(msg, patch)
assertMessageEqual(t, want, got)
})
}
Loading

0 comments on commit 4cb495b

Please sign in to comment.