Skip to content

Commit b19c079

Browse files
feat: implement consistent key ordering for YAML node creation and add related tests
1 parent fe3719b commit b19c079

File tree

2 files changed

+151
-2
lines changed

2 files changed

+151
-2
lines changed

internal/transform/vendor_extensions.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io/fs"
66
"path/filepath"
77
"regexp"
8+
"sort"
89
"strings"
910

1011
"gopkg.in/yaml.v3"
@@ -348,11 +349,31 @@ func addExtensionToOperation(operationNode *yaml.Node, extensionName string, ext
348349
return true
349350
}
350351

351-
// createYAMLNodeFromMap creates a YAML node from a map
352+
// createYAMLNodeFromMap creates a YAML node from a map with consistent key ordering
352353
func createYAMLNodeFromMap(data map[string]interface{}) *yaml.Node {
353354
node := &yaml.Node{Kind: yaml.MappingNode}
354355

355-
for key, value := range data {
356+
// Sort keys with custom ordering for pagination fields
357+
var keys []string
358+
for key := range data {
359+
keys = append(keys, key)
360+
}
361+
sort.Slice(keys, func(i, j int) bool {
362+
orderI := getPaginationFieldOrder(keys[i])
363+
orderJ := getPaginationFieldOrder(keys[j])
364+
365+
// If orders are the same, sort alphabetically
366+
if orderI == orderJ {
367+
return keys[i] < keys[j]
368+
}
369+
370+
return orderI < orderJ
371+
})
372+
373+
// Add keys and values in sorted order
374+
for _, key := range keys {
375+
value := data[key]
376+
356377
keyNode := &yaml.Node{
357378
Kind: yaml.ScalarNode,
358379
Value: key,
@@ -376,6 +397,30 @@ func createYAMLNodeFromMap(data map[string]interface{}) *yaml.Node {
376397
return node
377398
}
378399

400+
// getPaginationFieldOrder returns the priority order for pagination fields
401+
// Lower numbers appear first in the output
402+
func getPaginationFieldOrder(key string) int {
403+
switch key {
404+
case "offset":
405+
return 1
406+
case "cursor":
407+
return 1 // Same priority as offset - they're mutually exclusive
408+
case "page":
409+
return 1 // Same priority as offset/cursor - they're mutually exclusive
410+
case "results":
411+
return 2
412+
case "limit":
413+
return 3
414+
case "next":
415+
return 4
416+
case "previous":
417+
return 5
418+
default:
419+
// Unknown fields come last, sorted alphabetically by adding 1000 + ASCII value
420+
return 1000
421+
}
422+
}
423+
379424
// Helper functions
380425

381426
func extractParameterNames(params *yaml.Node, root *yaml.Node) []string {

internal/transform/vendor_extensions_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package transform
33
import (
44
"os"
55
"path/filepath"
6+
"strings"
67
"testing"
78

89
"gopkg.in/yaml.v3"
@@ -797,6 +798,67 @@ func TestCreateYAMLNodeFromMap(t *testing.T) {
797798
}
798799
}
799800

801+
func TestCreateYAMLNodeFromMapOrdering(t *testing.T) {
802+
tests := []struct {
803+
name string
804+
input map[string]interface{}
805+
expected []string // expected order of keys
806+
}{
807+
{
808+
name: "pagination fields in correct order",
809+
input: map[string]interface{}{
810+
"results": "$response.data",
811+
"offset": "$request.page",
812+
"limit": "$request.per_page",
813+
},
814+
expected: []string{"offset", "results", "limit"},
815+
},
816+
{
817+
name: "pagination fields with cursor",
818+
input: map[string]interface{}{
819+
"cursor": "$request.cursor",
820+
"results": "$response.items",
821+
"next": "$response.next_cursor",
822+
},
823+
expected: []string{"cursor", "results", "next"},
824+
},
825+
{
826+
name: "mixed known and unknown fields",
827+
input: map[string]interface{}{
828+
"unknown_field": "value",
829+
"results": "$response.data",
830+
"offset": "$request.page",
831+
"custom": "custom_value",
832+
},
833+
expected: []string{"offset", "results", "custom", "unknown_field"},
834+
},
835+
}
836+
837+
for _, tt := range tests {
838+
t.Run(tt.name, func(t *testing.T) {
839+
node := createYAMLNodeFromMap(tt.input)
840+
841+
// Extract the key order from the YAML node
842+
var actualKeys []string
843+
for i := 0; i < len(node.Content); i += 2 {
844+
actualKeys = append(actualKeys, node.Content[i].Value)
845+
}
846+
847+
// Verify the order matches expected
848+
if len(actualKeys) != len(tt.expected) {
849+
t.Errorf("Expected %d keys, got %d", len(tt.expected), len(actualKeys))
850+
return
851+
}
852+
853+
for i, expectedKey := range tt.expected {
854+
if actualKeys[i] != expectedKey {
855+
t.Errorf("At position %d: expected key %q, got %q", i, expectedKey, actualKeys[i])
856+
}
857+
}
858+
})
859+
}
860+
}
861+
800862
func TestAddProcessedExtension(t *testing.T) {
801863
result := &VendorExtensionResult{
802864
AddedExtensions: make(map[string][]string),
@@ -1127,3 +1189,45 @@ info:
11271189
})
11281190
}
11291191
}
1192+
1193+
func TestConsistentPaginationOrdering(t *testing.T) {
1194+
// Test the exact case mentioned in the user request
1195+
paginationData := map[string]interface{}{
1196+
"results": "$response.network_acls",
1197+
"offset": "$request.page",
1198+
}
1199+
1200+
// Generate the YAML node multiple times to ensure consistent ordering
1201+
for i := 0; i < 5; i++ {
1202+
node := createYAMLNodeFromMap(paginationData)
1203+
1204+
// Convert to YAML string to verify output
1205+
var buffer strings.Builder
1206+
encoder := yaml.NewEncoder(&buffer)
1207+
encoder.SetIndent(2)
1208+
err := encoder.Encode(node)
1209+
if err != nil {
1210+
t.Fatalf("Failed to encode YAML: %v", err)
1211+
}
1212+
encoder.Close()
1213+
1214+
yamlOutput := buffer.String()
1215+
1216+
// Verify that "offset" appears before "results" in the output
1217+
offsetPos := strings.Index(yamlOutput, "offset:")
1218+
resultsPos := strings.Index(yamlOutput, "results:")
1219+
1220+
if offsetPos == -1 || resultsPos == -1 {
1221+
t.Fatalf("Missing expected fields in YAML output: %s", yamlOutput)
1222+
}
1223+
1224+
if offsetPos >= resultsPos {
1225+
t.Errorf("Expected 'offset' to appear before 'results' in YAML output, but got:\n%s", yamlOutput)
1226+
}
1227+
1228+
// For the first iteration, log the output for visual verification
1229+
if i == 0 {
1230+
t.Logf("Generated YAML output:\n%s", yamlOutput)
1231+
}
1232+
}
1233+
}

0 commit comments

Comments
 (0)