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