Reputation: 3498
I'm trying to write a wrap around a function that uses an interface{}
parameter to return data, by adding cache.
My problem is that once I have a valid interface{}
I don't know how to assign it to be returned in the parameter. The wrapped call is (github.Client) .Do
in github API client and the problem hit me when I tried to add caching with go-cache
This somewhat my function
func (c *cachedClient) requestAPI(url string, v interface{}) error {
x, found := c.cache.Get(url)
if found { // Found in cache code
log.Printf("cached: %v", x)
v = x // HERE: this do not work. x contains a valid copy of what I want but how do I store it in v?
return nil
}
req, _ := c.githubClient.NewRequest("GET", url, nil) // not found I cache, request it
res, err := c.githubClient.Do(*c.context, req, v)
if err != nil {
return err
}
if res.StatusCode != 200 {
return fmt.Errorf("Error Getting %v: %v", url, res.Status)
}
c.cache.Add(url, v, cache.DefaultExpiration) // store in cache
return nil // once here v works as expected and contain a valid item
}
It fails when has to return a cached value when I try to use it like this:
// Some init code c is a cachedClient
i := new(github.Issue)
c.requestAPI(anAPIValidURLforAnIssue, i)
log.Printf("%+v", i) // var i correctly contains an issue from the github api
o := new(github.Issue)
c.requestAPI(anAPIValidURLforAnIssue, o)
log.Printf("%+v", o) // var o should have been get from the cache but here is empty
So basically my problem is that when I correctly recover a cached item it is good but I can not store it in the parameter meant to be used to store it. I can not work with subclasses because the call I'm wrapping is using an interface{}
already. And I can not move it to return values because you can't return a generic interface. How do I make the interface{} x
be stored in v to have it available outside?
Upvotes: -1
Views: 319
Reputation: 2087
To archive what you want you need to use a bit of reflection magic.
Please try to replace v = x
with next code snippet:
reflect.ValueOf(v).Elem().Set(reflect.ValueOf(x).Elem())
Note from OP: I had to add the last .Elem()
to make this work.
NOTE: in the call of the requestAPI
method you should use a pointer to the value:
let's say the cached value is of type int
. Then you should call requestAPI
like:
var dst int // destination of the cached value or a newly retrieved result
cc.requestAPI(url, &dst)
Upvotes: 1
Reputation: 3679
With certain assumptions like you are storing json data in your cache below is how I will try. Errors not handled.
package main
import (
"encoding/json"
"fmt"
)
type Data struct {
Name string
}
func main() {
var d Data
requestAPI(&d)
fmt.Println(d)
}
func requestAPI(v interface{}) {
var cache_res interface{} = []byte("{\"Name\":\"CCC\"}")
//assume you got cache_res from cache
x, _ := cache_res.([]byte)
_ = json.Unmarshal(x, &v)
}
Actually above is what githubClient.Do
is also doing. It checks whether v satisfies io.Writer
interface, if yes write data to v. If not then it unmarshals json into v as shown above. So same can be done from cache.
Check here: https://github.com/google/go-github/blob/v32.1.0/github/github.go#L586
If the cache object is specific then below can be used. You don't deal with empty interface{} because you should be able to pass your specific type to c.githubClient.Do
as v. Since it uses json package, it will detect the type information and accordingly fill the values into it.
Lets say you store type Data struct
In below code other details eliminated like condition checking whether to cache & error handling
package main
import (
"fmt"
)
type Data struct {
Name string
}
func main() {
var d Data
requestAPI(&d)
fmt.Println(d)
}
func requestAPI(v *Data) {
var cache_res interface{} = Data{"CCC"}
//assume you got cache_res from cache
x, _ := cache_res.(Data)
*v = x
//in case you did not find it in cache then githubClient.Do should unmarshal
//contents of response body into v *Data if Data fields match that of json
//res, err := c.githubClient.Do(*c.context, req, v)
}
Upvotes: 1