Skip to content

Commit bf37c1a

Browse files
committed
Add new function RenameYamlKey
1 parent 0a2fb24 commit bf37c1a

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

pkg/utils/yaml_utils/yaml_utils.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,58 @@ func lookupKey(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) {
9595

9696
return nil, nil
9797
}
98+
99+
// takes a yaml document in bytes, a path to a key, and a new name for the key.
100+
// Will rename the key to the new name if it exists, and do nothing otherwise.
101+
func RenameYamlKey(yamlBytes []byte, path []string, newKey string) ([]byte, error) {
102+
// Parse the YAML file.
103+
var node yaml.Node
104+
err := yaml.Unmarshal(yamlBytes, &node)
105+
if err != nil {
106+
return nil, fmt.Errorf("failed to parse YAML: %w", err)
107+
}
108+
109+
// Empty document: nothing to do.
110+
if len(node.Content) == 0 {
111+
return yamlBytes, nil
112+
}
113+
114+
body := node.Content[0]
115+
116+
if err := renameYamlKey(body, path, newKey); err != nil {
117+
return yamlBytes, err
118+
}
119+
120+
// Convert the updated YAML node back to YAML bytes.
121+
updatedYAMLBytes, err := yaml.Marshal(body)
122+
if err != nil {
123+
return nil, fmt.Errorf("failed to convert YAML node to bytes: %w", err)
124+
}
125+
126+
return updatedYAMLBytes, nil
127+
}
128+
129+
// Recursive function to rename the YAML key.
130+
func renameYamlKey(node *yaml.Node, path []string, newKey string) error {
131+
if node.Kind != yaml.MappingNode {
132+
return errors.New("yaml node in path is not a dictionary")
133+
}
134+
135+
keyNode, valueNode := lookupKey(node, path[0])
136+
if keyNode == nil {
137+
return nil
138+
}
139+
140+
// end of path reached: rename key
141+
if len(path) == 1 {
142+
// Check that new key doesn't exist yet
143+
if newKeyNode, _ := lookupKey(node, newKey); newKeyNode != nil {
144+
return fmt.Errorf("new key `%s' already exists", newKey)
145+
}
146+
147+
keyNode.Value = newKey
148+
return nil
149+
}
150+
151+
return renameYamlKey(valueNode, path[1:], newKey)
152+
}

pkg/utils/yaml_utils/yaml_utils_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,83 @@ func TestUpdateYamlValue(t *testing.T) {
106106
})
107107
}
108108
}
109+
110+
func TestRenameYamlKey(t *testing.T) {
111+
tests := []struct {
112+
name string
113+
in string
114+
path []string
115+
newKey string
116+
expectedOut string
117+
expectedErr string
118+
}{
119+
{
120+
name: "rename key",
121+
in: "foo: 5\n",
122+
path: []string{"foo"},
123+
newKey: "bar",
124+
expectedOut: "bar: 5\n",
125+
expectedErr: "",
126+
},
127+
{
128+
name: "rename key, nested",
129+
in: "foo:\n bar: 5\n",
130+
path: []string{"foo", "bar"},
131+
newKey: "baz",
132+
// indentation is not preserved. See https://github.com/go-yaml/yaml/issues/899
133+
expectedOut: "foo:\n baz: 5\n",
134+
expectedErr: "",
135+
},
136+
{
137+
name: "rename non-scalar key",
138+
in: "foo:\n bar: 5\n",
139+
path: []string{"foo"},
140+
newKey: "qux",
141+
// indentation is not preserved. See https://github.com/go-yaml/yaml/issues/899
142+
expectedOut: "qux:\n bar: 5\n",
143+
expectedErr: "",
144+
},
145+
146+
// Error cases
147+
{
148+
name: "existing document is not a dictionary",
149+
in: "42\n",
150+
path: []string{"foo"},
151+
newKey: "bar",
152+
expectedOut: "42\n",
153+
expectedErr: "yaml document is not a dictionary",
154+
},
155+
{
156+
name: "not all path elements are dictionaries",
157+
in: "foo:\n bar: [1, 2, 3]\n",
158+
path: []string{"foo", "bar", "baz"},
159+
newKey: "qux",
160+
expectedOut: "foo:\n bar: [1, 2, 3]\n",
161+
expectedErr: "yaml node in path is not a dictionary",
162+
},
163+
{
164+
name: "new key exists",
165+
in: "foo: 5\nbar: 7\n",
166+
path: []string{"foo"},
167+
newKey: "bar",
168+
expectedOut: "foo: 5\nbar: 7\n",
169+
expectedErr: "new key `bar' already exists",
170+
},
171+
}
172+
173+
for _, test := range tests {
174+
test := test
175+
t.Run(test.name, func(t *testing.T) {
176+
out, err := RenameYamlKey([]byte(test.in), test.path, test.newKey)
177+
if test.expectedErr != "" {
178+
if err == nil {
179+
t.Errorf("expected error %q but got none", test.expectedErr)
180+
}
181+
} else if err != nil {
182+
t.Errorf("unexpected error: %v", err)
183+
} else if string(out) != test.expectedOut {
184+
t.Errorf("expected %q but got %q", test.expectedOut, string(out))
185+
}
186+
})
187+
}
188+
}

0 commit comments

Comments
 (0)