jeudi 6 août 2020

Two-level reflection not setting values in embedded struct in go

Here are my types

type MyTypeFlagSkel struct {
    Name         string
    Short        string
    HelpMsg      string
    NeedsBinding bool
    Interactive  bool
    RegexEval    *RegexPattern
}

type MyTypeFlag interface {
    GetName() string
    BindPersistentFlag(cobraCommand *cobra.Command)
    IsInteractive() bool
    GetDefaultValue() interface{}
    GetHelpMsg() string
    GetRegexEval() *RegexPattern
}

type MyTypeFlagString struct {
    Value        string
    DefaultValue string
}

type MyTypeFlagStringSlice struct {
    Value        []string
    DefaultValue string
}

type MyTypeFlagBool struct {
    Value        bool
    DefaultValue bool
}

type MyTypeCompositeFlagString struct {
    MyTypeFlagSkel
    MyTypeFlagString
}



type MyTypeCompositeFlagStringSlice struct {
    MyTypeFlagSkel
    MyTypeFlagStringSlice
}


type MyTypeCompositeFlagBool struct {
    MyTypeFlagSkel
    MyTypeFlagBool
}

For brevity reasons, omitting implementations of MyTypeFlag interface methods by all Composite types.

Here is a type composed of such types as the ones above

type ChartOpts struct {
    Name              MyTypeCompositeFlagString
    Keywords          MyTypeCompositeFlagStringSlice
    Interactive       MyTypeCompositeFlagBool
}

And here is a function reflecting on its argument and trying to set its fields (

func FillInFlagsInteractively(e interface{}) error {
    var defaultValueMsg string

    fields := reflect.TypeOf(e)
    values := reflect.ValueOf(e)

    num := fields.Elem().NumField()
    for i := 0; i < num; i++ {
        switch v := values.Elem().Field(i).Interface().(type) {
        case MyTypeFlag:
            if v.IsInteractive() {
                if v.GetDefaultValue() == "" {
                    defaultValueMsg = "No default (or empty) value for this input"
                } else {
                    defaultValueMsg = fmt.Sprintf("Default value that will be used is %v (press Enter to accept the default value)", v.GetDefaultValue())
                }
                prompt := fmt.Sprintf("Please fill in %s - %s: ", strings.ToLower(v.GetHelpMsg()), defaultValueMsg)
                userInput, err := InteractiveInput(prompt, v.GetDefaultValue(), v.GetRegexEval())
                if err != nil {
                    return err
                }
                if userInput == "" {
                    fmt.Println("No user input provided, will proceed with default or auto-generated value")
                }
                // second level switch to handle each case distinctively depending on the flag type
                switch fType := v.(type) {
                case MyTypeCompositeFlagString:
                    s, ok := userInput.(string)
                    if !ok {
                        log.Printf("Erroneous input type:%T and input value: %v\n", userInput, userInput)
                        return ErrUnexpectedInput
                    }
                    fType.Value = s
                case MyTypeCompositeFlagStringSlice:
                    s, ok := userInput.(string)
                    if !ok {
                        log.Printf("Erroneous input type:%T and input value: %v\n", userInput, userInput)
                        return ErrUnexpectedInput
                    }
                    sSlice := strings.Split(s, ",")
                    for i, v := range sSlice {
                        sSlice[i] = strings.TrimSpace(v)
                    }
                    fType.Value = sSlice
                case MyTypeCompositeFlagBool:
                    s, ok := userInput.(bool)
                    if !ok {
                        log.Printf("Erroneous input type:%T and input value: %v\n", userInput, userInput)
                        return ErrUnexpectedInput
                    }
                    fType.Value = s
                default:
                    return ErrUnhandledType
                }
            }
        default:
            return ErrUnhandledType
        }
    }
    return nil
}

I am calling MyFunc and passing a pointer to a ChartOpts variable.

The problem is that after making the function call, and despite setting the field of the embedded struct as in fType.Value = s the original struct seems unaffected.

Why does the fType = s has no impact on the original struct?





Aucun commentaire:

Enregistrer un commentaire