1
votes

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

  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{} //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

1
all you want is to create zero value item of a struct is it ? - Sarath Sadasivan Pillai
not quite, if you look at the code I have that bit, its reassigning it to that interface as well - title edited to clarify - SwiftD
Please chkeck the answer - Sarath Sadasivan Pillai
Thanks Sarath, comment on asnswer - SwiftD

1 Answers

3
votes

You may use reflect.Zero or reflect.New please check the code snippet below for clarity

Here is how you may use it

package main

import (
    "fmt"
    "reflect"
)

type A struct {
    B int
}

func reset(p interface{}) interface{} {
    var isPtr bool
    v := reflect.ValueOf(p)
    for v.Kind() == reflect.Ptr {
        isPtr = true
        v = v.Elem()
    }
    if isPtr {
        return reflect.New(v.Type()).Interface()

    }

    return reflect.Zero(v.Type()).Interface()

}

func main() {
    p := A{1}
    fmt.Printf("Original : %+v,Pointer : %+v,Value : %+v", p, reset(&p), reset(p))
}

Play : https://play.golang.com/p/8CJHHBL6c2p