mirror of
https://github.com/hanzo-fi/go-libs.git
synced 2026-10-30 20:53:44 +00:00
84 lines
2.1 KiB
Go
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
|
|
}
|