diff --git a/internal/encoding/yaml/encode.go b/internal/encoding/yaml/encode.go index 155182017b7..05212bf9b80 100644 --- a/internal/encoding/yaml/encode.go +++ b/internal/encoding/yaml/encode.go @@ -234,6 +234,27 @@ func encodeExprs(exprs []ast.Expr) (n *yaml.Node, err error) { return n, nil } +// extractYAMLTag looks for @yaml(tag="...") attribute and returns the tag value. +// Returns an empty string if no @yaml attribute or no tag argument is found. +// Returns an error if the attribute is malformed. +func extractYAMLTag(attrs []*ast.Attribute) (string, error) { + for _, attr := range attrs { + key, body := attr.Split() + if key == "yaml" { + parsed := internal.ParseAttrBody(attr.Pos(), body) + if parsed.Err != nil { + return "", parsed.Err + } + if val, found, err := parsed.Lookup(0, "tag"); err != nil { + return "", err + } else if found { + return val, nil + } + } + } + return "", nil +} + // encodeDecls converts a sequence of declarations to a value. If it encounters // an embedded value, it will return this expression. This is more relaxed for // structs than is currently allowed for CUE, but the expectation is that this @@ -289,6 +310,15 @@ func encodeDecls(decls []ast.Decl) (n *yaml.Node, err error) { if err != nil { return nil, err } + + yamlTag, err := extractYAMLTag(x.Attrs) + if err != nil { + return nil, err + } + if yamlTag != "" { + value.Tag = yamlTag + } + lastHead = label lastFoot = value addDocs(x, label, value) diff --git a/internal/encoding/yaml/encode_test.go b/internal/encoding/yaml/encode_test.go index 10ed40b6bf6..f882c3bbf9c 100644 --- a/internal/encoding/yaml/encode_test.go +++ b/internal/encoding/yaml/encode_test.go @@ -243,6 +243,70 @@ route: receiver: pager group_by: [alertname, cluster] `, + }, { + name: "yaml_tag_scalar", + in: ` + key: "value" @yaml(tag="!Custom") + env: "VAR_NAME" @yaml(tag="!Env") + `, + out: ` +key: !Custom value +env: !Env VAR_NAME + `, + }, { + name: "yaml_tag_sequence", + in: ` + lookup: ["table", ["key", "value"]] @yaml(tag="!Find") + items: [1, 2, 3] @yaml(tag="!Seq") + `, + out: ` +lookup: !Find [table, [key, value]] +items: !Seq [1, 2, 3] + `, + }, { + name: "yaml_tag_mapping", + in: ` + config: { + key: "value" + count: 42 + } @yaml(tag="!Map") + `, + out: ` +config: !Map + key: value + count: 42 + `, + }, { + name: "yaml_tag_mixed", + in: ` + plain: "no-tag" + tagged: "has-tag" @yaml(tag="!Custom") + nested: { + field: "value" @yaml(tag="!Nested") + } + `, + out: ` +plain: no-tag +tagged: !Custom has-tag +nested: + field: !Nested value + `, + }, { + name: "yaml_tag_verbatim", + in: ` + custom: "value" @yaml(tag="!") + `, + out: ` +custom: !%3Ctag:example.com,2000:app/foo%3E value + `, + }, { + name: "yaml_attribute_without_tag", + in: ` + field: "value" @yaml(other="ignored") + `, + out: ` +field: value + `, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) {