Skip to content

Commit

Permalink
Fix nested maps and write a test for it.
Browse files Browse the repository at this point in the history
  • Loading branch information
gsergey418alt committed Jan 31, 2025
1 parent 7a3732f commit 9086971
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 66 deletions.
115 changes: 54 additions & 61 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Check failure on line 197 in config/config.go

View workflow job for this annotation

GitHub Actions / spellcheck

exclused ==> excluded, excused
// marshaling.
func GetValidationMap(conf interface{}) interface{} {
v := reflect.ValueOf(conf)
if !v.IsValid() {
return nil
}

Check warning on line 203 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L202-L203

Added lines #L202 - L203 were not covered by tests

// 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())
}

Check warning on line 237 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L236-L237

Added lines #L236 - L237 were not covered by tests
return result

default:
// For basic types (int, string, etc.), just return the value
if v.CanInterface() {
return v.Interface()
}
return nil

Check warning on line 245 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L245

Added line #L245 was not covered by tests
}
}
6 changes: 3 additions & 3 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}

Expand Down
11 changes: 9 additions & 2 deletions test/sharness/t0021-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
"
}

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 9086971

Please sign in to comment.