From 90869710ca7ecb57c10eda44dc1a53487fe7c38b Mon Sep 17 00:00:00 2001 From: Sergey Gorbunov Date: Fri, 31 Jan 2025 18:22:38 +0300 Subject: [PATCH] Fix nested maps and write a test for it. --- config/config.go | 115 ++++++++++++++++------------------ config/config_test.go | 6 +- test/sharness/t0021-config.sh | 11 +++- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/config/config.go b/config/config.go index 7519528970e..cf6cdcb2d9b 100644 --- a/config/config.go +++ b/config/config.go @@ -138,66 +138,6 @@ func ToMap(conf *Config) (map[string]interface{}, error) { return m, nil } -// Convert config to a map, without using encoding/json, since -// zero/empty/'omitempty' fields are exclused by encoding/json during -// marshaling. -func ReflectToMap(conf interface{}) interface{} { - v := reflect.ValueOf(conf) - if !v.IsValid() { - return nil - } - - // Handle pointer type - if v.Kind() == reflect.Ptr { - if v.IsNil() { - // Create a zero value of the pointer's element type - elemType := v.Type().Elem() - zero := reflect.Zero(elemType) - return ReflectToMap(zero.Interface()) - } - v = v.Elem() - } - - switch v.Kind() { - case reflect.Struct: - result := make(map[string]interface{}) - t := v.Type() - for i := 0; i < v.NumField(); i++ { - field := v.Field(i) - // Only include exported fields - if field.CanInterface() { - result[t.Field(i).Name] = ReflectToMap(field.Interface()) - } - } - return result - - case reflect.Map: - result := make(map[string]interface{}) - iter := v.MapRange() - for iter.Next() { - key := iter.Key() - // Convert map keys to strings for consistency - keyStr := fmt.Sprint(ReflectToMap(key.Interface())) - result[keyStr] = ReflectToMap(iter.Value().Interface()) - } - return result - - case reflect.Slice, reflect.Array: - result := make([]interface{}, v.Len()) - for i := 0; i < v.Len(); i++ { - result[i] = ReflectToMap(v.Index(i).Interface()) - } - return result - - default: - // For basic types (int, string, etc.), just return the value - if v.CanInterface() { - return v.Interface() - } - return nil - } -} - // Clone copies the config. Use when updating. func (c *Config) Clone() (*Config, error) { var newConfig Config @@ -219,7 +159,7 @@ func CheckKey(key string) error { conf := Config{} // Convert an empty config to a map without JSON. - confmap := ReflectToMap(&conf) + confmap := GetValidationMap(&conf) // Parse the key and verify it's presence in the map. var ok bool @@ -252,3 +192,56 @@ func CheckKey(key string) error { } return nil } + +// Convert config to a map, without using encoding/json, since +// zero/empty/'omitempty' fields are exclused by encoding/json during +// marshaling. +func GetValidationMap(conf interface{}) interface{} { + v := reflect.ValueOf(conf) + if !v.IsValid() { + return nil + } + + // Handle pointer type + if v.Kind() == reflect.Ptr { + if v.IsNil() { + // Create a zero value of the pointer's element type + elemType := v.Type().Elem() + zero := reflect.Zero(elemType) + return GetValidationMap(zero.Interface()) + } + v = v.Elem() + } + + switch v.Kind() { + case reflect.Struct: + result := make(map[string]interface{}) + t := v.Type() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + // Only include exported fields + if field.CanInterface() { + result[t.Field(i).Name] = GetValidationMap(field.Interface()) + } + } + return result + + case reflect.Map: + result := "map" + return result + + case reflect.Slice, reflect.Array: + result := make([]interface{}, v.Len()) + for i := 0; i < v.Len(); i++ { + result[i] = GetValidationMap(v.Index(i).Interface()) + } + return result + + default: + // For basic types (int, string, etc.), just return the value + if v.CanInterface() { + return v.Interface() + } + return nil + } +} diff --git a/config/config_test.go b/config/config_test.go index 9e166a606af..dfa61700f35 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -28,9 +28,9 @@ func TestClone(t *testing.T) { } } -func TestReflectToMap(t *testing.T) { +func TestGetValidationMap(t *testing.T) { // Helper function to create a test config with various field types - reflectedConfig := ReflectToMap(new(Config)) + reflectedConfig := GetValidationMap(new(Config)) mapConfig, ok := reflectedConfig.(map[string]interface{}) if !ok { @@ -116,7 +116,7 @@ func TestReflectToMap(t *testing.T) { t.Fatal("Resolvers field not found in DNS") } // Test map field - if _, ok := reflectedResolvers.(map[string]interface{}); !ok { + if v, _ := reflectedResolvers.(string); v != "map" { t.Fatal("Resolvers field didn't convert to map") } diff --git a/test/sharness/t0021-config.sh b/test/sharness/t0021-config.sh index 5d098ac3a0a..fcc4645f50a 100755 --- a/test/sharness/t0021-config.sh +++ b/test/sharness/t0021-config.sh @@ -19,8 +19,14 @@ test_config_cmd_set() { test_expect_success "ipfs config output looks good" " echo \"$cfg_val\" >expected && - ipfs config \"$cfg_key\" >actual && - test_cmp expected actual + if [$cfg_flags != \"--json\"]; then + ipfs config \"$cfg_key\" >actual && + test_cmp expected actual + else + ipfs config \"$cfg_key\" | tr -d \"\\n\\t \" >actual && + echo >>actual && + test_cmp expected actual + fi " } @@ -73,6 +79,7 @@ test_config_cmd() { test_config_cmd_set "--json" "Experimental.FilestoreEnabled" "true" test_config_cmd_set "--json" "Import.BatchMaxSize" "null" test_config_cmd_set "--json" "Import.UnixFSRawLeaves" "true" + test_config_cmd_set "--json" "Routing.Routers.Test" "{\\\"Parameters\\\":\\\"Test\\\",\\\"Type\\\":\\\"Test\\\"}" test_expect_success "'ipfs config show' works" ' ipfs config show >actual