Skip to content

Commit

Permalink
fix ut.
Browse files Browse the repository at this point in the history
Signed-off-by: morvencao <[email protected]>
  • Loading branch information
morvencao committed Dec 8, 2023
1 parent 6613ed0 commit c23ec5b
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 90 deletions.
25 changes: 15 additions & 10 deletions protocol/grpc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ replace github.com/cloudevents/sdk-go/binding/format/protobuf/v2 => ../../bindin
require (
github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.14.0
github.com/cloudevents/sdk-go/v2 v2.14.0
github.com/stretchr/testify v1.8.4
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
go.uber.org/atomic v1.4.0 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.10.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
43 changes: 36 additions & 7 deletions protocol/grpc/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,67 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
30 changes: 14 additions & 16 deletions protocol/grpc/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import (
const (
prefix = "ce-"
contenttype = "contenttype"
dataSchema = "dataschema"
subject = "subject"
time = "time"
// dataSchema = "dataschema"
subject = "subject"
time = "time"
)

var specs = spec.WithPrefix(prefix)
Expand Down Expand Up @@ -72,9 +72,6 @@ func (m *Message) ReadEncoding() binding.Encoding {
}

func (m *Message) ReadStructured(ctx context.Context, encoder binding.StructuredWriter) error {
if m.version != nil {
return binding.ErrNotStructured
}
if m.format == nil {
return binding.ErrNotStructured
}
Expand All @@ -83,11 +80,15 @@ func (m *Message) ReadStructured(ctx context.Context, encoder binding.Structured
}

func (m *Message) ReadBinary(ctx context.Context, encoder binding.BinaryWriter) error {
if m.version == nil {
return binding.ErrNotBinary
}

if m.format != nil {
return binding.ErrNotBinary
}

if m.internal.SpecVersion == "" {
if m.internal.SpecVersion != "" {
err := encoder.SetAttribute(m.version.AttributeFromKind(spec.SpecVersion), m.internal.SpecVersion)
if err != nil {
return err
Expand All @@ -113,29 +114,26 @@ func (m *Message) ReadBinary(ctx context.Context, encoder binding.BinaryWriter)
}

for name, value := range m.internal.Attributes {
v, err := valueFrom(value)
attrVal, err := valueFrom(value)
if err != nil {
return fmt.Errorf("failed to convert attribute %s: %s", name, err)
}

if strings.HasPrefix(name, prefix) {
attr := m.version.Attribute(name)
if attr != nil {
switch attr.Kind() {
case spec.DataContentType, spec.DataSchema, spec.Subject, spec.Time:
err = encoder.SetAttribute(attr, v)
if err != nil {
return err
}
err = encoder.SetAttribute(attr, attrVal)
if err != nil {
return err
}
} else {
err = encoder.SetExtension(strings.TrimPrefix(name, prefix), v)
err = encoder.SetExtension(strings.TrimPrefix(name, prefix), attrVal)
if err != nil {
return err
}
}
} else if name == contenttype {
err = encoder.SetAttribute(m.version.AttributeFromKind(spec.DataContentType), v)
err = encoder.SetAttribute(m.version.AttributeFromKind(spec.DataContentType), attrVal)
if err != nil {
return err
}
Expand Down
69 changes: 69 additions & 0 deletions protocol/grpc/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright 2023 The CloudEvents Authors
SPDX-License-Identifier: Apache-2.0
*/

package grpc

import (
"context"
"testing"

"github.com/cloudevents/sdk-go/binding/format/protobuf/v2/pb"
"github.com/cloudevents/sdk-go/v2/binding"
"github.com/cloudevents/sdk-go/v2/event"
)

func TestReadStructured(t *testing.T) {
tests := []struct {
name string
msg *pb.CloudEvent
wantErr error
}{
{
name: "nil format",
msg: &pb.CloudEvent{},
wantErr: binding.ErrNotStructured,
},
{
name: "json format",
msg: &pb.CloudEvent{
Attributes: map[string]*pb.CloudEventAttributeValue{
contenttype: &pb.CloudEventAttributeValue{
Attr: &pb.CloudEventAttributeValue_CeString{
CeString: event.ApplicationCloudEventsJSON,
},
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
msg := NewMessage(tc.msg)
err := msg.ReadStructured(context.Background(), (*pbEventWriter)(tc.msg))
if err != tc.wantErr {
t.Errorf("Error unexpected. got: %v, want: %v", err, tc.wantErr)
}
})
}
}

func TestReadBinary(t *testing.T) {
msg := &pb.CloudEvent{
SpecVersion: "1.0",
Id: "ABC-123",
Source: "test-source",
Type: "binary.test",
Attributes: map[string]*pb.CloudEventAttributeValue{},
Data: &pb.CloudEvent_BinaryData{
BinaryData: []byte("{hello:world}"),
},
}

message := NewMessage(msg)
err := message.ReadBinary(context.Background(), (*pbEventWriter)(msg))
if err != nil {
t.Errorf("Error unexpected. got: %v", err)
}
}
2 changes: 1 addition & 1 deletion protocol/grpc/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (p *Protocol) Send(ctx context.Context, m binding.Message, transformers ...
defer m.Finish(err)

msg := &pb.CloudEvent{}
err = WritePubMessage(ctx, m, msg, transformers...)
err = WritePBMessage(ctx, m, msg, transformers...)
if err != nil {
return err
}
Expand Down
57 changes: 34 additions & 23 deletions protocol/grpc/write_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
)

// WritePubMessage fills the provided pubMessage with the message m.
// WritePBMessage fills the provided pubMessage with the message m.
// Using context you can tweak the encoding processing (more details on binding.Write documentation).
func WritePubMessage(ctx context.Context, m binding.Message, pbEvt *pb.CloudEvent, transformers ...binding.Transformer) error {
func WritePBMessage(ctx context.Context, m binding.Message, pbEvt *pb.CloudEvent, transformers ...binding.Transformer) error {
structuredWriter := (*pbEventWriter)(pbEvt)
binaryWriter := (*pbEventWriter)(pbEvt)

Expand Down Expand Up @@ -100,70 +100,81 @@ func (b *pbEventWriter) SetAttribute(attribute spec.Attribute, value interface{}
return fmt.Errorf("invalid SpecVersion type, expected string got %T", value)
}
b.SpecVersion = val
return nil
case spec.ID:
val, ok := value.(string)
if !ok {
return fmt.Errorf("invalid ID type, expected string got %T", value)
}
b.Id = val
return nil
case spec.Source:
val, ok := value.(string)
if !ok {
return fmt.Errorf("invalid Source type, expected string got %T", value)
}
b.Source = val
return nil
case spec.Type:
val, ok := value.(string)
if !ok {
return fmt.Errorf("invalid Type type, expected string got %T", value)
}
b.Type = val
return nil
case spec.DataContentType:
if value == nil {
delete(b.Attributes, contenttype)
} else {
attrVal, err := attributeFor(value)
if err != nil {
return err
}
b.Attributes[contenttype] = attrVal
}
b.Attributes[contenttype], _ = attributeFor(value)
return nil
case spec.DataSchema:
if value == nil {
delete(b.Attributes, prefix+dataSchema)
}
b.Attributes[prefix+dataSchema], _ = attributeFor(value)
return nil
case spec.Subject:
if value == nil {
delete(b.Attributes, prefix+subject)
} else {
attrVal, err := attributeFor(value)
if err != nil {
return err
}
b.Attributes[prefix+subject] = attrVal
}
b.Attributes[prefix+subject], _ = attributeFor(value)
return nil
case spec.Time:
if value == nil {
delete(b.Attributes, prefix+time)
} else {
attrVal, err := attributeFor(value)
if err != nil {
return err
}
b.Attributes[prefix+time] = attrVal
}
b.Attributes[prefix+time], _ = attributeFor(value)
return nil
default:
if value == nil {
delete(b.Attributes, prefix+attribute.Name())
} else {
attrVal, err := attributeFor(value)
if err != nil {
return err
}
b.Attributes[prefix+attribute.Name()] = attrVal
}
b.Attributes[prefix+attribute.Name()], _ = attributeFor(value)
}

return nil
}

func (b *pbEventWriter) SetExtension(name string, value interface{}) (err error) {
func (b *pbEventWriter) SetExtension(name string, value interface{}) error {
if value == nil {
delete(b.Attributes, prefix+name)
} else {
attrVal, err := attributeFor(value)
if err != nil {
return err
}
b.Attributes[prefix+name] = attrVal
}

b.Attributes[prefix+name], err = attributeFor(value)

return
return nil
}

func attributeFor(v interface{}) (*pb.CloudEventAttributeValue, error) {
Expand Down
Loading

0 comments on commit c23ec5b

Please sign in to comment.