Background
I'm building 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{} //pointer to struct
Response interface{} //pointer to struct
}
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
add a second copy of the underlying request/response object at command registration (prior to population) and then copy it across on reset
just create a generic reset struct function that acts on any interface to an underlying struct pointer
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{} //pointer to struct
Response interface{} //pointer to struct
}
func (c *Command) register() 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
The ultimate goal is to zero this struct of unknown type via reflection and reassign the pointer back to the request interface. preferred order of approach is 3,2,1 - but anything that works would be just dandy.
It seems that I could solve this is if I could create a concrete type from a reflect.Type, but perhaps there is another approach here