1

Still a bit of a go newb. As a simple project, I'm trying to write an API wrapper for Kiva's API. I'm trying to avoid writing duplicate code for handling paged responses from the API, but, given Go's lack of generic types, I haven't found a way to do it and am not sure if it's possible.

I've tried using type switches and the reflect package to avoid doing the exact same thing in multiple functions but haven't had any luck. This is the current iteration of my (totally non-functional) code:

type PagingData struct {
    Total    int `json: "total"`
    Page     int `json: "page"`
    PageSize int `json: "page_size"`
    Pages    int `json: "pages"`
}

type PagedLoanResponse struct {
    Paging PagingData `json: "paging"`
    Items  []Loan     `json: "loans"`
}

type PagedLenderResponse struct {
    Paging PagingData `json: "paging"`
    Items  []Lender   `json: "lenders"`
}


func (c *Client) doPaged(method string, urlpath string, query url.Values, body io.Reader, v interface{}, numPages int) ([]interface{}, error) {

    if numPages < 0 {
        return nil, fmt.Errorf("less than zero is unacceptable")
    }

    pr := reflect.New(reflect.TypeOf(v))
    if query == nil {
        query = url.Values{}
    }

    // get the first page
    err := c.do(method, urlpath, query, body, &pr)
    if err != nil {
        return nil, err
    }

    if pr.Paging.Pages == 1 {
        return pr.Items, nil
    }

    if numPages == 0 {
        numPages = pr.Paging.Pages
    }

    items := make(reflect.New(reflect.TypeOf(pr.Items)), 0, pr.Paging.Total)
    items = append(items, pr.Items...)

    for i := 2; i <= numPages; i++ {
        query.Set("page", strconv.Itoa(i))
        err := c.do("GET", urlpath, query, nil, &pr)
        if err != nil {
            return nil
        }
        items = append(items, pr.Items...)
    }

    return items, nil
}


func (c *Client) GetNewestLoans(numPages int) ([]Loan, error) {
    baseURL := "/v1/loans/newest"
    var p PagedLoanResponse
    loans, nil := c.doPaged("GET", baseURL, nil, nil, p, numPages)
}

1 Answer 1

1

One thing you could do is have an interface for anything that is pagable. Perhaps called Pagable:

type Pagable interface { Paging() PagingData }

and a single type that implements it:

type Pager struct { 
  Paging PagingData `json:"paging"`
}
func(p Pager) Paging() PagingData {
   return p.Paging
}

Then your other types can "embed" a Pager to automatically implement Pagable and include those fields:

type PagedLenderResponse struct {
    Paging
    Items  []Lender   `json: "lenders"`
}

If doPaged then accepts a Pagable instead of interface{} for v, you can call Paging() on it, and get the page stuff, and then the json package can write the rest of it out to the response for you. No interface{} and no reflect needed.

Sign up to request clarification or add additional context in comments.

1 Comment

This approach still requires more repetition than I was hoping for in each function that calls doPaged, in order to strip out the paging info from the responses, but the approach you outline is probably as good as I'll get -- thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.