diff --git a/pkg/cli/lambda_server__added.go b/pkg/cli/lambda_server__added.go index 006f4b9d8..fae724ae3 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" @@ -22,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" @@ -334,7 +336,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 +490,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(cast.ToStringMapString(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..3a4f2e81d 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,44 @@ 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", + "isActive": true, + }, + } + 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", + "isActive": "true", + } + 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", + "isActive": "true", + } + 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 +166,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) }