Skip to content

Commit 66152bf

Browse files
authored
Merge pull request #642 from marle3003/develop
Develop
2 parents 15fb7e9 + c0eee7d commit 66152bf

File tree

17 files changed

+451
-81
lines changed

17 files changed

+451
-81
lines changed

config/dynamic/asyncApi/convert.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,15 @@ func convertChannel(name string, c *Channel, config *asyncapi3.Config) (*asyncap
132132
}
133133

134134
func addOperation(msgName, action string, op *Operation, ch *asyncapi3.ChannelRef, msg *asyncapi3.MessageRef, config *asyncapi3.Config) {
135-
name := fmt.Sprintf("%s_%s", action, msgName)
135+
var opName string
136+
if msgName == "publish" || msgName == "subscribe" {
137+
opName = fmt.Sprintf("%s_%s_%s", ch.Value.GetName(), action, msgName)
138+
} else {
139+
opName = fmt.Sprintf("%s_%s", action, msgName)
140+
}
141+
136142
if len(op.OperationId) > 0 {
137-
name = op.OperationId
143+
opName = op.OperationId
138144
}
139145

140146
result := &asyncapi3.Operation{
@@ -150,7 +156,7 @@ func addOperation(msgName, action string, op *Operation, ch *asyncapi3.ChannelRe
150156
config.Operations = map[string]*asyncapi3.OperationRef{}
151157
}
152158

153-
config.Operations[name] = &asyncapi3.OperationRef{Value: result}
159+
config.Operations[opName] = &asyncapi3.OperationRef{Value: result}
154160
}
155161

156162
func addMessage(target *asyncapi3.Channel, msg *asyncapi3.MessageRef, opId, ref, opName string) string {

config/dynamic/asyncApi/convert_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,47 @@ func TestConfig_Convert(t *testing.T) {
7474
require.Equal(t, channel, op.Channel.Value)
7575
require.Equal(t, "Inform about environmental lighting conditions of a particular streetlight.", op.Summary)
7676
}
77+
78+
func TestConfig_ConvertNoOperationId(t *testing.T) {
79+
s := `
80+
asyncapi: '2.6.0'
81+
info:
82+
title: Streetlights API
83+
channels:
84+
foo:
85+
publish:
86+
message:
87+
payload:
88+
schema:
89+
type: string
90+
bar:
91+
publish:
92+
message:
93+
payload:
94+
schema:
95+
type: string
96+
`
97+
var old *Config
98+
err := yaml.Unmarshal([]byte(s), &old)
99+
require.NoError(t, err)
100+
cfg, err := old.Convert()
101+
require.NoError(t, err)
102+
103+
require.Len(t, cfg.Channels, 2)
104+
require.Contains(t, cfg.Channels, "foo")
105+
require.Contains(t, cfg.Channels, "bar")
106+
107+
require.Len(t, cfg.Channels["foo"].Value.Messages, 1)
108+
require.Contains(t, cfg.Channels["foo"].Value.Messages, "publish")
109+
require.Len(t, cfg.Channels["bar"].Value.Messages, 1)
110+
require.Contains(t, cfg.Channels["bar"].Value.Messages, "publish")
111+
112+
require.Len(t, cfg.Operations, 2)
113+
require.Contains(t, cfg.Operations, "foo_send_publish")
114+
require.Contains(t, cfg.Operations, "bar_send_publish")
115+
116+
require.Len(t, cfg.Operations["foo_send_publish"].Value.Messages, 1)
117+
require.Equal(t, cfg.Channels["foo"].Value.Messages["publish"], cfg.Operations["foo_send_publish"].Value.Messages[0])
118+
require.Len(t, cfg.Operations["bar_send_publish"].Value.Messages, 1)
119+
require.Equal(t, cfg.Channels["bar"].Value.Messages["publish"], cfg.Operations["bar_send_publish"].Value.Messages[0])
120+
}

engine/kafka.go

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
log "github.com/sirupsen/logrus"
7+
"maps"
78
"math/rand"
89
"mokapi/engine/common"
910
"mokapi/kafka"
@@ -16,6 +17,7 @@ import (
1617
"mokapi/schema/encoding"
1718
"mokapi/schema/json/generator"
1819
"mokapi/schema/json/schema"
20+
"slices"
1921
"strings"
2022
"time"
2123
)
@@ -165,24 +167,38 @@ func (c *KafkaClient) getPartition(t *store.Topic, partition int) (*store.Partit
165167
}
166168

167169
func (c *KafkaClient) createRecordBatch(key, value interface{}, headers map[string]interface{}, topic *asyncapi3.Channel, config *asyncapi3.Config) (rb kafka.RecordBatch, err error) {
168-
n := len(topic.Messages)
169-
if n == 0 {
170-
err = fmt.Errorf("message configuration missing")
171-
return
170+
contentType := config.DefaultContentType
171+
var payload *asyncapi3.SchemaRef
172+
keySchema := &asyncapi3.SchemaRef{
173+
Value: &asyncapi3.MultiSchemaFormat{
174+
Schema: &schema.Schema{Type: schema.Types{"string"}, Pattern: "[a-z]{9}"},
175+
},
172176
}
177+
var headerSchema *schema.Schema
173178

174-
msg, err := selectMessage(value, topic.Name, config)
175-
if err != nil {
176-
return
177-
}
179+
if len(topic.Messages) > 0 {
180+
var msg *asyncapi3.Message
181+
msg, err = selectMessage(value, topic, config)
182+
if err != nil {
183+
return
184+
}
185+
payload = msg.Payload
186+
187+
if msg.Bindings.Kafka.Key != nil {
188+
keySchema = msg.Bindings.Kafka.Key
189+
}
190+
191+
if msg.ContentType != "" {
192+
contentType = msg.ContentType
193+
}
178194

179-
keySchema := msg.Bindings.Kafka.Key
180-
if keySchema == nil {
181-
// use default key schema
182-
keySchema = &asyncapi3.SchemaRef{
183-
Value: &asyncapi3.MultiSchemaFormat{
184-
Schema: &schema.Schema{Type: schema.Types{"string"}, Pattern: "[a-z]{9}"},
185-
},
195+
if msg.Headers != nil {
196+
var ok bool
197+
headerSchema, ok = msg.Headers.Value.Schema.(*schema.Schema)
198+
if !ok {
199+
err = fmt.Errorf("currently only json schema supported")
200+
return
201+
}
186202
}
187203
}
188204

@@ -194,16 +210,13 @@ func (c *KafkaClient) createRecordBatch(key, value interface{}, headers map[stri
194210
}
195211

196212
if value == nil {
197-
value, err = createValue(msg.Payload)
213+
value, err = createValue(payload)
198214
if err != nil {
199215
return rb, fmt.Errorf("unable to generate kafka value: %v", err)
200216
}
201217
}
202218

203-
contentType := config.DefaultContentType
204-
if msg.ContentType != "" {
205-
contentType = msg.ContentType
206-
} else if contentType == "" {
219+
if contentType == "" {
207220
// set default: https://github.com/asyncapi/spec/issues/319
208221
contentType = "application/json"
209222
}
@@ -212,7 +225,7 @@ func (c *KafkaClient) createRecordBatch(key, value interface{}, headers map[stri
212225
if b, ok := value.([]byte); ok {
213226
v = b
214227
} else {
215-
v, err = marshal(value, msg.Payload, contentType)
228+
v, err = marshal(value, payload, contentType)
216229
if err != nil {
217230
return
218231
}
@@ -229,16 +242,7 @@ func (c *KafkaClient) createRecordBatch(key, value interface{}, headers map[stri
229242
}
230243

231244
var recordHeaders []kafka.RecordHeader
232-
var hs *schema.Schema
233-
if msg.Headers != nil {
234-
var ok bool
235-
hs, ok = msg.Headers.Value.Schema.(*schema.Schema)
236-
if !ok {
237-
err = fmt.Errorf("currently only json schema supported")
238-
return
239-
}
240-
}
241-
recordHeaders, err = getHeaders(headers, hs)
245+
recordHeaders, err = getHeaders(headers, headerSchema)
242246
if err != nil {
243247
return
244248
}
@@ -352,22 +356,24 @@ func marshalKey(key interface{}, r *asyncapi3.SchemaRef) ([]byte, error) {
352356
}
353357
}
354358

355-
func selectMessage(value any, topic string, cfg *asyncapi3.Config) (*asyncapi3.Message, error) {
359+
func selectMessage(value any, topic *asyncapi3.Channel, cfg *asyncapi3.Config) (*asyncapi3.Message, error) {
356360
noOperationDefined := true
357361

358362
// first try to get send operation
359-
for name, op := range cfg.Operations {
363+
for _, op := range cfg.Operations {
360364
if op.Value == nil || op.Value.Channel.Value == nil {
361365
continue
362366
}
363-
if op.Value.Channel.Value.Name == topic && op.Value.Action == "send" {
367+
if op.Value.Channel.Value == topic && op.Value.Action == "send" {
364368
noOperationDefined = false
369+
var messages []*asyncapi3.MessageRef
365370
if len(op.Value.Messages) == 0 {
366-
log.Warnf("no message defined for operation %s", name)
371+
messages = slices.Collect(maps.Values(op.Value.Channel.Value.Messages))
372+
} else {
373+
messages = op.Value.Messages
367374
}
368-
for _, msg := range op.Value.Messages {
375+
for _, msg := range messages {
369376
if msg.Value == nil {
370-
log.Errorf("no message defined for operation %s", name)
371377
continue
372378
}
373379
if valueMatchMessagePayload(value, msg.Value) {
@@ -378,18 +384,20 @@ func selectMessage(value any, topic string, cfg *asyncapi3.Config) (*asyncapi3.M
378384
}
379385

380386
// second, try to get receive operation
381-
for name, op := range cfg.Operations {
387+
for _, op := range cfg.Operations {
382388
if op.Value == nil || op.Value.Channel.Value == nil {
383389
continue
384390
}
385-
if op.Value.Channel.Value.Name == topic && op.Value.Action == "receive" {
391+
if op.Value.Channel.Value == topic && op.Value.Action == "receive" {
386392
noOperationDefined = false
393+
var messages []*asyncapi3.MessageRef
387394
if len(op.Value.Messages) == 0 {
388-
log.Errorf("no message defined for operation %s", name)
395+
messages = slices.Collect(maps.Values(op.Value.Channel.Value.Messages))
396+
} else {
397+
messages = op.Value.Messages
389398
}
390-
for _, msg := range op.Value.Messages {
399+
for _, msg := range messages {
391400
if msg.Value == nil {
392-
log.Warnf("no message defined for operation %s", name)
393401
continue
394402
}
395403
if valueMatchMessagePayload(value, msg.Value) {
@@ -404,13 +412,13 @@ func selectMessage(value any, topic string, cfg *asyncapi3.Config) (*asyncapi3.M
404412
}
405413

406414
if value != nil {
407-
return nil, fmt.Errorf("no matching 'send' or 'receive' operation found for value: %v", value)
415+
return nil, fmt.Errorf("no message configuration matches the message value for topic '%s' and value: %v", topic.GetName(), value)
408416
}
409-
return nil, fmt.Errorf("no matching 'send' or 'receive' operation defined")
417+
return nil, fmt.Errorf("no message ")
410418
}
411419

412420
func valueMatchMessagePayload(value any, msg *asyncapi3.Message) bool {
413-
if value == nil {
421+
if value == nil || msg.Payload == nil {
414422
return true
415423
}
416424

engine/kafka_test.go

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package engine_test
33
import (
44
"bytes"
55
"fmt"
6-
"github.com/brianvoe/gofakeit/v6"
76
"github.com/sirupsen/logrus"
87
"github.com/sirupsen/logrus/hooks/test"
98
"github.com/stretchr/testify/require"
@@ -16,6 +15,7 @@ import (
1615
"mokapi/providers/asyncapi3"
1716
"mokapi/providers/asyncapi3/asyncapi3test"
1817
"mokapi/runtime"
18+
"mokapi/schema/json/generator"
1919
"mokapi/schema/json/schema/schematest"
2020
"net/url"
2121
"testing"
@@ -499,7 +499,7 @@ func TestKafkaClient(t *testing.T) {
499499
produce({ topic: 'foo', messages: [{ data: 12 }] })
500500
}
501501
`))
502-
require.EqualError(t, err, "producing kafka message to 'foo' failed: no matching 'send' or 'receive' operation found for value: 12 at mokapi/js/kafka.(*Module).Produce-fm (native)")
502+
require.EqualError(t, err, "producing kafka message to 'foo' failed: no message configuration matches the message value for topic 'foo' and value: 12 at mokapi/js/kafka.(*Module).Produce-fm (native)")
503503

504504
b, errCode := app.Kafka.Get("foo").Store.Topic("foo").Partition(0).Read(0, 1000)
505505
require.Equal(t, kafka.None, errCode)
@@ -508,7 +508,7 @@ func TestKafkaClient(t *testing.T) {
508508

509509
// logs
510510
require.Len(t, hook.Entries, 2)
511-
require.Equal(t, "js error: producing kafka message to 'foo' failed: no matching 'send' or 'receive' operation found for value: 12 in test.js", hook.LastEntry().Message)
511+
require.Equal(t, "js error: producing kafka message to 'foo' failed: no message configuration matches the message value for topic 'foo' and value: 12 in test.js", hook.LastEntry().Message)
512512
},
513513
},
514514
{
@@ -566,11 +566,91 @@ func TestKafkaClient(t *testing.T) {
566566
require.Contains(t, msg, "kafka topic 'retry' not found. Retry in 1s")
567567
},
568568
},
569+
{
570+
name: "channel does not define a message",
571+
cfg: func() *asyncapi3.Config {
572+
ch := asyncapi3test.NewChannel()
573+
574+
return asyncapi3test.NewConfig(
575+
asyncapi3test.WithInfo("foo", "", ""),
576+
asyncapi3test.AddChannel("foo", ch),
577+
asyncapi3test.WithOperation("sendAction",
578+
asyncapi3test.WithOperationAction("send"),
579+
asyncapi3test.WithOperationChannel(ch),
580+
),
581+
)
582+
},
583+
test: func(t *testing.T, e *engine.Engine, app *runtime.App) {
584+
err := e.AddScript(newScript("test.js", `
585+
import { produce } from 'mokapi/kafka'
586+
export default function() {
587+
produce({ topic: 'foo', messages: [{ data: 'foo' }] })
588+
}
589+
`))
590+
591+
require.NoError(t, err)
592+
b, errCode := app.Kafka.Get("foo").Store.Topic("foo").Partition(0).Read(0, 1000)
593+
require.Equal(t, kafka.None, errCode)
594+
require.NotNil(t, b)
595+
require.Equal(t, "gbrmarxhk", kafka.BytesToString(b.Records[0].Key))
596+
require.Equal(t, `"foo"`, kafka.BytesToString(b.Records[0].Value))
597+
598+
err = e.AddScript(newScript("test.js", `
599+
import { produce } from 'mokapi/kafka'
600+
export default function() {
601+
produce({ topic: 'foo', messages: [{ }] })
602+
}
603+
`))
604+
605+
require.NoError(t, err)
606+
b, errCode = app.Kafka.Get("foo").Store.Topic("foo").Partition(0).Read(1, 1000)
607+
require.Equal(t, kafka.None, errCode)
608+
require.NotNil(t, b)
609+
require.Equal(t, "ijbptapwy", kafka.BytesToString(b.Records[0].Key))
610+
require.Equal(t, `""`, kafka.BytesToString(b.Records[0].Value))
611+
},
612+
},
613+
{
614+
name: "no payload is defined and produce with value and nil",
615+
cfg: func() *asyncapi3.Config {
616+
msg := asyncapi3test.NewMessage()
617+
return createCfg("foo", msg)
618+
},
619+
test: func(t *testing.T, e *engine.Engine, app *runtime.App) {
620+
err := e.AddScript(newScript("test.js", `
621+
import { produce } from 'mokapi/kafka'
622+
export default function() {
623+
produce({ topic: 'foo', messages: [{ data: 'foo' }] })
624+
}
625+
`))
626+
627+
require.NoError(t, err)
628+
b, errCode := app.Kafka.Get("foo").Store.Topic("foo").Partition(0).Read(0, 1000)
629+
require.Equal(t, kafka.None, errCode)
630+
require.NotNil(t, b)
631+
require.Equal(t, "gbrmarxhk", kafka.BytesToString(b.Records[0].Key))
632+
require.Equal(t, `"foo"`, kafka.BytesToString(b.Records[0].Value))
633+
634+
err = e.AddScript(newScript("test.js", `
635+
import { produce } from 'mokapi/kafka'
636+
export default function() {
637+
produce({ topic: 'foo', messages: [{ }] })
638+
}
639+
`))
640+
641+
require.NoError(t, err)
642+
b, errCode = app.Kafka.Get("foo").Store.Topic("foo").Partition(0).Read(1, 1000)
643+
require.Equal(t, kafka.None, errCode)
644+
require.NotNil(t, b)
645+
require.Equal(t, "ijbptapwy", kafka.BytesToString(b.Records[0].Key))
646+
require.Equal(t, `""`, kafka.BytesToString(b.Records[0].Value))
647+
},
648+
},
569649
}
570650

571651
for _, tc := range testcases {
572652
t.Run(tc.name, func(t *testing.T) {
573-
gofakeit.Seed(11)
653+
generator.Seed(11)
574654

575655
app := runtime.New(&static.Config{})
576656
e := enginetest.NewEngine(

0 commit comments

Comments
 (0)