diff --git a/pkg/cli/audit_report_render_guard.go b/pkg/cli/audit_report_render_guard.go index ad154776fda..eab83520d21 100644 --- a/pkg/cli/audit_report_render_guard.go +++ b/pkg/cli/audit_report_render_guard.go @@ -44,7 +44,7 @@ func renderGuardPolicySummary(summary *GuardPolicySummary) { // Most frequently blocked tools if len(summary.BlockedToolCounts) > 0 { - toolNames := sliceutil.MapToSlice(summary.BlockedToolCounts) + toolNames := sliceutil.MapKeys(summary.BlockedToolCounts) sort.Slice(toolNames, func(i, j int) bool { return summary.BlockedToolCounts[toolNames[i]] > summary.BlockedToolCounts[toolNames[j]] }) diff --git a/pkg/cli/firewall_log.go b/pkg/cli/firewall_log.go index 7b0057b4b79..61055b119e8 100644 --- a/pkg/cli/firewall_log.go +++ b/pkg/cli/firewall_log.go @@ -354,8 +354,8 @@ func parseFirewallLog(logPath string, verbose bool) (*FirewallAnalysis, error) { } // Convert sets to sorted slices - analysis.AllowedDomains = sliceutil.MapToSlice(allowedDomainsSet) - analysis.BlockedDomains = sliceutil.MapToSlice(blockedDomainsSet) + analysis.AllowedDomains = sliceutil.MapKeys(allowedDomainsSet) + analysis.BlockedDomains = sliceutil.MapKeys(blockedDomainsSet) sort.Strings(analysis.AllowedDomains) sort.Strings(analysis.BlockedDomains) diff --git a/pkg/cli/gateway_logs_render.go b/pkg/cli/gateway_logs_render.go index 883f1c7fa53..8b651191bed 100644 --- a/pkg/cli/gateway_logs_render.go +++ b/pkg/cli/gateway_logs_render.go @@ -156,7 +156,7 @@ func renderGatewayMetricsTable(metrics *GatewayMetrics, verbose bool) string { } // Sort tools by call count - toolNames := sliceutil.MapToSlice(server.Tools) + toolNames := sliceutil.MapKeys(server.Tools) sort.Slice(toolNames, func(i, j int) bool { return server.Tools[toolNames[i]].CallCount > server.Tools[toolNames[j]].CallCount }) @@ -186,7 +186,7 @@ func renderGatewayMetricsTable(metrics *GatewayMetrics, verbose bool) string { // getSortedServerNames returns server names sorted by request count func getSortedServerNames(metrics *GatewayMetrics) []string { - names := sliceutil.MapToSlice(metrics.Servers) + names := sliceutil.MapKeys(metrics.Servers) sort.Slice(names, func(i, j int) bool { return metrics.Servers[names[i]].RequestCount > metrics.Servers[names[j]].RequestCount }) diff --git a/pkg/cli/run_push.go b/pkg/cli/run_push.go index 2ffd446d025..a0e6e7a627e 100644 --- a/pkg/cli/run_push.go +++ b/pkg/cli/run_push.go @@ -117,7 +117,7 @@ func collectWorkflowFiles(ctx context.Context, workflowPath string, verbose bool runPushLog.Printf("Import collection completed") // Convert map to slice - result := sliceutil.MapToSlice(files) + result := sliceutil.MapKeys(files) // Sort files for stable output sort.Strings(result) diff --git a/pkg/sliceutil/README.md b/pkg/sliceutil/README.md index 04a5a373680..20f5e45a4fc 100644 --- a/pkg/sliceutil/README.md +++ b/pkg/sliceutil/README.md @@ -14,7 +14,7 @@ All functions in this package are pure: they never modify their input. They are |----------|-----------|-------------| | `Filter` | `func[T any](slice []T, predicate func(T) bool) []T` | Returns a new slice containing only elements for which `predicate` returns `true` | | `Map` | `func[T, U any](slice []T, transform func(T) U) []U` | Applies `transform` to every element and returns the results as a new slice | -| `MapToSlice` | `func[K comparable, V any](m map[K]V) []K` | Converts the keys of a map into a slice; order is not guaranteed | +| `MapKeys` | `func[K comparable, V any](m map[K]V) []K` | Converts the keys of a map into a slice; order is not guaranteed | | `FilterMapKeys` | `func[K comparable, V any](m map[K]V, predicate func(K, V) bool) []K` | Returns map keys for which `predicate(key, value)` is `true`; order is not guaranteed | | `Any` | `func[T any](slice []T, predicate func(T) bool) bool` | Returns `true` if at least one element satisfies `predicate`; returns `false` for nil or empty slices | | `Deduplicate` | `func[T comparable](slice []T) []T` | Returns a new slice with duplicate elements removed, preserving order of first occurrence | diff --git a/pkg/sliceutil/sliceutil.go b/pkg/sliceutil/sliceutil.go index f4caacb6d1c..a993ffcf805 100644 --- a/pkg/sliceutil/sliceutil.go +++ b/pkg/sliceutil/sliceutil.go @@ -27,10 +27,10 @@ func Map[T, U any](slice []T, transform func(T) U) []U { return result } -// MapToSlice converts a map's keys to a slice. +// MapKeys converts a map's keys to a slice. // The order of elements is not guaranteed as map iteration order is undefined. // This is a pure function that does not modify the input map. -func MapToSlice[K comparable, V any](m map[K]V) []K { +func MapKeys[K comparable, V any](m map[K]V) []K { result := make([]K, 0, len(m)) for key := range m { result = append(result, key) diff --git a/pkg/sliceutil/sliceutil_test.go b/pkg/sliceutil/sliceutil_test.go index 717ca0147a3..319ab3bea0d 100644 --- a/pkg/sliceutil/sliceutil_test.go +++ b/pkg/sliceutil/sliceutil_test.go @@ -148,29 +148,29 @@ func TestDeduplicate(t *testing.T) { } } -func TestMapToSlice(t *testing.T) { +func TestMapKeys(t *testing.T) { t.Run("nil map returns empty slice", func(t *testing.T) { - result := MapToSlice[string, int](nil) - assert.Empty(t, result, "MapToSlice should return empty slice for nil map") + result := MapKeys[string, int](nil) + assert.Empty(t, result, "MapKeys should return empty slice for nil map") }) t.Run("empty map returns empty slice", func(t *testing.T) { - result := MapToSlice(map[string]int{}) - assert.Empty(t, result, "MapToSlice should return empty slice for empty map") + result := MapKeys(map[string]int{}) + assert.Empty(t, result, "MapKeys should return empty slice for empty map") }) t.Run("returns all keys in any order", func(t *testing.T) { m := map[string]int{"apple": 1, "banana": 2, "cherry": 3} - result := MapToSlice(m) + result := MapKeys(m) assert.ElementsMatch(t, []string{"apple", "banana", "cherry"}, result, - "MapToSlice should return all keys from map") + "MapKeys should return all keys from map") }) t.Run("single entry map", func(t *testing.T) { m := map[string]bool{"only": true} - result := MapToSlice(m) + result := MapKeys(m) assert.Equal(t, []string{"only"}, result, - "MapToSlice should return the single key from a one-entry map") + "MapKeys should return the single key from a one-entry map") }) } diff --git a/pkg/sliceutil/spec_test.go b/pkg/sliceutil/spec_test.go index b1bb1d025d3..381ed4b2013 100644 --- a/pkg/sliceutil/spec_test.go +++ b/pkg/sliceutil/spec_test.go @@ -45,19 +45,19 @@ func TestSpec_PublicAPI_Map(t *testing.T) { }) } -// TestSpec_PublicAPI_MapToSlice validates the documented behavior of MapToSlice +// TestSpec_PublicAPI_MapKeys validates the documented behavior of MapKeys // as described in the sliceutil README.md specification. -func TestSpec_PublicAPI_MapToSlice(t *testing.T) { +func TestSpec_PublicAPI_MapKeys(t *testing.T) { t.Run("returns all map keys as a slice", func(t *testing.T) { m := map[string]int{"a": 1, "b": 2} - keys := MapToSlice(m) - assert.ElementsMatch(t, []string{"a", "b"}, keys, "MapToSlice should return all map keys (order not guaranteed)") + keys := MapKeys(m) + assert.ElementsMatch(t, []string{"a", "b"}, keys, "MapKeys should return all map keys (order not guaranteed)") }) t.Run("returns empty slice for empty map", func(t *testing.T) { m := map[string]int{} - keys := MapToSlice(m) - assert.Empty(t, keys, "MapToSlice of empty map should return empty slice") + keys := MapKeys(m) + assert.Empty(t, keys, "MapKeys of empty map should return empty slice") }) } diff --git a/pkg/workflow/mcp_config_custom.go b/pkg/workflow/mcp_config_custom.go index 6c1162d33e7..afd24955b86 100644 --- a/pkg/workflow/mcp_config_custom.go +++ b/pkg/workflow/mcp_config_custom.go @@ -361,7 +361,7 @@ func renderSharedMCPConfig(yaml *strings.Builder, toolName string, toolConfig ma if renderer.Format == "toml" { fmt.Fprintf(yaml, "%senv = { ", renderer.IndentLevel) // Using functional helper to extract map keys - envKeys := sliceutil.MapToSlice(mcpConfig.Env) + envKeys := sliceutil.MapKeys(mcpConfig.Env) sort.Strings(envKeys) for i, envKey := range envKeys { if i > 0 { @@ -454,7 +454,7 @@ func renderSharedMCPConfig(yaml *strings.Builder, toolName string, toolConfig ma if len(mcpConfig.Headers) > 0 { fmt.Fprintf(yaml, "%shttp_headers = { ", renderer.IndentLevel) // Using functional helper to extract map keys - headerKeys := sliceutil.MapToSlice(mcpConfig.Headers) + headerKeys := sliceutil.MapKeys(mcpConfig.Headers) sort.Strings(headerKeys) for i, headerKey := range headerKeys { if i > 0 { @@ -471,7 +471,7 @@ func renderSharedMCPConfig(yaml *strings.Builder, toolName string, toolConfig ma } fmt.Fprintf(yaml, "%s\"headers\": {\n", renderer.IndentLevel) // Using functional helper to extract map keys - headerKeys := sliceutil.MapToSlice(mcpConfig.Headers) + headerKeys := sliceutil.MapKeys(mcpConfig.Headers) sort.Strings(headerKeys) for headerIndex, headerKey := range headerKeys { headerComma := "," diff --git a/pkg/workflow/mcp_setup_generator.go b/pkg/workflow/mcp_setup_generator.go index cca74d134de..5162d0ff78a 100644 --- a/pkg/workflow/mcp_setup_generator.go +++ b/pkg/workflow/mcp_setup_generator.go @@ -401,7 +401,7 @@ func generateMCPScriptsSetup(yaml *strings.Builder, workflowData *WorkflowData) yaml.WriteString(" - name: Write MCP Scripts Tool Files\n") yaml.WriteString(" run: |\n") - mcpScriptToolNames := sliceutil.MapToSlice(workflowData.MCPScripts.Tools) + mcpScriptToolNames := sliceutil.MapKeys(workflowData.MCPScripts.Tools) sort.Strings(mcpScriptToolNames) for _, toolName := range mcpScriptToolNames { toolConfig := workflowData.MCPScripts.Tools[toolName] @@ -436,7 +436,7 @@ func generateMCPScriptsSetup(yaml *strings.Builder, workflowData *WorkflowData) yaml.WriteString(" GH_AW_MCP_SCRIPTS_API_KEY: ${{ steps.mcp-scripts-config.outputs.mcp_scripts_api_key }}\n") mcpScriptsSecrets := collectMCPScriptsSecrets(workflowData.MCPScripts) if len(mcpScriptsSecrets) > 0 { - envVarNames := sliceutil.MapToSlice(mcpScriptsSecrets) + envVarNames := sliceutil.MapKeys(mcpScriptsSecrets) sort.Strings(envVarNames) for _, envVarName := range envVarNames { secretExpr := mcpScriptsSecrets[envVarName] @@ -551,7 +551,7 @@ func writeMCPGatewayStepEnv(yaml *strings.Builder, mcpEnvVars map[string]string) return } yaml.WriteString(" env:\n") - envVarNames := sliceutil.MapToSlice(mcpEnvVars) + envVarNames := sliceutil.MapKeys(mcpEnvVars) sort.Strings(envVarNames) for _, envVarName := range envVarNames { fmt.Fprintf(yaml, " %s: %s\n", envVarName, mcpEnvVars[envVarName]) @@ -624,7 +624,7 @@ func writeMCPGatewayExports(yaml *strings.Builder, tools map[string]any, engine yaml.WriteString(" export GITHUB_PERSONAL_ACCESS_TOKEN=\"$GITHUB_MCP_SERVER_TOKEN\"\n") } if len(gatewayConfig.Env) > 0 { - envVarNames := sliceutil.MapToSlice(gatewayConfig.Env) + envVarNames := sliceutil.MapKeys(gatewayConfig.Env) sort.Strings(envVarNames) for _, envVarName := range envVarNames { fmt.Fprintf(yaml, " export %s=%s\n", envVarName, gatewayConfig.Env[envVarName]) @@ -745,7 +745,7 @@ func appendMCPGatewayConditionalEnvFlags(containerCmd *strings.Builder, workflow func appendMCPGatewayCustomAndHTTPEnvFlags(containerCmd *strings.Builder, workflowData *WorkflowData, gatewayConfig *MCPGatewayRuntimeConfig, mcpEnvVars map[string]string, hasGitHub bool, githubTool any, tools map[string]any, engine CodingAgentEngine) { if len(gatewayConfig.Env) > 0 { - envVarNames := sliceutil.MapToSlice(gatewayConfig.Env) + envVarNames := sliceutil.MapKeys(gatewayConfig.Env) sort.Strings(envVarNames) for _, envVarName := range envVarNames { containerCmd.WriteString(" -e " + envVarName) diff --git a/pkg/workflow/yaml_env_helpers.go b/pkg/workflow/yaml_env_helpers.go index b86685bccfc..631a8e7b3b5 100644 --- a/pkg/workflow/yaml_env_helpers.go +++ b/pkg/workflow/yaml_env_helpers.go @@ -35,7 +35,7 @@ func writeHeadersToYAML(yaml *strings.Builder, headers map[string]string, indent envLog.Printf("Writing %d headers to YAML", len(headers)) // Sort keys for deterministic output - using functional helper - keys := sliceutil.MapToSlice(headers) + keys := sliceutil.MapKeys(headers) sort.Strings(keys) // Write each header with proper comma placement