theist
theist

Reputation: 3498

Error trying to use an empty interface parameter for returning data

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

Answers (2)

Pavlo Strokov
Pavlo Strokov

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

Abhijit-K
Abhijit-K

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

Related Questions