Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 510b002

Browse files
issue 814 - Add binary content mode for NATS and JetStream protocols
Signed-off-by: stephen-totty-hpe <[email protected]>
1 parent 70abff6 commit 510b002

File tree

10 files changed

+385
-26
lines changed

10 files changed

+385
-26
lines changed

.github/workflows/integration.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ jobs:
3232
ports:
3333
- 4222:4222
3434

35+
jetstream:
36+
image: bitnami/nats:latest
37+
env:
38+
NATS_EXTRA_ARGS: "--jetstream --port 4223"
39+
ports:
40+
- 4223:4223
41+
3542
amqp:
3643
image: scholzj/qpid-dispatch
3744
env:

protocol/nats_jetstream/v2/message.go

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@ package nats_jetstream
88
import (
99
"bytes"
1010
"context"
11+
"fmt"
12+
"strings"
1113

1214
"github.com/nats-io/nats.go"
1315

1416
"github.com/cloudevents/sdk-go/v2/binding"
1517
"github.com/cloudevents/sdk-go/v2/binding/format"
18+
"github.com/cloudevents/sdk-go/v2/binding/spec"
1619
)
1720

21+
const (
22+
// see https://github.com/cloudevents/spec/blob/main/cloudevents/bindings/nats-protocol-binding.md
23+
prefix = "ce-"
24+
contentTypeHeader = "content-type"
25+
)
26+
27+
var specs = spec.WithPrefix(prefix)
28+
1829
// Message implements binding.Message by wrapping an *nats.Msg.
1930
// This message *can* be read several times safely
2031
type Message struct {
@@ -24,8 +35,15 @@ type Message struct {
2435

2536
// NewMessage wraps an *nats.Msg in a binding.Message.
2637
// The returned message *can* be read several times safely
38+
// The default encoding returned is EncodingStructured unless the NATS message contains a specversion header.
2739
func NewMessage(msg *nats.Msg) *Message {
28-
return &Message{Msg: msg, encoding: binding.EncodingStructured}
40+
encoding := binding.EncodingStructured
41+
if msg.Header != nil {
42+
if msg.Header.Get(specs.PrefixedSpecVersionName()) != "" {
43+
encoding = binding.EncodingBinary
44+
}
45+
}
46+
return &Message{Msg: msg, encoding: encoding}
2947
}
3048

3149
var _ binding.Message = (*Message)(nil)
@@ -37,15 +55,91 @@ func (m *Message) ReadEncoding() binding.Encoding {
3755

3856
// ReadStructured transfers a structured-mode event to a StructuredWriter.
3957
func (m *Message) ReadStructured(ctx context.Context, encoder binding.StructuredWriter) error {
58+
if m.encoding != binding.EncodingStructured {
59+
return binding.ErrNotStructured
60+
}
4061
return encoder.SetStructuredEvent(ctx, format.JSON, bytes.NewReader(m.Msg.Data))
4162
}
4263

4364
// ReadBinary transfers a binary-mode event to an BinaryWriter.
4465
func (m *Message) ReadBinary(ctx context.Context, encoder binding.BinaryWriter) error {
45-
return binding.ErrNotBinary
66+
if m.encoding != binding.EncodingBinary {
67+
return binding.ErrNotBinary
68+
}
69+
70+
version := m.GetVersion()
71+
if version == nil {
72+
return binding.ErrNotBinary
73+
}
74+
75+
var err error
76+
for k, v := range m.Msg.Header {
77+
headerValue := v[0]
78+
if strings.HasPrefix(k, prefix) {
79+
attr := version.Attribute(k)
80+
if attr != nil {
81+
err = encoder.SetAttribute(attr, headerValue)
82+
} else {
83+
err = encoder.SetExtension(strings.TrimPrefix(k, prefix), headerValue)
84+
}
85+
} else if k == contentTypeHeader {
86+
err = encoder.SetAttribute(version.AttributeFromKind(spec.DataContentType), headerValue)
87+
}
88+
if err != nil {
89+
return err
90+
}
91+
}
92+
93+
if m.Msg.Data != nil {
94+
err = encoder.SetData(bytes.NewBuffer(m.Msg.Data))
95+
}
96+
97+
return err
4698
}
4799

48100
// Finish *must* be called when message from a Receiver can be forgotten by the receiver.
49101
func (m *Message) Finish(err error) error {
50102
return nil
51103
}
104+
105+
// GetAttribute implements binding.MessageMetadataReader
106+
func (m *Message) GetAttribute(attributeKind spec.Kind) (spec.Attribute, interface{}) {
107+
key := withPrefix(attributeKind.String())
108+
if m.Msg.Header != nil {
109+
headerValue := m.Msg.Header.Get(key)
110+
if headerValue != "" {
111+
version := m.GetVersion()
112+
return version.Attribute(key), headerValue
113+
}
114+
}
115+
return nil, nil
116+
}
117+
118+
// GetExtension implements binding.MessageMetadataReader
119+
func (m *Message) GetExtension(name string) interface{} {
120+
key := withPrefix(name)
121+
if m.Msg.Header != nil {
122+
headerValue := m.Msg.Header.Get(key)
123+
if headerValue != "" {
124+
return headerValue
125+
}
126+
}
127+
return nil
128+
}
129+
130+
// GetVersion looks for specVersion header and returns a Version object
131+
func (m *Message) GetVersion() spec.Version {
132+
if m.Msg.Header == nil {
133+
return nil
134+
}
135+
versionValue := m.Msg.Header.Get(specs.PrefixedSpecVersionName())
136+
if versionValue == "" {
137+
return nil
138+
}
139+
return specs.Version(versionValue)
140+
}
141+
142+
// withPrefix prepends the prefix to the attribute name
143+
func withPrefix(attributeName string) string {
144+
return fmt.Sprintf("%s%s", prefix, attributeName)
145+
}

protocol/nats_jetstream/v2/message_test.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@ package nats_jetstream
33
import (
44
"context"
55
"encoding/json"
6-
bindingtest "github.com/cloudevents/sdk-go/v2/binding/test"
76
"testing"
87

8+
"github.com/cloudevents/sdk-go/v2/binding/spec"
9+
bindingtest "github.com/cloudevents/sdk-go/v2/binding/test"
10+
911
"github.com/cloudevents/sdk-go/v2/binding"
1012
"github.com/cloudevents/sdk-go/v2/test"
1113
"github.com/nats-io/nats.go"
1214
)
1315

1416
var (
15-
outBinaryMessage = bindingtest.MockBinaryMessage{}
17+
outBinaryMessage = bindingtest.MockBinaryMessage{
18+
Metadata: map[spec.Attribute]interface{}{},
19+
Extensions: map[string]interface{}{},
20+
}
1621
outStructMessage = bindingtest.MockStructuredMessage{}
1722

1823
testEvent = test.FullEvent()
@@ -31,18 +36,44 @@ var (
3136
Subject: "hello",
3237
Data: binaryData,
3338
}
39+
binaryConsumerMessage = &nats.Msg{
40+
Subject: "hello",
41+
Data: testEvent.Data(),
42+
Header: nats.Header{
43+
"ce-type": {testEvent.Type()},
44+
"ce-source": {testEvent.Source()},
45+
"ce-id": {testEvent.ID()},
46+
"ce-time": {test.Timestamp.String()},
47+
"ce-specversion": {"1.0"},
48+
"ce-dataschema": {test.Schema.String()},
49+
"ce-datacontenttype": {"text/json"},
50+
"ce-subject": {"receiverTopic"},
51+
"ce-exta": {"someext"},
52+
},
53+
}
3454
)
3555

3656
func TestNewMessage(t *testing.T) {
3757
tests := []struct {
38-
name string
39-
consumerMessage *nats.Msg
40-
expectedEncoding binding.Encoding
58+
name string
59+
consumerMessage *nats.Msg
60+
expectedEncoding binding.Encoding
61+
expectedStructuredError error
62+
expectedBinaryError error
4163
}{
4264
{
43-
name: "Structured encoding",
44-
consumerMessage: structuredConsumerMessage,
45-
expectedEncoding: binding.EncodingStructured,
65+
name: "Structured encoding",
66+
consumerMessage: structuredConsumerMessage,
67+
expectedEncoding: binding.EncodingStructured,
68+
expectedStructuredError: nil,
69+
expectedBinaryError: binding.ErrNotBinary,
70+
},
71+
{
72+
name: "Binary encoding",
73+
consumerMessage: binaryConsumerMessage,
74+
expectedEncoding: binding.EncodingBinary,
75+
expectedStructuredError: binding.ErrNotStructured,
76+
expectedBinaryError: nil,
4677
},
4778
}
4879
for _, tt := range tests {
@@ -52,17 +83,16 @@ func TestNewMessage(t *testing.T) {
5283
t.Errorf("Error in NewMessage!")
5384
}
5485
err := got.ReadBinary(context.TODO(), &outBinaryMessage)
55-
if err == nil {
56-
t.Errorf("Response in ReadBinary should err")
86+
if err != tt.expectedBinaryError {
87+
t.Errorf("ReadBinary err:%s", err.Error())
5788
}
5889
err = got.ReadStructured(context.TODO(), &outStructMessage)
59-
if err != nil {
90+
if err != tt.expectedStructuredError {
6091
t.Errorf("ReadStructured err:%s", err.Error())
6192
}
6293
if got.ReadEncoding() != tt.expectedEncoding {
6394
t.Errorf("ExpectedEncoding %s, while got %s", tt.expectedEncoding, got.ReadEncoding())
6495
}
65-
6696
})
6797
}
6898
}

protocol/nats_jetstream/v2/options.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ func NatsOptions(opts ...nats.Option) []nats.Option {
2222
// ProtocolOption is the function signature required to be considered an nats.ProtocolOption.
2323
type ProtocolOption func(*Protocol) error
2424

25+
func WithConsumerOptions(opts ...ConsumerOption) ProtocolOption {
26+
return func(p *Protocol) error {
27+
p.consumerOptions = opts
28+
return nil
29+
}
30+
}
31+
32+
func WithSenderOptions(opts ...SenderOption) ProtocolOption {
33+
return func(p *Protocol) error {
34+
p.senderOptions = opts
35+
return nil
36+
}
37+
}
38+
2539
type SenderOption func(*Sender) error
2640

2741
type ConsumerOption func(*Consumer) error

protocol/nats_jetstream/v2/protocol.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package nats_jetstream
77

88
import (
99
"context"
10+
1011
"github.com/cloudevents/sdk-go/v2/binding"
1112
"github.com/cloudevents/sdk-go/v2/protocol"
1213

@@ -18,11 +19,11 @@ import (
1819
type Protocol struct {
1920
Conn *nats.Conn
2021

21-
Consumer *Consumer
22-
//consumerOptions []ConsumerOption
22+
Consumer *Consumer
23+
consumerOptions []ConsumerOption
2324

24-
Sender *Sender
25-
//senderOptions []SenderOption
25+
Sender *Sender
26+
senderOptions []SenderOption
2627

2728
connOwned bool // whether this protocol created the nats connection
2829
}
@@ -55,11 +56,11 @@ func NewProtocolFromConn(conn *nats.Conn, stream, sendSubject, receiveSubject st
5556
return nil, err
5657
}
5758

58-
if p.Consumer, err = NewConsumerFromConn(conn, stream, receiveSubject, jsOpts, subOpts); err != nil {
59+
if p.Consumer, err = NewConsumerFromConn(conn, stream, receiveSubject, jsOpts, subOpts, p.consumerOptions...); err != nil {
5960
return nil, err
6061
}
6162

62-
if p.Sender, err = NewSenderFromConn(conn, stream, sendSubject, jsOpts); err != nil {
63+
if p.Sender, err = NewSenderFromConn(conn, stream, sendSubject, jsOpts, p.senderOptions...); err != nil {
6364
return nil, err
6465
}
6566

protocol/nats_jetstream/v2/sender.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,18 @@ func (s *Sender) Send(ctx context.Context, in binding.Message, transformers ...b
9191
}()
9292

9393
writer := new(bytes.Buffer)
94-
if err = WriteMsg(ctx, in, writer, transformers...); err != nil {
94+
header, err := WriteMsg(ctx, in, writer, transformers...)
95+
if err != nil {
9596
return err
9697
}
97-
_, err = s.Jsm.Publish(s.Subject, writer.Bytes())
98+
99+
natsMsg := &nats.Msg{
100+
Subject: s.Subject,
101+
Data: writer.Bytes(),
102+
Header: header,
103+
}
104+
105+
_, err = s.Jsm.PublishMsg(natsMsg)
98106

99107
return err
100108
}

0 commit comments

Comments
 (0)