-1

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?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
theist
  • 3,270
  • 2
  • 22
  • 23
  • if the cached data is json? Then you need to unmarshal that into v. Try this `json.Unmarshal(b, &v)`. You need to get b from x first. – Abhijit-K Dec 31 '20 at 13:19
  • Cached data is not json I'm afraid, but I was able to make it working by marshall and unmarshall it, but it feels odd. – theist Dec 31 '20 at 18:54

2 Answers2

1

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)
}
Abhijit-K
  • 3,569
  • 1
  • 23
  • 31
  • The contents of cache are not json, so this did not work. However I was able to marshall the cache item as a son and then unmarshal it like you pointed. This works but feels odd – theist Dec 31 '20 at 18:55
  • 1
    json was just an assumption the important thing that I want to show is how to deal with empty interface. Which is you need to detect the type information and accordingly fill the values into it. json package uses reflection to do that. – Abhijit-K Jan 01 '21 at 04:35
  • If you are not storing json and lets day you store `Data{}` then why can't you pass it instead of interface{}? Like `func requestAPI(v *Data)` ; then you can type check `x,_ := cache_res.(Data)` and finally copy to v like `*v = x`. Also `githubClient.Do` can also take v as *Data as it uses eventually json unmarshal. I will modify above in answer for clarity. – Abhijit-K Jan 01 '21 at 04:45
  • I'm not using json or a custom type. I'm using `interface{}` because I need this parameter to behave like the one in the original `.Do` call and use a `interface{}` parameter because later it has to be cast to the real datatype which is a github.* (issue, card, repo...) So I'm not storing in the cache my datatypes, just Github's. And I can't `*v = x` on an `interface{}` variable. I'm just realizing by your comment above that I want to clone an interface var and that's done using reflection maybe. – theist Jan 01 '21 at 11:16
  • I think the ` the original .Do call` should accept the real type so no need to pass `interface{}` to it; is the point. – Abhijit-K Jan 01 '21 at 11:21
1

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)
theist
  • 3,270
  • 2
  • 22
  • 23
Pavlo Strokov
  • 1,967
  • 14
  • 14
  • This worked nicely but had to add an `.Elem()` to x also it worked w/o the use of `&dst` I'll edit and accept – theist Jan 02 '21 at 10:07