diff --git a/internal/protohcl/decoder.go b/internal/protohcl/decoder.go index 67ee074ec1..95b3bb86ff 100644 --- a/internal/protohcl/decoder.go +++ b/internal/protohcl/decoder.go @@ -173,22 +173,22 @@ func (bd bodyDecoder) schema(desc protoreflect.MessageDescriptor) (*hcl.BodySche f := fields.Get(i) kind := f.Kind() - // maps are special and whether they use block or attribute syntax depends - // on the value type + // maps are special and whether they can use block syntax depends on the value type if f.IsMap() { valueDesc := f.MapValue() valueKind := valueDesc.Kind() wktHint := wellKnownTypeSchemaHint(valueDesc) - // Message types should generally be encoded as blocks unless its a special Well Known Type - // that should use attribute encoding + // Maps with values that are Messages can generally be decoded using the block syntax. + // The exception are some of the Well-Known-Types that appear as scalar values with + // either string or numeric encoding but get parsed into message types. It is still + // fine to also decode these from the attribute syntax. if valueKind == protoreflect.MessageKind && wktHint != wellKnownAttribute { schema.Blocks = append(schema.Blocks, hcl.BlockHeaderSchema{ Type: bd.namer.NameField(f), LabelNames: []string{"key"}, }) - continue } // non-message types or Well Known Message types that need attribute encoding diff --git a/internal/protohcl/unmarshal_test.go b/internal/protohcl/unmarshal_test.go index ef00de3ffb..b1fc854279 100644 --- a/internal/protohcl/unmarshal_test.go +++ b/internal/protohcl/unmarshal_test.go @@ -109,6 +109,56 @@ func TestNestedAndCollections(t *testing.T) { require.Equal(t, out.IntList[1], int32(2)) } +func TestNestedAndCollections_AttributeSyntax(t *testing.T) { + hcl := ` + primitives { + uint32_val = 42 + } + + primitives_map = { + "foo" = { + uint32_val = 42 + } + } + + protocol_map = { + "foo" = "PROTOCOL_TCP" + } + + primitives_list = [ + { + uint32_val = 42 + }, + { + uint32_val = 56 + } + ] + + int_list = [ + 1, + 2 + ] + + ` + + var out testproto.NestedAndCollections + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + + require.NotNil(t, out.Primitives) + require.Equal(t, out.Primitives.Uint32Val, uint32(42)) + require.NotNil(t, out.PrimitivesMap) + require.Equal(t, out.PrimitivesMap["foo"].Uint32Val, uint32(42)) + require.NotNil(t, out.ProtocolMap) + require.Equal(t, out.ProtocolMap["foo"], testproto.Protocol_PROTOCOL_TCP) + require.Len(t, out.PrimitivesList, 2) + require.Equal(t, out.PrimitivesList[0].Uint32Val, uint32(42)) + require.Equal(t, out.PrimitivesList[1].Uint32Val, uint32(56)) + require.Len(t, out.IntList, 2) + require.Equal(t, out.IntList[1], int32(2)) +} + func TestPrimitiveWrappers(t *testing.T) { hcl := ` double_val = 1.234