Skip to content

Commit bbd78f9

Browse files
committed
confluent: resolve named-type references in lame Avro unions too
The lame-union path -- raw_unions: false on schema_registry_decode, which is the documented default -- carried the same bug as the raw path: string branches like "Fee" in ["null", "Fee"] went through ecsAvroTypeToCommon directly and collapsed to schema.Any, even when "Fee" was a previously- defined record. The tagged-JSON envelope around each branch then wrapped an Any inner, producing a structureless metadata tree. Reroute the lame hydrator through the same ecsAvroResolveTypeRef helper the raw path now uses, then re-apply the lame-specific wrapping (tagged-Object envelope, type-name preserved as Common.Name to match the wire-form tag). The non-Avro behavior of the lame envelope is unchanged; only the inner Common is now correctly populated for name references. This closes the same CON-468 bug class for the default-config path that commit 4531c51 closed for the raw-unions path.
1 parent 67ded3c commit bbd78f9

2 files changed

Lines changed: 57 additions & 13 deletions

File tree

internal/impl/confluent/ecs_avro.go

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -534,19 +534,14 @@ func ecsAvroHydrateRawUnion(cfg ecsAvroConfig, c *schema.Common, types []any) er
534534
func ecsAvroHydrateLameUnion(cfg ecsAvroConfig, c *schema.Common, types []any) error {
535535
c.Type = schema.Union
536536
for i, uObj := range types {
537-
var childT schema.Common
538-
539-
switch ut := uObj.(type) {
540-
case string:
541-
childT = schema.Common{
542-
Name: ut,
543-
Type: ecsAvroTypeToCommon(ut),
544-
}
545-
case map[string]any:
546-
var err error
547-
if childT, err = ecsAvroFromAnyMap(cfg, ut); err != nil {
548-
return fmt.Errorf("union `%v` child '%v': %w", c.Name, i, err)
549-
}
537+
childT, ok := ecsAvroResolveTypeRef(cfg, uObj)
538+
if !ok {
539+
return fmt.Errorf("union `%v` child '%v': could not resolve type %T", c.Name, i, uObj)
540+
}
541+
if s, isStr := uObj.(string); isStr {
542+
// Lame-union children keep the type-name as the Common.Name so
543+
// the tagged-JSON envelope key matches the Avro wire form.
544+
childT.Name = s
550545
}
551546

552547
if childT.Type == schema.Null {

internal/impl/confluent/ecs_avro_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,55 @@ func TestEcsAvroRawUnionNullableRecordNested(t *testing.T) {
467467
assert.Equal(t, "code", inner.Children[0].Name)
468468
}
469469

470+
// TestEcsAvroLameUnionNameResolution covers the same CON-468 bug class in
471+
// the lame-union (rawUnion=false) path that the schema_registry_decode
472+
// processor takes by default. The lame envelope wraps each branch in a
473+
// tagged Object so the wire shape stays the same, but the inner Common
474+
// must still expand previously-defined named-type references rather than
475+
// collapsing them to schema.Any.
476+
func TestEcsAvroLameUnionNameResolution(t *testing.T) {
477+
spec := []byte(`{
478+
"type": "record",
479+
"name": "Transfer",
480+
"fields": [
481+
{
482+
"name": "primary_fee",
483+
"type": {
484+
"type": "record",
485+
"name": "Fee",
486+
"fields": [{"name": "amount", "type": "long"}]
487+
}
488+
},
489+
{"name": "secondary_fee", "type": ["null", "Fee"]}
490+
]
491+
}`)
492+
c, err := ecsAvroParseFromBytes(ecsAvroConfig{}, spec)
493+
require.NoError(t, err)
494+
495+
secondary := c.Children[1]
496+
assert.Equal(t, "secondary_fee", secondary.Name)
497+
assert.Equal(t, schema.Union, secondary.Type)
498+
require.Len(t, secondary.Children, 2)
499+
500+
// One child is the null branch (Common.Type=Null, Name="").
501+
// The other is the tagged-Object envelope wrapping the resolved Fee.
502+
var feeEnvelope schema.Common
503+
for _, ch := range secondary.Children {
504+
if ch.Type == schema.Object {
505+
feeEnvelope = ch
506+
break
507+
}
508+
}
509+
require.Equal(t, schema.Object, feeEnvelope.Type, "Fee branch should be tagged-Object envelope")
510+
require.Len(t, feeEnvelope.Children, 1)
511+
feeInner := feeEnvelope.Children[0]
512+
assert.Equal(t, "Fee", feeInner.Name)
513+
assert.Equal(t, schema.Object, feeInner.Type, "name reference to Fee should resolve to its record structure, not schema.Any")
514+
require.Len(t, feeInner.Children, 1)
515+
assert.Equal(t, "amount", feeInner.Children[0].Name)
516+
assert.Equal(t, schema.Int64, feeInner.Children[0].Type)
517+
}
518+
470519
// TestEcsAvroRecordWithNilFields is a regression test for schemas where a
471520
// field's type is a record object without a "fields" key (e.g. back-reference
472521
// form {"type":"record","name":"Party"} or "fields":null from some generators).

0 commit comments

Comments
 (0)