From 29e297fd6927fe530824da420eafdc5acd3b5dce Mon Sep 17 00:00:00 2001 From: Steve Gontzes Date: Fri, 22 May 2026 16:38:52 -0400 Subject: [PATCH 1/2] Preserve lambda Viper StringMap keys --- pkg/cli/lambda_server__added.go | 39 ++++++++++++++++++++++++++++-- pkg/cli/lambda_server_test.go | 43 +++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/pkg/cli/lambda_server__added.go b/pkg/cli/lambda_server__added.go index 006f4b9d8..062cfd91a 100644 --- a/pkg/cli/lambda_server__added.go +++ b/pkg/cli/lambda_server__added.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "runtime/debug" + "strings" "sync" "time" @@ -334,7 +335,7 @@ func OptionallyAddLambdaCommand[T field.Configurable]( } connectorConfig := configStruct.AsMap() - effectiveConfig := effectiveLambdaConfig(v, connectorConfig) + effectiveConfig := effectiveLambdaConfig(v, connectorConfig, connectorSchema) logLevelConfig, err := lambdaLogLevelConfigFromViper(effectiveConfig) if err != nil { return nil, err @@ -488,14 +489,48 @@ func cloneViperSettings(v *viper.Viper) *viper.Viper { return cloned } -func effectiveLambdaConfig(v *viper.Viper, connectorConfig map[string]any) *viper.Viper { +func effectiveLambdaConfig(v *viper.Viper, connectorConfig map[string]any, connectorSchema field.Configuration) *viper.Viper { effectiveConfig := cloneViperSettings(v) + stringMapFields := lambdaStringMapFields(connectorSchema) for key, value := range connectorConfig { + if _, ok := stringMapFields[strings.ToLower(key)]; ok { + value = lambdaStringMapValueForViper(value) + } effectiveConfig.Set(key, value) } return effectiveConfig } +func lambdaStringMapFields(connectorSchema field.Configuration) map[string]struct{} { + out := make(map[string]struct{}) + for _, schemaField := range connectorSchema.Fields { + if schemaField.Variant == field.StringMapVariant { + out[strings.ToLower(schemaField.FieldName)] = struct{}{} + } + } + for _, fieldGroup := range connectorSchema.FieldGroups { + for _, schemaField := range fieldGroup.Fields { + if schemaField.Variant == field.StringMapVariant { + out[strings.ToLower(schemaField.FieldName)] = struct{}{} + } + } + } + return out +} + +func lambdaStringMapValueForViper(value any) any { + if _, ok := value.(string); ok { + return value + } + // Viper.GetStringMap and GetStringMapString parse JSON strings, while + // Viper.Set lowercases nested map[string]any keys. + encoded, err := json.Marshal(value) + if err != nil { + return value + } + return string(encoded) +} + // Typed configs must decode the raw lambda payload directly. Decoding them from // effectiveConfig would send StringMap values through Viper.Set, which lowercases // nested map keys. diff --git a/pkg/cli/lambda_server_test.go b/pkg/cli/lambda_server_test.go index 7cf168a5e..f3e18e4db 100644 --- a/pkg/cli/lambda_server_test.go +++ b/pkg/cli/lambda_server_test.go @@ -8,6 +8,7 @@ import ( "testing" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/field" "github.com/spf13/viper" ) @@ -107,7 +108,7 @@ func TestEffectiveLambdaConfigSyncResourceTypeIDs(t *testing.T) { "sync-resource-types": []any{"user", "group"}, } - effectiveConfig := effectiveLambdaConfig(base, connectorConfig) + effectiveConfig := effectiveLambdaConfig(base, connectorConfig, field.Configuration{}) got := effectiveConfig.GetStringSlice("sync-resource-types") want := []string{"user", "group"} @@ -116,6 +117,41 @@ func TestEffectiveLambdaConfigSyncResourceTypeIDs(t *testing.T) { } } +func TestEffectiveLambdaConfigPreservesStringMapKeyCaseForViper(t *testing.T) { + t.Parallel() + + base := viper.New() + connectorConfig := map[string]any{ + "user-custom-fields": map[string]any{ + "departmentNav/name": "Department", + "customString10": "Custom String 10", + }, + } + schema := field.NewConfiguration([]field.SchemaField{ + field.StringMapField("user-custom-fields"), + }) + + effectiveConfig := effectiveLambdaConfig(base, connectorConfig, schema) + + gotStringMap := effectiveConfig.GetStringMap("user-custom-fields") + wantStringMap := map[string]any{ + "departmentNav/name": "Department", + "customString10": "Custom String 10", + } + if !reflect.DeepEqual(gotStringMap, wantStringMap) { + t.Fatalf("GetStringMap(user-custom-fields) = %#v, want %#v", gotStringMap, wantStringMap) + } + + gotStringMapString := effectiveConfig.GetStringMapString("user-custom-fields") + wantStringMapString := map[string]string{ + "departmentNav/name": "Department", + "customString10": "Custom String 10", + } + if !reflect.DeepEqual(gotStringMapString, wantStringMapString) { + t.Fatalf("GetStringMapString(user-custom-fields) = %#v, want %#v", gotStringMapString, wantStringMapString) + } +} + func TestMakeLambdaConnectorConfigurationPreservesStringMapKeyCase(t *testing.T) { t.Parallel() @@ -127,7 +163,10 @@ func TestMakeLambdaConnectorConfigurationPreservesStringMapKeyCase(t *testing.T) }, } - config, err := makeLambdaConnectorConfiguration[*testLambdaStringMapConfig](base, effectiveLambdaConfig(base, connectorConfig), connectorConfig) + schema := field.NewConfiguration([]field.SchemaField{ + field.StringMapField("user-custom-fields"), + }) + config, err := makeLambdaConnectorConfiguration[*testLambdaStringMapConfig](base, effectiveLambdaConfig(base, connectorConfig, schema), connectorConfig) if err != nil { t.Fatalf("makeLambdaConnectorConfiguration: %v", err) } From 066aa0b414a6b1ac1edc15eb030729c30980726c Mon Sep 17 00:00:00 2001 From: Steve Gontzes Date: Fri, 22 May 2026 16:54:10 -0400 Subject: [PATCH 2/2] Stringify lambda Viper StringMap values --- pkg/cli/lambda_server__added.go | 3 ++- pkg/cli/lambda_server_test.go | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/cli/lambda_server__added.go b/pkg/cli/lambda_server__added.go index 062cfd91a..fae724ae3 100644 --- a/pkg/cli/lambda_server__added.go +++ b/pkg/cli/lambda_server__added.go @@ -23,6 +23,7 @@ import ( "github.com/go-jose/go-jose/v4" "github.com/maypok86/otter/v2" "github.com/mitchellh/mapstructure" + "github.com/spf13/cast" "github.com/spf13/cobra" "github.com/spf13/viper" "go.uber.org/zap" @@ -524,7 +525,7 @@ func lambdaStringMapValueForViper(value any) any { } // Viper.GetStringMap and GetStringMapString parse JSON strings, while // Viper.Set lowercases nested map[string]any keys. - encoded, err := json.Marshal(value) + encoded, err := json.Marshal(cast.ToStringMapString(value)) if err != nil { return value } diff --git a/pkg/cli/lambda_server_test.go b/pkg/cli/lambda_server_test.go index f3e18e4db..3a4f2e81d 100644 --- a/pkg/cli/lambda_server_test.go +++ b/pkg/cli/lambda_server_test.go @@ -125,6 +125,7 @@ func TestEffectiveLambdaConfigPreservesStringMapKeyCaseForViper(t *testing.T) { "user-custom-fields": map[string]any{ "departmentNav/name": "Department", "customString10": "Custom String 10", + "isActive": true, }, } schema := field.NewConfiguration([]field.SchemaField{ @@ -137,6 +138,7 @@ func TestEffectiveLambdaConfigPreservesStringMapKeyCaseForViper(t *testing.T) { wantStringMap := map[string]any{ "departmentNav/name": "Department", "customString10": "Custom String 10", + "isActive": "true", } if !reflect.DeepEqual(gotStringMap, wantStringMap) { t.Fatalf("GetStringMap(user-custom-fields) = %#v, want %#v", gotStringMap, wantStringMap) @@ -146,6 +148,7 @@ func TestEffectiveLambdaConfigPreservesStringMapKeyCaseForViper(t *testing.T) { wantStringMapString := map[string]string{ "departmentNav/name": "Department", "customString10": "Custom String 10", + "isActive": "true", } if !reflect.DeepEqual(gotStringMapString, wantStringMapString) { t.Fatalf("GetStringMapString(user-custom-fields) = %#v, want %#v", gotStringMapString, wantStringMapString)