Files

84 lines
2.1 KiB
Go

package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
bunpaginate "github.com/hanzo-fi/go-libs/v5/pkg/storage/bun/paginate"
)
type BaseResponse[T any] struct {
Data *T `json:"data,omitempty"`
Cursor *bunpaginate.Cursor[T] `json:"cursor,omitempty"`
}
type ErrorResponse struct {
ErrorCode string `json:"errorCode,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
Details string `json:"details,omitempty"`
}
func (e ErrorResponse) Error() string {
return fmt.Sprintf("[%s] %s", e.ErrorCode, e.ErrorMessage)
}
func FetchAllPaginated[T any](ctx context.Context, client *http.Client, _url string, queryParams url.Values) ([]T, error) {
ret := make([]T, 0)
var nextToken string
for {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, _url, nil)
if err != nil {
return nil, err
}
if nextToken == "" {
req.URL.RawQuery = queryParams.Encode()
} else {
req.URL.RawQuery = url.Values{
"cursor": []string{nextToken},
}.Encode()
}
rsp, err := client.Do(req)
if err != nil {
return nil, err
}
apiResponse, err := decodePaginatedResponse[T](rsp)
if err != nil {
return nil, err
}
ret = append(ret, apiResponse.Cursor.Data...)
if !apiResponse.Cursor.HasMore {
break
}
if apiResponse.Cursor.Next == "" || apiResponse.Cursor.Next == nextToken {
return nil, errors.New("paginated response did not advance: empty or repeated next cursor while hasMore is true")
}
nextToken = apiResponse.Cursor.Next
}
return ret, nil
}
func decodePaginatedResponse[T any](rsp *http.Response) (*BaseResponse[T], error) {
defer func() {
_, _ = io.Copy(io.Discard, rsp.Body)
_ = rsp.Body.Close()
}()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d while waiting for %d", rsp.StatusCode, http.StatusOK)
}
apiResponse := &BaseResponse[T]{}
if err := json.NewDecoder(rsp.Body).Decode(apiResponse); err != nil {
return nil, fmt.Errorf("decoding cursor: %w", err)
}
if apiResponse.Cursor == nil {
return nil, errors.New("missing cursor in paginated response")
}
return apiResponse, nil
}