Skip to content

Commit ff7d67d

Browse files
committed
Retain key ordering
1 parent dfeebff commit ff7d67d

File tree

2 files changed

+32
-27
lines changed

2 files changed

+32
-27
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/hashicorp/terraform-plugin-go v0.29.0
1212
github.com/hashicorp/terraform-plugin-log v0.9.0
1313
github.com/hashicorp/terraform-plugin-testing v1.13.3
14-
github.com/netascode/go-netconf v0.0.0-20251101111307-2b06d4b121d8
14+
github.com/netascode/go-netconf v0.0.0-20251101121259-d1fa38b4407e
1515
github.com/netascode/go-restconf v0.1.18
1616
github.com/netascode/xmldot v0.4.1
1717
github.com/openconfig/goyang v1.6.3

internal/provider/helpers/utils.go

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -288,18 +288,18 @@ func buildXPathStructure(body netconf.Body, xPath string) (netconf.Body, []strin
288288
pathSegments := make([]string, 0, len(segments))
289289

290290
for i, segment := range segments {
291-
// Parse segment: element[key='value'][key2='value2'] -> element, map[key:value, key2:value2]
291+
// Parse segment: element[key='value'][key2='value2'] -> element, []KeyValue
292292
elementName, keys := parseXPathSegment(segment)
293293

294294
// Add element name to path (without predicates)
295295
pathSegments = append(pathSegments, elementName)
296296
fullPath := strings.Join(pathSegments[:i+1], ".")
297297

298-
// If this segment has keys, set all key values
298+
// If this segment has keys, set all key values in order
299299
if len(keys) > 0 {
300-
for keyName, keyValue := range keys {
301-
keyPath := fullPath + "." + keyName
302-
body = setWithNamespaces(body, keyPath, keyValue)
300+
for _, kv := range keys {
301+
keyPath := fullPath + "." + kv.Key
302+
body = setWithNamespaces(body, keyPath, kv.Value)
303303
}
304304
}
305305
}
@@ -318,18 +318,24 @@ func buildXPathStructure(body netconf.Body, xPath string) (netconf.Body, []strin
318318
return body, pathSegments
319319
}
320320

321+
// KeyValue represents a key-value pair with preserved order
322+
type KeyValue struct {
323+
Key string
324+
Value string
325+
}
326+
321327
// parseXPathSegment parses an XPath segment with single or multiple keys
322328
// Supports formats:
323329
// - element[key='value']
324330
// - element[key1='value1'][key2='value2']
325331
// - element[key1='value1' and key2='value2']
326332
//
327-
// Returns: (elementName, map[keyName]keyValue)
328-
func parseXPathSegment(segment string) (string, map[string]string) {
333+
// Returns: (elementName, []KeyValue) - order is preserved from the XPath
334+
func parseXPathSegment(segment string) (string, []KeyValue) {
329335
// Check for predicate: element[...]
330336
if idx := strings.Index(segment, "["); idx != -1 {
331337
elementName := segment[:idx]
332-
keys := make(map[string]string)
338+
keys := make([]KeyValue, 0)
333339

334340
// Extract all predicates - handle both [key1='val1'][key2='val2'] and [key1='val1' and key2='val2']
335341
remainingPredicates := segment[idx:]
@@ -346,11 +352,11 @@ func parseXPathSegment(segment string) (string, map[string]string) {
346352
for _, condition := range conditions {
347353
// Parse each condition: key='value' or key="value"
348354
if eqIdx := strings.Index(condition, "="); eqIdx != -1 {
349-
keyName := condition[:eqIdx]
355+
keyName := strings.TrimSpace(condition[:eqIdx])
350356
value := condition[eqIdx+1:]
351357
// Remove quotes
352358
keyValue := strings.Trim(value, `'"`)
353-
keys[keyName] = keyValue
359+
keys = append(keys, KeyValue{Key: keyName, Value: keyValue})
354360
}
355361
}
356362
}
@@ -508,8 +514,8 @@ func SetRawFromXPath(body netconf.Body, xPath string, value string) netconf.Body
508514
// Build any keys if present
509515
if len(keys) > 0 {
510516
tempBody := netconf.Body{}
511-
for keyName, keyValue := range keys {
512-
tempBody = setWithNamespaces(tempBody, keyName, keyValue)
517+
for _, kv := range keys {
518+
tempBody = setWithNamespaces(tempBody, kv.Key, kv.Value)
513519
}
514520
wrappedContent = "<" + finalElementClean + ">" + tempBody.Res() + value + "</" + finalElementClean + ">"
515521
}
@@ -584,10 +590,9 @@ func GetFromXPath(res xmldot.Result, xPath string) xmldot.Result {
584590
if len(keys) == 1 {
585591
// Single predicate - use xmldot's native filter syntax
586592
// Also remove namespace prefix from key name
587-
for keyName, keyValue := range keys {
588-
keyName = removeNamespacePrefix(keyName)
589-
pathParts = append(pathParts, elementName+".#("+keyName+"=="+keyValue+")")
590-
}
593+
kv := keys[0]
594+
keyName := removeNamespacePrefix(kv.Key)
595+
pathParts = append(pathParts, elementName+".#("+keyName+"=="+kv.Value+")")
591596
} else {
592597
// No predicates
593598
pathParts = append(pathParts, elementName)
@@ -627,11 +632,11 @@ func GetFromXPath(res xmldot.Result, xPath string) xmldot.Result {
627632
item := xmldot.Get(xml, indexedPath)
628633

629634
allMatch := true
630-
for keyName, keyValue := range keys {
635+
for _, kv := range keys {
631636
// Remove namespace prefix from key name
632-
keyName = removeNamespacePrefix(keyName)
637+
keyName := removeNamespacePrefix(kv.Key)
633638
keyResult := item.Get(keyName)
634-
if !keyResult.Exists() || keyResult.String() != keyValue {
639+
if !keyResult.Exists() || keyResult.String() != kv.Value {
635640
allMatch = false
636641
break
637642
}
@@ -648,11 +653,11 @@ func GetFromXPath(res xmldot.Result, xPath string) xmldot.Result {
648653
// Single element - check directly
649654
currentResult := xmldot.Get(xml, currentPath)
650655
allMatch := true
651-
for keyName, keyValue := range keys {
656+
for _, kv := range keys {
652657
// Remove namespace prefix from key name
653-
keyName = removeNamespacePrefix(keyName)
658+
keyName := removeNamespacePrefix(kv.Key)
654659
keyResult := currentResult.Get(keyName)
655-
if !keyResult.Exists() || keyResult.String() != keyValue {
660+
if !keyResult.Exists() || keyResult.String() != kv.Value {
656661
allMatch = false
657662
break
658663
}
@@ -710,12 +715,12 @@ func GetXpathFilter(xPath string) netconf.Filter {
710715

711716
// Reconstruct segment with predicates
712717
if len(keys) > 0 {
713-
// Build predicates
718+
// Build predicates in order
714719
predicates := make([]string, 0, len(keys))
715-
for keyName, keyValue := range keys {
720+
for _, kv := range keys {
716721
// Remove namespace prefix from key name
717-
keyName = removeNamespacePrefix(keyName)
718-
predicates = append(predicates, fmt.Sprintf("%s='%s'", keyName, keyValue))
722+
keyName := removeNamespacePrefix(kv.Key)
723+
predicates = append(predicates, fmt.Sprintf("%s='%s'", keyName, kv.Value))
719724
}
720725
// Reconstruct segment with all predicates
721726
reconstructed := elementName

0 commit comments

Comments
 (0)