mercredi 7 mars 2018

zero a struct via reflection without knowing its underlying type

Background

I'm build a wrapper package which combines defined commands and allows them to be executed in either a cli or interactive shell context. Commands are defined in a struct something like this (relevant fields shown):

type Handler func(c *Command) error

type Command struct {
    RequestHandler     Handler
    ResponseHandler    Handler
    Request            interface{}
    Response           interface{}
}

The underlying Request/Response objects are always pointers to structs so I make use of reflection quite heavily to recursively iterate through the underlying structs fields to present their fields as either flags or prompts based on the calling context (shell/cli). I then populate the request object using reflection based on the users input.

So far, so good...

Issue

When I am in an interactive shell context a command may be called and I populate the request struct and its values - all good. However, if the command is called again the request is still populated (and the response may be too) so I am looking to implement a 'reset' method on the command which can be called internally when a command is called from the shell.

Now as I say i can guarantee the request and response are both pointers to structs. but how can I set them back to their respective initialised nil values when all I have is an interface and no advanced knowledge of the underlying type ((and internal fields may be struct values).

Possible Approaches

1 - add a second copy of the underlying request/response object at command registration (prior to population) and then copy it across on reset

2 - just create a generic reset struct function that acts on any interface to an underlying struct pointer

3 - some method Im unaware of which does this for me

I've tried both and I'm struggling to implement either

approach 1:

type Command struct {
    RequestHandler     Handler
    ResponseHandler    Handler
    zeroRequest        interface{}
    zeroResponse       interface{}
    Request            interface{}
    Response           interface{}
}

func (c *Command) register() error {

    func (c *Command) preRegister() error {

    // validate is pointer
    reqPtrVal := reflect.ValueOf(c.Request)
    if reqPtrVal.Kind() != reflect.Ptr {
        return ErrStructPtrExpected
    }

    // reflect the underlying value and validate is struct
    reqVal := reflect.Indirect(reqPtrVal)
    if reqVal.Kind() != reflect.Struct {
        return ErrStructPtrExpected
    }

    c.zeroRequest = reqVal.Interface()

    // other logic... 
}

func (c *Command) handleShell(sc *ishell.Context) {

    // reset request / response objects

    // reflect the underlying zero value and validate is struct
    reqVal := reflect.ValueOf(c.zeroRequest)
    if reqVal.Kind() != reflect.Struct {
        c.Commander.HandleError(ErrStructPtrExpected)
    }

    newRequest := reflect.New(reqVal.Type())

    // unfortunately below doesn't work as newRequest.CanAddr() == false
    c.zeroRequest = newRequest.Addr().Interface()

    // other logic

}

approach 2: This feels a little cleaner (no zero value copies/ less code), but I can't even get this approach to compile:

func (c *Command) resetStruct(structPtr interface{}) error {

// validate is pointer
ptrVal := reflect.ValueOf(structPtr)
if ptrVal.Kind() != reflect.Ptr {
    return ErrStructPtrExpected
}

// reflect the underlying value and validate is struct
val := reflect.Indirect(ptrVal)
if val.Kind() != reflect.Struct {
    return ErrStructPtrExpected
}

// create a new val from the underlying type and reassign the pointer
newVal := reflect.New(val.Type())
ptrVal.SetPointer(newVal.Addr())
return nil

}

Summary

Need help to zero this struct via reflection. preferred order of approach is 3,2,1 - but anything that works would be just dandy.

Thanks in advance





Aucun commentaire:

Enregistrer un commentaire